1
0
mirror of synced 2026-07-08 20:30:04 +00:00

Compare commits

...

163 Commits

Author SHA1 Message Date
github-actions[bot] dd5bfc0b31 Release 6.1.7 2024-02-16 18:46:45 +00:00
Rob Winch 750cb30ce4 Add AuthenticationTrustResolver.isAuthenticated 2024-02-16 13:08:29 -03:00
Marcus Hert Da Coregio 3093908a6e Merge branch '5.8.x' into 6.1.x 2024-02-16 11:52:54 -03:00
Marcus Hert Da Coregio 6230806d56 Change branch pattern
Issue gh-14535
2024-02-16 11:51:53 -03:00
Marcus Hert Da Coregio ac87a1acc9 Merge branch '5.8.x' into 6.1.x 2024-02-16 09:51:28 -03:00
Marcus Hert Da Coregio 2159f3a93d Fix branch pattern
Issue gh-14535
2024-02-16 09:50:42 -03:00
github-actions[bot] cfe2a1842c Merge branch '5.8.x' into 6.1.x 2024-02-16 11:27:42 +00: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
dependabot[bot] 093a83a1ed Bump org.springframework:spring-framework-bom from 6.0.16 to 6.0.17
Bumps [org.springframework:spring-framework-bom](https://github.com/spring-projects/spring-framework) from 6.0.16 to 6.0.17.
- [Release notes](https://github.com/spring-projects/spring-framework/releases)
- [Commits](https://github.com/spring-projects/spring-framework/compare/v6.0.16...v6.0.17)

---
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 03:50:38 +00:00
Marcus Hert Da Coregio 8b0cbb1b1e Merge branch '5.8.x' into 6.1.x 2024-02-15 09:45:55 -03: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 6caa077746 Merge branch '5.8.x' into 6.1.x 2024-02-15 09:23:49 -03:00
Marcus Hert Da Coregio 59be5553a1 Fix workflow syntax
Issue gh-14535
2024-02-15 09:23:35 -03:00
dependabot[bot] cae7e5d7a0 Bump io.projectreactor:reactor-bom from 2022.0.15 to 2022.0.16
Bumps [io.projectreactor:reactor-bom](https://github.com/reactor/reactor) from 2022.0.15 to 2022.0.16.
- [Release notes](https://github.com/reactor/reactor/releases)
- [Commits](https://github.com/reactor/reactor/compare/2022.0.15...2022.0.16)

---
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 03:19:28 +00:00
Marcus Hert Da Coregio 42daef9290 Merge branch '5.8.x' into 6.1.x 2024-02-14 01:15:47 -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
github-actions[bot] 22807b7803 Merge branch '5.8.x' into 6.1.x 2024-02-14 04:10:34 +00: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
dependabot[bot] a7a5d64644 Bump org-aspectj from 1.9.21 to 1.9.21.1
Bumps `org-aspectj` from 1.9.21 to 1.9.21.1.

Updates `org.aspectj:aspectjrt` from 1.9.21 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.21 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:52:00 +00:00
Marcus Hert Da Coregio 1ac0aef28c Merge branch '5.8.x' into 6.1.x 2024-02-13 10:45:53 -03: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
github-actions[bot] e66e0afc6f Merge branch '5.8.x' into 6.1.x 2024-02-12 19:55:33 +00: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
dependabot[bot] 85404e1aa6 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:31:05 +00:00
dependabot[bot] 66ab637068 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:31:00 +00:00
Josh Cummings 75fdcd10f7 Use synchronized
Closes gh-14445
2024-02-07 08:48:49 -07:00
Marcus Hert Da Coregio 54a407b159 Merge branch '5.8.x' into 6.1.x 2024-02-07 10:13:45 -03: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
Marcus Hert Da Coregio ba1068e368 Merge branch '5.8.x' into 6.1.x
Closes gh-14564
2024-02-07 09:37:52 -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 547552ebd6 Merge branch '5.8.x' into 6.1.x 2024-02-06 11:06:02 -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 c1b9c9a4f0 Merge branch '5.8.x' into 6.1.x 2024-02-06 08:20:06 -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
dependabot[bot] 27151837ee Bump org.slf4j:slf4j-api from 2.0.11 to 2.0.12
Bumps org.slf4j:slf4j-api from 2.0.11 to 2.0.12.

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-06 08:16:51 -03:00
Marcus Hert Da Coregio 6938cd9798 Merge branch '5.8.x' into 6.1.x 2024-02-06 08:07:54 -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 ffe9577487 Merge branch '5.8.x' into 6.1.x 2024-02-05 15:41:35 -07: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
Steve Riesenberg 91f0a5a434 Remove missed test
Note: This test was missed when removing unused code in
f11ba8f7f1.

Issue gh-14242
2024-02-05 14:43:26 -06:00
github-actions[bot] 0d82f9bf22 Bump Gradle Wrapper from 8.5 to 8.6 2024-02-05 08:59:10 -07:00
Steve Riesenberg f11ba8f7f1 Switch to reusable workflows
Issue gh-14538
Closes gh-14242
Closes gh-13195
Closes gh-10460
Closes gh-11308
2024-02-02 17:17:57 -06:00
Steve Riesenberg ad71f57143 Add spring-security-release-plugin
Issue gh-14242
2024-02-02 15:47:31 -06:00
Steve Riesenberg b5bd62130b Support consistent build info
Closes gh-14538
2024-02-02 14:00:20 -06:00
Marcus Hert Da Coregio 4e5780a30c Fix setters not working for CasAuthenticationFilter
The setSecurityContextRepository and setSecurityContextHolderStrategy only works for the parent class.
This commit overrides the method and make sure that we set the objects in the super class and the CasAuthenticationFilter.

Closes gh-14529
2024-02-02 15:29:28 -03:00
dependabot[bot] 9fb2f73348 Bump org-eclipse-jetty from 11.0.19 to 11.0.20
Bumps `org-eclipse-jetty` from 11.0.19 to 11.0.20.

Updates `org.eclipse.jetty:jetty-server` from 11.0.19 to 11.0.20

Updates `org.eclipse.jetty:jetty-servlet` from 11.0.19 to 11.0.20

---
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-02 09:33:04 -03:00
Marcus Hert Da Coregio 2efc282f56 Merge branch '5.8.x' into 6.1.x 2024-02-02 09:31:46 -03: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
Habin Song 49f447f3d0 Update session-management.adoc
lambda expression typo. I changed '(session) - session' to '(session) -> session'
2024-01-31 09:51:00 -03:00
Habin Song fbb35a1e3d Typo: Update ldap.adoc
there is no word like 'byusing'. I fixed 'byusing' to 'by using'.
2024-01-31 09:50:51 -03:00
y-tomida cb84efd06d Fix command in CONTRIBUTING.adoc 2024-01-31 09:50:31 -03:00
Josh Cummings ebfe8e3580 Merge branch '5.8.x' into 6.1.x 2024-01-29 17:45:04 -07: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
Josh Cummings b0fe1da98e Merge branch '5.8.x' into 6.1.x 2024-01-29 17:42:29 -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
Josh Cummings 44f22ee5cf Merge branch '5.8.x' into 6.1.x
Closes gh-14495
2024-01-25 17:32:57 -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
Marcus Hert Da Coregio ce5f5e6e33 Add native hint for CsrfTokenRequestAttributeHandler$SupplierCsrfToken
Closes gh-14397
2024-01-17 16:14:59 -03:00
Marcus Hert Da Coregio 2eba7eb672 Merge branch '5.8.x' into 6.1.x 2024-01-17 14:31:29 -03:00
dependabot[bot] 44b785bfb1 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:11 -03: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
Marcus Hert Da Coregio fccd9379d5 WebTestUtilsTestRuntimeHints implements RuntimeHintsRegistrar
Closes gh-14399
2024-01-17 08:47:28 -03:00
Steve Riesenberg b4ac2fadba Merge branch '5.8.x' into 6.1.x 2024-01-12 09:53:58 -06: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] 83b8a2e9d1 Bump org.springframework:spring-framework-bom from 6.0.15 to 6.0.16
Bumps [org.springframework:spring-framework-bom](https://github.com/spring-projects/spring-framework) from 6.0.15 to 6.0.16.
- [Release notes](https://github.com/spring-projects/spring-framework/releases)
- [Commits](https://github.com/spring-projects/spring-framework/compare/v6.0.15...v6.0.16)

---
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-01-12 09:44:30 -06:00
dependabot[bot] 9ce8aa13cb Bump io.projectreactor:reactor-bom from 2022.0.14 to 2022.0.15
Bumps [io.projectreactor:reactor-bom](https://github.com/reactor/reactor) from 2022.0.14 to 2022.0.15.
- [Release notes](https://github.com/reactor/reactor/releases)
- [Commits](https://github.com/reactor/reactor/compare/2022.0.14...2022.0.15)

---
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:43:21 -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] 8ced725d28 Bump org.slf4j:slf4j-api from 2.0.10 to 2.0.11
Bumps org.slf4j:slf4j-api from 2.0.10 to 2.0.11.

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-12 09:41:10 -06:00
dependabot[bot] 90ef7d4c74 Bump org.apereo.cas.client:cas-client-core from 4.0.3 to 4.0.4
Bumps [org.apereo.cas.client:cas-client-core](https://github.com/apereo/java-cas-client) from 4.0.3 to 4.0.4.
- [Release notes](https://github.com/apereo/java-cas-client/releases)
- [Commits](https://github.com/apereo/java-cas-client/compare/cas-client-4.0.3...cas-client-4.0.4)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-12 09:39:34 -06:00
dependabot[bot] 7c401f35ed 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:38: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
dependabot[bot] 574ac94cfc Bump io.mockk:mockk from 1.13.8 to 1.13.9
Bumps [io.mockk:mockk](https://github.com/mockk/mockk) from 1.13.8 to 1.13.9.
- [Release notes](https://github.com/mockk/mockk/releases)
- [Commits](https://github.com/mockk/mockk/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-08 11:22:53 -07:00
Marcus Hert Da Coregio d032b23ab7 Make OAuth2AuthorizationExchange serializable
Closes gh-14402
2024-01-05 07:53:28 -03:00
Taeik Lim 6c5b3d69c5 Fix broken sample code in Authorize HttpServletRequests
Signed-off-by: Taeik Lim <sibera21@gmail.com>
2024-01-03 11:29:46 -03:00
Steve Riesenberg 6f3a1fe1cd Polish kotlin.adoc
Issue gh-14384
2023-12-29 10:57:21 -06:00
Steve Riesenberg 63556b6636 Polish java.adoc
Issue gh-14384
2023-12-29 10:57:21 -06:00
Siddharth1605 e11ed2d313 Updated the Configuration examples in docs
Closes gh-14384
2023-12-29 10:57:21 -06:00
dependabot[bot] 79eef6cc54 Bump org.slf4j:slf4j-api from 2.0.9 to 2.0.10
Bumps org.slf4j:slf4j-api from 2.0.9 to 2.0.10.

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-29 11:40:41 -03:00
Steve Riesenberg 3beb583207 Merge branch '5.8.x' into 6.1.x 2023-12-28 12:56:25 -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
Taeik Lim 3cb181aece Fix Spring initializr link in 'Getting Spring Security'
Signed-off-by: Taeik Lim <sibera21@gmail.com>

Issue gh-9869
2023-12-28 11:11:14 -06:00
nkilchenmann ae7ae1ffee Update configuration.adoc
Fixing typo in documentation

Issue gh-12628
2023-12-28 10:57:53 -06:00
Krishna Chaitanya Surapaneni 7ec9188f5b Remove Irrelevant Documentation Lines
Refactor: Remove two lines that lack proper context due to earlier
deletions/movements. They are no longer relevant and contribute little
to the overall meaning.

Issue gh-12974
2023-12-28 10:47:17 -06:00
Marcus Hert Da Coregio 92af758f1f Make springSecurityHandlerMappingIntrospectorBeanDefinitionRegistryPostProcessor passive
Instead of excluding the bean from AOT processing, we avoid redefining the beans if they are present or in the expected state.

Issue gh-14362
2023-12-26 15:58:16 -03:00
Marcus Hert Da Coregio 778a63a763 Revert "Exclude SpringSecurityHandlerMappingIntrospectorBeanDefinitionRegistryPostProcessor from AOT processing"
This reverts commit 8a93178da7.
2023-12-26 15:10:15 -03:00
DingHao 7cd626fe25 Fix FilterChainProxy cannot be found when @EnableWebSecurity(debug = true)
Closes gh-14370
2023-12-26 11:20:09 -03:00
Marcus Hert Da Coregio 364bc10e78 Add hints for CompositeFilterChainProxy
Closes gh-14359
2023-12-26 10:41:56 -03:00
Marcus Hert Da Coregio 8a93178da7 Exclude SpringSecurityHandlerMappingIntrospectorBeanDefinitionRegistryPostProcessor from AOT processing
Closes gh-14362
2023-12-22 08:40:07 -03:00
dependabot[bot] 3d277fbf86 Bump org-eclipse-jetty from 11.0.18 to 11.0.19
Bumps `org-eclipse-jetty` from 11.0.18 to 11.0.19.

Updates `org.eclipse.jetty:jetty-server` from 11.0.18 to 11.0.19
- [Release notes](https://github.com/jetty/jetty.project/releases)
- [Commits](https://github.com/jetty/jetty.project/compare/jetty-11.0.18...jetty-11.0.19)

Updates `org.eclipse.jetty:jetty-servlet` from 11.0.18 to 11.0.19
- [Release notes](https://github.com/jetty/jetty.project/releases)
- [Commits](https://github.com/jetty/jetty.project/compare/jetty-11.0.18...jetty-11.0.19)

---
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>
2023-12-21 08:58:11 -03:00
Josh Cummings c38c443d2f Merge branch '5.8.x' into 6.1.x 2023-12-19 18:06:23 -07:00
daniKir 9203567a20 improve Multitenancy Issuer Validator example Closes gh-14229 2023-12-19 18:04:50 -07:00
Josh Cummings fbafe41991 Merge branch '5.8.x' into 6.1.x 2023-12-18 15:19:40 -07:00
Josh Cummings e058b559b8 Polish Method Security Eager-Loading
Issue gh-11596
2023-12-18 15:18:09 -07:00
Josh Cummings 9a5d991383 Address eager-loading of infrastructure beans
Closes gh-11596
2023-12-18 15:16:00 -07:00
Josh Cummings 85b5756fe6 Merge branch '5.8.x' into 6.1.x
Closes gh-14349
2023-12-18 15:15:15 -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 3f881eb127 Merge branch '5.8.x' into 6.1.x
Closes gh-14347
2023-12-18 12:04:37 -07:00
Josh Cummings 59461d94b0 Clarify RSocket Configuration Docs
Closes gh-13718
2023-12-18 12:02:49 -07:00
Josh Cummings 659635a767 Merge branch '5.8.x' into 6.1.x 2023-12-18 11:51:57 -07:00
Josh Cummings fc007aa373 Check OpenSAML Version in XML Support
Closes gh-12483
2023-12-18 11:51:15 -07:00
Josh Cummings b855ccdb09 Merge branch '5.8.x' into 6.1.x
Closes gh-14345
2023-12-18 11:46:04 -07:00
Josh Cummings 519f539c01 Next Development Version 2023-12-18 11:16:13 -07:00
Josh Cummings eaaa813ede Fix header value typo
Closes gh-11948
2023-12-18 10:42:50 -07:00
github-actions[bot] f5c9c7db40 Release 6.1.6 2023-12-18 15:21:23 +00:00
Rob Winch 142b268a21 Use CompositeFilterChainProxy
By extending FilterChainProxy CompositeFilterChainProxy is more passive since
users often depend on the type of the springSecurityFilterChain Bean being
FilterChainProxy (even though it can already be other types - when debug is
enabled).

Issue gh-14128
2023-12-15 01:15:38 -06:00
Rob Winch 70dfb3d391 Add HandlerMappingIntrospector Caching
Closes gh-14128
2023-12-14 16:08:36 -06:00
dependabot[bot] 1bb5fe409b 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 15:15:08 -03:00
Marcus Hert Da Coregio 87e6986de5 Revert "Bump sjohnr/slack-workflow-status from 1.pre.beta to 1.1.0"
This reverts commit 40dd7a0d2d.
2023-12-14 15:04:02 -03:00
dependabot[bot] b2c3bc6b3a 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 09:01:21 -03:00
dependabot[bot] 652f98a88c 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 09:01:10 -03:00
dependabot[bot] fd02d7077d Bump org.springframework:spring-framework-bom from 6.0.14 to 6.0.15
Bumps [org.springframework:spring-framework-bom](https://github.com/spring-projects/spring-framework) from 6.0.14 to 6.0.15.
- [Release notes](https://github.com/spring-projects/spring-framework/releases)
- [Commits](https://github.com/spring-projects/spring-framework/compare/v6.0.14...v6.0.15)

---
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-12-14 09:01:02 -03:00
dependabot[bot] 40dd7a0d2d Bump sjohnr/slack-workflow-status from 1.pre.beta to 1.1.0
Bumps [sjohnr/slack-workflow-status](https://github.com/sjohnr/slack-workflow-status) from 1.pre.beta to 1.1.0.
- [Commits](https://github.com/sjohnr/slack-workflow-status/compare/v1-beta...v1.1.0)

---
updated-dependencies:
- dependency-name: sjohnr/slack-workflow-status
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-14 09:00:53 -03:00
dependabot[bot] 53b640e8e2 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 09:00:31 -03:00
dependabot[bot] c6326062da 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 09:00:22 -03:00
Marcus Hert Da Coregio 03e37e79ab Merge branch '5.8.x' into 6.1.x 2023-12-14 08:59:11 -03: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] 426b61cd3c Bump org-aspectj from 1.9.20.1 to 1.9.21
Bumps `org-aspectj` from 1.9.20.1 to 1.9.21.

Updates `org.aspectj:aspectjrt` from 1.9.20.1 to 1.9.21
- [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
- [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>
2023-12-13 09:17:49 -03:00
dependabot[bot] 3f11908eac Bump io.projectreactor:reactor-bom from 2022.0.13 to 2022.0.14
Bumps [io.projectreactor:reactor-bom](https://github.com/reactor/reactor) from 2022.0.13 to 2022.0.14.
- [Release notes](https://github.com/reactor/reactor/releases)
- [Commits](https://github.com/reactor/reactor/compare/2022.0.13...2022.0.14)

---
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:17:36 -03:00
Marcus Hert Da Coregio 4308b284a7 Merge branch '5.8.x' into 6.1.x 2023-12-13 09:16:58 -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 a650fe9e87 Merge branch '5.8.x' into 6.1.x
Closes gh-14294
2023-12-13 09:12:20 -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 f0772dc788 Merge branch '5.8.x' into 6.1.x
Closes gh-14279
2023-12-11 11:44:44 -07:00
Josh Cummings 74d06f020d Update to Latest Boot Property
Closes gh-14252
2023-12-11 11:44:08 -07:00
Josh Cummings c4a99fc942 Merge branch '5.8.x' into 6.1.x
Closes gh-14277
2023-12-11 10:20:38 -07:00
Candelario e896b14046 Dropped Nimbus Error Message
Closes gh-13730
2023-12-11 10:19:02 -07:00
Josh Cummings 9f90661b6f Merge branch '5.8.x' into 6.1.x
Closes gh-14266
2023-12-09 11:43:04 -07:00
Josh Cummings be11812fe4 Account for Super-super-interface Inheritance
Closes gh-13625
2023-12-09 11:41:02 -07:00
Steve Riesenberg 1783a70fdb Merge branch '5.8.x' into 6.1.x 2023-12-07 14:39:10 -06:00
Steve Riesenberg 9c44b700f5 Add Gradle Enterprise creds to PR builds
Closes gh-14249
2023-12-07 14:28:51 -06:00
Steve Riesenberg bc648e0491 Remove unused apollo-runtime dependency
Closes gh-14259
2023-12-07 11:35:26 -06:00
Steve Riesenberg a969f67c14 Merge branch '6.0.x' into 6.1.x 2023-12-05 18:05:39 -06:00
Steve Riesenberg c453c82948 Allow env vars to determine Artifactory repoKey
Closes gh-14250
2023-12-05 18:02:25 -06:00
Steve Riesenberg 7aa6e0d787 Merge branch '6.0.x' into 6.1.x 2023-12-05 13:35:45 -06:00
Steve Riesenberg f6843b2bd9 Use env var to determine Artifactory URL
Closes gh-14246
2023-12-05 13:33:45 -06:00
dependabot[bot] aa732107dc Bump ch.qos.logback:logback-classic from 1.4.13 to 1.4.14
Bumps [ch.qos.logback:logback-classic](https://github.com/qos-ch/logback) from 1.4.13 to 1.4.14.
- [Commits](https://github.com/qos-ch/logback/compare/v_1.4.13...v_1.4.14)

---
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 10:51:10 -07:00
Josh Cummings f4314edc78 Merge branch '6.0.x' into 6.1.x 2023-12-04 10:18:07 -07:00
Josh Cummings 4e37499e28 Merge branch '5.8.x' into 6.0.x 2023-12-04 10:17:52 -07: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 641722823e Merge branch '6.0.x' into 6.1.x 2023-12-01 15:45:32 -07:00
Josh Cummings f536b2652f Merge branch '5.8.x' into 6.0.x 2023-12-01 15:45:03 -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] 34c4b8904e Bump com.unboundid:unboundid-ldapsdk from 6.0.10 to 6.0.11
Bumps [com.unboundid:unboundid-ldapsdk](https://github.com/pingidentity/ldapsdk) from 6.0.10 to 6.0.11.
- [Release notes](https://github.com/pingidentity/ldapsdk/releases)
- [Changelog](https://github.com/pingidentity/ldapsdk/blob/master/docs/release-notes.html)
- [Commits](https://github.com/pingidentity/ldapsdk/compare/6.0.10...6.0.11)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-01 11:39:23 -03:00
dependabot[bot] 34cbf8ed10 Bump ch.qos.logback:logback-classic from 1.4.11 to 1.4.13
Bumps [ch.qos.logback:logback-classic](https://github.com/qos-ch/logback) from 1.4.11 to 1.4.13.
- [Commits](https://github.com/qos-ch/logback/compare/v_1.4.11...v_1.4.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-11-30 13:06:23 -06:00
github-actions[bot] 60d310e315 Bump Gradle Wrapper from 8.4 to 8.5 2023-11-30 15:54:57 -03:00
Steve Riesenberg c0355884f3 Merge branch '6.0.x' into 6.1.x 2023-11-27 12:35:04 -06:00
Steve Riesenberg f25b3fba2f Disable automation to open new milestones on 6.0.x
Note: This change is only applied to 6.0.x and won't
be applied to newer branches.

Issue gh-13195

Closes gh-14203
2023-11-27 12:34:53 -06:00
Marcus Hert Da Coregio 008f0d413d Use samples branch 6.1.x 2023-11-27 13:48:16 -03:00
130 changed files with 1764 additions and 6090 deletions
@@ -9,299 +9,111 @@ on:
workflow_dispatch: # Manual trigger
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
GRADLE_ENTERPRISE_CACHE_USER: ${{ secrets.GRADLE_ENTERPRISE_CACHE_USER }}
GRADLE_ENTERPRISE_CACHE_USERNAME: ${{ secrets.GRADLE_ENTERPRISE_CACHE_USER }}
GRADLE_ENTERPRISE_CACHE_PASSWORD: ${{ secrets.GRADLE_ENTERPRISE_CACHE_PASSWORD }}
GRADLE_ENTERPRISE_SECRET_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }}
COMMIT_OWNER: ${{ github.event.pusher.name }}
COMMIT_SHA: ${{ github.sha }}
STRUCTURE101_LICENSEID: ${{ secrets.STRUCTURE101_LICENSEID }}
ARTIFACTORY_USERNAME: ${{ secrets.ARTIFACTORY_USERNAME }}
ARTIFACTORY_PASSWORD: ${{ secrets.ARTIFACTORY_PASSWORD }}
GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }}
permissions:
contents: read
jobs:
prerequisites:
name: Pre-requisites for building
runs-on: ubuntu-latest
if: ${{ github.repository == 'spring-projects/spring-security' }}
outputs:
runjobs: ${{ steps.continue.outputs.runjobs }}
project_version: ${{ steps.continue.outputs.project_version }}
samples_branch: ${{ steps.continue.outputs.samples_branch }}
steps:
- uses: actions/checkout@v3
- id: continue
name: Determine if should continue
run: |
# Run jobs if in upstream repository
echo "runjobs=true" >>$GITHUB_OUTPUT
# Extract version from gradle.properties
version=$(cat gradle.properties | grep "version=" | awk -F'=' '{print $2}')
echo "project_version=$version" >>$GITHUB_OUTPUT
samples_branch=$(cat gradle.properties | grep "samplesBranch=" | awk -F'=' '{print $2}')
echo "samples_branch=$samples_branch" >>$GITHUB_OUTPUT
build_jdk_17:
name: Build JDK 17
needs: [prerequisites]
build:
name: Build
uses: spring-io/spring-security-release-tools/.github/workflows/build.yml@v1
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
runs-on: ${{ matrix.os }}
if: needs.prerequisites.outputs.runjobs
steps:
- uses: actions/checkout@v3
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
cache: 'gradle'
- name: Set up Gradle
uses: gradle/gradle-build-action@v2
- name: Set up gradle user name
run: echo 'systemProp.user.name=spring-builds+github' >> gradle.properties
- name: Build with Gradle
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 }}
run: ./gradlew clean build --continue -PartifactoryUsername="$ARTIFACTORY_USERNAME" -PartifactoryPassword="$ARTIFACTORY_PASSWORD"
snapshot_tests:
name: Test against snapshots
needs: [prerequisites]
os: [ ubuntu-latest, windows-latest ]
jdk: [ 17 ]
with:
runs-on: ${{ matrix.os }}
java-version: ${{ matrix.jdk }}
distribution: temurin
secrets: inherit
test:
name: Test Against Snapshots
uses: spring-io/spring-security-release-tools/.github/workflows/test.yml@v1
strategy:
matrix:
java-version: [ 17 ]
with:
java-version: ${{ matrix.java-version }}
test-args: --refresh-dependencies -PforceMavenRepositories=snapshot -PisOverrideVersionCatalog -PspringFrameworkVersion=6.0.+ -PreactorVersion=2022.0.+ -PspringDataVersion=2022.0.+ --stacktrace
secrets: inherit
check-samples:
name: Check Samples
runs-on: ubuntu-latest
if: needs.prerequisites.outputs.runjobs
if: ${{ github.repository_owner == 'spring-projects' }}
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'
- 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
check_samples:
name: Check Samples project
needs: [prerequisites]
runs-on: ubuntu-latest
if: needs.prerequisites.outputs.runjobs
steps:
- uses: actions/checkout@v3
- name: Set up gradle
uses: spring-io/spring-gradle-build-action@v1
with:
java-version: '17'
distribution: 'temurin'
java-version: 17
distribution: temurin
- name: Check samples project
env:
LOCAL_REPOSITORY_PATH: ${{ github.workspace }}/build/publications/repos
SAMPLES_DIR: ../spring-security-samples
VERSION: ${{ needs.prerequisites.outputs.project_version }}
SAMPLES_BRANCH: ${{ needs.prerequisites.outputs.samples_branch }}
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"
# Extract version from gradle.properties
version=$(cat gradle.properties | grep "version=" | awk -F'=' '{print $2}')
# Extract samplesBranch from gradle.properties
samples_branch=$(cat gradle.properties | grep "samplesBranch=" | awk -F'=' '{print $2}')
./gradlew publishMavenJavaPublicationToLocalRepository
./gradlew cloneRepository -PrepositoryName="spring-projects/spring-security-samples" -Pref="$SAMPLES_BRANCH" -PcloneOutputDirectory="$SAMPLES_DIR"
./gradlew --project-dir "$SAMPLES_DIR" --init-script spring-security-ci.gradle -PlocalRepositoryPath="$LOCAL_REPOSITORY_PATH" -PspringSecurityVersion="$VERSION" :runAllTests
check_tangles:
./gradlew cloneRepository -PrepositoryName="spring-projects/spring-security-samples" -Pref="$samples_branch" -PcloneOutputDirectory="$SAMPLES_DIR"
./gradlew --project-dir "$SAMPLES_DIR" --init-script spring-security-ci.gradle -PlocalRepositoryPath="$LOCAL_REPOSITORY_PATH" -PspringSecurityVersion="$version" :runAllTests
check-tangles:
name: Check for Package Tangles
needs: [ prerequisites ]
runs-on: ubuntu-latest
if: needs.prerequisites.outputs.runjobs
if: ${{ github.repository_owner == 'spring-projects' }}
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: 17
distribution: temurin
- name: Check for package tangles
env:
STRUCTURE101_LICENSEID: ${{ secrets.STRUCTURE101_LICENSEID }}
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 check s101 -Ps101.licenseId="$STRUCTURE101_LICENSEID" --stacktrace
deploy_artifacts:
deploy-artifacts:
name: Deploy Artifacts
needs: [build_jdk_17, snapshot_tests, check_samples, check_tangles]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up gradle
uses: spring-io/spring-gradle-build-action@v1
with:
java-version: '17'
distribution: 'temurin'
- name: Deploy artifacts
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 publishArtifacts finalizeDeployArtifacts -PossrhUsername="$OSSRH_TOKEN_USERNAME" -PossrhPassword="$OSSRH_TOKEN_PASSWORD" -PartifactoryUsername="$ARTIFACTORY_USERNAME" -PartifactoryPassword="$ARTIFACTORY_PASSWORD" --stacktrace
env:
ORG_GRADLE_PROJECT_signingKey: ${{ secrets.GPG_PRIVATE_KEY }}
ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.GPG_PASSPHRASE }}
OSSRH_TOKEN_USERNAME: ${{ secrets.OSSRH_S01_TOKEN_USERNAME }}
OSSRH_TOKEN_PASSWORD: ${{ secrets.OSSRH_S01_TOKEN_PASSWORD }}
ARTIFACTORY_USERNAME: ${{ secrets.ARTIFACTORY_USERNAME }}
ARTIFACTORY_PASSWORD: ${{ secrets.ARTIFACTORY_PASSWORD }}
deploy_docs:
needs: [ build, test, check-samples, check-tangles ]
uses: spring-io/spring-security-release-tools/.github/workflows/deploy-artifacts.yml@v1
with:
should-deploy-artifacts: ${{ needs.build.outputs.should-deploy-artifacts }}
secrets: inherit
deploy-docs:
name: Deploy Docs
needs: [build_jdk_17, snapshot_tests, check_samples, check_tangles]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up gradle
uses: spring-io/spring-gradle-build-action@v1
with:
java-version: '17'
distribution: 'temurin'
- name: Deploy Docs
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 deployDocs -PdeployDocsSshKey="$DOCS_SSH_KEY" -PdeployDocsSshUsername="$DOCS_USERNAME" -PdeployDocsHost="$DOCS_HOST" --stacktrace
env:
DOCS_USERNAME: ${{ secrets.DOCS_USERNAME }}
DOCS_SSH_KEY: ${{ secrets.DOCS_SSH_KEY }}
DOCS_HOST: ${{ secrets.DOCS_HOST }}
deploy_schema:
needs: [ build, test, check-samples, check-tangles ]
uses: spring-io/spring-security-release-tools/.github/workflows/deploy-docs.yml@v1
with:
should-deploy-docs: ${{ needs.build.outputs.should-deploy-artifacts }}
secrets: inherit
deploy-schema:
name: Deploy Schema
needs: [build_jdk_17, snapshot_tests, check_samples, check_tangles]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up gradle
uses: spring-io/spring-gradle-build-action@v1
with:
java-version: '17'
distribution: 'temurin'
- name: Deploy Schema
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 deploySchema -PdeployDocsSshKey="$DOCS_SSH_KEY" -PdeployDocsSshUsername="$DOCS_USERNAME" -PdeployDocsHost="$DOCS_HOST" --stacktrace --info
env:
DOCS_USERNAME: ${{ secrets.DOCS_USERNAME }}
DOCS_SSH_KEY: ${{ secrets.DOCS_SSH_KEY }}
DOCS_HOST: ${{ secrets.DOCS_HOST }}
perform_release:
name: Perform release
needs: [prerequisites, deploy_artifacts, deploy_docs, deploy_schema]
runs-on: ubuntu-latest
permissions:
contents: write
timeout-minutes: 90
if: ${{ !endsWith(needs.prerequisites.outputs.project_version, '-SNAPSHOT') }}
env:
REPO: ${{ github.repository }}
BRANCH: ${{ github.ref_name }}
TOKEN: ${{ github.token }}
VERSION: ${{ needs.prerequisites.outputs.project_version }}
steps:
- uses: actions/checkout@v3
with:
token: ${{ secrets.GH_ACTIONS_REPO_TOKEN }}
- name: Set up gradle
uses: spring-io/spring-gradle-build-action@v1
with:
java-version: '17'
distribution: 'temurin'
- name: Wait for Artifactory Artifacts
if: ${{ contains(needs.prerequisites.outputs.project_version, '-RC') || contains(needs.prerequisites.outputs.project_version, '-M') }}
run: |
echo "Wait for artifacts of $REPO@$VERSION to appear on Artifactory."
until curl -f -s https://repo.spring.io/artifactory/milestone/org/springframework/security/spring-security-core/$VERSION/ > /dev/null
do
sleep 30
echo "."
done
echo "Artifacts for $REPO@$VERSION have been released to Artifactory."
- name: Wait for Maven Central Artifacts
if: ${{ !contains(needs.prerequisites.outputs.project_version, '-RC') && !contains(needs.prerequisites.outputs.project_version, '-M') }}
run: |
echo "Wait for artifacts of $REPO@$VERSION to appear on Maven Central."
until curl -f -s https://repo1.maven.org/maven2/org/springframework/security/spring-security-core/$VERSION/ > /dev/null
do
sleep 30
echo "."
done
echo "Artifacts for $REPO@$VERSION have been released to Maven Central."
- name: Create GitHub Release
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"
echo "Tagging and publishing $REPO@$VERSION release on GitHub."
./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
with:
payload: |
{
"text": "spring-security-announcing `${{ env.VERSION }}` is available now",
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "spring-security-announcing `${{ env.VERSION }}` is available now"
}
}
]
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SPRING_RELEASE_SLACK_WEBHOOK_URL }}
SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK
- name: Setup git config
run: |
git config user.name 'github-actions[bot]'
git config user.email 'github-actions[bot]@users.noreply.github.com'
- name: Update to next Snapshot Version
run: |
export GRADLE_ENTERPRISE_CACHE_USERNAME="$GRADLE_ENTERPRISE_CACHE_USER"
export GRADLE_ENTERPRISE_CACHE_PASSWORD="$GRADLE_ENTERPRISE_CACHE_PASSWORD"
export GRADLE_ENTERPRISE_ACCESS_KEY="$GRADLE_ENTERPRISE_SECRET_ACCESS_KEY"
echo "Updating $REPO@$VERSION to next snapshot version."
./gradlew :updateToSnapshotVersion
git commit -am "Next development version"
git push
perform_post_release:
name: Perform post-release
needs: [prerequisites, deploy_artifacts, deploy_docs, deploy_schema]
runs-on: ubuntu-latest
permissions:
contents: read
issues: write
timeout-minutes: 90
if: ${{ endsWith(needs.prerequisites.outputs.project_version, '-SNAPSHOT') }}
env:
TOKEN: ${{ github.token }}
VERSION: ${{ needs.prerequisites.outputs.project_version }}
steps:
- uses: actions/checkout@v3
- name: Set up gradle
uses: spring-io/spring-gradle-build-action@v1
with:
java-version: '17'
distribution: 'temurin'
- name: Schedule next release (if not already scheduled)
run: ./gradlew scheduleNextRelease -PnextVersion=$VERSION -PgitHubAccessToken=$TOKEN
needs: [ build, test, check-samples, check-tangles ]
uses: spring-io/spring-security-release-tools/.github/workflows/deploy-schema.yml@v1
with:
should-deploy-schema: ${{ needs.build.outputs.should-deploy-artifacts }}
secrets: inherit
perform-release:
name: Perform Release
needs: [ deploy-artifacts, deploy-docs, deploy-schema ]
uses: spring-io/spring-security-release-tools/.github/workflows/perform-release.yml@v1
with:
should-perform-release: ${{ needs.deploy-artifacts.outputs.artifacts-deployed }}
project-version: ${{ needs.deploy-artifacts.outputs.project-version }}
milestone-repo-url: https://repo.spring.io/artifactory/milestone
release-repo-url: https://repo1.maven.org/maven2
artifact-path: org/springframework/security/spring-security-core
slack-announcing-id: spring-security-announcing
secrets: inherit
notify_result:
name: Check for failures
needs: [perform_release, perform_post_release]
needs: [ perform-release ]
if: failure()
runs-on: ubuntu-latest
permissions:
+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
+13
View File
@@ -0,0 +1,13 @@
name: Merge Dependabot PR
on:
pull_request:
run-name: Merge Dependabot PR ${{ github.ref_name }}
jobs:
merge-dependabot-pr:
permissions: write-all
uses: spring-io/spring-github-workflows/.github/workflows/spring-merge-dependabot-pr.yml@1e8b0587a1f4f01697f9753fa3339c3e0d30f396
with:
mergeArguments: '--auto --rebase'
@@ -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 }}
+7 -2
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,9 +16,9 @@ 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'
+1 -1
View File
@@ -15,7 +15,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Dispatch
@@ -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
@@ -3,78 +3,11 @@ name: Update Scheduled Release Version
on:
workflow_dispatch: # Manual trigger only. Triggered by release-scheduler.yml on main.
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
GRADLE_ENTERPRISE_CACHE_USER: ${{ secrets.GRADLE_ENTERPRISE_CACHE_USER }}
GRADLE_ENTERPRISE_CACHE_PASSWORD: ${{ secrets.GRADLE_ENTERPRISE_CACHE_PASSWORD }}
GRADLE_ENTERPRISE_SECRET_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }}
permissions:
contents: read
jobs:
update_scheduled_release_version:
name: Initiate Release If Scheduled
if: ${{ github.repository == 'spring-projects/spring-security' }}
runs-on: ubuntu-latest
permissions:
contents: read
actions: read
steps:
- id: checkout-source
name: Checkout Source Code
uses: actions/checkout@v3
with:
token: ${{ secrets.GH_ACTIONS_REPO_TOKEN }}
- name: Set up gradle
uses: spring-io/spring-gradle-build-action@v1
with:
java-version: '17'
distribution: 'temurin'
- id: check-release-due
name: Check Release Due
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 gitHubCheckNextVersionDueToday
echo "is_due_today=$(cat build/github/milestones/is-due-today)" >>$GITHUB_OUTPUT
- id: check-open-issues
name: Check for open issues
if: steps.check-release-due.outputs.is_due_today == 'true'
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 gitHubCheckMilestoneHasNoOpenIssues
echo "is_open_issues=$(cat build/github/milestones/is-open-issues)" >>$GITHUB_OUTPUT
- id: validate-release-state
name: Validate State of Release
if: steps.check-release-due.outputs.is_due_today == 'true' && steps.check-open-issues.outputs.is_open_issues == 'true'
run: |
echo "The release is due today but there are open issues"
exit 1
- id: update-version-and-push
name: Update version and push
if: steps.check-release-due.outputs.is_due_today == 'true' && steps.check-open-issues.outputs.is_open_issues == 'false'
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"
git config user.name 'github-actions[bot]'
git config user.email 'github-actions[bot]@users.noreply.github.com'
./gradlew :updateProjectVersion
updatedVersion=$(cat gradle.properties | grep "version=" | awk -F'=' '{print $2}')
git commit -am "Release $updatedVersion"
git tag $updatedVersion
git push
git push origin $updatedVersion
- id: send-slack-notification
name: Send Slack message
if: failure()
uses: Gamesight/slack-workflow-status@v1.0.1
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
slack_webhook_url: ${{ secrets.SLACK_WEBHOOK_URL }}
channel: '#spring-security-ci'
name: 'CI Notifier'
update-scheduled-release-version:
name: Update Scheduled Release Version
uses: spring-io/spring-security-release-tools/.github/workflows/update-scheduled-release-version.yml@v1
secrets: inherit
+1 -1
View File
@@ -79,7 +79,7 @@ See https://github.com/spring-projects/spring-security/tree/main#building-from-s
The wiki pages https://github.com/spring-projects/spring-framework/wiki/Code-Style[Code Style] and https://github.com/spring-projects/spring-framework/wiki/IntelliJ-IDEA-Editor-Settings[IntelliJ IDEA Editor Settings] define the source file coding standards we use along with some IDEA editor settings we customize.
To format the code as well as check the style, run `./gradle format check`.
To format the code as well as check the style, run `./gradlew format check`.
[[submit-a-pull-request]]
=== Submit a Pull Request
+5 -50
View File
@@ -19,12 +19,8 @@ apply plugin: 'locks'
apply plugin: 's101'
apply plugin: 'io.spring.convention.root'
apply plugin: 'org.jetbrains.kotlin.jvm'
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'
apply plugin: 'io.spring.security.release'
group = 'org.springframework.security'
description = 'Spring Security'
@@ -37,53 +33,12 @@ repositories {
mavenCentral()
}
tasks.named("saganCreateRelease") {
referenceDocUrl = "https://docs.spring.io/spring-security/reference/{version}/index.html"
apiDocUrl = "https://docs.spring.io/spring-security/site/docs/{version}/api/"
}
tasks.named("gitHubCheckMilestoneHasNoOpenIssues") {
repository {
owner = "spring-projects"
name = "spring-security"
}
}
tasks.named("gitHubNextReleaseMilestone") {
repository {
owner = "spring-projects"
name = "spring-security"
}
}
tasks.named("gitHubCheckNextVersionDueToday") {
repository {
owner = "spring-projects"
name = "spring-security"
}
}
tasks.named("scheduleNextRelease") {
repository {
owner = "spring-projects"
name = "spring-security"
}
springRelease {
weekOfMonth = 3
dayOfWeek = 1
}
tasks.named("createGitHubRelease") {
repository {
owner = "spring-projects"
name = "spring-security"
}
}
tasks.named("dispatchGitHubWorkflow") {
repository {
owner = "spring-projects"
name = "spring-security"
}
referenceDocUrl = "https://docs.spring.io/spring-security/reference/{version}/index.html"
apiDocUrl = "https://docs.spring.io/spring-security/docs/{version}/api/"
replaceSnapshotVersionInReferenceDocUrl = true
}
subprojects {
+4 -25
View File
@@ -26,10 +26,6 @@ sourceSets {
gradlePlugin {
plugins {
checkAntoraVersion {
id = "org.springframework.antora.check-version"
implementationClass = "org.springframework.gradle.antora.AntoraVersionPlugin"
}
trang {
id = "trang"
implementationClass = "trang.TrangPlugin"
@@ -42,26 +38,6 @@ gradlePlugin {
id = "io.spring.convention.management-configuration"
implementationClass = "io.spring.gradle.convention.ManagementConfigurationPlugin"
}
updateProjectVersion {
id = "org.springframework.security.update-version"
implementationClass = "org.springframework.security.convention.versions.UpdateProjectVersionPlugin"
}
sagan {
id = "org.springframework.security.sagan"
implementationClass = "org.springframework.gradle.sagan.SaganPlugin"
}
githubMilestone {
id = "org.springframework.github.milestone"
implementationClass = "org.springframework.gradle.github.milestones.GitHubMilestonePlugin"
}
githubChangelog {
id = "org.springframework.github.changelog"
implementationClass = "org.springframework.gradle.github.changelog.GitHubChangelogPlugin"
}
githubRelease {
id = "org.springframework.github.release"
implementationClass = "org.springframework.gradle.github.release.GitHubReleasePlugin"
}
s101 {
id = "s101"
implementationClass = "s101.S101Plugin"
@@ -95,11 +71,14 @@ dependencies {
implementation libs.com.github.spullara.mustache.java.compiler
implementation libs.io.spring.javaformat.spring.javaformat.gradle.plugin
implementation libs.io.spring.nohttp.nohttp.gradle
implementation libs.net.sourceforge.htmlunit
implementation (libs.net.sourceforge.htmlunit) {
exclude group: 'org.eclipse.jetty.websocket', module: 'websocket-client'
}
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
implementation libs.io.spring.security.release.plugin
testImplementation platform(libs.org.junit.junit.bom)
testImplementation platform(libs.org.mockito.mockito.bom)
@@ -21,23 +21,100 @@ import org.gradle.api.publish.maven.plugins.MavenPublishPlugin
class ArtifactoryPlugin implements Plugin<Project> {
private static final String ARTIFACTORY_URL_NAME = "ARTIFACTORY_URL"
private static final String ARTIFACTORY_SNAPSHOT_REPOSITORY = "ARTIFACTORY_SNAPSHOT_REPOSITORY"
private static final String ARTIFACTORY_MILESTONE_REPOSITORY = "ARTIFACTORY_MILESTONE_REPOSITORY"
private static final String ARTIFACTORY_RELEASE_REPOSITORY = "ARTIFACTORY_RELEASE_REPOSITORY"
private static final String ARTIFACTORY_PROJECT_KEY = "ARTIFACTORY_PROJECT_KEY"
private static final String ARTIFACTORY_BUILD_NAME = "ARTIFACTORY_BUILD_NAME"
private static final String ARTIFACTORY_BUILD_NUMBER = "ARTIFACTORY_BUILD_NUMBER"
private static final String ARTIFACTORY_BUILD_URL = "ARTIFACTORY_BUILD_URL"
private static final String ARTIFACTORY_BUILD_AGENT_NAME = "ARTIFACTORY_BUILD_AGENT_NAME"
private static final String ARTIFACTORY_BUILD_AGENT_VERSION = "ARTIFACTORY_BUILD_AGENT_VERSION"
private static final String ARTIFACTORY_USER_AGENT_NAME = "ARTIFACTORY_USER_AGENT_NAME"
private static final String ARTIFACTORY_USER_AGENT_VERSION = "ARTIFACTORY_USER_AGENT_VERSION"
private static final String ARTIFACTORY_VCS_REVISION = "ARTIFACTORY_VCS_REVISION"
private static final String DEFAULT_ARTIFACTORY_URL = "https://repo.spring.io"
private static final String DEFAULT_ARTIFACTORY_SNAPSHOT_REPOSITORY = "libs-snapshot-local"
private static final String DEFAULT_ARTIFACTORY_MILESTONE_REPOSITORY = "libs-milestone-local"
private static final String DEFAULT_ARTIFACTORY_RELEASE_REPOSITORY = "libs-release-local"
@Override
void apply(Project project) {
project.plugins.apply('com.jfrog.artifactory')
String name = Utils.getProjectName(project);
boolean isSnapshot = Utils.isSnapshot(project);
boolean isMilestone = Utils.isMilestone(project);
Map<String, String> env = System.getenv()
String artifactoryUrl = env.getOrDefault(ARTIFACTORY_URL_NAME, DEFAULT_ARTIFACTORY_URL)
String snapshotRepository = env.getOrDefault(ARTIFACTORY_SNAPSHOT_REPOSITORY, DEFAULT_ARTIFACTORY_SNAPSHOT_REPOSITORY)
String milestoneRepository = env.getOrDefault(ARTIFACTORY_MILESTONE_REPOSITORY, DEFAULT_ARTIFACTORY_MILESTONE_REPOSITORY)
String releaseRepository = env.getOrDefault(ARTIFACTORY_RELEASE_REPOSITORY, DEFAULT_ARTIFACTORY_RELEASE_REPOSITORY)
String projectKey = env.get(ARTIFACTORY_PROJECT_KEY)
String buildName = env.get(ARTIFACTORY_BUILD_NAME)
String buildNumber = env.get(ARTIFACTORY_BUILD_NUMBER)
String buildUrl = env.get(ARTIFACTORY_BUILD_URL)
String buildAgentName = env.get(ARTIFACTORY_BUILD_AGENT_NAME)
String buildAgentVersion = env.get(ARTIFACTORY_BUILD_AGENT_VERSION)
String userAgentName = env.get(ARTIFACTORY_USER_AGENT_NAME)
String userAgentVersion = env.get(ARTIFACTORY_USER_AGENT_VERSION)
String vcsRevision = env.get(ARTIFACTORY_VCS_REVISION)
project.artifactory {
contextUrl = 'https://repo.spring.io'
contextUrl = artifactoryUrl
publish {
repository {
repoKey = isSnapshot ? 'libs-snapshot-local' : isMilestone ? 'libs-milestone-local' : 'libs-release-local'
repoKey = isSnapshot ? snapshotRepository : isMilestone ? milestoneRepository : releaseRepository
if(project.hasProperty('artifactoryUsername')) {
username = artifactoryUsername
password = artifactoryPassword
}
}
}
def buildInfo = clientConfig.info
if (projectKey != null) {
buildInfo.setProject(projectKey)
}
if (buildName != null) {
buildInfo.setBuildName(buildName)
}
if (buildNumber != null) {
buildInfo.setBuildNumber(buildNumber)
}
if (buildUrl != null) {
buildInfo.setBuildUrl(buildUrl)
}
if (buildAgentName != null) {
buildInfo.setBuildAgentName(buildAgentName)
}
if (buildAgentVersion != null) {
buildInfo.setBuildAgentVersion(buildAgentVersion)
}
if (userAgentName != null) {
buildInfo.setAgentName(userAgentName)
}
if (userAgentVersion != null) {
buildInfo.setAgentVersion(userAgentVersion)
}
if (vcsRevision != null) {
buildInfo.setVcsRevision(vcsRevision)
}
}
project.plugins.withType(MavenPublishPlugin) {
project.artifactory {
@@ -1,71 +0,0 @@
package org.springframework.gradle.antora;
import org.gradle.api.Action;
import org.gradle.api.GradleException;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.Task;
import org.gradle.api.tasks.TaskProvider;
import org.gradle.language.base.plugins.LifecycleBasePlugin;
public class AntoraVersionPlugin implements Plugin<Project> {
public static final String ANTORA_CHECK_VERSION_TASK_NAME = "antoraCheckVersion";
@Override
public void apply(Project project) {
TaskProvider<CheckAntoraVersionTask> antoraCheckVersion = project.getTasks().register(ANTORA_CHECK_VERSION_TASK_NAME, CheckAntoraVersionTask.class, new Action<CheckAntoraVersionTask>() {
@Override
public void execute(CheckAntoraVersionTask antoraCheckVersion) {
antoraCheckVersion.setGroup(LifecycleBasePlugin.VERIFICATION_GROUP);
antoraCheckVersion.setDescription("Checks the antora.yml version properties match the Gradle version");
antoraCheckVersion.getAntoraVersion().convention(project.provider(() -> getDefaultAntoraVersion(project)));
antoraCheckVersion.getAntoraPrerelease().convention(project.provider(() -> getDefaultAntoraPrerelease(project)));
antoraCheckVersion.getAntoraDisplayVersion().convention(project.provider(() -> getDefaultAntoraDisplayVersion(project)));
antoraCheckVersion.getAntoraYmlFile().fileProvider(project.provider(() -> project.file("antora.yml")));
}
});
project.getPlugins().withType(LifecycleBasePlugin.class, new Action<LifecycleBasePlugin>() {
@Override
public void execute(LifecycleBasePlugin lifecycleBasePlugin) {
project.getTasks().named(LifecycleBasePlugin.CHECK_TASK_NAME).configure(new Action<Task>() {
@Override
public void execute(Task check) {
check.dependsOn(antoraCheckVersion);
}
});
}
});
project.getTasks().register("antoraUpdateVersion", UpdateAntoraVersionTask.class, new Action<UpdateAntoraVersionTask>() {
@Override
public void execute(UpdateAntoraVersionTask antoraUpdateVersion) {
antoraUpdateVersion.setGroup("Release");
antoraUpdateVersion.setDescription("Updates the antora.yml version properties to match the Gradle version");
antoraUpdateVersion.getAntoraYmlFile().fileProvider(project.provider(() -> project.file("antora.yml")));
}
});
}
private static String getDefaultAntoraVersion(Project project) {
String projectVersion = getProjectVersion(project);
return AntoraVersionUtils.getDefaultAntoraVersion(projectVersion);
}
private static String getDefaultAntoraPrerelease(Project project) {
String projectVersion = getProjectVersion(project);
return AntoraVersionUtils.getDefaultAntoraPrerelease(projectVersion);
}
private static String getDefaultAntoraDisplayVersion(Project project) {
String projectVersion = getProjectVersion(project);
return AntoraVersionUtils.getDefaultAntoraDisplayVersion(projectVersion);
}
private static String getProjectVersion(Project project) {
Object projectVersion = project.getVersion();
if (projectVersion == null) {
throw new GradleException("Please define antoraVersion and antoraPrerelease on " + ANTORA_CHECK_VERSION_TASK_NAME + " or provide a Project version so they can be defaulted");
}
return String.valueOf(projectVersion);
}
}
@@ -1,55 +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.gradle.antora;
public class AntoraVersionUtils {
public static String getDefaultAntoraVersion(String projectVersion) {
int preReleaseIndex = getSnapshotIndex(projectVersion);
return isSnapshot(projectVersion) ? projectVersion.substring(0, preReleaseIndex) : projectVersion;
}
public static String getDefaultAntoraPrerelease(String projectVersion) {
if (isSnapshot(projectVersion)) {
int preReleaseIndex = getSnapshotIndex(projectVersion);
return projectVersion.substring(preReleaseIndex);
}
if (isPreRelease(projectVersion)) {
return Boolean.TRUE.toString();
}
return null;
}
public static String getDefaultAntoraDisplayVersion(String projectVersion) {
if (!isSnapshot(projectVersion) && isPreRelease(projectVersion)) {
return getDefaultAntoraVersion(projectVersion);
}
return null;
}
private static boolean isSnapshot(String projectVersion) {
return getSnapshotIndex(projectVersion) >= 0;
}
private static int getSnapshotIndex(String projectVersion) {
return projectVersion.lastIndexOf("-SNAPSHOT");
}
private static boolean isPreRelease(String projectVersion) {
return projectVersion.lastIndexOf("-") >= 0;
}
}
@@ -1,96 +0,0 @@
package org.springframework.gradle.antora;
import org.gradle.api.DefaultTask;
import org.gradle.api.GradleException;
import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.provider.Property;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.InputFile;
import org.gradle.api.tasks.Optional;
import org.gradle.api.tasks.TaskAction;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.constructor.Constructor;
import org.yaml.snakeyaml.representer.Representer;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
public abstract class CheckAntoraVersionTask extends DefaultTask {
@TaskAction
public void check() throws FileNotFoundException {
File antoraYmlFile = getAntoraYmlFile().getAsFile().get();
String expectedAntoraVersion = getAntoraVersion().get();
String expectedAntoraPrerelease = getAntoraPrerelease().getOrElse(null);
String expectedAntoraDisplayVersion = getAntoraDisplayVersion().getOrElse(null);
Representer representer = new Representer();
representer.getPropertyUtils().setSkipMissingProperties(true);
Yaml yaml = new Yaml(new Constructor(AntoraYml.class), representer);
AntoraYml antoraYml = yaml.load(new FileInputStream(antoraYmlFile));
String actualAntoraPrerelease = antoraYml.getPrerelease();
boolean preReleaseMatches = antoraYml.getPrerelease() == null && expectedAntoraPrerelease == null ||
(actualAntoraPrerelease != null && actualAntoraPrerelease.equals(expectedAntoraPrerelease));
String actualAntoraDisplayVersion = antoraYml.getDisplay_version();
boolean displayVersionMatches = antoraYml.getDisplay_version() == null && expectedAntoraDisplayVersion == null ||
(actualAntoraDisplayVersion != null && actualAntoraDisplayVersion.equals(expectedAntoraDisplayVersion));
String actualAntoraVersion = antoraYml.getVersion();
if (!preReleaseMatches ||
!displayVersionMatches ||
!expectedAntoraVersion.equals(actualAntoraVersion)) {
throw new GradleException("The Gradle version of '" + getProject().getVersion() + "' should have version: '"
+ expectedAntoraVersion + "' prerelease: '" + expectedAntoraPrerelease + "' display_version: '"
+ expectedAntoraDisplayVersion + "' defined in " + antoraYmlFile + " but got version: '"
+ actualAntoraVersion + "' prerelease: '" + actualAntoraPrerelease + "' display_version: '" + actualAntoraDisplayVersion + "'");
}
}
@InputFile
public abstract RegularFileProperty getAntoraYmlFile();
@Input
public abstract Property<String> getAntoraVersion();
@Input
@Optional
public abstract Property<String> getAntoraPrerelease();
@Input
@Optional
public abstract Property<String> getAntoraDisplayVersion();
public static class AntoraYml {
private String version;
private String prerelease;
private String display_version;
public String getVersion() {
return version;
}
public void setVersion(String version) {
this.version = version;
}
public String getPrerelease() {
return prerelease;
}
public void setPrerelease(String prerelease) {
this.prerelease = prerelease;
}
public String getDisplay_version() {
return display_version;
}
public void setDisplay_version(String display_version) {
this.display_version = display_version;
}
}
}
@@ -1,138 +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.gradle.antora;
import org.gradle.api.DefaultTask;
import org.gradle.api.GradleException;
import org.gradle.api.Project;
import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.provider.Property;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.InputFile;
import org.gradle.api.tasks.Optional;
import org.gradle.api.tasks.OutputFile;
import org.gradle.api.tasks.TaskAction;
import org.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.constructor.Constructor;
import org.yaml.snakeyaml.nodes.NodeTuple;
import org.yaml.snakeyaml.nodes.Tag;
import org.yaml.snakeyaml.representer.Representer;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import org.springframework.gradle.github.milestones.NextVersionYml;
public abstract class UpdateAntoraVersionTask extends DefaultTask {
@TaskAction
public void update() throws IOException {
String projectVersion = getProject().getVersion().toString();
File antoraYmlFile = getAntoraYmlFile().getAsFile().get();
String updatedAntoraVersion = AntoraVersionUtils.getDefaultAntoraVersion(projectVersion);
String updatedAntoraPrerelease = AntoraVersionUtils.getDefaultAntoraPrerelease(projectVersion);
String updatedAntoraDisplayVersion = AntoraVersionUtils.getDefaultAntoraDisplayVersion(projectVersion);
Representer representer = new Representer();
representer.getPropertyUtils().setSkipMissingProperties(true);
Yaml yaml = new Yaml(new Constructor(AntoraYml.class), representer);
AntoraYml antoraYml = yaml.load(new FileInputStream(antoraYmlFile));
System.out.println("Updating the version parameters in " + antoraYmlFile.getName() + " to version: "
+ updatedAntoraVersion + ", prerelease: " + updatedAntoraPrerelease + ", display_version: "
+ updatedAntoraDisplayVersion);
antoraYml.setVersion(updatedAntoraVersion);
antoraYml.setPrerelease(updatedAntoraPrerelease);
antoraYml.setDisplay_version(updatedAntoraDisplayVersion);
FileWriter outputWriter = new FileWriter(antoraYmlFile);
getYaml().dump(antoraYml, outputWriter);
}
@InputFile
public abstract RegularFileProperty getAntoraYmlFile();
public static class AntoraYml {
private String name;
private String version;
private String prerelease;
private String display_version;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getVersion() {
return version;
}
public void setVersion(String version) {
this.version = version;
}
public String getPrerelease() {
return prerelease;
}
public void setPrerelease(String prerelease) {
this.prerelease = prerelease;
}
public String getDisplay_version() {
return display_version;
}
public void setDisplay_version(String display_version) {
this.display_version = display_version;
}
}
private Yaml getYaml() {
Representer representer = new Representer() {
@Override
protected NodeTuple representJavaBeanProperty(Object javaBean,
org.yaml.snakeyaml.introspector.Property property, Object propertyValue, Tag customTag) {
// Don't write out null values
if (propertyValue == null) {
return null;
}
else {
return super.representJavaBeanProperty(javaBean, property, propertyValue, customTag);
}
}
};
representer.addClassTag(AntoraYml.class, Tag.MAP);
DumperOptions ymlOptions = new DumperOptions();
ymlOptions.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
ymlOptions.setDefaultScalarStyle(DumperOptions.ScalarStyle.SINGLE_QUOTED);
return new Yaml(representer, ymlOptions);
}
}
@@ -1,70 +0,0 @@
package org.springframework.gradle.github;
import java.io.Serializable;
public class RepositoryRef implements Serializable {
private static final long serialVersionUID = 7151218536746822797L;
private String owner;
private String name;
public RepositoryRef() {
}
public RepositoryRef(String owner, String name) {
this.owner = owner;
this.name = name;
}
public String getOwner() {
return owner;
}
public void setOwner(String owner) {
this.owner = owner;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "RepositoryRef{" +
"owner='" + owner + '\'' +
", name='" + name + '\'' +
'}';
}
public static RepositoryRefBuilder owner(String owner) {
return new RepositoryRefBuilder().owner(owner);
}
public static final class RepositoryRefBuilder {
private String owner;
private String repository;
private RepositoryRefBuilder() {
}
private RepositoryRefBuilder owner(String owner) {
this.owner = owner;
return this;
}
public RepositoryRefBuilder repository(String repository) {
this.repository = repository;
return this;
}
public RepositoryRef build() {
return new RepositoryRef(owner, repository);
}
}
}
@@ -1,103 +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.gradle.github.changelog;
import java.io.File;
import java.nio.file.Paths;
import org.gradle.api.Action;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.Task;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.artifacts.DependencySet;
import org.gradle.api.artifacts.repositories.ExclusiveContentRepository;
import org.gradle.api.artifacts.repositories.InclusiveRepositoryContentDescriptor;
import org.gradle.api.artifacts.repositories.IvyArtifactRepository;
import org.gradle.api.artifacts.repositories.IvyPatternRepositoryLayout;
import org.gradle.api.tasks.JavaExec;
public class GitHubChangelogPlugin implements Plugin<Project> {
public static final String CHANGELOG_GENERATOR_CONFIGURATION_NAME = "changelogGenerator";
public static final String RELEASE_NOTES_PATH = "changelog/release-notes.md";
@Override
public void apply(Project project) {
createRepository(project);
createChangelogGeneratorConfiguration(project);
project.getTasks().register("generateChangelog", JavaExec.class, new Action<JavaExec>() {
@Override
public void execute(JavaExec generateChangelog) {
File outputFile = project.file(Paths.get(project.getBuildDir().getPath(), RELEASE_NOTES_PATH));
outputFile.getParentFile().mkdirs();
generateChangelog.setGroup("Release");
generateChangelog.setDescription("Generates the changelog");
generateChangelog.setWorkingDir(project.getRootDir());
generateChangelog.classpath(project.getConfigurations().getAt(CHANGELOG_GENERATOR_CONFIGURATION_NAME));
generateChangelog.doFirst(new Action<Task>() {
@Override
public void execute(Task task) {
generateChangelog.args("--spring.config.location=scripts/release/release-notes-sections.yml", project.property("nextVersion"), outputFile.toString());
}
});
}
});
}
private void createChangelogGeneratorConfiguration(Project project) {
project.getConfigurations().create(CHANGELOG_GENERATOR_CONFIGURATION_NAME, new Action<Configuration>() {
@Override
public void execute(Configuration configuration) {
configuration.defaultDependencies(new Action<DependencySet>() {
@Override
public void execute(DependencySet dependencies) {
dependencies.add(project.getDependencies().create("spring-io:github-changelog-generator:0.0.6"));
}
});
}
});
}
private void createRepository(Project project) {
IvyArtifactRepository repository = project.getRepositories().ivy(new Action<IvyArtifactRepository>() {
@Override
public void execute(IvyArtifactRepository repository) {
repository.setUrl("https://github.com/");
repository.patternLayout(new Action<IvyPatternRepositoryLayout>() {
@Override
public void execute(IvyPatternRepositoryLayout layout) {
layout.artifact("[organization]/[artifact]/releases/download/v[revision]/[artifact].[ext]");
}
});
repository.getMetadataSources().artifact();
}
});
project.getRepositories().exclusiveContent(new Action<ExclusiveContentRepository>() {
@Override
public void execute(ExclusiveContentRepository exclusiveContentRepository) {
exclusiveContentRepository.forRepositories(repository);
exclusiveContentRepository.filter(new Action<InclusiveRepositoryContentDescriptor>() {
@Override
public void execute(InclusiveRepositoryContentDescriptor descriptor) {
descriptor.includeGroup("spring-io");
}
});
}
});
}
}
@@ -1,271 +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.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;
import com.google.common.reflect.TypeToken;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import okhttp3.Interceptor;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import org.springframework.gradle.github.RepositoryRef;
public class GitHubMilestoneApi {
private String baseUrl = "https://api.github.com";
private OkHttpClient client;
private final Gson gson = new GsonBuilder()
.registerTypeAdapter(LocalDate.class, new LocalDateAdapter().nullSafe())
.registerTypeAdapter(LocalDateTime.class, new LocalDateTimeAdapter().nullSafe())
.create();
public GitHubMilestoneApi() {
this.client = new OkHttpClient.Builder().build();
}
public GitHubMilestoneApi(String gitHubToken) {
this.client = new OkHttpClient.Builder()
.addInterceptor(new AuthorizationInterceptor(gitHubToken))
.build();
}
public void setBaseUrl(String baseUrl) {
this.baseUrl = baseUrl;
}
public long findMilestoneNumberByTitle(RepositoryRef repositoryRef, String milestoneTitle) {
List<Milestone> milestones = this.getMilestones(repositoryRef);
for (Milestone milestone : milestones) {
if (milestoneTitle.equals(milestone.getTitle())) {
return milestone.getNumber();
}
}
if (milestones.size() <= 100) {
throw new RuntimeException("Could not find open milestone with title " + milestoneTitle + " for repository " + repositoryRef + " Got " + milestones);
}
throw new RuntimeException("It is possible there are too many open milestones (only 100 are supported). Could not find open milestone with title " + milestoneTitle + " for repository " + repositoryRef + " Got " + milestones);
}
public List<Milestone> getMilestones(RepositoryRef repositoryRef) {
String url = this.baseUrl + "/repos/" + repositoryRef.getOwner() + "/" + repositoryRef.getName() + "/milestones?per_page=100";
Request request = new Request.Builder().get().url(url)
.build();
try {
Response response = this.client.newCall(request).execute();
if (!response.isSuccessful()) {
throw new RuntimeException("Could not retrieve milestones for repository " + repositoryRef + ". Response " + response);
}
return this.gson.fromJson(response.body().charStream(), new TypeToken<List<Milestone>>(){}.getType());
} catch (IOException e) {
throw new RuntimeException("Could not retrieve milestones for repository " + repositoryRef, e);
}
}
public boolean isOpenIssuesForMilestoneNumber(RepositoryRef repositoryRef, long milestoneNumber) {
String url = this.baseUrl + "/repos/" + repositoryRef.getOwner() + "/" + repositoryRef.getName() + "/issues?per_page=1&milestone=" + milestoneNumber;
Request request = new Request.Builder().get().url(url)
.build();
try {
Response response = this.client.newCall(request).execute();
if (!response.isSuccessful()) {
throw new RuntimeException("Could not find issues for milestone number " + milestoneNumber + " for repository " + repositoryRef + ". Response " + response);
}
List<Object> issues = this.gson.fromJson(response.body().charStream(), new TypeToken<List<Object>>(){}.getType());
return !issues.isEmpty();
} catch (IOException e) {
throw new RuntimeException("Could not find issues for milestone number " + milestoneNumber + " for repository " + repositoryRef, e);
}
}
/**
* Check if the given milestone is due today or past due.
*
* @param repositoryRef The repository owner/name
* @param milestoneTitle The title of the milestone whose due date should be checked
* @return true if the given milestone is due today or past due, false otherwise
*/
public boolean isMilestoneDueToday(RepositoryRef repositoryRef, String milestoneTitle) {
String url = this.baseUrl + "/repos/" + repositoryRef.getOwner() + "/" + repositoryRef.getName()
+ "/milestones?per_page=100";
Request request = new Request.Builder().get().url(url).build();
try {
Response response = this.client.newCall(request).execute();
if (!response.isSuccessful()) {
throw new RuntimeException("Could not find milestone with title " + milestoneTitle + " for repository "
+ repositoryRef + ". Response " + response);
}
List<Milestone> milestones = this.gson.fromJson(response.body().charStream(),
new TypeToken<List<Milestone>>() {
}.getType());
for (Milestone milestone : milestones) {
if (milestoneTitle.equals(milestone.getTitle())) {
LocalDate today = LocalDate.now();
return milestone.getDueOn() != null && today.compareTo(milestone.getDueOn().toLocalDate()) >= 0;
}
}
if (milestones.size() <= 100) {
throw new RuntimeException("Could not find open milestone with title " + milestoneTitle
+ " for repository " + repositoryRef + " Got " + milestones);
}
throw new RuntimeException(
"It is possible there are too many open milestones open (only 100 are supported). Could not find open milestone with title "
+ milestoneTitle + " for repository " + repositoryRef + " Got " + milestones);
}
catch (IOException e) {
throw new RuntimeException(
"Could not find open milestone with title " + milestoneTitle + " for repository " + repositoryRef,
e);
}
}
/**
* Calculate the next release version based on the current version.
*
* The current version must conform to the pattern MAJOR.MINOR.PATCH-SNAPSHOT. If the
* current version is a snapshot of a patch release, then the patch release will be
* returned. For example, if the current version is 5.6.1-SNAPSHOT, then 5.6.1 will be
* returned. If the current version is a snapshot of a version that is not GA (i.e the
* PATCH segment is 0), then GitHub will be queried to find the next milestone or
* release candidate. If no pre-release versions are found, then the next version will
* be assumed to be the GA.
* @param repositoryRef The repository owner/name
* @param currentVersion The current project version
* @return the next matching milestone/release candidate or null if none exist
*/
public String getNextReleaseMilestone(RepositoryRef repositoryRef, String currentVersion) {
Pattern snapshotPattern = Pattern.compile("^([0-9]+)\\.([0-9]+)\\.([0-9]+)-SNAPSHOT$");
Matcher snapshotVersion = snapshotPattern.matcher(currentVersion);
if (snapshotVersion.find()) {
String patchSegment = snapshotVersion.group(3);
String currentVersionNoIdentifier = currentVersion.replace("-SNAPSHOT", "");
if (patchSegment.equals("0")) {
String nextPreRelease = getNextPreRelease(repositoryRef, currentVersionNoIdentifier);
return nextPreRelease != null ? nextPreRelease : currentVersionNoIdentifier;
}
else {
return currentVersionNoIdentifier;
}
}
else {
throw new IllegalStateException(
"Cannot calculate next release version because the current project version does not conform to the expected format");
}
}
/**
* Calculate the next pre-release version (milestone or release candidate) based on
* the current version.
*
* The current version must conform to the pattern MAJOR.MINOR.PATCH. If no matching
* milestone or release candidate is found in GitHub then it will return null.
* @param repositoryRef The repository owner/name
* @param currentVersionNoIdentifier The current project version without any
* identifier
* @return the next matching milestone/release candidate or null if none exist
*/
private String getNextPreRelease(RepositoryRef repositoryRef, String currentVersionNoIdentifier) {
String url = this.baseUrl + "/repos/" + repositoryRef.getOwner() + "/" + repositoryRef.getName()
+ "/milestones?per_page=100";
Request request = new Request.Builder().get().url(url).build();
try {
Response response = this.client.newCall(request).execute();
if (!response.isSuccessful()) {
throw new RuntimeException(
"Could not get milestones for repository " + repositoryRef + ". Response " + response);
}
List<Milestone> milestones = this.gson.fromJson(response.body().charStream(),
new TypeToken<List<Milestone>>() {
}.getType());
Optional<String> nextPreRelease = milestones.stream().map(Milestone::getTitle)
.filter(m -> m.startsWith(currentVersionNoIdentifier + "-"))
.min((m1, m2) -> {
Pattern preReleasePattern = Pattern.compile("^.*-([A-Z]+)([0-9]+)$");
Matcher matcher1 = preReleasePattern.matcher(m1);
Matcher matcher2 = preReleasePattern.matcher(m2);
matcher1.find();
matcher2.find();
if (!matcher1.group(1).equals(matcher2.group(1))) {
return m1.compareTo(m2);
}
else {
return Integer.valueOf(matcher1.group(2)).compareTo(Integer.valueOf(matcher2.group(2)));
}
});
return nextPreRelease.orElse(null);
}
catch (IOException e) {
throw new RuntimeException("Could not find open milestones with for repository " + repositoryRef, e);
}
}
/**
* Create a milestone.
*
* @param repository The repository owner/name
* @param milestone The milestone containing a title and due date
*/
public void createMilestone(RepositoryRef repository, Milestone milestone) {
String url = this.baseUrl + "/repos/" + repository.getOwner() + "/" + repository.getName() + "/milestones";
String json = this.gson.toJson(milestone);
RequestBody body = RequestBody.create(MediaType.parse("application/json"), json);
Request request = new Request.Builder().url(url).post(body).build();
try {
Response response = this.client.newCall(request).execute();
if (!response.isSuccessful()) {
throw new RuntimeException(String.format("Could not create milestone %s for repository %s/%s. Got response %s",
milestone.getTitle(), repository.getOwner(), repository.getName(), response));
}
} catch (IOException ex) {
throw new RuntimeException(String.format("Could not create release %s for repository %s/%s",
milestone.getTitle(), repository.getOwner(), repository.getName()), ex);
}
}
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,110 +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.gradle.github.milestones;
import org.gradle.api.Action;
import org.gradle.api.DefaultTask;
import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.InputFile;
import org.gradle.api.tasks.Optional;
import org.gradle.api.tasks.OutputFile;
import org.gradle.api.tasks.TaskAction;
import org.gradle.work.DisableCachingByDefault;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.constructor.Constructor;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import org.springframework.gradle.github.RepositoryRef;
@DisableCachingByDefault(because = "the due date needs to be checked every time in case it changes")
public abstract class GitHubMilestoneHasNoOpenIssuesTask extends DefaultTask {
@Input
private RepositoryRef repository = new RepositoryRef();
@Input @Optional
private String milestoneTitle;
@InputFile @Optional
public abstract RegularFileProperty getNextVersionFile();
@Input @Optional
private String gitHubAccessToken;
@OutputFile
public abstract RegularFileProperty getIsOpenIssuesFile();
private GitHubMilestoneApi milestones = new GitHubMilestoneApi();
@TaskAction
public void checkHasNoOpenIssues() throws IOException {
if (this.milestoneTitle == null) {
File nextVersionFile = getNextVersionFile().getAsFile().get();
Yaml yaml = new Yaml(new Constructor(NextVersionYml.class));
NextVersionYml nextVersionYml = yaml.load(new FileInputStream(nextVersionFile));
String nextVersion = nextVersionYml.getVersion();
if (nextVersion == null) {
throw new IllegalArgumentException(
"Could not find version property in provided file " + nextVersionFile.getName());
}
this.milestoneTitle = nextVersion;
}
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));
if (isOpenIssues) {
System.out.println("The repository " + this.repository + " has open issues for milestone with the title " + this.milestoneTitle + " and number " + milestoneNumber);
}
else {
System.out.println("The repository " + this.repository + " has no open issues for milestone with the title " + this.milestoneTitle + " and number " + milestoneNumber);
}
}
public RepositoryRef getRepository() {
return repository;
}
public void repository(Action<RepositoryRef> repository) {
repository.execute(this.repository);
}
public void setRepository(RepositoryRef repository) {
this.repository = repository;
}
public String getMilestoneTitle() {
return milestoneTitle;
}
public void setMilestoneTitle(String milestoneTitle) {
this.milestoneTitle = milestoneTitle;
}
public String getGitHubAccessToken() {
return gitHubAccessToken;
}
public void setGitHubAccessToken(String gitHubAccessToken) {
this.gitHubAccessToken = gitHubAccessToken;
this.milestones = new GitHubMilestoneApi(gitHubAccessToken);
}
}
@@ -1,93 +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.gradle.github.milestones;
import org.gradle.api.Action;
import org.gradle.api.DefaultTask;
import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.Optional;
import org.gradle.api.tasks.OutputFile;
import org.gradle.api.tasks.TaskAction;
import org.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.nodes.Tag;
import org.yaml.snakeyaml.representer.Representer;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import org.springframework.gradle.github.RepositoryRef;
public abstract class GitHubMilestoneNextReleaseTask extends DefaultTask {
@Input
private RepositoryRef repository = new RepositoryRef();
@Input
@Optional
private String gitHubAccessToken;
private GitHubMilestoneApi milestones = new GitHubMilestoneApi();
@TaskAction
public void calculateNextReleaseMilestone() throws IOException {
String currentVersion = getProject().getVersion().toString();
String nextPreRelease = this.milestones.getNextReleaseMilestone(this.repository, currentVersion);
System.out.println("The next release milestone is: " + nextPreRelease);
NextVersionYml nextVersionYml = new NextVersionYml();
nextVersionYml.setVersion(nextPreRelease);
File outputFile = getNextReleaseFile().get().getAsFile();
FileWriter outputWriter = new FileWriter(outputFile);
Yaml yaml = getYaml();
yaml.dump(nextVersionYml, outputWriter);
}
@OutputFile
public abstract RegularFileProperty getNextReleaseFile();
public RepositoryRef getRepository() {
return repository;
}
public void repository(Action<RepositoryRef> repository) {
repository.execute(this.repository);
}
public void setRepository(RepositoryRef repository) {
this.repository = repository;
}
public String getGitHubAccessToken() {
return gitHubAccessToken;
}
public void setGitHubAccessToken(String gitHubAccessToken) {
this.gitHubAccessToken = gitHubAccessToken;
this.milestones = new GitHubMilestoneApi(gitHubAccessToken);
}
private Yaml getYaml() {
Representer representer = new Representer();
representer.addClassTag(NextVersionYml.class, Tag.MAP);
DumperOptions ymlOptions = new DumperOptions();
ymlOptions.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
return new Yaml(representer, ymlOptions);
}
}
@@ -1,102 +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.gradle.github.milestones;
import org.gradle.api.Action;
import org.gradle.api.DefaultTask;
import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.InputFile;
import org.gradle.api.tasks.Optional;
import org.gradle.api.tasks.OutputFile;
import org.gradle.api.tasks.TaskAction;
import org.gradle.work.DisableCachingByDefault;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.constructor.Constructor;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import org.springframework.gradle.github.RepositoryRef;
@DisableCachingByDefault(because = "the due date needs to be checked every time in case it changes")
public abstract class GitHubMilestoneNextVersionDueTodayTask extends DefaultTask {
@Input
private RepositoryRef repository = new RepositoryRef();
@Input
@Optional
private String gitHubAccessToken;
@InputFile
public abstract RegularFileProperty getNextVersionFile();
@OutputFile
public abstract RegularFileProperty getIsDueTodayFile();
private GitHubMilestoneApi milestones = new GitHubMilestoneApi();
@TaskAction
public void checkReleaseDueToday() throws IOException {
File nextVersionFile = getNextVersionFile().getAsFile().get();
Yaml yaml = new Yaml(new Constructor(NextVersionYml.class));
NextVersionYml nextVersionYml = yaml.load(new FileInputStream(nextVersionFile));
String nextVersion = nextVersionYml.getVersion();
if (nextVersion == null) {
throw new IllegalArgumentException(
"Could not find version property in provided file " + nextVersionFile.getName());
}
boolean milestoneDueToday = this.milestones.isMilestoneDueToday(this.repository, nextVersion);
Path isDueTodayPath = getIsDueTodayFile().getAsFile().get().toPath();
Files.writeString(isDueTodayPath, String.valueOf(milestoneDueToday));
if (milestoneDueToday) {
System.out.println("The milestone with the title " + nextVersion + " in the repository " + this.repository
+ " is due today");
}
else {
System.out.println("The milestone with the title " + nextVersion + " in the repository "
+ this.repository + " is not due yet");
}
}
public RepositoryRef getRepository() {
return repository;
}
public void repository(Action<RepositoryRef> repository) {
repository.execute(this.repository);
}
public void setRepository(RepositoryRef repository) {
this.repository = repository;
}
public String getGitHubAccessToken() {
return gitHubAccessToken;
}
public void setGitHubAccessToken(String gitHubAccessToken) {
this.gitHubAccessToken = gitHubAccessToken;
this.milestones = new GitHubMilestoneApi(gitHubAccessToken);
}
}
@@ -1,73 +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.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) {
TaskProvider<GitHubMilestoneNextReleaseTask> nextReleaseMilestoneTask = project.getTasks().register("gitHubNextReleaseMilestone", GitHubMilestoneNextReleaseTask.class, (gitHubMilestoneNextReleaseTask) -> {
gitHubMilestoneNextReleaseTask.doNotTrackState("API call to GitHub needs to check for new milestones every time");
gitHubMilestoneNextReleaseTask.setGroup("Release");
gitHubMilestoneNextReleaseTask.setDescription("Calculates the next release version based on the current version and outputs it to a yaml file");
gitHubMilestoneNextReleaseTask.getNextReleaseFile()
.fileProvider(project.provider(() -> project.file("next-release.yml")));
if (project.hasProperty("gitHubAccessToken")) {
gitHubMilestoneNextReleaseTask
.setGitHubAccessToken((String) project.findProperty("gitHubAccessToken"));
}
});
project.getTasks().register("gitHubCheckMilestoneHasNoOpenIssues", GitHubMilestoneHasNoOpenIssuesTask.class, (githubCheckMilestoneHasNoOpenIssues) -> {
githubCheckMilestoneHasNoOpenIssues.setGroup("Release");
githubCheckMilestoneHasNoOpenIssues.setDescription("Checks if there are any open issues for the specified repository and milestone");
githubCheckMilestoneHasNoOpenIssues.getIsOpenIssuesFile().value(project.getLayout().getBuildDirectory().file("github/milestones/is-open-issues"));
githubCheckMilestoneHasNoOpenIssues.setMilestoneTitle((String) project.findProperty("nextVersion"));
if (!project.hasProperty("nextVersion")) {
githubCheckMilestoneHasNoOpenIssues.getNextVersionFile().convention(
nextReleaseMilestoneTask.flatMap(GitHubMilestoneNextReleaseTask::getNextReleaseFile));
}
if (project.hasProperty("gitHubAccessToken")) {
githubCheckMilestoneHasNoOpenIssues.setGitHubAccessToken((String) project.findProperty("gitHubAccessToken"));
}
});
project.getTasks().register("gitHubCheckNextVersionDueToday", GitHubMilestoneNextVersionDueTodayTask.class, (gitHubMilestoneNextVersionDueTodayTask) -> {
gitHubMilestoneNextVersionDueTodayTask.setGroup("Release");
gitHubMilestoneNextVersionDueTodayTask.setDescription("Checks if the next release version is due today or past due, will fail if the next version is not due yet");
gitHubMilestoneNextVersionDueTodayTask.getIsDueTodayFile().value(project.getLayout().getBuildDirectory().file("github/milestones/is-due-today"));
gitHubMilestoneNextVersionDueTodayTask.getNextVersionFile().convention(
nextReleaseMilestoneTask.flatMap(GitHubMilestoneNextReleaseTask::getNextReleaseFile));
if (project.hasProperty("gitHubAccessToken")) {
gitHubMilestoneNextVersionDueTodayTask
.setGitHubAccessToken((String) project.findProperty("gitHubAccessToken"));
}
});
project.getTasks().register("scheduleNextRelease", ScheduleNextReleaseTask.class, (scheduleNextRelease) -> {
scheduleNextRelease.doNotTrackState("API call to GitHub needs to check for new milestones every time");
scheduleNextRelease.setGroup("Release");
scheduleNextRelease.setDescription("Schedule the next release (even months only) or release train (series of milestones starting in January or July) based on the current version");
scheduleNextRelease.setVersion((String) project.findProperty("nextVersion"));
scheduleNextRelease.setGitHubAccessToken((String) project.findProperty("gitHubAccessToken"));
});
}
}
@@ -1,23 +0,0 @@
package org.springframework.gradle.github.milestones;
import java.io.IOException;
import java.time.LocalDate;
import com.google.gson.TypeAdapter;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
/**
* @author Steve Riesenberg
*/
class LocalDateAdapter extends TypeAdapter<LocalDate> {
@Override
public void write(JsonWriter jsonWriter, LocalDate localDate) throws IOException {
jsonWriter.value(localDate.toString());
}
@Override
public LocalDate read(JsonReader jsonReader) throws IOException {
return LocalDate.parse(jsonReader.nextString());
}
}
@@ -1,25 +0,0 @@
package org.springframework.gradle.github.milestones;
import java.io.IOException;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import com.google.gson.TypeAdapter;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
/**
* @author Steve Riesenberg
*/
class LocalDateTimeAdapter extends TypeAdapter<LocalDateTime> {
@Override
public void write(JsonWriter jsonWriter, LocalDateTime localDateTime) throws IOException {
jsonWriter.value(localDateTime.atOffset(ZoneOffset.UTC).format(DateTimeFormatter.ISO_ZONED_DATE_TIME));
}
@Override
public LocalDateTime read(JsonReader jsonReader) throws IOException {
return LocalDateTime.parse(jsonReader.nextString(), DateTimeFormatter.ISO_ZONED_DATE_TIME);
}
}
@@ -1,67 +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.gradle.github.milestones;
import com.google.gson.annotations.SerializedName;
import java.time.LocalDateTime;
import java.util.Date;
/**
* @author Steve Riesenberg
*/
public class Milestone {
private String title;
private Long number;
@SerializedName("due_on")
private LocalDateTime dueOn;
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public Long getNumber() {
return number;
}
public void setNumber(Long number) {
this.number = number;
}
public LocalDateTime getDueOn() {
return dueOn;
}
public void setDueOn(LocalDateTime dueOn) {
this.dueOn = dueOn;
}
@Override
public String toString() {
return "Milestone{" +
"title='" + title + '\'' +
", number='" + number + '\'' +
", dueOn='" + dueOn + '\'' +
'}';
}
}
@@ -1,29 +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.gradle.github.milestones;
public class NextVersionYml {
private String version;
public String getVersion() {
return version;
}
public void setVersion(String version) {
this.version = version;
}
}
@@ -1,147 +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.gradle.github.milestones;
import java.time.LocalDate;
import java.time.LocalTime;
import org.gradle.api.Action;
import org.gradle.api.DefaultTask;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.TaskAction;
import org.springframework.gradle.github.RepositoryRef;
/**
* @author Steve Riesenberg
*/
public class ScheduleNextReleaseTask extends DefaultTask {
@Input
private RepositoryRef repository = new RepositoryRef();
@Input
private String gitHubAccessToken;
@Input
private String version;
@Input
private Integer weekOfMonth;
@Input
private Integer dayOfWeek;
@TaskAction
public void scheduleNextRelease() {
GitHubMilestoneApi gitHubMilestoneApi = new GitHubMilestoneApi(this.gitHubAccessToken);
String nextReleaseMilestone = gitHubMilestoneApi.getNextReleaseMilestone(this.repository, this.version);
// If the next release contains a dash (e.g. 5.6.0-RC1), it is already scheduled
if (nextReleaseMilestone.contains("-")) {
return;
}
// Check to see if a scheduled GA version already exists
boolean hasExistingMilestone = gitHubMilestoneApi.getMilestones(this.repository).stream()
.anyMatch(milestone -> nextReleaseMilestone.equals(milestone.getTitle()));
if (hasExistingMilestone) {
return;
}
// Next milestone is either a patch version or minor version
// Note: Major versions will be handled like minor and get a release
// train which can be manually updated to match the desired schedule.
if (nextReleaseMilestone.endsWith(".0")) {
// Create M1, M2, M3, RC1 and GA milestones for release train
getReleaseTrain(nextReleaseMilestone).getTrainDates().forEach((milestoneTitle, dueOn) -> {
Milestone milestone = new Milestone();
milestone.setTitle(milestoneTitle);
// Note: GitHub seems to store full date/time as UTC then displays
// as a date (no time) in your timezone, which means the date will
// not always be the same date as we intend.
// Using 12pm/noon UTC allows GitHub to schedule and display the
// correct date.
milestone.setDueOn(dueOn.atTime(LocalTime.NOON));
gitHubMilestoneApi.createMilestone(this.repository, milestone);
});
} else {
// Create GA milestone for patch release on the next even month
LocalDate startDate = LocalDate.now();
LocalDate dueOn = getReleaseTrain(nextReleaseMilestone).getNextReleaseDate(startDate);
Milestone milestone = new Milestone();
milestone.setTitle(nextReleaseMilestone);
milestone.setDueOn(dueOn.atTime(LocalTime.NOON));
gitHubMilestoneApi.createMilestone(this.repository, milestone);
}
}
private SpringReleaseTrain getReleaseTrain(String nextReleaseMilestone) {
SpringReleaseTrainSpec releaseTrainSpec =
SpringReleaseTrainSpec.builder()
.nextTrain()
.version(nextReleaseMilestone)
.weekOfMonth(this.weekOfMonth)
.dayOfWeek(this.dayOfWeek)
.build();
return new SpringReleaseTrain(releaseTrainSpec);
}
public RepositoryRef getRepository() {
return this.repository;
}
public void repository(Action<RepositoryRef> repository) {
repository.execute(this.repository);
}
public void setRepository(RepositoryRef repository) {
this.repository = repository;
}
public String getGitHubAccessToken() {
return this.gitHubAccessToken;
}
public void setGitHubAccessToken(String gitHubAccessToken) {
this.gitHubAccessToken = gitHubAccessToken;
}
public String getVersion() {
return this.version;
}
public void setVersion(String version) {
this.version = version;
}
public Integer getWeekOfMonth() {
return weekOfMonth;
}
public void setWeekOfMonth(Integer weekOfMonth) {
this.weekOfMonth = weekOfMonth;
}
public Integer getDayOfWeek() {
return dayOfWeek;
}
public void setDayOfWeek(Integer dayOfWeek) {
this.dayOfWeek = dayOfWeek;
}
}
@@ -1,136 +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.gradle.github.milestones;
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.Month;
import java.time.Year;
import java.time.temporal.TemporalAdjuster;
import java.time.temporal.TemporalAdjusters;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* Spring release train generator based on rules contained in a specification.
* <p>
* The rules are:
* <ol>
* <li>Train 1 (January-May) or 2 (July-November)</li>
* <li>Version number (e.g. 0.1.2, 1.0.0, etc.)</li>
* <li>Week of month (1st, 2nd, 3rd, 4th)</li>
* <li>Day of week (Monday-Friday)</li>
* <li>Year (e.g. 2020, 2021, etc.)</li>
* </ol>
*
* The release train generated will contain M1, M2, M3, RC1 and GA versions
* mapped to their respective dates in the train.
*
* @author Steve Riesenberg
*/
public final class SpringReleaseTrain {
private final SpringReleaseTrainSpec releaseTrainSpec;
public SpringReleaseTrain(SpringReleaseTrainSpec releaseTrainSpec) {
this.releaseTrainSpec = releaseTrainSpec;
}
/**
* Calculate release train dates based on the release train specification.
*
* @return A mapping of release milestones to scheduled release dates
*/
public Map<String, LocalDate> getTrainDates() {
Map<String, LocalDate> releaseDates = new LinkedHashMap<>();
switch (this.releaseTrainSpec.getTrain()) {
case ONE:
addTrainDate(releaseDates, "M1", Month.JANUARY);
addTrainDate(releaseDates, "M2", Month.FEBRUARY);
addTrainDate(releaseDates, "M3", Month.MARCH);
addTrainDate(releaseDates, "RC1", Month.APRIL);
addTrainDate(releaseDates, null, Month.MAY);
break;
case TWO:
addTrainDate(releaseDates, "M1", Month.JULY);
addTrainDate(releaseDates, "M2", Month.AUGUST);
addTrainDate(releaseDates, "M3", Month.SEPTEMBER);
addTrainDate(releaseDates, "RC1", Month.OCTOBER);
addTrainDate(releaseDates, null, Month.NOVEMBER);
break;
}
return releaseDates;
}
/**
* Determine if a given date matches the due date of given version.
*
* @param version The version number (e.g. 5.6.0-M1, 5.6.0, etc.)
* @param expectedDate The expected date
* @return true if the given date matches the due date of the given version, false otherwise
*/
public boolean isTrainDate(String version, LocalDate expectedDate) {
return expectedDate.isEqual(getTrainDates().get(version));
}
/**
* Calculate the next release date following the given date.
* <p>
* The next release date is always on an even month so that a patch release
* is the month after the GA version of a release train. This method does
* not consider the year of the release train, only the given start date.
*
* @param startDate The start date
* @return The next release date following the given date
*/
public LocalDate getNextReleaseDate(LocalDate startDate) {
LocalDate trainDate;
LocalDate currentDate = startDate;
do {
trainDate = calculateReleaseDate(
Year.of(currentDate.getYear()),
currentDate.getMonth(),
this.releaseTrainSpec.getDayOfWeek().getDayOfWeek(),
this.releaseTrainSpec.getWeekOfMonth().getDayOffset()
);
currentDate = currentDate.plusMonths(1);
} while (!trainDate.isAfter(startDate) || trainDate.getMonthValue() % 2 != 0);
return trainDate;
}
private void addTrainDate(Map<String, LocalDate> releaseDates, String milestone, Month month) {
LocalDate releaseDate = calculateReleaseDate(
this.releaseTrainSpec.getYear(),
month,
this.releaseTrainSpec.getDayOfWeek().getDayOfWeek(),
this.releaseTrainSpec.getWeekOfMonth().getDayOffset()
);
String suffix = (milestone == null) ? "" : "-" + milestone;
releaseDates.put(this.releaseTrainSpec.getVersion() + suffix, releaseDate);
}
private static LocalDate calculateReleaseDate(Year year, Month month, DayOfWeek dayOfWeek, int dayOffset) {
TemporalAdjuster nextMonday = TemporalAdjusters.nextOrSame(DayOfWeek.MONDAY);
TemporalAdjuster nextDayOfWeek = TemporalAdjusters.nextOrSame(dayOfWeek);
LocalDate firstDayOfMonth = year.atMonth(month).atDay(1);
LocalDate firstMondayOfMonth = firstDayOfMonth.with(nextMonday);
return firstMondayOfMonth.with(nextDayOfWeek).plusDays(dayOffset);
}
}
@@ -1,205 +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.gradle.github.milestones;
import java.time.LocalDate;
import java.time.Month;
import java.time.Year;
import org.springframework.util.Assert;
/**
* A specification for a release train.
*
* @author Steve Riesenberg
* @see SpringReleaseTrain
*/
public final class SpringReleaseTrainSpec {
private final Train train;
private final String version;
private final WeekOfMonth weekOfMonth;
private final DayOfWeek dayOfWeek;
private final Year year;
public SpringReleaseTrainSpec(Train train, String version, WeekOfMonth weekOfMonth, DayOfWeek dayOfWeek, Year year) {
this.train = train;
this.version = version;
this.weekOfMonth = weekOfMonth;
this.dayOfWeek = dayOfWeek;
this.year = year;
}
public Train getTrain() {
return train;
}
public String getVersion() {
return version;
}
public WeekOfMonth getWeekOfMonth() {
return weekOfMonth;
}
public DayOfWeek getDayOfWeek() {
return dayOfWeek;
}
public Year getYear() {
return year;
}
public static Builder builder() {
return new Builder();
}
public enum WeekOfMonth {
FIRST(0), SECOND(7), THIRD(14), FOURTH(21);
private final int dayOffset;
WeekOfMonth(int dayOffset) {
this.dayOffset = dayOffset;
}
public int getDayOffset() {
return dayOffset;
}
}
public enum DayOfWeek {
MONDAY(java.time.DayOfWeek.MONDAY),
TUESDAY(java.time.DayOfWeek.TUESDAY),
WEDNESDAY(java.time.DayOfWeek.WEDNESDAY),
THURSDAY(java.time.DayOfWeek.THURSDAY),
FRIDAY(java.time.DayOfWeek.FRIDAY);
private final java.time.DayOfWeek dayOfWeek;
DayOfWeek(java.time.DayOfWeek dayOfWeek) {
this.dayOfWeek = dayOfWeek;
}
public java.time.DayOfWeek getDayOfWeek() {
return dayOfWeek;
}
}
public enum Train {
ONE, TWO
}
public static class Builder {
private Train train;
private String version;
private WeekOfMonth weekOfMonth;
private DayOfWeek dayOfWeek;
private Year year;
private Builder() {
}
public Builder train(int train) {
switch (train) {
case 1: this.train = Train.ONE; break;
case 2: this.train = Train.TWO; break;
default: throw new IllegalArgumentException("Invalid train: " + train);
}
return this;
}
public Builder train(Train train) {
this.train = train;
return this;
}
public Builder nextTrain() {
// Search for next train starting with this month
return nextTrain(LocalDate.now().withDayOfMonth(1));
}
public Builder nextTrain(LocalDate startDate) {
Train nextTrain = null;
// Search for next train from a given start date
LocalDate currentDate = startDate;
while (nextTrain == null) {
if (currentDate.getMonth() == Month.JANUARY) {
nextTrain = Train.ONE;
} else if (currentDate.getMonth() == Month.JULY) {
nextTrain = Train.TWO;
}
currentDate = currentDate.plusMonths(1);
}
return train(nextTrain).year(currentDate.getYear());
}
public Builder version(String version) {
this.version = version;
return this;
}
public Builder weekOfMonth(int weekOfMonth) {
switch (weekOfMonth) {
case 1: this.weekOfMonth = WeekOfMonth.FIRST; break;
case 2: this.weekOfMonth = WeekOfMonth.SECOND; break;
case 3: this.weekOfMonth = WeekOfMonth.THIRD; break;
case 4: this.weekOfMonth = WeekOfMonth.FOURTH; break;
default: throw new IllegalArgumentException("Invalid weekOfMonth: " + weekOfMonth);
}
return this;
}
public Builder weekOfMonth(WeekOfMonth weekOfMonth) {
this.weekOfMonth = weekOfMonth;
return this;
}
public Builder dayOfWeek(int dayOfWeek) {
switch (dayOfWeek) {
case 1: this.dayOfWeek = DayOfWeek.MONDAY; break;
case 2: this.dayOfWeek = DayOfWeek.TUESDAY; break;
case 3: this.dayOfWeek = DayOfWeek.WEDNESDAY; break;
case 4: this.dayOfWeek = DayOfWeek.THURSDAY; break;
case 5: this.dayOfWeek = DayOfWeek.FRIDAY; break;
default: throw new IllegalArgumentException("Invalid dayOfWeek: " + dayOfWeek);
}
return this;
}
public Builder dayOfWeek(DayOfWeek dayOfWeek) {
this.dayOfWeek = dayOfWeek;
return this;
}
public Builder year(int year) {
this.year = Year.of(year);
return this;
}
public SpringReleaseTrainSpec build() {
Assert.notNull(train, "train cannot be null");
Assert.notNull(version, "version cannot be null");
Assert.notNull(weekOfMonth, "weekOfMonth cannot be null");
Assert.notNull(dayOfWeek, "dayOfWeek cannot be null");
Assert.notNull(year, "year cannot be null");
return new SpringReleaseTrainSpec(train, version, weekOfMonth, dayOfWeek, year);
}
}
}
@@ -1,130 +0,0 @@
/*
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.gradle.github.release;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import org.gradle.api.Action;
import org.gradle.api.DefaultTask;
import org.gradle.api.Project;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.Optional;
import org.gradle.api.tasks.TaskAction;
import org.springframework.gradle.github.RepositoryRef;
import org.springframework.gradle.github.changelog.GitHubChangelogPlugin;
/**
* @author Steve Riesenberg
*/
public class CreateGitHubReleaseTask extends DefaultTask {
@Input
private RepositoryRef repository = new RepositoryRef();
@Input @Optional
private String gitHubAccessToken;
@Input
private String version;
@Input @Optional
private String branch = "main";
@Input
private boolean createRelease = false;
@TaskAction
public void createGitHubRelease() {
String body = readReleaseNotes();
Release release = Release.tag(this.version)
.commit(this.branch)
.name(this.version)
.body(body)
.preRelease(this.version.contains("-"))
.build();
System.out.printf("%sCreating GitHub release for %s/%s@%s\n",
this.createRelease ? "" : "[DRY RUN] ",
this.repository.getOwner(),
this.repository.getName(),
this.version
);
System.out.printf(" Release Notes:\n\n----\n%s\n----\n\n", body.trim());
if (this.createRelease) {
GitHubReleaseApi github = new GitHubReleaseApi(this.gitHubAccessToken);
github.publishRelease(this.repository, release);
}
}
private String readReleaseNotes() {
Project project = getProject();
File inputFile = project.file(Paths.get(project.getBuildDir().getPath(), GitHubChangelogPlugin.RELEASE_NOTES_PATH));
try {
return Files.readString(inputFile.toPath());
} catch (IOException ex) {
throw new RuntimeException("Unable to read release notes from " + inputFile, ex);
}
}
public RepositoryRef getRepository() {
return repository;
}
public void repository(Action<RepositoryRef> repository) {
repository.execute(this.repository);
}
public void setRepository(RepositoryRef repository) {
this.repository = repository;
}
public String getGitHubAccessToken() {
return gitHubAccessToken;
}
public void setGitHubAccessToken(String gitHubAccessToken) {
this.gitHubAccessToken = gitHubAccessToken;
}
public String getVersion() {
return version;
}
public void setVersion(String version) {
this.version = version;
}
public String getBranch() {
return branch;
}
public void setBranch(String branch) {
this.branch = branch;
}
public boolean isCreateRelease() {
return createRelease;
}
public void setCreateRelease(boolean createRelease) {
this.createRelease = createRelease;
}
}
@@ -1,84 +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.gradle.github.release;
import org.gradle.api.Action;
import org.gradle.api.DefaultTask;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.TaskAction;
import org.springframework.gradle.github.RepositoryRef;
/**
* @author Steve Riesenberg
*/
public class DispatchGitHubWorkflowTask extends DefaultTask {
@Input
private RepositoryRef repository = new RepositoryRef();
@Input
private String gitHubAccessToken;
@Input
private String branch;
@Input
private String workflowId;
@TaskAction
public void dispatchGitHubWorkflow() {
GitHubActionsApi gitHubActionsApi = new GitHubActionsApi(this.gitHubAccessToken);
WorkflowDispatch workflowDispatch = new WorkflowDispatch(this.branch, null);
gitHubActionsApi.dispatchWorkflow(this.repository, this.workflowId, workflowDispatch);
}
public RepositoryRef getRepository() {
return repository;
}
public void repository(Action<RepositoryRef> repository) {
repository.execute(this.repository);
}
public void setRepository(RepositoryRef repository) {
this.repository = repository;
}
public String getGitHubAccessToken() {
return gitHubAccessToken;
}
public void setGitHubAccessToken(String gitHubAccessToken) {
this.gitHubAccessToken = gitHubAccessToken;
}
public String getBranch() {
return branch;
}
public void setBranch(String branch) {
this.branch = branch;
}
public String getWorkflowId() {
return workflowId;
}
public void setWorkflowId(String workflowId) {
this.workflowId = workflowId;
}
}
@@ -1,98 +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.gradle.github.release;
import java.io.IOException;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import okhttp3.Interceptor;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import org.springframework.gradle.github.RepositoryRef;
/**
* Manage GitHub Actions.
*
* @author Steve Riesenberg
*/
public class GitHubActionsApi {
private String baseUrl = "https://api.github.com";
private final OkHttpClient client;
private final Gson gson = new GsonBuilder().create();
public GitHubActionsApi() {
this.client = new OkHttpClient.Builder().build();
}
public GitHubActionsApi(String gitHubToken) {
this.client = new OkHttpClient.Builder()
.addInterceptor(new AuthorizationInterceptor(gitHubToken))
.build();
}
public void setBaseUrl(String baseUrl) {
this.baseUrl = baseUrl;
}
/**
* Create a workflow dispatch event.
*
* @param repository The repository owner/name
* @param workflowId The ID of the workflow or the name of the workflow file name
* @param workflowDispatch The workflow dispatch containing a ref (branch) and optional inputs
*/
public void dispatchWorkflow(RepositoryRef repository, String workflowId, WorkflowDispatch workflowDispatch) {
String url = this.baseUrl + "/repos/" + repository.getOwner() + "/" + repository.getName() + "/actions/workflows/" + workflowId + "/dispatches";
String json = this.gson.toJson(workflowDispatch);
RequestBody body = RequestBody.create(MediaType.parse("application/json"), json);
Request request = new Request.Builder().url(url).post(body).build();
try {
Response response = this.client.newCall(request).execute();
if (!response.isSuccessful()) {
throw new RuntimeException(String.format("Could not create workflow dispatch %s for repository %s/%s. Got response %s",
workflowId, repository.getOwner(), repository.getName(), response));
}
} catch (IOException ex) {
throw new RuntimeException(String.format("Could not create workflow dispatch %s for repository %s/%s",
workflowId, repository.getOwner(), repository.getName()), ex);
}
}
private static class AuthorizationInterceptor implements Interceptor {
private final String token;
public AuthorizationInterceptor(String token) {
this.token = token;
}
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request().newBuilder()
.addHeader("Authorization", "Bearer " + this.token)
.build();
return chain.proceed(request);
}
}
}
@@ -1,91 +0,0 @@
/*
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.gradle.github.release;
import java.io.IOException;
import com.google.gson.Gson;
import okhttp3.Interceptor;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import org.springframework.gradle.github.RepositoryRef;
/**
* Manage GitHub releases.
*
* @author Steve Riesenberg
*/
public class GitHubReleaseApi {
private String baseUrl = "https://api.github.com";
private final OkHttpClient httpClient;
private Gson gson = new Gson();
public GitHubReleaseApi(String gitHubAccessToken) {
this.httpClient = new OkHttpClient.Builder()
.addInterceptor(new AuthorizationInterceptor(gitHubAccessToken))
.build();
}
public void setBaseUrl(String baseUrl) {
this.baseUrl = baseUrl;
}
/**
* Publish a release with no binary attachments.
*
* @param repository The repository owner/name
* @param release The contents of the release
*/
public void publishRelease(RepositoryRef repository, Release release) {
String url = this.baseUrl + "/repos/" + repository.getOwner() + "/" + repository.getName() + "/releases";
String json = this.gson.toJson(release);
RequestBody body = RequestBody.create(MediaType.parse("application/json"), json);
Request request = new Request.Builder().url(url).post(body).build();
try {
Response response = this.httpClient.newCall(request).execute();
if (!response.isSuccessful()) {
throw new RuntimeException(String.format("Could not create release %s for repository %s/%s. Got response %s",
release.getName(), repository.getOwner(), repository.getName(), response));
}
} catch (IOException ex) {
throw new RuntimeException(String.format("Could not create release %s for repository %s/%s",
release.getName(), repository.getOwner(), repository.getName()), ex);
}
}
private static class AuthorizationInterceptor implements Interceptor {
private final String token;
public AuthorizationInterceptor(String token) {
this.token = token;
}
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request().newBuilder()
.addHeader("Authorization", "Bearer " + this.token)
.build();
return chain.proceed(request);
}
}
}
@@ -1,54 +0,0 @@
/*
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.gradle.github.release;
import groovy.lang.MissingPropertyException;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
/**
* @author Steve Riesenberg
*/
public class GitHubReleasePlugin implements Plugin<Project> {
@Override
public void apply(Project project) {
project.getTasks().register("createGitHubRelease", CreateGitHubReleaseTask.class, (createGitHubRelease) -> {
createGitHubRelease.setGroup("Release");
createGitHubRelease.setDescription("Create a github release");
createGitHubRelease.dependsOn("generateChangelog");
createGitHubRelease.setCreateRelease("true".equals(project.findProperty("createRelease")));
createGitHubRelease.setVersion((String) project.findProperty("nextVersion"));
if (project.hasProperty("branch")) {
createGitHubRelease.setBranch((String) project.findProperty("branch"));
}
createGitHubRelease.setGitHubAccessToken((String) project.findProperty("gitHubAccessToken"));
if (createGitHubRelease.isCreateRelease() && createGitHubRelease.getGitHubAccessToken() == null) {
throw new MissingPropertyException("Please provide an access token with -PgitHubAccessToken=...");
}
});
project.getTasks().register("dispatchGitHubWorkflow", DispatchGitHubWorkflowTask.class, (dispatchGitHubWorkflow) -> {
dispatchGitHubWorkflow.setGroup("Release");
dispatchGitHubWorkflow.setDescription("Create a workflow_dispatch event on a given branch");
dispatchGitHubWorkflow.setBranch((String) project.findProperty("branch"));
dispatchGitHubWorkflow.setWorkflowId((String) project.findProperty("workflowId"));
dispatchGitHubWorkflow.setGitHubAccessToken((String) project.findProperty("gitHubAccessToken"));
});
}
}
@@ -1,156 +0,0 @@
/*
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.gradle.github.release;
import com.google.gson.annotations.SerializedName;
/**
* @author Steve Riesenberg
*/
public class Release {
@SerializedName("tag_name")
private final String tag;
@SerializedName("target_commitish")
private final String commit;
@SerializedName("name")
private final String name;
@SerializedName("body")
private final String body;
@SerializedName("draft")
private final boolean draft;
@SerializedName("prerelease")
private final boolean preRelease;
@SerializedName("generate_release_notes")
private final boolean generateReleaseNotes;
private Release(String tag, String commit, String name, String body, boolean draft, boolean preRelease, boolean generateReleaseNotes) {
this.tag = tag;
this.commit = commit;
this.name = name;
this.body = body;
this.draft = draft;
this.preRelease = preRelease;
this.generateReleaseNotes = generateReleaseNotes;
}
public String getTag() {
return tag;
}
public String getCommit() {
return commit;
}
public String getName() {
return name;
}
public String getBody() {
return body;
}
public boolean isDraft() {
return draft;
}
public boolean isPreRelease() {
return preRelease;
}
public boolean isGenerateReleaseNotes() {
return generateReleaseNotes;
}
@Override
public String toString() {
return "Release{" +
"tag='" + tag + '\'' +
", commit='" + commit + '\'' +
", name='" + name + '\'' +
", body='" + body + '\'' +
", draft=" + draft +
", preRelease=" + preRelease +
", generateReleaseNotes=" + generateReleaseNotes +
'}';
}
public static Builder tag(String tag) {
return new Builder().tag(tag);
}
public static Builder commit(String commit) {
return new Builder().commit(commit);
}
public static final class Builder {
private String tag;
private String commit;
private String name;
private String body;
private boolean draft;
private boolean preRelease;
private boolean generateReleaseNotes;
private Builder() {
}
public Builder tag(String tag) {
this.tag = tag;
return this;
}
public Builder commit(String commit) {
this.commit = commit;
return this;
}
public Builder name(String name) {
this.name = name;
return this;
}
public Builder body(String body) {
this.body = body;
return this;
}
public Builder draft(boolean draft) {
this.draft = draft;
return this;
}
public Builder preRelease(boolean preRelease) {
this.preRelease = preRelease;
return this;
}
public Builder generateReleaseNotes(boolean generateReleaseNotes) {
this.generateReleaseNotes = generateReleaseNotes;
return this;
}
public Release build() {
return new Release(tag, commit, name, body, draft, preRelease, generateReleaseNotes);
}
}
}
@@ -1,51 +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.gradle.github.release;
import java.util.Map;
/**
* @author Steve Riesenberg
*/
public class WorkflowDispatch {
private String ref;
private Map<String, Object> inputs;
public WorkflowDispatch() {
}
public WorkflowDispatch(String ref, Map<String, Object> inputs) {
this.ref = ref;
this.inputs = inputs;
}
public String getRef() {
return ref;
}
public void setRef(String ref) {
this.ref = ref;
}
public Map<String, Object> getInputs() {
return inputs;
}
public void setInputs(Map<String, Object> inputs) {
this.inputs = inputs;
}
}
@@ -1,82 +0,0 @@
/*
* Copyright 2020-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.gradle.github.user;
import java.io.IOException;
import com.google.gson.Gson;
import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
/**
* @author Steve Riesenberg
*/
public class GitHubUserApi {
private String baseUrl = "https://api.github.com";
private final OkHttpClient httpClient;
private Gson gson = new Gson();
public GitHubUserApi(String gitHubAccessToken) {
this.httpClient = new OkHttpClient.Builder()
.addInterceptor(new AuthorizationInterceptor(gitHubAccessToken))
.build();
}
public void setBaseUrl(String baseUrl) {
this.baseUrl = baseUrl;
}
/**
* Retrieve a GitHub user by the personal access token.
*
* @return The GitHub user
*/
public User getUser() {
String url = this.baseUrl + "/user";
Request request = new Request.Builder().url(url).get().build();
try (Response response = this.httpClient.newCall(request).execute()) {
if (!response.isSuccessful()) {
throw new RuntimeException(
String.format("Unable to retrieve GitHub user." +
" Please check the personal access token and try again." +
" Got response %s", response));
}
return this.gson.fromJson(response.body().charStream(), User.class);
} catch (IOException ex) {
throw new RuntimeException("Unable to retrieve GitHub user.", ex);
}
}
private static class AuthorizationInterceptor implements Interceptor {
private final String token;
public AuthorizationInterceptor(String token) {
this.token = token;
}
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request().newBuilder()
.addHeader("Authorization", "Bearer " + this.token)
.build();
return chain.proceed(request);
}
}
}
@@ -1,53 +0,0 @@
package org.springframework.gradle.github.user;
/**
* @author Steve Riesenberg
*/
public class User {
private Long id;
private String login;
private String name;
private String url;
public Long getId() {
return this.id;
}
public void setId(Long id) {
this.id = id;
}
public String getLogin() {
return this.login;
}
public void setLogin(String login) {
this.login = login;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public String getUrl() {
return this.url;
}
public void setUrl(String url) {
this.url = url;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", login='" + login + '\'' +
", name='" + name + '\'' +
", url='" + url + '\'' +
'}';
}
}
@@ -1,123 +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.gradle.sagan;
import java.util.regex.Pattern;
/**
* Domain object for creating a new release version.
*/
public class Release {
private String version;
private ReleaseStatus status;
private boolean current;
private String referenceDocUrl;
private String apiDocUrl;
public String getVersion() {
return version;
}
public void setVersion(String version) {
this.version = version;
}
public ReleaseStatus getStatus() {
return status;
}
public void setStatus(ReleaseStatus status) {
this.status = status;
}
public boolean isCurrent() {
return current;
}
public void setCurrent(boolean current) {
this.current = current;
}
public String getReferenceDocUrl() {
return referenceDocUrl;
}
public void setReferenceDocUrl(String referenceDocUrl) {
this.referenceDocUrl = referenceDocUrl;
}
public String getApiDocUrl() {
return apiDocUrl;
}
public void setApiDocUrl(String apiDocUrl) {
this.apiDocUrl = apiDocUrl;
}
@Override
public String toString() {
return "Release{" +
"version='" + version + '\'' +
", status=" + status +
", current=" + current +
", referenceDocUrl='" + referenceDocUrl + '\'' +
", apiDocUrl='" + apiDocUrl + '\'' +
'}';
}
public enum ReleaseStatus {
/**
* Unstable version with limited support
*/
SNAPSHOT,
/**
* Pre-Release version meant to be tested by the community
*/
PRERELEASE,
/**
* Release Generally Available on public artifact repositories and enjoying full support from maintainers
*/
GENERAL_AVAILABILITY;
private static final Pattern PRERELEASE_PATTERN = Pattern.compile("[A-Za-z0-9\\.\\-]+?(M|RC)\\d+");
private static final String SNAPSHOT_SUFFIX = "SNAPSHOT";
/**
* Parse the ReleaseStatus from a String
* @param version a project version
* @return the release status for this version
*/
public static ReleaseStatus parse(String version) {
if (version == null) {
throw new IllegalArgumentException("version cannot be null");
}
if (version.endsWith(SNAPSHOT_SUFFIX)) {
return SNAPSHOT;
}
if (PRERELEASE_PATTERN.matcher(version).matches()) {
return PRERELEASE;
}
return GENERAL_AVAILABILITY;
}
}
}
@@ -1,93 +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.gradle.sagan;
import com.google.gson.Gson;
import okhttp3.*;
import java.io.IOException;
import java.util.Base64;
/**
* Implements necessary calls to the Sagan API See https://spring.io/restdocs/index.html
*/
public class SaganApi {
private String baseUrl = "https://api.spring.io";
private OkHttpClient client;
private Gson gson = new Gson();
public SaganApi(String username, String gitHubToken) {
this.client = new OkHttpClient.Builder()
.addInterceptor(new BasicInterceptor(username, gitHubToken))
.build();
}
public void setBaseUrl(String baseUrl) {
this.baseUrl = baseUrl;
}
public void createReleaseForProject(Release release, String projectName) {
String url = this.baseUrl + "/projects/" + projectName + "/releases";
String releaseJsonString = gson.toJson(release);
RequestBody body = RequestBody.create(MediaType.parse("application/json"), releaseJsonString);
Request request = new Request.Builder()
.url(url)
.post(body)
.build();
try {
Response response = this.client.newCall(request).execute();
if (!response.isSuccessful()) {
throw new RuntimeException("Could not create release " + release + ". Got response " + response);
}
} catch (IOException fail) {
throw new RuntimeException("Could not create release " + release, fail);
}
}
public void deleteReleaseForProject(String release, String projectName) {
String url = this.baseUrl + "/projects/" + projectName + "/releases/" + release;
Request request = new Request.Builder()
.url(url)
.delete()
.build();
try {
Response response = this.client.newCall(request).execute();
if (!response.isSuccessful()) {
throw new RuntimeException("Could not delete release " + release + ". Got response " + response);
}
} catch (IOException fail) {
throw new RuntimeException("Could not delete release " + release, fail);
}
}
private static class BasicInterceptor implements Interceptor {
private final String token;
public BasicInterceptor(String username, String token) {
this.token = Base64.getEncoder().encodeToString((username + ":" + token).getBytes());
}
@Override
public okhttp3.Response intercept(Chain chain) throws IOException {
Request request = chain.request().newBuilder()
.addHeader("Authorization", "Basic " + this.token).build();
return chain.proceed(request);
}
}
}
@@ -1,109 +0,0 @@
/*
* Copyright 2019-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.gradle.sagan;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.gradle.api.DefaultTask;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.TaskAction;
import org.springframework.gradle.github.user.GitHubUserApi;
import org.springframework.gradle.github.user.User;
import org.springframework.util.Assert;
public class SaganCreateReleaseTask extends DefaultTask {
private static final Pattern VERSION_PATTERN = Pattern.compile("^([0-9]+)\\.([0-9]+)\\.([0-9]+)(-.+)?$");
@Input
private String gitHubAccessToken;
@Input
private String version;
@Input
private String apiDocUrl;
@Input
private String referenceDocUrl;
@Input
private String projectName;
@TaskAction
public void saganCreateRelease() {
GitHubUserApi github = new GitHubUserApi(this.gitHubAccessToken);
User user = github.getUser();
// Antora reference docs URLs for snapshots do not contain -SNAPSHOT
String referenceDocUrl = this.referenceDocUrl;
if (this.version.endsWith("-SNAPSHOT")) {
Matcher versionMatcher = VERSION_PATTERN.matcher(this.version);
Assert.isTrue(versionMatcher.matches(), "Version " + this.version + " does not match expected pattern");
String majorVersion = versionMatcher.group(1);
String minorVersion = versionMatcher.group(2);
String majorMinorVersion = String.format("%s.%s-SNAPSHOT", majorVersion, minorVersion);
referenceDocUrl = this.referenceDocUrl.replace("{version}", majorMinorVersion);
}
SaganApi sagan = new SaganApi(user.getLogin(), this.gitHubAccessToken);
Release release = new Release();
release.setVersion(this.version);
release.setApiDocUrl(this.apiDocUrl);
release.setReferenceDocUrl(referenceDocUrl);
sagan.createReleaseForProject(release, this.projectName);
}
public String getGitHubAccessToken() {
return gitHubAccessToken;
}
public void setGitHubAccessToken(String gitHubAccessToken) {
this.gitHubAccessToken = gitHubAccessToken;
}
public String getVersion() {
return version;
}
public void setVersion(String version) {
this.version = version;
}
public String getApiDocUrl() {
return apiDocUrl;
}
public void setApiDocUrl(String apiDocUrl) {
this.apiDocUrl = apiDocUrl;
}
public String getReferenceDocUrl() {
return referenceDocUrl;
}
public void setReferenceDocUrl(String referenceDocUrl) {
this.referenceDocUrl = referenceDocUrl;
}
public String getProjectName() {
return projectName;
}
public void setProjectName(String projectName) {
this.projectName = projectName;
}
}
@@ -1,68 +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.gradle.sagan;
import org.gradle.api.DefaultTask;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.TaskAction;
import org.springframework.gradle.github.user.GitHubUserApi;
import org.springframework.gradle.github.user.User;
public class SaganDeleteReleaseTask extends DefaultTask {
@Input
private String gitHubAccessToken;
@Input
private String version;
@Input
private String projectName;
@TaskAction
public void saganCreateRelease() {
GitHubUserApi github = new GitHubUserApi(this.gitHubAccessToken);
User user = github.getUser();
SaganApi sagan = new SaganApi(user.getLogin(), this.gitHubAccessToken);
sagan.deleteReleaseForProject(this.version, this.projectName);
}
public String getGitHubAccessToken() {
return gitHubAccessToken;
}
public void setGitHubAccessToken(String gitHubAccessToken) {
this.gitHubAccessToken = gitHubAccessToken;
}
public String getVersion() {
return version;
}
public void setVersion(String version) {
this.version = version;
}
public String getProjectName() {
return projectName;
}
public void setProjectName(String projectName) {
this.projectName = projectName;
}
}
@@ -1,47 +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.gradle.sagan;
import io.spring.gradle.convention.Utils;
import org.gradle.api.*;
public class SaganPlugin implements Plugin<Project> {
@Override
public void apply(Project project) {
project.getTasks().register("saganCreateRelease", SaganCreateReleaseTask.class, new Action<SaganCreateReleaseTask>() {
@Override
public void execute(SaganCreateReleaseTask saganCreateVersion) {
saganCreateVersion.setGroup("Release");
saganCreateVersion.setDescription("Creates a new version for the specified project on spring.io");
saganCreateVersion.setVersion((String) project.findProperty("nextVersion"));
saganCreateVersion.setProjectName(Utils.getProjectName(project));
saganCreateVersion.setGitHubAccessToken((String) project.findProperty("gitHubAccessToken"));
}
});
project.getTasks().register("saganDeleteRelease", SaganDeleteReleaseTask.class, new Action<SaganDeleteReleaseTask>() {
@Override
public void execute(SaganDeleteReleaseTask saganDeleteVersion) {
saganDeleteVersion.setGroup("Release");
saganDeleteVersion.setDescription("Delete a version for the specified project on spring.io");
saganDeleteVersion.setVersion((String) project.findProperty("previousVersion"));
saganDeleteVersion.setProjectName(Utils.getProjectName(project));
saganDeleteVersion.setGitHubAccessToken((String) project.findProperty("gitHubAccessToken"));
}
});
}
}
@@ -1,44 +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 org.gradle.api.Action;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
public class UpdateProjectVersionPlugin implements Plugin<Project> {
@Override
public void apply(Project project) {
project.getTasks().register("updateProjectVersion", UpdateProjectVersionTask.class, new Action<UpdateProjectVersionTask>() {
@Override
public void execute(UpdateProjectVersionTask updateProjectVersionTask) {
updateProjectVersionTask.setGroup("Release");
updateProjectVersionTask.setDescription("Updates the project version to the next release in gradle.properties");
updateProjectVersionTask.dependsOn("gitHubNextReleaseMilestone");
updateProjectVersionTask.getNextVersionFile().fileProvider(project.provider(() -> project.file("next-release.yml")));
}
});
project.getTasks().register("updateToSnapshotVersion", UpdateToSnapshotVersionTask.class, new Action<UpdateToSnapshotVersionTask>() {
@Override
public void execute(UpdateToSnapshotVersionTask updateToSnapshotVersionTask) {
updateToSnapshotVersionTask.setGroup("Release");
updateToSnapshotVersionTask.setDescription(
"Updates the project version to the next snapshot in gradle.properties");
}
});
}
}
@@ -1,63 +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 org.gradle.api.DefaultTask;
import org.gradle.api.Project;
import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.InputFile;
import org.gradle.api.tasks.Optional;
import org.gradle.api.tasks.TaskAction;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.constructor.Constructor;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import org.springframework.gradle.github.milestones.NextVersionYml;
public abstract class UpdateProjectVersionTask extends DefaultTask {
@InputFile
public abstract RegularFileProperty getNextVersionFile();
@TaskAction
public void checkReleaseDueToday() throws FileNotFoundException {
File nextVersionFile = getNextVersionFile().getAsFile().get();
Yaml yaml = new Yaml(new Constructor(NextVersionYml.class));
NextVersionYml nextVersionYml = yaml.load(new FileInputStream(nextVersionFile));
String nextVersion = nextVersionYml.getVersion();
if (nextVersion == null) {
throw new IllegalArgumentException(
"Could not find version property in provided file " + nextVersionFile.getName());
}
String currentVersion = getProject().getVersion().toString();
File gradlePropertiesFile = getProject().getRootProject().file(Project.GRADLE_PROPERTIES);
if (!gradlePropertiesFile.exists()) {
return;
}
System.out.println("Updating the project version in " + Project.GRADLE_PROPERTIES + " from " + currentVersion
+ " to " + nextVersion);
FileUtils.replaceFileText(gradlePropertiesFile, (gradlePropertiesText) -> {
gradlePropertiesText = gradlePropertiesText.replace("version=" + currentVersion, "version=" + nextVersion);
return gradlePropertiesText;
});
}
}
@@ -1,68 +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 org.gradle.api.DefaultTask;
import org.gradle.api.Project;
import org.gradle.api.tasks.TaskAction;
import java.io.File;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public abstract class UpdateToSnapshotVersionTask extends DefaultTask {
private static final String RELEASE_VERSION_PATTERN = "^([0-9]+)\\.([0-9]+)\\.([0-9]+)(-M\\d+|-RC\\d+)?$";
@TaskAction
public void updateToSnapshotVersion() {
String currentVersion = getProject().getVersion().toString();
File gradlePropertiesFile = getProject().getRootProject().file(Project.GRADLE_PROPERTIES);
if (!gradlePropertiesFile.exists()) {
return;
}
String nextVersion = calculateNextSnapshotVersion(currentVersion);
System.out.println("Updating the project version in " + Project.GRADLE_PROPERTIES + " from " + currentVersion
+ " to " + nextVersion);
FileUtils.replaceFileText(gradlePropertiesFile, (gradlePropertiesText) -> {
gradlePropertiesText = gradlePropertiesText.replace("version=" + currentVersion, "version=" + nextVersion);
return gradlePropertiesText;
});
}
private String calculateNextSnapshotVersion(String currentVersion) {
Pattern releaseVersionPattern = Pattern.compile(RELEASE_VERSION_PATTERN);
Matcher releaseVersion = releaseVersionPattern.matcher(currentVersion);
if (releaseVersion.find()) {
String majorSegment = releaseVersion.group(1);
String minorSegment = releaseVersion.group(2);
String patchSegment = releaseVersion.group(3);
String modifier = releaseVersion.group(4);
if (modifier == null) {
patchSegment = String.valueOf(Integer.parseInt(patchSegment) + 1);
}
System.out.println("modifier = " + modifier);
return String.format("%s.%s.%s-SNAPSHOT", majorSegment, minorSegment, patchSegment);
}
else {
throw new IllegalStateException(
"Cannot calculate next snapshot version because the current project version does not conform to the expected format");
}
}
}
@@ -1,85 +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 io.spring.gradle.convention.sagan;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import okhttp3.mockwebserver.RecordedRequest;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.gradle.sagan.Release;
import org.springframework.gradle.sagan.SaganApi;
import java.nio.charset.Charset;
import java.util.concurrent.TimeUnit;
import static org.assertj.core.api.Assertions.assertThat;
public class SaganApiTests {
private MockWebServer server;
private SaganApi sagan;
private String baseUrl;
@BeforeEach
public void setup() throws Exception {
this.server = new MockWebServer();
this.server.start();
this.sagan = new SaganApi("user", "mock-oauth-token");
this.baseUrl = this.server.url("/api").toString();
this.sagan.setBaseUrl(this.baseUrl);
}
@AfterEach
public void cleanup() throws Exception {
this.server.shutdown();
}
@Test
public void createWhenValidThenNoException() throws Exception {
this.server.enqueue(new MockResponse());
Release release = new Release();
release.setVersion("5.6.0-SNAPSHOT");
release.setApiDocUrl("https://docs.spring.io/spring-security/site/docs/{version}/api/");
release.setReferenceDocUrl("https://docs.spring.io/spring-security/reference/{version}/index.html");
this.sagan.createReleaseForProject(release, "spring-security");
RecordedRequest request = this.server.takeRequest(1, TimeUnit.SECONDS);
assertThat(request.getRequestUrl().toString()).isEqualTo(this.baseUrl + "/projects/spring-security/releases");
assertThat(request.getMethod()).isEqualToIgnoringCase("post");
assertThat(request.getHeaders().get("Authorization")).isEqualTo("Basic dXNlcjptb2NrLW9hdXRoLXRva2Vu");
assertThat(request.getBody().readString(Charset.defaultCharset())).isEqualToIgnoringWhitespace("{\n" +
" \"version\":\"5.6.0-SNAPSHOT\",\n" +
" \"current\":false,\n" +
" \"referenceDocUrl\":\"https://docs.spring.io/spring-security/reference/{version}/index.html\",\n" +
" \"apiDocUrl\":\"https://docs.spring.io/spring-security/site/docs/{version}/api/\"\n" +
"}");
}
@Test
public void deleteWhenValidThenNoException() throws Exception {
this.server.enqueue(new MockResponse());
this.sagan.deleteReleaseForProject("5.6.0-SNAPSHOT", "spring-security");
RecordedRequest request = this.server.takeRequest(1, TimeUnit.SECONDS);
assertThat(request.getRequestUrl().toString()).isEqualTo(this.baseUrl + "/projects/spring-security/releases/5.6.0-SNAPSHOT");
assertThat(request.getMethod()).isEqualToIgnoringCase("delete");
assertThat(request.getHeaders().get("Authorization")).isEqualTo("Basic dXNlcjptb2NrLW9hdXRoLXRva2Vu");
assertThat(request.getBody().readString(Charset.defaultCharset())).isEmpty();
}
}
@@ -1,270 +0,0 @@
package org.springframework.gradle.antora;
import org.apache.commons.io.IOUtils;
import org.gradle.api.GradleException;
import org.gradle.api.Project;
import org.gradle.api.Task;
import org.gradle.testfixtures.ProjectBuilder;
import org.junit.jupiter.api.Test;
import java.io.File;
import java.io.FileOutputStream;
import java.nio.charset.StandardCharsets;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatIOException;
class AntoraVersionPluginTests {
@Test
void defaultsPropertiesWhenSnapshot() {
String expectedVersion = "1.0.0-SNAPSHOT";
Project project = ProjectBuilder.builder().build();
project.setVersion(expectedVersion);
project.getPluginManager().apply(AntoraVersionPlugin.class);
Task task = project.getTasks().findByName(AntoraVersionPlugin.ANTORA_CHECK_VERSION_TASK_NAME);
assertThat(task).isInstanceOf(CheckAntoraVersionTask.class);
CheckAntoraVersionTask checkAntoraVersionTask = (CheckAntoraVersionTask) task;
assertThat(checkAntoraVersionTask.getAntoraVersion().get()).isEqualTo("1.0.0");
assertThat(checkAntoraVersionTask.getAntoraPrerelease().get()).isEqualTo("-SNAPSHOT");
assertThat(checkAntoraVersionTask.getAntoraDisplayVersion().isPresent()).isFalse();
assertThat(checkAntoraVersionTask.getAntoraYmlFile().getAsFile().get()).isEqualTo(project.file("antora.yml"));
}
@Test
void defaultsPropertiesWhenMilestone() {
String expectedVersion = "1.0.0-M1";
Project project = ProjectBuilder.builder().build();
project.setVersion(expectedVersion);
project.getPluginManager().apply(AntoraVersionPlugin.class);
Task task = project.getTasks().findByName(AntoraVersionPlugin.ANTORA_CHECK_VERSION_TASK_NAME);
assertThat(task).isInstanceOf(CheckAntoraVersionTask.class);
CheckAntoraVersionTask checkAntoraVersionTask = (CheckAntoraVersionTask) task;
assertThat(checkAntoraVersionTask.getAntoraVersion().get()).isEqualTo("1.0.0-M1");
assertThat(checkAntoraVersionTask.getAntoraPrerelease().get()).isEqualTo("true");
assertThat(checkAntoraVersionTask.getAntoraDisplayVersion().get()).isEqualTo(checkAntoraVersionTask.getAntoraVersion().get());
assertThat(checkAntoraVersionTask.getAntoraYmlFile().getAsFile().get()).isEqualTo(project.file("antora.yml"));
}
@Test
void defaultsPropertiesWhenRc() {
String expectedVersion = "1.0.0-RC1";
Project project = ProjectBuilder.builder().build();
project.setVersion(expectedVersion);
project.getPluginManager().apply(AntoraVersionPlugin.class);
Task task = project.getTasks().findByName(AntoraVersionPlugin.ANTORA_CHECK_VERSION_TASK_NAME);
assertThat(task).isInstanceOf(CheckAntoraVersionTask.class);
CheckAntoraVersionTask checkAntoraVersionTask = (CheckAntoraVersionTask) task;
assertThat(checkAntoraVersionTask.getAntoraVersion().get()).isEqualTo("1.0.0-RC1");
assertThat(checkAntoraVersionTask.getAntoraPrerelease().get()).isEqualTo("true");
assertThat(checkAntoraVersionTask.getAntoraDisplayVersion().get()).isEqualTo(checkAntoraVersionTask.getAntoraVersion().get());
assertThat(checkAntoraVersionTask.getAntoraYmlFile().getAsFile().get()).isEqualTo(project.file("antora.yml"));
}
@Test
void defaultsPropertiesWhenRelease() {
String expectedVersion = "1.0.0";
Project project = ProjectBuilder.builder().build();
project.setVersion(expectedVersion);
project.getPluginManager().apply(AntoraVersionPlugin.class);
Task task = project.getTasks().findByName(AntoraVersionPlugin.ANTORA_CHECK_VERSION_TASK_NAME);
assertThat(task).isInstanceOf(CheckAntoraVersionTask.class);
CheckAntoraVersionTask checkAntoraVersionTask = (CheckAntoraVersionTask) task;
assertThat(checkAntoraVersionTask.getAntoraVersion().get()).isEqualTo("1.0.0");
assertThat(checkAntoraVersionTask.getAntoraPrerelease().isPresent()).isFalse();
assertThat(checkAntoraVersionTask.getAntoraDisplayVersion().isPresent()).isFalse();
assertThat(checkAntoraVersionTask.getAntoraYmlFile().getAsFile().get()).isEqualTo(project.file("antora.yml"));
}
@Test
void explicitProperties() {
Project project = ProjectBuilder.builder().build();
project.getPluginManager().apply(AntoraVersionPlugin.class);
Task task = project.getTasks().findByName(AntoraVersionPlugin.ANTORA_CHECK_VERSION_TASK_NAME);
CheckAntoraVersionTask checkAntoraVersionTask = (CheckAntoraVersionTask) task;
checkAntoraVersionTask.getAntoraVersion().set("1.0.0");
checkAntoraVersionTask.getAntoraPrerelease().set("-SNAPSHOT");
assertThat(checkAntoraVersionTask.getAntoraVersion().get()).isEqualTo("1.0.0");
assertThat(checkAntoraVersionTask.getAntoraPrerelease().get()).isEqualTo("-SNAPSHOT");
assertThat(checkAntoraVersionTask.getAntoraDisplayVersion().isPresent()).isFalse();
assertThat(checkAntoraVersionTask.getAntoraYmlFile().getAsFile().get()).isEqualTo(project.file("antora.yml"));
}
@Test
void versionNotDefined() throws Exception {
Project project = ProjectBuilder.builder().build();
File rootDir = project.getRootDir();
IOUtils.write("version: '1.0.0'", new FileOutputStream(new File(rootDir, "antora.yml")), StandardCharsets.UTF_8);
project.getPluginManager().apply(AntoraVersionPlugin.class);
Task task = project.getTasks().findByName(AntoraVersionPlugin.ANTORA_CHECK_VERSION_TASK_NAME);
assertThat(task).isInstanceOf(CheckAntoraVersionTask.class);
CheckAntoraVersionTask checkAntoraVersionTask = (CheckAntoraVersionTask) task;
assertThatExceptionOfType(GradleException.class).isThrownBy(() -> checkAntoraVersionTask.check());
}
@Test
void antoraFileNotFound() throws Exception {
String expectedVersion = "1.0.0-SNAPSHOT";
Project project = ProjectBuilder.builder().build();
project.setVersion(expectedVersion);
project.getPluginManager().apply(AntoraVersionPlugin.class);
Task task = project.getTasks().findByName(AntoraVersionPlugin.ANTORA_CHECK_VERSION_TASK_NAME);
assertThat(task).isInstanceOf(CheckAntoraVersionTask.class);
CheckAntoraVersionTask checkAntoraVersionTask = (CheckAntoraVersionTask) task;
assertThatIOException().isThrownBy(() -> checkAntoraVersionTask.check());
}
@Test
void actualAntoraPrereleaseNull() throws Exception {
String expectedVersion = "1.0.0-SNAPSHOT";
Project project = ProjectBuilder.builder().build();
File rootDir = project.getRootDir();
IOUtils.write("version: '1.0.0'", new FileOutputStream(new File(rootDir, "antora.yml")), StandardCharsets.UTF_8);
project.setVersion(expectedVersion);
project.getPluginManager().apply(AntoraVersionPlugin.class);
Task task = project.getTasks().findByName(AntoraVersionPlugin.ANTORA_CHECK_VERSION_TASK_NAME);
assertThat(task).isInstanceOf(CheckAntoraVersionTask.class);
CheckAntoraVersionTask checkAntoraVersionTask = (CheckAntoraVersionTask) task;
assertThatExceptionOfType(GradleException.class).isThrownBy(() -> checkAntoraVersionTask.check());
}
@Test
void matchesWhenSnapshot() throws Exception {
String expectedVersion = "1.0.0-SNAPSHOT";
Project project = ProjectBuilder.builder().build();
File rootDir = project.getRootDir();
IOUtils.write("version: '1.0.0'\nprerelease: '-SNAPSHOT'", new FileOutputStream(new File(rootDir, "antora.yml")), StandardCharsets.UTF_8);
project.setVersion(expectedVersion);
project.getPluginManager().apply(AntoraVersionPlugin.class);
Task task = project.getTasks().findByName(AntoraVersionPlugin.ANTORA_CHECK_VERSION_TASK_NAME);
assertThat(task).isInstanceOf(CheckAntoraVersionTask.class);
CheckAntoraVersionTask checkAntoraVersionTask = (CheckAntoraVersionTask) task;
checkAntoraVersionTask.check();
}
@Test
void matchesWhenMilestone() throws Exception {
String expectedVersion = "1.0.0-M1";
Project project = ProjectBuilder.builder().build();
File rootDir = project.getRootDir();
IOUtils.write("version: '1.0.0-M1'\nprerelease: 'true'\ndisplay_version: '1.0.0-M1'", new FileOutputStream(new File(rootDir, "antora.yml")), StandardCharsets.UTF_8);
project.setVersion(expectedVersion);
project.getPluginManager().apply(AntoraVersionPlugin.class);
Task task = project.getTasks().findByName(AntoraVersionPlugin.ANTORA_CHECK_VERSION_TASK_NAME);
assertThat(task).isInstanceOf(CheckAntoraVersionTask.class);
CheckAntoraVersionTask checkAntoraVersionTask = (CheckAntoraVersionTask) task;
checkAntoraVersionTask.check();
}
@Test
void matchesWhenRc() throws Exception {
String expectedVersion = "1.0.0-RC1";
Project project = ProjectBuilder.builder().build();
File rootDir = project.getRootDir();
IOUtils.write("version: '1.0.0-RC1'\nprerelease: 'true'\ndisplay_version: '1.0.0-RC1'", new FileOutputStream(new File(rootDir, "antora.yml")), StandardCharsets.UTF_8);
project.setVersion(expectedVersion);
project.getPluginManager().apply(AntoraVersionPlugin.class);
Task task = project.getTasks().findByName(AntoraVersionPlugin.ANTORA_CHECK_VERSION_TASK_NAME);
assertThat(task).isInstanceOf(CheckAntoraVersionTask.class);
CheckAntoraVersionTask checkAntoraVersionTask = (CheckAntoraVersionTask) task;
checkAntoraVersionTask.check();
}
@Test
void matchesWhenReleaseAndPrereleaseUndefined() throws Exception {
String expectedVersion = "1.0.0";
Project project = ProjectBuilder.builder().build();
File rootDir = project.getRootDir();
IOUtils.write("version: '1.0.0'", new FileOutputStream(new File(rootDir, "antora.yml")), StandardCharsets.UTF_8);
project.setVersion(expectedVersion);
project.getPluginManager().apply(AntoraVersionPlugin.class);
Task task = project.getTasks().findByName(AntoraVersionPlugin.ANTORA_CHECK_VERSION_TASK_NAME);
assertThat(task).isInstanceOf(CheckAntoraVersionTask.class);
CheckAntoraVersionTask checkAntoraVersionTask = (CheckAntoraVersionTask) task;
checkAntoraVersionTask.check();
}
@Test
void matchesWhenExplicitRelease() throws Exception {
Project project = ProjectBuilder.builder().build();
File rootDir = project.getRootDir();
IOUtils.write("version: '1.0.0'", new FileOutputStream(new File(rootDir, "antora.yml")), StandardCharsets.UTF_8);
project.getPluginManager().apply(AntoraVersionPlugin.class);
Task task = project.getTasks().findByName(AntoraVersionPlugin.ANTORA_CHECK_VERSION_TASK_NAME);
assertThat(task).isInstanceOf(CheckAntoraVersionTask.class);
CheckAntoraVersionTask checkAntoraVersionTask = (CheckAntoraVersionTask) task;
((CheckAntoraVersionTask) task).getAntoraVersion().set("1.0.0");
checkAntoraVersionTask.check();
}
@Test
void matchesWhenExplicitPrerelease() throws Exception {
Project project = ProjectBuilder.builder().build();
File rootDir = project.getRootDir();
IOUtils.write("version: '1.0.0'\nprerelease: '-SNAPSHOT'", new FileOutputStream(new File(rootDir, "antora.yml")), StandardCharsets.UTF_8);
project.getPluginManager().apply(AntoraVersionPlugin.class);
Task task = project.getTasks().findByName(AntoraVersionPlugin.ANTORA_CHECK_VERSION_TASK_NAME);
assertThat(task).isInstanceOf(CheckAntoraVersionTask.class);
CheckAntoraVersionTask checkAntoraVersionTask = (CheckAntoraVersionTask) task;
((CheckAntoraVersionTask) task).getAntoraVersion().set("1.0.0");
((CheckAntoraVersionTask) task).getAntoraPrerelease().set("-SNAPSHOT");
checkAntoraVersionTask.check();
}
@Test
void matchesWhenMissingPropertyDefined() throws Exception {
Project project = ProjectBuilder.builder().build();
File rootDir = project.getRootDir();
IOUtils.write("name: 'ROOT'\nversion: '1.0.0'", new FileOutputStream(new File(rootDir, "antora.yml")), StandardCharsets.UTF_8);
project.getPluginManager().apply(AntoraVersionPlugin.class);
Task task = project.getTasks().findByName(AntoraVersionPlugin.ANTORA_CHECK_VERSION_TASK_NAME);
assertThat(task).isInstanceOf(CheckAntoraVersionTask.class);
CheckAntoraVersionTask checkAntoraVersionTask = (CheckAntoraVersionTask) task;
((CheckAntoraVersionTask) task).getAntoraVersion().set("1.0.0");
checkAntoraVersionTask.check();
}
}
@@ -1,245 +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.gradle.github.milestones;
import java.time.LocalDate;
import java.time.Year;
import java.util.Map;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.springframework.gradle.github.milestones.SpringReleaseTrainSpec.Train;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Steve Riesenberg
*/
public class SpringReleaseTrainTests {
@ParameterizedTest
@CsvSource({
"2019-12-31, ONE, 2020",
"2020-01-01, ONE, 2020",
"2020-01-31, ONE, 2020",
"2020-02-01, TWO, 2020",
"2020-07-31, TWO, 2020",
"2020-08-01, ONE, 2021"
})
public void nextTrainWhenBoundaryConditionsThenSuccess(LocalDate startDate, Train expectedTrain, Year expectedYear) {
SpringReleaseTrainSpec releaseTrainSpec =
SpringReleaseTrainSpec.builder()
.nextTrain(startDate)
.version("1.0.0")
.weekOfMonth(2)
.dayOfWeek(2)
.build();
assertThat(releaseTrainSpec.getTrain()).isEqualTo(expectedTrain);
assertThat(releaseTrainSpec.getYear()).isEqualTo(expectedYear);
}
@Test
public void getTrainDatesWhenTrainOneIsSecondTuesdayOf2020ThenSuccess() {
SpringReleaseTrainSpec releaseTrainSpec =
SpringReleaseTrainSpec.builder()
.train(1)
.version("1.0.0")
.weekOfMonth(2)
.dayOfWeek(2)
.year(2020)
.build();
SpringReleaseTrain releaseTrain = new SpringReleaseTrain(releaseTrainSpec);
Map<String, LocalDate> trainDates = releaseTrain.getTrainDates();
assertThat(trainDates).hasSize(5);
assertThat(trainDates.get("1.0.0-M1")).isEqualTo(LocalDate.of(2020, 1, 14));
assertThat(trainDates.get("1.0.0-M2")).isEqualTo(LocalDate.of(2020, 2, 11));
assertThat(trainDates.get("1.0.0-M3")).isEqualTo(LocalDate.of(2020, 3, 10));
assertThat(trainDates.get("1.0.0-RC1")).isEqualTo(LocalDate.of(2020, 4, 14));
assertThat(trainDates.get("1.0.0")).isEqualTo(LocalDate.of(2020, 5, 12));
}
@Test
public void getTrainDatesWhenTrainTwoIsSecondTuesdayOf2020ThenSuccess() {
SpringReleaseTrainSpec releaseTrainSpec =
SpringReleaseTrainSpec.builder()
.train(2)
.version("1.0.0")
.weekOfMonth(2)
.dayOfWeek(2)
.year(2020)
.build();
SpringReleaseTrain releaseTrain = new SpringReleaseTrain(releaseTrainSpec);
Map<String, LocalDate> trainDates = releaseTrain.getTrainDates();
assertThat(trainDates).hasSize(5);
assertThat(trainDates.get("1.0.0-M1")).isEqualTo(LocalDate.of(2020, 7, 14));
assertThat(trainDates.get("1.0.0-M2")).isEqualTo(LocalDate.of(2020, 8, 11));
assertThat(trainDates.get("1.0.0-M3")).isEqualTo(LocalDate.of(2020, 9, 15));
assertThat(trainDates.get("1.0.0-RC1")).isEqualTo(LocalDate.of(2020, 10, 13));
assertThat(trainDates.get("1.0.0")).isEqualTo(LocalDate.of(2020, 11, 10));
}
@Test
public void getTrainDatesWhenTrainOneIsSecondTuesdayOf2022ThenSuccess() {
SpringReleaseTrainSpec releaseTrainSpec =
SpringReleaseTrainSpec.builder()
.train(1)
.version("1.0.0")
.weekOfMonth(2)
.dayOfWeek(2)
.year(2022)
.build();
SpringReleaseTrain releaseTrain = new SpringReleaseTrain(releaseTrainSpec);
Map<String, LocalDate> trainDates = releaseTrain.getTrainDates();
assertThat(trainDates).hasSize(5);
assertThat(trainDates.get("1.0.0-M1")).isEqualTo(LocalDate.of(2022, 1, 11));
assertThat(trainDates.get("1.0.0-M2")).isEqualTo(LocalDate.of(2022, 2, 15));
assertThat(trainDates.get("1.0.0-M3")).isEqualTo(LocalDate.of(2022, 3, 15));
assertThat(trainDates.get("1.0.0-RC1")).isEqualTo(LocalDate.of(2022, 4, 12));
assertThat(trainDates.get("1.0.0")).isEqualTo(LocalDate.of(2022, 5, 10));
}
@Test
public void getTrainDatesWhenTrainTwoIsSecondTuesdayOf2022ThenSuccess() {
SpringReleaseTrainSpec releaseTrainSpec =
SpringReleaseTrainSpec.builder()
.train(2)
.version("1.0.0")
.weekOfMonth(2)
.dayOfWeek(2)
.year(2022)
.build();
SpringReleaseTrain releaseTrain = new SpringReleaseTrain(releaseTrainSpec);
Map<String, LocalDate> trainDates = releaseTrain.getTrainDates();
assertThat(trainDates).hasSize(5);
assertThat(trainDates.get("1.0.0-M1")).isEqualTo(LocalDate.of(2022, 7, 12));
assertThat(trainDates.get("1.0.0-M2")).isEqualTo(LocalDate.of(2022, 8, 9));
assertThat(trainDates.get("1.0.0-M3")).isEqualTo(LocalDate.of(2022, 9, 13));
assertThat(trainDates.get("1.0.0-RC1")).isEqualTo(LocalDate.of(2022, 10, 11));
assertThat(trainDates.get("1.0.0")).isEqualTo(LocalDate.of(2022, 11, 15));
}
@Test
public void getTrainDatesWhenTrainOneIsThirdMondayOf2022ThenSuccess() {
SpringReleaseTrainSpec releaseTrainSpec =
SpringReleaseTrainSpec.builder()
.train(1)
.version("1.0.0")
.weekOfMonth(3)
.dayOfWeek(1)
.year(2022)
.build();
SpringReleaseTrain releaseTrain = new SpringReleaseTrain(releaseTrainSpec);
Map<String, LocalDate> trainDates = releaseTrain.getTrainDates();
assertThat(trainDates).hasSize(5);
assertThat(trainDates.get("1.0.0-M1")).isEqualTo(LocalDate.of(2022, 1, 17));
assertThat(trainDates.get("1.0.0-M2")).isEqualTo(LocalDate.of(2022, 2, 21));
assertThat(trainDates.get("1.0.0-M3")).isEqualTo(LocalDate.of(2022, 3, 21));
assertThat(trainDates.get("1.0.0-RC1")).isEqualTo(LocalDate.of(2022, 4, 18));
assertThat(trainDates.get("1.0.0")).isEqualTo(LocalDate.of(2022, 5, 16));
}
@Test
public void getTrainDatesWhenTrainTwoIsThirdMondayOf2022ThenSuccess() {
SpringReleaseTrainSpec releaseTrainSpec =
SpringReleaseTrainSpec.builder()
.train(2)
.version("1.0.0")
.weekOfMonth(3)
.dayOfWeek(1)
.year(2022)
.build();
SpringReleaseTrain releaseTrain = new SpringReleaseTrain(releaseTrainSpec);
Map<String, LocalDate> trainDates = releaseTrain.getTrainDates();
assertThat(trainDates).hasSize(5);
assertThat(trainDates.get("1.0.0-M1")).isEqualTo(LocalDate.of(2022, 7, 18));
assertThat(trainDates.get("1.0.0-M2")).isEqualTo(LocalDate.of(2022, 8, 15));
assertThat(trainDates.get("1.0.0-M3")).isEqualTo(LocalDate.of(2022, 9, 19));
assertThat(trainDates.get("1.0.0-RC1")).isEqualTo(LocalDate.of(2022, 10, 17));
assertThat(trainDates.get("1.0.0")).isEqualTo(LocalDate.of(2022, 11, 21));
}
@Test
public void isTrainDateWhenTrainOneIsThirdMondayOf2022ThenSuccess() {
SpringReleaseTrainSpec releaseTrainSpec =
SpringReleaseTrainSpec.builder()
.train(1)
.version("1.0.0")
.weekOfMonth(3)
.dayOfWeek(1)
.year(2022)
.build();
SpringReleaseTrain releaseTrain = new SpringReleaseTrain(releaseTrainSpec);
for (int dayOfMonth = 1; dayOfMonth <= 31; dayOfMonth++) {
assertThat(releaseTrain.isTrainDate("1.0.0-M1", LocalDate.of(2022, 1, dayOfMonth))).isEqualTo(dayOfMonth == 17);
}
for (int dayOfMonth = 1; dayOfMonth <= 28; dayOfMonth++) {
assertThat(releaseTrain.isTrainDate("1.0.0-M2", LocalDate.of(2022, 2, dayOfMonth))).isEqualTo(dayOfMonth == 21);
}
for (int dayOfMonth = 1; dayOfMonth <= 31; dayOfMonth++) {
assertThat(releaseTrain.isTrainDate("1.0.0-M3", LocalDate.of(2022, 3, dayOfMonth))).isEqualTo(dayOfMonth == 21);
}
for (int dayOfMonth = 1; dayOfMonth <= 30; dayOfMonth++) {
assertThat(releaseTrain.isTrainDate("1.0.0-RC1", LocalDate.of(2022, 4, dayOfMonth))).isEqualTo(dayOfMonth == 18);
}
for (int dayOfMonth = 1; dayOfMonth <= 31; dayOfMonth++) {
assertThat(releaseTrain.isTrainDate("1.0.0", LocalDate.of(2022, 5, dayOfMonth))).isEqualTo(dayOfMonth == 16);
}
}
@ParameterizedTest
@CsvSource({
"2022-01-01, 2022-02-21",
"2022-02-01, 2022-02-21",
"2022-02-21, 2022-04-18",
"2022-03-01, 2022-04-18",
"2022-04-01, 2022-04-18",
"2022-04-18, 2022-06-20",
"2022-05-01, 2022-06-20",
"2022-06-01, 2022-06-20",
"2022-06-20, 2022-08-15",
"2022-07-01, 2022-08-15",
"2022-08-01, 2022-08-15",
"2022-08-15, 2022-10-17",
"2022-09-01, 2022-10-17",
"2022-10-01, 2022-10-17",
"2022-10-17, 2022-12-19",
"2022-11-01, 2022-12-19",
"2022-12-01, 2022-12-19",
"2022-12-19, 2023-02-20"
})
public void getNextReleaseDateWhenBoundaryConditionsThenSuccess(LocalDate startDate, LocalDate expectedDate) {
SpringReleaseTrainSpec releaseTrainSpec =
SpringReleaseTrainSpec.builder()
.train(1)
.version("1.0.0")
.weekOfMonth(3)
.dayOfWeek(1)
.year(2022)
.build();
SpringReleaseTrain releaseTrain = new SpringReleaseTrain(releaseTrainSpec);
assertThat(releaseTrain.getNextReleaseDate(startDate)).isEqualTo(expectedDate);
}
}
@@ -1,89 +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.gradle.github.release;
import java.nio.charset.Charset;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import okhttp3.mockwebserver.RecordedRequest;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.gradle.github.RepositoryRef;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
/**
* @author Steve Riesenberg
*/
public class GitHubActionsApiTests {
private GitHubActionsApi gitHubActionsApi;
private MockWebServer server;
private String baseUrl;
private RepositoryRef repository;
@BeforeEach
public void setup() throws Exception {
this.server = new MockWebServer();
this.server.start();
this.baseUrl = this.server.url("/api").toString();
this.gitHubActionsApi = new GitHubActionsApi("mock-oauth-token");
this.gitHubActionsApi.setBaseUrl(this.baseUrl);
this.repository = new RepositoryRef("spring-projects", "spring-security");
}
@AfterEach
public void cleanup() throws Exception {
this.server.shutdown();
}
@Test
public void dispatchWorkflowWhenValidParametersThenSuccess() throws Exception {
this.server.enqueue(new MockResponse().setResponseCode(204));
Map<String, Object> inputs = new LinkedHashMap<>();
inputs.put("input-1", "value");
inputs.put("input-2", false);
WorkflowDispatch workflowDispatch = new WorkflowDispatch("main", inputs);
this.gitHubActionsApi.dispatchWorkflow(this.repository, "test-workflow.yml", workflowDispatch);
RecordedRequest recordedRequest = this.server.takeRequest(1, TimeUnit.SECONDS);
assertThat(recordedRequest.getMethod()).isEqualToIgnoringCase("post");
assertThat(recordedRequest.getRequestUrl().toString())
.isEqualTo(this.baseUrl + "/repos/spring-projects/spring-security/actions/workflows/test-workflow.yml/dispatches");
assertThat(recordedRequest.getBody().readString(Charset.defaultCharset()))
.isEqualTo("{\"ref\":\"main\",\"inputs\":{\"input-1\":\"value\",\"input-2\":false}}");
}
@Test
public void dispatchWorkflowWhenErrorResponseThenException() throws Exception {
this.server.enqueue(new MockResponse().setResponseCode(400));
WorkflowDispatch workflowDispatch = new WorkflowDispatch("main", null);
assertThatExceptionOfType(RuntimeException.class)
.isThrownBy(() -> this.gitHubActionsApi.dispatchWorkflow(this.repository, "test-workflow.yml", workflowDispatch));
}
}
@@ -1,155 +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.gradle.github.release;
import java.nio.charset.Charset;
import java.util.concurrent.TimeUnit;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import okhttp3.mockwebserver.RecordedRequest;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.gradle.github.RepositoryRef;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
/**
* @author Steve Riesenberg
*/
public class GitHubReleaseApiTests {
private GitHubReleaseApi gitHubReleaseApi;
private MockWebServer server;
private String baseUrl;
private RepositoryRef repository;
@BeforeEach
public void setup() throws Exception {
this.server = new MockWebServer();
this.server.start();
this.baseUrl = this.server.url("/api").toString();
this.gitHubReleaseApi = new GitHubReleaseApi("mock-oauth-token");
this.gitHubReleaseApi.setBaseUrl(this.baseUrl);
this.repository = new RepositoryRef("spring-projects", "spring-security");
}
@AfterEach
public void cleanup() throws Exception {
this.server.shutdown();
}
@Test
public void publishReleaseWhenValidParametersThenSuccess() throws Exception {
String responseJson = "{\n" +
" \"url\": \"https://api.github.com/spring-projects/spring-security/releases/1\",\n" +
" \"html_url\": \"https://github.com/spring-projects/spring-security/releases/tags/v1.0.0\",\n" +
" \"assets_url\": \"https://api.github.com/spring-projects/spring-security/releases/1/assets\",\n" +
" \"upload_url\": \"https://uploads.github.com/spring-projects/spring-security/releases/1/assets{?name,label}\",\n" +
" \"tarball_url\": \"https://api.github.com/spring-projects/spring-security/tarball/v1.0.0\",\n" +
" \"zipball_url\": \"https://api.github.com/spring-projects/spring-security/zipball/v1.0.0\",\n" +
" \"discussion_url\": \"https://github.com/spring-projects/spring-security/discussions/90\",\n" +
" \"id\": 1,\n" +
" \"node_id\": \"MDc6UmVsZWFzZTE=\",\n" +
" \"tag_name\": \"v1.0.0\",\n" +
" \"target_commitish\": \"main\",\n" +
" \"name\": \"v1.0.0\",\n" +
" \"body\": \"Description of the release\",\n" +
" \"draft\": false,\n" +
" \"prerelease\": false,\n" +
" \"created_at\": \"2013-02-27T19:35:32Z\",\n" +
" \"published_at\": \"2013-02-27T19:35:32Z\",\n" +
" \"author\": {\n" +
" \"login\": \"sjohnr\",\n" +
" \"id\": 1,\n" +
" \"node_id\": \"MDQ6VXNlcjE=\",\n" +
" \"avatar_url\": \"https://github.com/images/avatar.gif\",\n" +
" \"gravatar_id\": \"\",\n" +
" \"url\": \"https://api.github.com/users/sjohnr\",\n" +
" \"html_url\": \"https://github.com/sjohnr\",\n" +
" \"followers_url\": \"https://api.github.com/users/sjohnr/followers\",\n" +
" \"following_url\": \"https://api.github.com/users/sjohnr/following{/other_user}\",\n" +
" \"gists_url\": \"https://api.github.com/users/sjohnr/gists{/gist_id}\",\n" +
" \"starred_url\": \"https://api.github.com/users/sjohnr/starred{/owner}{/repo}\",\n" +
" \"subscriptions_url\": \"https://api.github.com/users/sjohnr/subscriptions\",\n" +
" \"organizations_url\": \"https://api.github.com/users/sjohnr/orgs\",\n" +
" \"repos_url\": \"https://api.github.com/users/sjohnr/repos\",\n" +
" \"events_url\": \"https://api.github.com/users/sjohnr/events{/privacy}\",\n" +
" \"received_events_url\": \"https://api.github.com/users/sjohnr/received_events\",\n" +
" \"type\": \"User\",\n" +
" \"site_admin\": false\n" +
" },\n" +
" \"assets\": [\n" +
" {\n" +
" \"url\": \"https://api.github.com/spring-projects/spring-security/releases/assets/1\",\n" +
" \"browser_download_url\": \"https://github.com/spring-projects/spring-security/releases/download/v1.0.0/example.zip\",\n" +
" \"id\": 1,\n" +
" \"node_id\": \"MDEyOlJlbGVhc2VBc3NldDE=\",\n" +
" \"name\": \"example.zip\",\n" +
" \"label\": \"short description\",\n" +
" \"state\": \"uploaded\",\n" +
" \"content_type\": \"application/zip\",\n" +
" \"size\": 1024,\n" +
" \"download_count\": 42,\n" +
" \"created_at\": \"2013-02-27T19:35:32Z\",\n" +
" \"updated_at\": \"2013-02-27T19:35:32Z\",\n" +
" \"uploader\": {\n" +
" \"login\": \"sjohnr\",\n" +
" \"id\": 1,\n" +
" \"node_id\": \"MDQ6VXNlcjE=\",\n" +
" \"avatar_url\": \"https://github.com/images/avatar.gif\",\n" +
" \"gravatar_id\": \"\",\n" +
" \"url\": \"https://api.github.com/users/sjohnr\",\n" +
" \"html_url\": \"https://github.com/sjohnr\",\n" +
" \"followers_url\": \"https://api.github.com/users/sjohnr/followers\",\n" +
" \"following_url\": \"https://api.github.com/users/sjohnr/following{/other_user}\",\n" +
" \"gists_url\": \"https://api.github.com/users/sjohnr/gists{/gist_id}\",\n" +
" \"starred_url\": \"https://api.github.com/users/sjohnr/starred{/owner}{/repo}\",\n" +
" \"subscriptions_url\": \"https://api.github.com/users/sjohnr/subscriptions\",\n" +
" \"organizations_url\": \"https://api.github.com/users/sjohnr/orgs\",\n" +
" \"repos_url\": \"https://api.github.com/users/sjohnr/repos\",\n" +
" \"events_url\": \"https://api.github.com/users/sjohnr/events{/privacy}\",\n" +
" \"received_events_url\": \"https://api.github.com/users/sjohnr/received_events\",\n" +
" \"type\": \"User\",\n" +
" \"site_admin\": false\n" +
" }\n" +
" }\n" +
" ]\n" +
"}";
this.server.enqueue(new MockResponse().setBody(responseJson));
this.gitHubReleaseApi.publishRelease(this.repository, Release.tag("1.0.0").build());
RecordedRequest recordedRequest = this.server.takeRequest(1, TimeUnit.SECONDS);
assertThat(recordedRequest.getMethod()).isEqualToIgnoringCase("post");
assertThat(recordedRequest.getRequestUrl().toString())
.isEqualTo(this.baseUrl + "/repos/spring-projects/spring-security/releases");
assertThat(recordedRequest.getBody().readString(Charset.defaultCharset()))
.isEqualTo("{\"tag_name\":\"1.0.0\",\"draft\":false,\"prerelease\":false,\"generate_release_notes\":false}");
}
@Test
public void publishReleaseWhenErrorResponseThenException() throws Exception {
this.server.enqueue(new MockResponse().setResponseCode(400));
assertThatExceptionOfType(RuntimeException.class)
.isThrownBy(() -> this.gitHubReleaseApi.publishRelease(this.repository, Release.tag("1.0.0").build()));
}
}
@@ -1,106 +0,0 @@
/*
* Copyright 2020-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.gradle.github.user;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
/**
* @author Steve Riesenberg
*/
public class GitHubUserApiTests {
private GitHubUserApi gitHubUserApi;
private MockWebServer server;
private String baseUrl;
@BeforeEach
public void setup() throws Exception {
this.server = new MockWebServer();
this.server.start();
this.baseUrl = this.server.url("/api").toString();
this.gitHubUserApi = new GitHubUserApi("mock-oauth-token");
this.gitHubUserApi.setBaseUrl(this.baseUrl);
}
@AfterEach
public void cleanup() throws Exception {
this.server.shutdown();
}
@Test
public void getUserWhenValidParametersThenSuccess() {
// @formatter:off
String responseJson = "{\n" +
" \"avatar_url\": \"https://avatars.githubusercontent.com/u/583231?v=4\",\n" +
" \"bio\": null,\n" +
" \"blog\": \"https://github.blog\",\n" +
" \"company\": \"@github\",\n" +
" \"created_at\": \"2011-01-25T18:44:36Z\",\n" +
" \"email\": null,\n" +
" \"events_url\": \"https://api.github.com/users/octocat/events{/privacy}\",\n" +
" \"followers\": 8481,\n" +
" \"followers_url\": \"https://api.github.com/users/octocat/followers\",\n" +
" \"following\": 9,\n" +
" \"following_url\": \"https://api.github.com/users/octocat/following{/other_user}\",\n" +
" \"gists_url\": \"https://api.github.com/users/octocat/gists{/gist_id}\",\n" +
" \"gravatar_id\": \"\",\n" +
" \"hireable\": null,\n" +
" \"html_url\": \"https://github.com/octocat\",\n" +
" \"id\": 583231,\n" +
" \"location\": \"San Francisco\",\n" +
" \"login\": \"octocat\",\n" +
" \"name\": \"The Octocat\",\n" +
" \"node_id\": \"MDQ6VXNlcjU4MzIzMQ==\",\n" +
" \"organizations_url\": \"https://api.github.com/users/octocat/orgs\",\n" +
" \"public_gists\": 8,\n" +
" \"public_repos\": 8,\n" +
" \"received_events_url\": \"https://api.github.com/users/octocat/received_events\",\n" +
" \"repos_url\": \"https://api.github.com/users/octocat/repos\",\n" +
" \"site_admin\": false,\n" +
" \"starred_url\": \"https://api.github.com/users/octocat/starred{/owner}{/repo}\",\n" +
" \"subscriptions_url\": \"https://api.github.com/users/octocat/subscriptions\",\n" +
" \"twitter_username\": null,\n" +
" \"type\": \"User\",\n" +
" \"updated_at\": \"2023-02-25T12:14:58Z\",\n" +
" \"url\": \"https://api.github.com/users/octocat\"\n" +
"}";
// @formatter:on
this.server.enqueue(new MockResponse().setBody(responseJson));
User user = this.gitHubUserApi.getUser();
assertThat(user.getId()).isEqualTo(583231);
assertThat(user.getLogin()).isEqualTo("octocat");
assertThat(user.getName()).isEqualTo("The Octocat");
assertThat(user.getUrl()).isEqualTo("https://api.github.com/users/octocat");
}
@Test
public void getUserWhenErrorResponseThenException() {
this.server.enqueue(new MockResponse().setResponseCode(400));
// @formatter:off
assertThatExceptionOfType(RuntimeException.class)
.isThrownBy(() -> this.gitHubUserApi.getUser());
// @formatter:on
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 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.
@@ -27,8 +27,9 @@ import org.apereo.cas.client.util.WebUtils;
import org.apereo.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.AuthenticationTrustResolver;
import org.springframework.security.authentication.AuthenticationTrustResolverImpl;
import org.springframework.security.authentication.event.InteractiveAuthenticationSuccessEvent;
import org.springframework.security.cas.ServiceProperties;
import org.springframework.security.cas.authentication.CasServiceTicketAuthenticationToken;
@@ -195,6 +196,8 @@ public class CasAuthenticationFilter extends AbstractAuthenticationProcessingFil
private SecurityContextRepository securityContextRepository = new HttpSessionSecurityContextRepository();
private final AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();
public CasAuthenticationFilter() {
super("/login/cas");
setAuthenticationFailureHandler(new SimpleUrlAuthenticationFailureHandler());
@@ -298,6 +301,12 @@ public class CasAuthenticationFilter extends AbstractAuthenticationProcessingFil
this.authenticateAllArtifacts = serviceProperties.isAuthenticateAllArtifacts();
}
@Override
public void setSecurityContextRepository(SecurityContextRepository securityContextRepository) {
super.setSecurityContextRepository(securityContextRepository);
this.securityContextRepository = securityContextRepository;
}
/**
* Indicates if the request is elgible to process a service ticket. This method exists
* for readability.
@@ -331,8 +340,7 @@ public class CasAuthenticationFilter extends AbstractAuthenticationProcessingFil
*/
private boolean authenticated() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
return authentication != null && authentication.isAuthenticated()
&& !(authentication instanceof AnonymousAuthenticationToken);
return this.trustResolver.isAuthenticated(authentication);
}
/**
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 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.
@@ -16,7 +16,10 @@
package org.springframework.security.cas.web;
import java.io.IOException;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import org.apereo.cas.client.proxy.ProxyGrantingTicketStorage;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
@@ -219,4 +222,14 @@ public class CasAuthenticationFilterTests {
verify(securityContextRepository).saveContext(any(SecurityContext.class), eq(request), eq(response));
}
@Test
void successfulAuthenticationWhenSecurityContextRepositorySetThenUses() throws ServletException, IOException {
SecurityContextRepository securityContextRepository = mock(SecurityContextRepository.class);
CasAuthenticationFilter filter = new CasAuthenticationFilter();
filter.setSecurityContextRepository(securityContextRepository);
filter.successfulAuthentication(new MockHttpServletRequest(), new MockHttpServletResponse(),
new MockFilterChain(), mock(Authentication.class));
verify(securityContextRepository).saveContext(any(SecurityContext.class), any(), any());
}
}
@@ -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
@@ -43,6 +43,7 @@ import org.springframework.security.config.core.GrantedAuthorityDefaults;
* @since 5.0
*/
@Configuration(proxyBeanMethods = false)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
class ReactiveMethodSecurityConfiguration implements ImportAware {
private int advisorOrder;
@@ -51,16 +52,17 @@ class ReactiveMethodSecurityConfiguration implements ImportAware {
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
MethodSecurityMetadataSourceAdvisor methodSecurityInterceptor(AbstractMethodSecurityMetadataSource source) {
static MethodSecurityMetadataSourceAdvisor methodSecurityInterceptor(AbstractMethodSecurityMetadataSource source,
ReactiveMethodSecurityConfiguration configuration) {
MethodSecurityMetadataSourceAdvisor advisor = new MethodSecurityMetadataSourceAdvisor(
"securityMethodInterceptor", source, "methodMetadataSource");
advisor.setOrder(this.advisorOrder);
advisor.setOrder(configuration.advisorOrder);
return advisor;
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
DelegatingMethodSecurityMetadataSource methodMetadataSource(
static DelegatingMethodSecurityMetadataSource methodMetadataSource(
MethodSecurityExpressionHandler methodSecurityExpressionHandler) {
ExpressionBasedAnnotationAttributeFactory attributeFactory = new ExpressionBasedAnnotationAttributeFactory(
methodSecurityExpressionHandler);
@@ -70,7 +72,7 @@ class ReactiveMethodSecurityConfiguration implements ImportAware {
}
@Bean
PrePostAdviceReactiveMethodInterceptor securityMethodInterceptor(AbstractMethodSecurityMetadataSource source,
static PrePostAdviceReactiveMethodInterceptor securityMethodInterceptor(AbstractMethodSecurityMetadataSource source,
MethodSecurityExpressionHandler handler) {
ExpressionBasedPostInvocationAdvice postAdvice = new ExpressionBasedPostInvocationAdvice(handler);
ExpressionBasedPreInvocationAdvice preAdvice = new ExpressionBasedPreInvocationAdvice();
@@ -80,10 +82,11 @@ class ReactiveMethodSecurityConfiguration implements ImportAware {
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
DefaultMethodSecurityExpressionHandler methodSecurityExpressionHandler() {
static DefaultMethodSecurityExpressionHandler methodSecurityExpressionHandler(
ReactiveMethodSecurityConfiguration configuration) {
DefaultMethodSecurityExpressionHandler handler = new DefaultMethodSecurityExpressionHandler();
if (this.grantedAuthorityDefaults != null) {
handler.setDefaultRolePrefix(this.grantedAuthorityDefaults.getRolePrefix());
if (configuration.grantedAuthorityDefaults != null) {
handler.setDefaultRolePrefix(configuration.grantedAuthorityDefaults.getRolePrefix());
}
return handler;
}
@@ -23,12 +23,14 @@ import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Supplier;
import java.util.function.Function;
import jakarta.servlet.DispatcherType;
import jakarta.servlet.ServletContext;
import jakarta.servlet.ServletRegistration;
import jakarta.servlet.http.HttpServletRequest;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.context.ApplicationContext;
@@ -44,7 +46,6 @@ import org.springframework.security.web.util.matcher.RegexRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.function.SingletonSupplier;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
@@ -76,6 +77,8 @@ public abstract class AbstractRequestMatcherRegistry<C> {
AbstractRequestMatcherRegistry.class.getClassLoader());
}
private final Log logger = LogFactory.getLog(getClass());
protected final void setApplicationContext(ApplicationContext context) {
this.context = context;
}
@@ -209,7 +212,12 @@ public abstract class AbstractRequestMatcherRegistry<C> {
matchers.add(resolve(ant, mvc, servletContext));
}
else {
matchers.add(new DeferredRequestMatcher(() -> resolve(ant, mvc, servletContext), mvc, ant));
this.logger
.warn("The ServletRegistration API was not available at startup time. This may be due to a misconfiguration; "
+ "if you are using AbstractSecurityWebApplicationInitializer, please double-check the recommendations outlined in "
+ "https://docs.spring.io/spring-security/reference/servlet/configuration/java.html#abstractsecuritywebapplicationinitializer-with-spring-mvc");
matchers.add(new DeferredRequestMatcher((request) -> resolve(ant, mvc, request.getServletContext()),
mvc, ant));
}
}
return requestMatchers(matchers.toArray(new RequestMatcher[0]));
@@ -466,27 +474,34 @@ public abstract class AbstractRequestMatcherRegistry<C> {
static class DeferredRequestMatcher implements RequestMatcher {
final Supplier<RequestMatcher> requestMatcher;
final Function<HttpServletRequest, RequestMatcher> requestMatcherFactory;
final AtomicReference<String> description = new AtomicReference<>();
DeferredRequestMatcher(Supplier<RequestMatcher> resolver, RequestMatcher... candidates) {
this.requestMatcher = SingletonSupplier.of(() -> {
RequestMatcher matcher = resolver.get();
this.description.set(matcher.toString());
return matcher;
});
volatile RequestMatcher requestMatcher;
DeferredRequestMatcher(Function<HttpServletRequest, RequestMatcher> resolver, RequestMatcher... candidates) {
this.requestMatcherFactory = (request) -> {
if (this.requestMatcher == null) {
synchronized (this) {
if (this.requestMatcher == null) {
this.requestMatcher = resolver.apply(request);
}
}
}
return this.requestMatcher;
};
this.description.set("Deferred " + Arrays.toString(candidates));
}
@Override
public boolean matches(HttpServletRequest request) {
return this.requestMatcher.get().matches(request);
return this.requestMatcherFactory.apply(request).matches(request);
}
@Override
public MatchResult matcher(HttpServletRequest request) {
return this.requestMatcher.get().matcher(request);
return this.requestMatcherFactory.apply(request).matcher(request);
}
@Override
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 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.
@@ -27,6 +27,7 @@ import org.springframework.security.oauth2.server.resource.web.authentication.Be
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.access.ExceptionTranslationFilter;
import org.springframework.security.web.access.channel.ChannelProcessingFilter;
import org.springframework.security.web.access.intercept.AuthorizationFilter;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.AnonymousAuthenticationFilter;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@@ -37,7 +38,11 @@ import org.springframework.security.web.authentication.rememberme.RememberMeAuth
import org.springframework.security.web.authentication.switchuser.SwitchUserFilter;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.security.web.authentication.www.DigestAuthenticationFilter;
import org.springframework.security.web.context.SecurityContextHolderFilter;
import org.springframework.security.web.context.SecurityContextPersistenceFilter;
import org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter;
import org.springframework.security.web.csrf.CsrfFilter;
import org.springframework.security.web.header.HeaderWriterFilter;
import org.springframework.security.web.jaasapi.JaasApiIntegrationFilter;
import org.springframework.security.web.savedrequest.RequestCacheAwareFilter;
import org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter;
@@ -45,6 +50,7 @@ import org.springframework.security.web.session.ConcurrentSessionFilter;
import org.springframework.security.web.session.DisableEncodeUrlFilter;
import org.springframework.security.web.session.ForceEagerSessionCreationFilter;
import org.springframework.security.web.session.SessionManagementFilter;
import org.springframework.web.filter.CorsFilter;
/**
* @param <H>
@@ -126,15 +132,24 @@ public interface HttpSecurityBuilder<H extends HttpSecurityBuilder<H>>
* The ordering of the Filters is:
*
* <ul>
* <li>{@link ForceEagerSessionCreationFilter}</li>
* <li>{@link DisableEncodeUrlFilter}</li>
* <li>{@link ForceEagerSessionCreationFilter}</li>
* <li>{@link ChannelProcessingFilter}</li>
* <li>{@link WebAsyncManagerIntegrationFilter}</li>
* <li>{@link SecurityContextHolderFilter}</li>
* <li>{@link SecurityContextPersistenceFilter}</li>
* <li>{@link HeaderWriterFilter}</li>
* <li>{@link CorsFilter}</li>
* <li>{@link CsrfFilter}</li>
* <li>{@link LogoutFilter}</li>
* <li>{@link org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter}</li>
* <li>{@link org.springframework.security.saml2.provider.service.web.Saml2WebSsoAuthenticationRequestFilter}</li>
* <li>{@link X509AuthenticationFilter}</li>
* <li>{@link AbstractPreAuthenticatedProcessingFilter}</li>
* <li><a href="
* {@docRoot}/org/springframework/security/cas/web/CasAuthenticationFilter.html">CasAuthenticationFilter</a></li>
* <li>{@link org.springframework.security.oauth2.client.web.OAuth2LoginAuthenticationFilter}</li>
* <li>{@link org.springframework.security.saml2.provider.service.web.authentication.Saml2WebSsoAuthenticationFilter}</li>
* <li>{@link UsernamePasswordAuthenticationFilter}</li>
* <li>{@link org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter}</li>
* <li>{@link org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter}</li>
@@ -147,9 +162,11 @@ public interface HttpSecurityBuilder<H extends HttpSecurityBuilder<H>>
* <li>{@link JaasApiIntegrationFilter}</li>
* <li>{@link RememberMeAuthenticationFilter}</li>
* <li>{@link AnonymousAuthenticationFilter}</li>
* <li>{@link org.springframework.security.oauth2.client.web.OAuth2AuthorizationCodeGrantFilter}</li>
* <li>{@link SessionManagementFilter}</li>
* <li>{@link ExceptionTranslationFilter}</li>
* <li>{@link FilterSecurityInterceptor}</li>
* <li>{@link AuthorizationFilter}</li>
* <li>{@link SwitchUserFilter}</li>
* </ul>
* @param filter the {@link Filter} to add
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 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.
@@ -49,6 +49,7 @@ import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.ObservationFilterChainDecorator;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.AuthorizationManagerWebInvocationPrivilegeEvaluator;
import org.springframework.security.web.access.AuthorizationManagerWebInvocationPrivilegeEvaluator.HttpServletRequestTransformer;
import org.springframework.security.web.access.DefaultWebInvocationPrivilegeEvaluator;
import org.springframework.security.web.access.RequestMatcherDelegatingWebInvocationPrivilegeEvaluator;
import org.springframework.security.web.access.WebInvocationPrivilegeEvaluator;
@@ -108,6 +109,8 @@ public final class WebSecurity extends AbstractConfiguredSecurityBuilder<Filter,
private ObservationRegistry observationRegistry = ObservationRegistry.NOOP;
private HttpServletRequestTransformer privilegeEvaluatorRequestTransformer;
private DefaultWebSecurityExpressionHandler defaultWebSecurityExpressionHandler = new DefaultWebSecurityExpressionHandler();
private SecurityExpressionHandler<FilterInvocation> expressionHandler = this.defaultWebSecurityExpressionHandler;
@@ -350,6 +353,9 @@ public final class WebSecurity extends AbstractConfiguredSecurityBuilder<Filter,
AuthorizationManagerWebInvocationPrivilegeEvaluator evaluator = new AuthorizationManagerWebInvocationPrivilegeEvaluator(
authorizationManager);
evaluator.setServletContext(this.servletContext);
if (this.privilegeEvaluatorRequestTransformer != null) {
evaluator.setRequestTransformer(this.privilegeEvaluatorRequestTransformer);
}
privilegeEvaluators.add(evaluator);
}
}
@@ -386,6 +392,9 @@ public final class WebSecurity extends AbstractConfiguredSecurityBuilder<Filter,
}
catch (NoSuchBeanDefinitionException ex) {
}
Class<HttpServletRequestTransformer> requestTransformerClass = HttpServletRequestTransformer.class;
this.privilegeEvaluatorRequestTransformer = applicationContext.getBeanProvider(requestTransformerClass)
.getIfUnique();
}
@Override
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 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.
@@ -16,9 +16,25 @@
package org.springframework.security.config.annotation.web.configuration;
import java.io.IOException;
import java.util.List;
import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.beans.BeanMetadataElement;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.beans.factory.support.ManagedList;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Bean;
@@ -26,13 +42,22 @@ import org.springframework.context.expression.BeanFactoryResolver;
import org.springframework.expression.BeanResolver;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.context.SecurityContextHolderStrategy;
import org.springframework.security.web.FilterChainProxy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.HandlerMappingIntrospectorRequestTransformer;
import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;
import org.springframework.security.web.debug.DebugFilter;
import org.springframework.security.web.firewall.HttpFirewall;
import org.springframework.security.web.firewall.RequestRejectedHandler;
import org.springframework.security.web.method.annotation.AuthenticationPrincipalArgumentResolver;
import org.springframework.security.web.method.annotation.CsrfTokenArgumentResolver;
import org.springframework.security.web.method.annotation.CurrentSecurityContextArgumentResolver;
import org.springframework.security.web.servlet.support.csrf.CsrfRequestDataValueProcessor;
import org.springframework.web.filter.CompositeFilter;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
import org.springframework.web.servlet.support.RequestDataValueProcessor;
/**
@@ -50,6 +75,8 @@ import org.springframework.web.servlet.support.RequestDataValueProcessor;
*/
class WebMvcSecurityConfiguration implements WebMvcConfigurer, ApplicationContextAware {
private static final String HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME = "mvcHandlerMappingIntrospector";
private BeanResolver beanResolver;
private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder
@@ -84,4 +111,205 @@ class WebMvcSecurityConfiguration implements WebMvcConfigurer, ApplicationContex
}
}
/**
* Used to ensure Spring MVC request matching is cached.
*
* Creates a {@link BeanDefinitionRegistryPostProcessor} that detects if a bean named
* HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME is defined. If so, it moves the
* AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME to another bean name
* and then adds a {@link CompositeFilter} that contains
* {@link HandlerMappingIntrospector#createCacheFilter()} and the original
* FilterChainProxy under the original Bean name.
* @return
*/
@Bean
static BeanDefinitionRegistryPostProcessor springSecurityHandlerMappingIntrospectorBeanDefinitionRegistryPostProcessor() {
return new BeanDefinitionRegistryPostProcessor() {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
}
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
if (!registry.containsBeanDefinition(HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME)) {
return;
}
String hmiRequestTransformerBeanName = HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME + "RequestTransformer";
if (!registry.containsBeanDefinition(hmiRequestTransformerBeanName)) {
BeanDefinition hmiRequestTransformer = BeanDefinitionBuilder
.rootBeanDefinition(HandlerMappingIntrospectorRequestTransformer.class)
.addConstructorArgReference(HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME)
.getBeanDefinition();
registry.registerBeanDefinition(hmiRequestTransformerBeanName, hmiRequestTransformer);
}
BeanDefinition filterChainProxy = registry
.getBeanDefinition(AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME);
if (!filterChainProxy.getResolvableType().isInstance(CompositeFilterChainProxy.class)) {
BeanDefinitionBuilder hmiCacheFilterBldr = BeanDefinitionBuilder
.rootBeanDefinition(HandlerMappingIntrospectorCacheFilterFactoryBean.class)
.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
ManagedList<BeanMetadataElement> filters = new ManagedList<>();
filters.add(hmiCacheFilterBldr.getBeanDefinition());
filters.add(filterChainProxy);
BeanDefinitionBuilder compositeSpringSecurityFilterChainBldr = BeanDefinitionBuilder
.rootBeanDefinition(CompositeFilterChainProxy.class)
.addConstructorArgValue(filters);
registry.removeBeanDefinition(AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME);
registry.registerBeanDefinition(AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME,
compositeSpringSecurityFilterChainBldr.getBeanDefinition());
}
}
};
}
/**
* {@link FactoryBean} to defer creation of
* {@link HandlerMappingIntrospector#createCacheFilter()}
*/
static class HandlerMappingIntrospectorCacheFilterFactoryBean
implements ApplicationContextAware, FactoryBean<Filter> {
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
@Override
public Filter getObject() throws Exception {
HandlerMappingIntrospector handlerMappingIntrospector = this.applicationContext
.getBean(HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME, HandlerMappingIntrospector.class);
return handlerMappingIntrospector.createCacheFilter();
}
@Override
public Class<?> getObjectType() {
return Filter.class;
}
}
/**
* Extends {@link FilterChainProxy} to provide as much passivity as possible but
* delegates to {@link CompositeFilter} for
* {@link #doFilter(ServletRequest, ServletResponse, FilterChain)}.
*/
static class CompositeFilterChainProxy extends FilterChainProxy {
/**
* Used for {@link #doFilter(ServletRequest, ServletResponse, FilterChain)}
*/
private final Filter doFilterDelegate;
private final FilterChainProxy springSecurityFilterChain;
/**
* Creates a new instance
* @param filters the Filters to delegate to. One of which must be
* FilterChainProxy.
*/
CompositeFilterChainProxy(List<? extends Filter> filters) {
this.doFilterDelegate = createDoFilterDelegate(filters);
this.springSecurityFilterChain = findFilterChainProxy(filters);
}
@Override
public void afterPropertiesSet() {
this.springSecurityFilterChain.afterPropertiesSet();
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
this.doFilterDelegate.doFilter(request, response, chain);
}
@Override
public List<Filter> getFilters(String url) {
return this.springSecurityFilterChain.getFilters(url);
}
@Override
public List<SecurityFilterChain> getFilterChains() {
return this.springSecurityFilterChain.getFilterChains();
}
@Override
public void setSecurityContextHolderStrategy(SecurityContextHolderStrategy securityContextHolderStrategy) {
this.springSecurityFilterChain.setSecurityContextHolderStrategy(securityContextHolderStrategy);
}
@Override
public void setFilterChainValidator(FilterChainValidator filterChainValidator) {
this.springSecurityFilterChain.setFilterChainValidator(filterChainValidator);
}
@Override
public void setFilterChainDecorator(FilterChainDecorator filterChainDecorator) {
this.springSecurityFilterChain.setFilterChainDecorator(filterChainDecorator);
}
@Override
public void setFirewall(HttpFirewall firewall) {
this.springSecurityFilterChain.setFirewall(firewall);
}
@Override
public void setRequestRejectedHandler(RequestRejectedHandler requestRejectedHandler) {
this.springSecurityFilterChain.setRequestRejectedHandler(requestRejectedHandler);
}
/**
* Used through reflection by Spring Security's Test support to lookup the
* FilterChainProxy Filters for a specific HttpServletRequest.
* @param request
* @return
*/
private List<? extends Filter> getFilters(HttpServletRequest request) {
List<SecurityFilterChain> filterChains = this.springSecurityFilterChain.getFilterChains();
for (SecurityFilterChain chain : filterChains) {
if (chain.matches(request)) {
return chain.getFilters();
}
}
return null;
}
/**
* Creates the Filter to delegate to for doFilter
* @param filters the Filters to delegate to.
* @return the Filter for doFilter
*/
private static Filter createDoFilterDelegate(List<? extends Filter> filters) {
CompositeFilter delegate = new CompositeFilter();
delegate.setFilters(filters);
return delegate;
}
/**
* Find the FilterChainProxy in a List of Filter
* @param filters
* @return non-null FilterChainProxy
* @throws IllegalStateException if the FilterChainProxy cannot be found
*/
private static FilterChainProxy findFilterChainProxy(List<? extends Filter> filters) {
for (Filter filter : filters) {
if (filter instanceof FilterChainProxy fcp) {
return fcp;
}
if (filter instanceof DebugFilter debugFilter) {
return debugFilter.getFilterChainProxy();
}
}
throw new IllegalStateException("Couldn't find FilterChainProxy in " + filters);
}
}
}
@@ -823,7 +823,7 @@ public class HeadersConfigurer<H extends HttpSecurityBuilder<H>>
* replaced with "#". For example:
*
* <pre>
* X-XSS-Protection: 1 ; mode=block
* X-XSS-Protection: 1; mode=block
* </pre>
* @param headerValue the new header value
* @since 5.8
@@ -270,12 +270,14 @@ public final class Saml2LogoutConfigurer<H extends HttpSecurityBuilder<H>>
return postProcess(logoutResponseFilter);
}
private LogoutFilter createRelyingPartyLogoutFilter(RelyingPartyRegistrationRepository registrations) {
private Saml2RelyingPartyInitiatedLogoutFilter createRelyingPartyLogoutFilter(
RelyingPartyRegistrationRepository registrations) {
LogoutHandler[] logoutHandlers = this.logoutHandlers.toArray(new LogoutHandler[0]);
Saml2RelyingPartyInitiatedLogoutSuccessHandler logoutRequestSuccessHandler = createSaml2LogoutRequestSuccessHandler(
registrations);
logoutRequestSuccessHandler.setLogoutRequestRepository(this.logoutRequestConfigurer.logoutRequestRepository);
LogoutFilter logoutFilter = new LogoutFilter(logoutRequestSuccessHandler, logoutHandlers);
Saml2RelyingPartyInitiatedLogoutFilter logoutFilter = new Saml2RelyingPartyInitiatedLogoutFilter(
logoutRequestSuccessHandler, logoutHandlers);
logoutFilter.setLogoutRequestMatcher(createLogoutMatcher());
return postProcess(logoutFilter);
}
@@ -523,4 +525,12 @@ public final class Saml2LogoutConfigurer<H extends HttpSecurityBuilder<H>>
}
private static class Saml2RelyingPartyInitiatedLogoutFilter extends LogoutFilter {
Saml2RelyingPartyInitiatedLogoutFilter(LogoutSuccessHandler logoutSuccessHandler, LogoutHandler... handlers) {
super(logoutSuccessHandler, handlers);
}
}
}
@@ -0,0 +1,44 @@
/*
* 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.config.aot.hint;
import org.springframework.aot.hint.MemberCategory;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;
import org.springframework.aot.hint.TypeReference;
/**
* Runtime hints for
* {@link org.springframework.security.config.annotation.web.configuration.WebMvcSecurityConfiguration}
*
* @author Marcus da Coregio
*/
class WebMvcSecurityConfigurationRuntimeHints implements RuntimeHintsRegistrar {
@Override
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
hints.reflection()
.registerType(TypeReference
.of("org.springframework.security.config.annotation.web.configuration.WebMvcSecurityConfiguration$CompositeFilterChainProxy"),
MemberCategory.INVOKE_DECLARED_CONSTRUCTORS);
hints.reflection()
.registerType(TypeReference
.of("org.springframework.security.config.annotation.web.configuration.WebMvcSecurityConfiguration$HandlerMappingIntrospectorCacheFilterFactoryBean"),
MemberCategory.INVOKE_DECLARED_CONSTRUCTORS);
}
}
@@ -24,6 +24,7 @@ import jakarta.servlet.ServletRequest;
import org.w3c.dom.Element;
import org.springframework.beans.BeanMetadataElement;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanReference;
@@ -36,6 +37,8 @@ import org.springframework.beans.factory.support.ManagedList;
import org.springframework.beans.factory.support.ManagedMap;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.security.access.vote.AffirmativeBased;
import org.springframework.security.access.vote.AuthenticatedVoter;
import org.springframework.security.access.vote.RoleVoter;
@@ -46,6 +49,7 @@ import org.springframework.security.core.context.SecurityContextHolderStrategy;
import org.springframework.security.core.session.SessionRegistryImpl;
import org.springframework.security.web.access.AuthorizationManagerWebInvocationPrivilegeEvaluator;
import org.springframework.security.web.access.DefaultWebInvocationPrivilegeEvaluator;
import org.springframework.security.web.access.HandlerMappingIntrospectorRequestTransformer;
import org.springframework.security.web.access.channel.ChannelDecisionManagerImpl;
import org.springframework.security.web.access.channel.ChannelProcessingFilter;
import org.springframework.security.web.access.channel.InsecureChannelProcessor;
@@ -82,6 +86,7 @@ import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
import org.springframework.util.xml.DomUtils;
import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
/**
* Stateful class which helps HttpSecurityBDP to create the configuration for the
@@ -93,6 +98,11 @@ import org.springframework.util.xml.DomUtils;
*/
class HttpConfigurationBuilder {
private static final String HANDLER_MAPPING_INTROSPECTOR = "org.springframework.web.servlet.handler.HandlerMappingIntrospector";
private static final boolean mvcPresent = ClassUtils.isPresent(HANDLER_MAPPING_INTROSPECTOR,
HttpConfigurationBuilder.class.getClassLoader());
private static final String ATT_CREATE_SESSION = "create-session";
private static final String ATT_SESSION_FIXATION_PROTECTION = "session-fixation-protection";
@@ -744,10 +754,14 @@ class HttpConfigurationBuilder {
// Create and register a AuthorizationManagerWebInvocationPrivilegeEvaluator for
// use with
// taglibs etc.
BeanDefinition wipe = BeanDefinitionBuilder
BeanDefinitionBuilder wipeBldr = BeanDefinitionBuilder
.rootBeanDefinition(AuthorizationManagerWebInvocationPrivilegeEvaluator.class)
.addConstructorArgReference(authorizationFilterParser.getAuthorizationManagerRef())
.getBeanDefinition();
.addConstructorArgReference(authorizationFilterParser.getAuthorizationManagerRef());
if (mvcPresent) {
wipeBldr.addPropertyValue("requestTransformer",
new RootBeanDefinition(HandlerMappingIntrospectorRequestTransformerFactoryBean.class));
}
BeanDefinition wipe = wipeBldr.getBeanDefinition();
this.pc.registerBeanComponent(
new BeanComponentDefinition(wipe, this.pc.getReaderContext().generateBeanName(wipe)));
this.fsi = new RuntimeBeanReference(fsiId);
@@ -913,6 +927,33 @@ class HttpConfigurationBuilder {
return BeanDefinitionBuilder.rootBeanDefinition(ObservationRegistryFactory.class).getBeanDefinition();
}
static class HandlerMappingIntrospectorRequestTransformerFactoryBean
implements FactoryBean<AuthorizationManagerWebInvocationPrivilegeEvaluator.HttpServletRequestTransformer>,
ApplicationContextAware {
private ApplicationContext applicationContext;
@Override
public AuthorizationManagerWebInvocationPrivilegeEvaluator.HttpServletRequestTransformer getObject()
throws Exception {
HandlerMappingIntrospector hmi = this.applicationContext.getBeanProvider(HandlerMappingIntrospector.class)
.getIfAvailable();
return (hmi != null) ? new HandlerMappingIntrospectorRequestTransformer(hmi)
: AuthorizationManagerWebInvocationPrivilegeEvaluator.HttpServletRequestTransformer.IDENTITY;
}
@Override
public Class<?> getObjectType() {
return AuthorizationManagerWebInvocationPrivilegeEvaluator.HttpServletRequestTransformer.class;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
static class RoleVoterBeanFactory extends AbstractGrantedAuthorityDefaultsBeanFactory {
private RoleVoter voter = new RoleVoter();
@@ -2,4 +2,5 @@ org.springframework.beans.factory.aot.BeanRegistrationAotProcessor=\
org.springframework.security.config.annotation.authentication.configuration.AuthenticationManagerBeanRegistrationAotProcessor
org.springframework.aot.hint.RuntimeHintsRegistrar=\
org.springframework.security.config.aot.hint.OAuth2LoginRuntimeHints
org.springframework.security.config.aot.hint.OAuth2LoginRuntimeHints,\
org.springframework.security.config.aot.hint.WebMvcSecurityConfigurationRuntimeHints
@@ -328,7 +328,7 @@ public class AbstractRequestMatcherRegistryTests {
List<RequestMatcher> requestMatchers = new ArrayList<>();
for (RequestMatcher requestMatcher : wrappedMatchers) {
if (requestMatcher instanceof AbstractRequestMatcherRegistry.DeferredRequestMatcher) {
requestMatchers.add(((DeferredRequestMatcher) requestMatcher).requestMatcher.get());
requestMatchers.add(((DeferredRequestMatcher) requestMatcher).requestMatcher);
}
else {
requestMatchers.add(requestMatcher);
@@ -20,6 +20,7 @@ import java.io.IOException;
import io.micrometer.observation.ObservationRegistry;
import io.micrometer.observation.ObservationTextPublisher;
import jakarta.servlet.Filter;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletResponse;
import org.junit.jupiter.api.AfterEach;
@@ -39,7 +40,6 @@ import org.springframework.security.config.annotation.web.configuration.WebSecur
import org.springframework.security.core.userdetails.PasswordEncodedUser;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.FilterChainProxy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.firewall.HttpStatusRequestRejectedHandler;
import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher;
@@ -67,7 +67,7 @@ public class WebSecurityTests {
MockFilterChain chain;
@Autowired
FilterChainProxy springSecurityFilterChain;
Filter springSecurityFilterChain;
@BeforeEach
public void setup() {
@@ -0,0 +1,105 @@
/*
* 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.config.annotation.web.configuration;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.TestAuthentication;
import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.test.context.annotation.SecurityTestExecutionListeners;
import org.springframework.security.web.access.AuthorizationManagerWebInvocationPrivilegeEvaluator.HttpServletRequestTransformer;
import org.springframework.security.web.access.HandlerMappingIntrospectorRequestTransformer;
import org.springframework.security.web.access.WebInvocationPrivilegeEvaluator;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
/**
* Checks that HandlerMappingIntrospectorRequestTransformer is autowired into
* {@link org.springframework.security.web.access.AuthorizationManagerWebInvocationPrivilegeEvaluator}.
*
* @author Rob Winch
*/
@ContextConfiguration
@WebAppConfiguration
@ExtendWith({ SpringExtension.class })
@SecurityTestExecutionListeners
public class AuthorizationManagerWebInvocationPrivilegeEvaluatorConfigTests {
public final SpringTestContext spring = new SpringTestContext(this);
@Autowired(required = false)
HttpServletRequestTransformer requestTransformer;
@Autowired
WebInvocationPrivilegeEvaluator wipe;
@Test
void mvcEnabledConfigThenHandlerMappingIntrospectorRequestTransformerBeanExists() {
this.spring.register(MvcEnabledConfig.class).autowire();
assertThat(this.requestTransformer).isInstanceOf(HandlerMappingIntrospectorRequestTransformer.class);
}
@Test
void mvcNotEnabledThenNoRequestTransformerBeanExists() {
this.spring.register(MvcNotEnabledConfig.class).autowire();
assertThat(this.requestTransformer).isNull();
}
@Test
void mvcNotEnabledAndTransformerThenWIPEDelegatesToTransformer() {
this.spring.register(MvcNotEnabledConfig.class, TransformerConfig.class).autowire();
this.wipe.isAllowed("/uri", TestAuthentication.authenticatedUser());
verify(this.requestTransformer).transform(any());
}
@Configuration
static class TransformerConfig {
@Bean
HttpServletRequestTransformer httpServletRequestTransformer() {
return mock(HttpServletRequestTransformer.class);
}
}
@Configuration
@EnableWebMvc
@EnableWebSecurity
static class MvcEnabledConfig {
}
@Configuration
@EnableWebSecurity
static class MvcNotEnabledConfig {
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 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.
@@ -35,6 +35,7 @@ import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatNoException;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
@@ -56,6 +57,12 @@ public class EnableWebSecurityTests {
this.spring.getContext().getBean("springSecurityFilterChain", DebugFilter.class);
}
// gh-14370
@Test
public void loadConfigWhenEnableWebMvcDebugConfigThenContextIsBuilt() {
assertThatNoException().isThrownBy(() -> this.spring.register(EnableWebMvcDebugConfig.class).autowire());
}
@Test
public void configureWhenEnableWebMvcThenAuthenticationPrincipalResolvable() throws Exception {
this.spring.register(AuthenticationPrincipalConfig.class).autowire();
@@ -86,6 +93,13 @@ public class EnableWebSecurityTests {
assertThat(parentBean.getChild()).isNotSameAs(childBean);
}
@Configuration
@EnableWebMvc
@EnableWebSecurity(debug = true)
static class EnableWebMvcDebugConfig {
}
@Configuration
static class ChildSecurityConfig extends DebugSecurityConfig {
@@ -0,0 +1,127 @@
/*
* 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.config.annotation.web.configuration;
import java.io.IOException;
import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import 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.test.SpringTestContext;
import org.springframework.security.test.context.annotation.SecurityTestExecutionListeners;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.stereotype.Component;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
import org.springframework.web.servlet.handler.HandlerMappingIntrospector.CachedResult;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
/**
* @author Rob Winch
*/
@ContextConfiguration
@WebAppConfiguration
@ExtendWith({ SpringExtension.class })
@SecurityTestExecutionListeners
class HandlerMappingIntrospectorCacheFilterConfigTests {
@Autowired
WebApplicationContext context;
MockMvc mockMvc;
public final SpringTestContext spring = new SpringTestContext(this);
@Autowired(required = false)
MvcEnabledConfig.CaptureHandlerMappingIntrospectorCache captureCacheFilter;
@Autowired(required = false)
HandlerMappingIntrospector hmi;
@Test
@WithMockUser
void hmiIsCached() throws Exception {
this.spring.register(MvcEnabledConfig.class).autowire();
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(springSecurity())
.addFilter(this.captureCacheFilter)
.build();
this.mockMvc.perform(get("/"));
assertThat(this.captureCacheFilter.cachedResult).isNotNull();
}
@Test
@WithMockUser
void configurationLoadsIfNoHMI() {
// no BeanCreationException due to missing HandlerMappingIntrospector
this.spring.register(MvcNotEnabledConfig.class).autowire();
// ensure assumption of HandlerMappingIntrospector is null is true
assertThat(this.hmi).isNull();
}
@Configuration
@EnableWebMvc
@EnableWebSecurity
static class MvcEnabledConfig {
@Component
static class CaptureHandlerMappingIntrospectorCache implements Filter {
final HandlerMappingIntrospector hmi;
private CachedResult cachedResult;
CaptureHandlerMappingIntrospectorCache(HandlerMappingIntrospector hmi) {
this.hmi = hmi;
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
// capture the old cached value to check that caching has already occurred
this.cachedResult = this.hmi.setCache((HttpServletRequest) request);
chain.doFilter(request, response);
}
}
}
@Configuration
@EnableWebSecurity
static class MvcNotEnabledConfig {
}
}
@@ -29,6 +29,7 @@ import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Answers;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
@@ -82,6 +83,7 @@ import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.withSettings;
import static org.springframework.security.config.Customizer.withDefaults;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic;
@@ -304,7 +306,8 @@ public class SessionManagementConfigurerTests {
@Test
public void getWhenAnonymousRequestAndTrustResolverSharedObjectReturnsAnonymousFalseThenSessionIsSaved()
throws Exception {
SharedTrustResolverConfig.TR = mock(AuthenticationTrustResolver.class);
SharedTrustResolverConfig.TR = mock(AuthenticationTrustResolver.class,
withSettings().defaultAnswer(Answers.CALLS_REAL_METHODS));
given(SharedTrustResolverConfig.TR.isAnonymous(any())).willReturn(false);
this.spring.register(SharedTrustResolverConfig.class).autowire();
MvcResult mvcResult = this.mvc.perform(get("/")).andReturn();
@@ -0,0 +1,64 @@
/*
* 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.config.aot.hint;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.aot.hint.MemberCategory;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;
import org.springframework.aot.hint.TypeReference;
import org.springframework.aot.hint.predicate.RuntimeHintsPredicates;
import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.util.ClassUtils;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link WebMvcSecurityConfigurationRuntimeHints}
*
* @author Marcus da Coregio
*/
class WebMvcSecurityConfigurationRuntimeHintsTests {
private final RuntimeHints hints = new RuntimeHints();
@BeforeEach
void setup() {
SpringFactoriesLoader.forResourceLocation("META-INF/spring/aot.factories")
.load(RuntimeHintsRegistrar.class)
.forEach((registrar) -> registrar.registerHints(this.hints, ClassUtils.getDefaultClassLoader()));
}
@Test
void compositeFilterChainProxyHasHints() {
assertThat(RuntimeHintsPredicates.reflection()
.onType(TypeReference
.of("org.springframework.security.config.annotation.web.configuration.WebMvcSecurityConfiguration$CompositeFilterChainProxy"))
.withMemberCategory(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS)).accepts(this.hints);
}
@Test
void handlerMappingIntrospectorCacheFilterFactoryBeanHasHints() {
assertThat(RuntimeHintsPredicates.reflection()
.onType(TypeReference
.of("org.springframework.security.config.annotation.web.configuration.WebMvcSecurityConfiguration$HandlerMappingIntrospectorCacheFilterFactoryBean"))
.withMemberCategory(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS)).accepts(this.hints);
}
}
@@ -320,7 +320,7 @@ public class HeaderSpecTests {
@Test
public void headersWhenXssProtectionValueEnabledModeBlockThenXssProtectionWritten() {
this.expectedHeaders.set(XXssProtectionServerHttpHeadersWriter.X_XSS_PROTECTION, "1 ; mode=block");
this.expectedHeaders.set(XXssProtectionServerHttpHeadersWriter.X_XSS_PROTECTION, "1; mode=block");
// @formatter:off
this.http.headers()
.xssProtection()
@@ -28,6 +28,7 @@ import org.springframework.beans.factory.annotation.Autowired
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.mock.web.MockHttpSession
import org.springframework.security.authentication.TestingAuthenticationToken
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.config.http.SessionCreationPolicy
@@ -118,7 +119,7 @@ class SessionManagementDslTests {
@Test
fun `session management when session authentication error url then redirected to url`() {
this.spring.register(SessionAuthenticationErrorUrlConfig::class.java).autowire()
val authentication: Authentication = mockk()
val authentication: Authentication = TestingAuthenticationToken("user", "password", "ROLE_USER")
val session: MockHttpSession = mockk(relaxed = true)
every { session.changeSessionId() } throws SessionAuthenticationException("any SessionAuthenticationException")
every<Any?> { session.getAttribute(any()) } returns null
@@ -150,7 +151,7 @@ class SessionManagementDslTests {
@Test
fun `session management when session authentication failure handler then handler used`() {
this.spring.register(SessionAuthenticationFailureHandlerConfig::class.java).autowire()
val authentication: Authentication = mockk()
val authentication: Authentication = TestingAuthenticationToken("user", "password", "ROLE_USER")
val session: MockHttpSession = mockk(relaxed = true)
every { session.changeSessionId() } throws SessionAuthenticationException("any SessionAuthenticationException")
every<Any?> { session.getAttribute(any()) } returns null
@@ -210,7 +211,7 @@ class SessionManagementDslTests {
fun `session management when session authentication strategy then strategy used`() {
this.spring.register(SessionAuthenticationStrategyConfig::class.java).autowire()
mockkObject(SessionAuthenticationStrategyConfig.STRATEGY)
val authentication: Authentication = mockk(relaxed = true)
val authentication: Authentication = TestingAuthenticationToken("user", "password", "ROLE_USER")
val session: MockHttpSession = mockk(relaxed = true)
every { session.changeSessionId() } throws SessionAuthenticationException("any SessionAuthenticationException")
every<Any?> { session.getAttribute(any()) } returns null
@@ -147,7 +147,7 @@ public abstract class SecurityExpressionRoot implements SecurityExpressionOperat
@Override
public final boolean isAuthenticated() {
return !isAnonymous();
return this.trustResolver.isAuthenticated(getAuthentication());
}
@Override
@@ -61,13 +61,25 @@ public interface AuthenticationTrustResolver {
* <p>
* @param authentication to test (may be <code>null</code> in which case the method
* will always return <code>false</code>)
* @return <code>true</code> the passed authentication token represented an anonymous
* principal and is authenticated using a remember-me token, <code>false</code>
* otherwise
* @return <code>true</code> the passed authentication token represented an
* authenticated user ({@link #isAuthenticated(Authentication)} and not
* {@link #isRememberMe(Authentication)}, <code>false</code> otherwise
* @since 6.1
*/
default boolean isFullyAuthenticated(Authentication authentication) {
return !isAnonymous(authentication) && !isRememberMe(authentication);
return isAuthenticated(authentication) && !isRememberMe(authentication);
}
/**
* Checks if the {@link Authentication} is not null, authenticated, and not anonymous.
* @param authentication the {@link Authentication} to check.
* @return true if the {@link Authentication} is not null,
* {@link #isAnonymous(Authentication)} returns false, &
* {@link Authentication#isAuthenticated()} is true.
* @since 6.1.7
*/
default boolean isAuthenticated(Authentication authentication) {
return authentication != null && authentication.isAuthenticated() && !isAnonymous(authentication);
}
}
@@ -133,8 +133,7 @@ public final class AuthenticatedAuthorizationManager<T> implements Authorization
@Override
boolean isGranted(Authentication authentication) {
return authentication != null && !this.trustResolver.isAnonymous(authentication)
&& authentication.isAuthenticated();
return this.trustResolver.isAuthenticated(authentication);
}
}
@@ -143,7 +142,7 @@ public final class AuthenticatedAuthorizationManager<T> implements Authorization
@Override
boolean isGranted(Authentication authentication) {
return authentication != null && this.trustResolver.isFullyAuthenticated(authentication);
return this.trustResolver.isFullyAuthenticated(authentication);
}
}
@@ -95,17 +95,28 @@ final class AuthorizationAnnotationUtils {
private static <A extends Annotation> boolean hasDuplicate(MergedAnnotations mergedAnnotations,
Class<A> annotationType) {
boolean alreadyFound = false;
MergedAnnotation<Annotation> alreadyFound = null;
for (MergedAnnotation<Annotation> mergedAnnotation : mergedAnnotations) {
if (isSynthetic(mergedAnnotation.getSource())) {
continue;
}
if (mergedAnnotation.getType() == annotationType) {
if (alreadyFound) {
return true;
}
alreadyFound = true;
if (mergedAnnotation.getType() != annotationType) {
continue;
}
if (alreadyFound == null) {
alreadyFound = mergedAnnotation;
continue;
}
// https://github.com/spring-projects/spring-framework/issues/31803
if (!mergedAnnotation.getSource().equals(alreadyFound.getSource())) {
return true;
}
if (mergedAnnotation.getRoot().getType() != alreadyFound.getRoot().getType()) {
return true;
}
}
return false;
@@ -25,6 +25,7 @@ import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.AuthorityUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
@@ -134,4 +135,27 @@ public class SecurityExpressionRootTests {
assertThat(this.root.hasAnyAuthority("ROLE_A", "NOT")).isTrue();
}
@Test
void isAuthenticatedWhenAuthenticatedNullThenException() {
this.root = new SecurityExpressionRoot((Authentication) null) {
};
assertThatIllegalArgumentException().isThrownBy(() -> this.root.isAuthenticated());
}
@Test
void isAuthenticatedWhenTrustResolverFalseThenFalse() {
AuthenticationTrustResolver atr = mock(AuthenticationTrustResolver.class);
given(atr.isAuthenticated(JOE)).willReturn(false);
this.root.setTrustResolver(atr);
assertThat(this.root.isAuthenticated()).isFalse();
}
@Test
void isAuthenticatedWhenTrustResolverTrueThenTrue() {
AuthenticationTrustResolver atr = mock(AuthenticationTrustResolver.class);
given(atr.isAuthenticated(JOE)).willReturn(true);
this.root.setTrustResolver(atr);
assertThat(this.root.isAuthenticated()).isTrue();
}
}
@@ -18,6 +18,7 @@ package org.springframework.security.authentication;
import org.junit.jupiter.api.Test;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.AuthorityUtils;
import static org.assertj.core.api.Assertions.assertThat;
@@ -63,4 +64,56 @@ public class AuthenticationTrustResolverImplTests {
assertThat(trustResolver.getRememberMeClass()).isEqualTo(TestingAuthenticationToken.class);
}
@Test
void isAuthenticatedWhenAuthenticationNullThenFalse() {
AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();
Authentication authentication = null;
assertThat(trustResolver.isAuthenticated(authentication)).isFalse();
}
@Test
void isAuthenticatedWhenAuthenticationNotAuthenticatedThenFalse() {
AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();
TestingAuthenticationToken authentication = new TestingAuthenticationToken("user", "password");
assertThat(trustResolver.isAuthenticated(authentication)).isFalse();
}
@Test
void isAuthenticatedWhenAnonymousThenFalse() {
AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();
AnonymousAuthenticationToken authentication = new AnonymousAuthenticationToken("key", "principal",
AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS"));
assertThat(trustResolver.isAuthenticated(authentication)).isFalse();
}
@Test
void isFullyAuthenticatedWhenAuthenticationNullThenFalse() {
AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();
Authentication authentication = null;
assertThat(trustResolver.isFullyAuthenticated(authentication)).isFalse();
}
@Test
void isFullyAuthenticatedWhenAuthenticationNotAuthenticatedThenFalse() {
AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();
TestingAuthenticationToken authentication = new TestingAuthenticationToken("user", "password");
assertThat(trustResolver.isFullyAuthenticated(authentication)).isFalse();
}
@Test
void isFullyAuthenticatedWhenAnonymousThenFalse() {
AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();
AnonymousAuthenticationToken authentication = new AnonymousAuthenticationToken("key", "principal",
AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS"));
assertThat(trustResolver.isFullyAuthenticated(authentication)).isFalse();
}
@Test
void isFullyAuthenticatedWhenRememberMeThenFalse() {
AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();
RememberMeAuthenticationToken authentication = new RememberMeAuthenticationToken("key", "user",
AuthorityUtils.createAuthorityList("ROLE_USER"));
assertThat(trustResolver.isFullyAuthenticated(authentication)).isFalse();
}
}
@@ -41,6 +41,13 @@ class AuthorizationAnnotationUtilsTests {
.isThrownBy(() -> AuthorizationAnnotationUtils.findUniqueAnnotation(method, PreAuthorize.class));
}
@Test // gh-13625
void annotationsFromSuperSuperInterfaceShouldNotTriggerAnnotationConfigurationException() throws Exception {
Method method = HelloImpl.class.getMethod("sayHello");
assertThatNoException()
.isThrownBy(() -> AuthorizationAnnotationUtils.findUniqueAnnotation(method, PreAuthorize.class));
}
private interface BaseRepository<T> {
Iterable<T> findAll();
@@ -55,4 +62,24 @@ class AuthorizationAnnotationUtilsTests {
}
private interface Hello {
@PreAuthorize("hasRole('someRole')")
String sayHello();
}
private interface SayHello extends Hello {
}
private static class HelloImpl implements SayHello {
@Override
public String sayHello() {
return "hello";
}
}
}
@@ -24,7 +24,7 @@ The topics in this section describe how to consume Spring Security when using Ma
=== Spring Boot with Maven
Spring Boot provides a `spring-boot-starter-security` starter that aggregates Spring Security-related dependencies.
The simplest and preferred way to use the starter is to use https://docs.spring.io/initializr/docs/current/reference/htmlsingle/[Spring Initializr] by using an IDE integration in (https://joshlong.com/jl/blogPost/tech_tip_geting_started_with_spring_boot.html[Eclipse] or https://www.jetbrains.com/help/idea/spring-boot.html#d1489567e2[IntelliJ], https://github.com/AlexFalappa/nb-springboot/wiki/Quick-Tour[NetBeans]) or through https://start.spring.io.
The simplest and preferred way to use the starter is to use https://docs.spring.io/initializr/docs/current/reference/html/[Spring Initializr] by using an IDE integration in (https://joshlong.com/jl/blogPost/tech_tip_geting_started_with_spring_boot.html[Eclipse] or https://www.jetbrains.com/help/idea/spring-boot.html#d1489567e2[IntelliJ], https://github.com/AlexFalappa/nb-springboot/wiki/Quick-Tour[NetBeans]) or through https://start.spring.io.
Alternatively, you can manually add the starter, as the following example shows:
@@ -182,7 +182,7 @@ The following topics describe how to consume Spring Security when using Gradle.
=== Spring Boot with Gradle
Spring Boot provides a `spring-boot-starter-security` starter that aggregates Spring Security related dependencies.
The simplest and preferred method to use the starter is to use https://docs.spring.io/initializr/docs/current/reference/htmlsingle/[Spring Initializr] by using an IDE integration in (https://joshlong.com/jl/blogPost/tech_tip_geting_started_with_spring_boot.html[Eclipse] or https://www.jetbrains.com/help/idea/spring-boot.html#d1489567e2[IntelliJ], https://github.com/AlexFalappa/nb-springboot/wiki/Quick-Tour[NetBeans]) or through https://start.spring.io.
The simplest and preferred method to use the starter is to use https://docs.spring.io/initializr/docs/current/reference/html/[Spring Initializr] by using an IDE integration in (https://joshlong.com/jl/blogPost/tech_tip_geting_started_with_spring_boot.html[Eclipse] or https://www.jetbrains.com/help/idea/spring-boot.html#d1489567e2[IntelliJ], https://github.com/AlexFalappa/nb-springboot/wiki/Quick-Tour[NetBeans]) or through https://start.spring.io.
Alternatively, you can manually add the starter:
@@ -114,5 +114,5 @@ public class SecurityConfig {
The Lambda DSL was created to accomplish to following goals:
- Automatic indentation makes the configuration more readable.
- The is no need to chain configuration options using `.and()`
- There is no need to chain configuration options using `.and()`
- The Spring Security DSL has a similar configuration style to other Spring DSLs such as Spring Integration and Spring Cloud Gateway.
@@ -62,7 +62,10 @@ This configuration enables <<rsocket-authentication-simple,simple authentication
For Spring Security to work, we need to apply `SecuritySocketAcceptorInterceptor` to the `ServerRSocketFactory`.
Doing so connects our `PayloadSocketAcceptorInterceptor` with the RSocket infrastructure.
In a Spring Boot application, you can do this automatically by using `RSocketSecurityAutoConfiguration` with the following code:
Spring Boot registers it automatically in `RSocketSecurityAutoConfiguration` when you include {gh-samples-url}/reactive/rsocket/hello-security/build.gradle[the correct dependencies].
Or, if you are not using Boot's auto-configuration, you can register it manually in the following way:
[tabs]
======
@@ -91,6 +94,8 @@ fun springSecurityRSocketSecurity(interceptor: SecuritySocketAcceptorInterceptor
----
======
To customize the interceptor itself, use `RSocketSecurity` to add <<rsocket-authentication,authentication>> and <<rsocket-authorization,authorization>>.
[[rsocket-authentication]]
== RSocket Authentication
@@ -430,7 +430,7 @@ spring:
oauth2:
resourceserver:
jwt:
jws-algorithm: RS512
jws-algorithms: RS512
jwk-set-uri: https://idp.example.org/.well-known/jwks.json
----
@@ -614,7 +614,7 @@ You need only supply the domain name and an LDAP URL that supplies the address o
[NOTE]
====
It is also possible to obtain the server's IP address byusing a DNS lookup.
It is also possible to obtain the server's IP address by using a DNS lookup.
This is not currently supported, but hopefully will be in a future version.
====
@@ -788,7 +788,7 @@ Java::
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) {
http
.sessionManagement((session) - session
.sessionManagement((session) -> session
.sessionFixation((sessionFixation) -> sessionFixation
.newSession()
)
@@ -188,7 +188,7 @@ Java::
SecurityFilterChain web(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((authorize) -> authorize
.requestMatchers("/endpoint").hasAuthority('USER')
.requestMatchers("/endpoint").hasAuthority("USER")
.anyRequest().authenticated()
)
// ...
@@ -205,7 +205,7 @@ Kotlin::
SecurityFilterChain web(HttpSecurity http) throws Exception {
http {
authorizeHttpRequests {
authorize("/endpoint", hasAuthority('USER'))
authorize("/endpoint", hasAuthority("USER"))
authorize(anyRequest, authenticated)
}
}
@@ -394,11 +394,6 @@ fun readAccountWithWrongRoleThenAccessDenied() {
While `@PreAuthorize` is quite helpful for declaring needed authorities, it can also be used to evaluate more complex <<using_method_parameters,expressions that involve the method parameters>>.
The above two snippets are ensuring that the user can only request orders that belong to them by comparing the username parameter to xref:servlet/authentication/architecture.adoc#servlet-authentication-authentication[`Authentication#getName`].
The result is that the above method will only be invoked if the `username` in the request path matches the logged-in user's `name`.
If not, Spring Security will throw an `AccessDeniedException` and return a 403 status code.
[[use-postauthorize]]
=== Authorization Method Results with `@PostAuthorize`
@@ -112,9 +112,9 @@ public class SecurityWebApplicationInitializer
}
----
This onlys register the `springSecurityFilterChain` for every URL in your application.
This only registers the `springSecurityFilterChain` for every URL in your application.
After that, we need to ensure that `WebSecurityConfig` was loaded in our existing `ApplicationInitializer`.
For example, if we use Spring MVC it is added in the `getRootConfigClasses()`:
For example, if we use Spring MVC it is added in the `getServletConfigClasses()`:
[[message-web-application-inititializer-java]]
[source,java]
@@ -123,14 +123,42 @@ public class MvcWebApplicationInitializer extends
AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[] { WebSecurityConfig.class };
protected Class<?>[] getServletConfigClasses() {
return new Class[] { WebSecurityConfig.class, WebMvcConfig.class };
}
// ... other overrides ...
}
----
The reason for this is that Spring Security needs to be able to inspect some Spring MVC configuration in order to appropriately configure xref:servlet/authorization/authorize-http-requests.adoc#authorizing-endpoints[underlying request matchers], so they need to be in the same application context.
Placing Spring Security in `getRootConfigClasses` places it into a parent application context that may not be able to find Spring MVC's `HandlerMappingIntrospector`.
==== Configuring for Multiple Spring MVC Dispatchers
If desired, any Spring Security configuration that is unrelated to Spring MVC may be placed in a different configuration class like so:
[source,java]
----
public class MvcWebApplicationInitializer extends
AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[] { NonWebSecurityConfig.class };
}
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[] { WebSecurityConfig.class, WebMvcConfig.class };
}
// ... other overrides ...
}
----
This can be helpful if you have multiple instances of `AbstractAnnotationConfigDispatcherServletInitializer` and don't want to duplicate the general security configuration across both of them.
[[jc-httpsecurity]]
== HttpSecurity
@@ -145,7 +173,7 @@ It is configured with the following default implementation:
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeRequests(authorize -> authorize
.authorizeHttpRequests(authorize -> authorize
.anyRequest().authenticated()
)
.formLogin(withDefaults())
@@ -175,7 +203,7 @@ Note that this configuration is parallels the XML Namespace configuration:
We can configure multiple `HttpSecurity` instances just as we can have multiple `<http>` blocks in XML.
The key is to register multiple `SecurityFilterChain` ``@Bean``s.
The following example has a different configuration for URL's that start with `/api/`.
The following example has a different configuration for URLs that start with `/api/`.
[source,java]
----
@@ -196,7 +224,7 @@ public class MultiHttpSecurityConfig {
@Order(1) <2>
public SecurityFilterChain apiFilterChain(HttpSecurity http) throws Exception {
http
.securityMatcher("/api/**") <3>
.securityMatcher("/api/**") <3>
.authorizeHttpRequests(authorize -> authorize
.anyRequest().hasRole("ADMIN")
)
@@ -262,7 +290,7 @@ public class MyCustomDsl extends AbstractHttpConfigurer<MyCustomDsl, HttpSecurit
[NOTE]
====
This is actually how methods like `HttpSecurity.authorizeRequests()` are implemented.
This is actually how methods like `HttpSecurity.authorizeHttpRequests()` are implemented.
====
You can then use the custom DSL:
@@ -332,7 +360,7 @@ For example, to configure the `filterSecurityPublishAuthorizationSuccess` proper
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeRequests(authorize -> authorize
.authorizeHttpRequests(authorize -> authorize
.anyRequest().authenticated()
.withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
public <O extends FilterSecurityInterceptor> O postProcess(
@@ -1,6 +1,7 @@
[[kotlin-config]]
= Kotlin Configuration
Spring Security Kotlin configuration has been available since Spring Security 5.3.
It lets users configure Spring Security by using a native Kotlin DSL.
@@ -23,19 +24,19 @@ import org.springframework.security.config.annotation.web.invoke
@Bean
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
http {
authorizeRequests {
http {
authorizeHttpRequests {
authorize(anyRequest, authenticated)
}
formLogin { }
httpBasic { }
formLogin { }
httpBasic { }
}
return http.build()
}
----
[NOTE]
Make sure that import the `invoke` function in your class, sometimes the IDE will not auto-import it causing compilation issues.
Make sure to import the `invoke` function in your class, as the IDE will not always auto-import the method, causing compilation issues.
The default configuration (shown in the preceding listing):
@@ -43,7 +44,7 @@ The default configuration (shown in the preceding listing):
* Lets users authenticate with form-based login
* Lets users authenticate with HTTP Basic authentication
Note that this configuration is parallels the XML namespace configuration:
Note that this configuration parallels the XML namespace configuration:
[source,xml]
----
@@ -58,13 +59,13 @@ Note that this configuration is parallels the XML namespace configuration:
We can configure multiple `HttpSecurity` instances, just as we can have multiple `<http>` blocks.
The key is to register multiple `SecurityFilterChain` ``@Bean``s.
The following example has a different configuration for URL's that start with `/api/`:
The following example has a different configuration for URLs that start with `/api/`:
[source,kotlin]
----
@Configuration
import org.springframework.security.config.annotation.web.invoke
@Configuration
@EnableWebSecurity
class MultiHttpSecurityConfig {
@Bean <1>
@@ -81,7 +82,7 @@ class MultiHttpSecurityConfig {
open fun apiFilterChain(http: HttpSecurity): SecurityFilterChain {
http {
securityMatcher("/api/**") <3>
authorizeRequests {
authorizeHttpRequests {
authorize(anyRequest, hasRole("ADMIN"))
}
httpBasic { }
@@ -92,7 +93,7 @@ class MultiHttpSecurityConfig {
@Bean <4>
open fun formLoginFilterChain(http: HttpSecurity): SecurityFilterChain {
http {
authorizeRequests {
authorizeHttpRequests {
authorize(anyRequest, authenticated)
}
formLogin { }
@@ -104,7 +105,7 @@ class MultiHttpSecurityConfig {
<1> Configure Authentication as usual.
<2> Create an instance of `SecurityFilterChain` that contains `@Order` to specify which `SecurityFilterChain` should be considered first.
<3> The `http.antMatcher` states that this `HttpSecurity` is applicable only to URLs that start with `/api/`
<3> The `http.securityMatcher` states that this `HttpSecurity` is applicable only to URLs that start with `/api/`
<4> Create another instance of `SecurityFilterChain`.
If the URL does not start with `/api/`, this configuration is used.
This configuration is considered after `apiFilterChain`, since it has an `@Order` value after `1` (no `@Order` defaults to last).
@@ -570,7 +570,7 @@ spring:
oauth2:
resourceserver:
jwt:
jws-algorithm: RS512
jws-algorithms: RS512
jwk-set-uri: https://idp.example.org/.well-known/jwks.json
----
@@ -374,29 +374,22 @@ Java::
----
@Component
public class TenantJwtIssuerValidator implements OAuth2TokenValidator<Jwt> {
private final TenantRepository tenants;
private final Map<String, JwtIssuerValidator> validators = new ConcurrentHashMap<>();
private final TenantRepository tenants;
public TenantJwtIssuerValidator(TenantRepository tenants) {
this.tenants = tenants;
}
private final OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.INVALID_TOKEN, "The iss claim is not valid",
"https://tools.ietf.org/html/rfc6750#section-3.1");
@Override
public OAuth2TokenValidatorResult validate(Jwt token) {
return this.validators.computeIfAbsent(toTenant(token), this::fromTenant)
.validate(token);
}
public TenantJwtIssuerValidator(TenantRepository tenants) {
this.tenants = tenants;
}
private String toTenant(Jwt jwt) {
return jwt.getIssuer();
}
private JwtIssuerValidator fromTenant(String tenant) {
return Optional.ofNullable(this.tenants.findById(tenant))
.map(t -> t.getAttribute("issuer"))
.map(JwtIssuerValidator::new)
.orElseThrow(() -> new IllegalArgumentException("unknown tenant"));
}
@Override
public OAuth2TokenValidatorResult validate(Jwt token) {
if(this.tenants.findById(token.getIssuer()) != null) {
return OAuth2TokenValidatorResult.success();
}
return OAuth2TokenValidatorResult.failure(this.error);
}
}
----
@@ -405,32 +398,17 @@ Kotlin::
[source,kotlin,role="secondary"]
----
@Component
class TenantJwtIssuerValidator(tenants: TenantRepository) : OAuth2TokenValidator<Jwt> {
private val tenants: TenantRepository
private val validators: MutableMap<String, JwtIssuerValidator> = ConcurrentHashMap()
class TenantJwtIssuerValidator(private val tenants: TenantRepository) : OAuth2TokenValidator<Jwt> {
private val error: OAuth2Error = OAuth2Error(OAuth2ErrorCodes.INVALID_TOKEN, "The iss claim is not valid",
"https://tools.ietf.org/html/rfc6750#section-3.1")
override fun validate(token: Jwt): OAuth2TokenValidatorResult {
return validators.computeIfAbsent(toTenant(token)) { tenant: String -> fromTenant(tenant) }
.validate(token)
}
private fun toTenant(jwt: Jwt): String {
return jwt.issuer.toString()
}
private fun fromTenant(tenant: String): JwtIssuerValidator {
return Optional.ofNullable(tenants.findById(tenant))
.map({ t -> t.getAttribute("issuer") })
.map({ JwtIssuerValidator() })
.orElseThrow({ IllegalArgumentException("unknown tenant") })
}
init {
this.tenants = tenants
return if (tenants.findById(token.issuer) != null)
OAuth2TokenValidatorResult.success() else OAuth2TokenValidatorResult.failure(error)
}
}
----
======
Now that we have a tenant-aware processor and a tenant-aware validator, we can proceed with creating our xref:servlet/oauth2/resource-server/jwt.adoc#oauth2resourceserver-jwt-architecture-jwtdecoder[`JwtDecoder`]:
[tabs]
@@ -63,7 +63,42 @@ image:{icondir}/number_4.png[] If authentication is successful, then _Success_.
== Minimal Dependencies
SAML 2.0 service provider support resides in `spring-security-saml2-service-provider`.
It builds off of the OpenSAML library.
It builds off of the OpenSAML library, and, for that reason, you must also include the Shibboleth Maven repository in your build configuration.
Check https://shibboleth.atlassian.net/wiki/spaces/DEV/pages/1123844333/Use+of+Maven+Central#Publishing-to-Maven-Central[this link] for more details about why a separate repository is needed.
[tabs]
======
Maven::
+
[source,xml,role="primary"]
----
<repositories>
<!-- ... -->
<repository>
<id>shibboleth-releases</id>
<url>https://build.shibboleth.net/nexus/content/repositories/releases/</url>
</repository>
</repositories>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-saml2-service-provider</artifactId>
</dependency>
----
Gradle::
+
[source,groovy,role="secondary"]
----
repositories {
// ...
maven { url "https://build.shibboleth.net/nexus/content/repositories/releases/" }
}
dependencies {
// ...
implementation 'org.springframework.security:spring-security-saml2-service-provider'
}
----
======
[[servlet-saml2login-minimalconfiguration]]
== Minimal Configuration
+2 -2
View File
@@ -1,6 +1,6 @@
springBootVersion=3.1.1
version=6.1.6-SNAPSHOT
samplesBranch=main
version=6.1.7
samplesBranch=6.1.x
org.gradle.jvmargs=-Xmx3g -XX:+HeapDumpOnOutOfMemoryError
org.gradle.parallel=true
org.gradle.caching=true
+11 -11
View File
@@ -1,22 +1,22 @@
[versions]
com-squareup-okhttp3 = "3.14.9"
io-rsocket = "1.1.4"
io-spring-javaformat = "0.0.40"
io-spring-javaformat = "0.0.41"
io-spring-nohttp = "0.0.11"
jakarta-websocket = "2.1.1"
org-apache-directory-server = "1.5.5"
org-apache-maven-resolver = "1.8.2"
org-aspectj = "1.9.20.1"
org-aspectj = "1.9.21.1"
org-bouncycastle = "1.70"
org-eclipse-jetty = "11.0.18"
org-eclipse-jetty = "11.0.20"
org-jetbrains-kotlin = "1.8.22"
org-jetbrains-kotlinx = "1.6.4"
org-mockito = "4.8.1"
org-opensaml = "4.1.1"
org-springframework = "6.0.14"
org-springframework = "6.0.17"
[libraries]
ch-qos-logback-logback-classic = "ch.qos.logback:logback-classic:1.4.11"
ch-qos-logback-logback-classic = "ch.qos.logback:logback-classic:1.4.14"
com-fasterxml-jackson-jackson-bom = "com.fasterxml.jackson:jackson-bom:2.14.3"
com-google-inject-guice = "com.google.inject:guice:3.0"
com-netflix-nebula-nebula-project-plugin = "com.netflix.nebula:nebula-project-plugin:8.2.0"
@@ -24,17 +24,18 @@ com-nimbusds-nimbus-jose-jwt = "com.nimbusds:nimbus-jose-jwt:9.24.4"
com-nimbusds-oauth2-oidc-sdk = "com.nimbusds:oauth2-oidc-sdk:9.43.3"
com-squareup-okhttp3-mockwebserver = { module = "com.squareup.okhttp3:mockwebserver", version.ref = "com-squareup-okhttp3" }
com-squareup-okhttp3-okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "com-squareup-okhttp3" }
com-unboundid-unboundid-ldapsdk = "com.unboundid:unboundid-ldapsdk:6.0.10"
com-unboundid-unboundid-ldapsdk = "com.unboundid:unboundid-ldapsdk:6.0.11"
commons-collections = "commons-collections:commons-collections:3.2.2"
io-freefair-gradle-aspectj-plugin = "io.freefair.gradle:aspectj-plugin:6.6.3"
io-micrometer-micrometer-observation = "io.micrometer:micrometer-observation:1.10.13"
io-mockk = "io.mockk:mockk:1.13.8"
io-projectreactor-reactor-bom = "io.projectreactor:reactor-bom:2022.0.13"
io-mockk = "io.mockk:mockk:1.13.9"
io-projectreactor-reactor-bom = "io.projectreactor:reactor-bom:2022.0.16"
io-rsocket-rsocket-bom = { module = "io.rsocket:rsocket-bom", version.ref = "io-rsocket" }
io-spring-javaformat-spring-javaformat-checkstyle = { module = "io.spring.javaformat:spring-javaformat-checkstyle", version.ref = "io-spring-javaformat" }
io-spring-javaformat-spring-javaformat-gradle-plugin = { module = "io.spring.javaformat:spring-javaformat-gradle-plugin", version.ref = "io-spring-javaformat" }
io-spring-nohttp-nohttp-checkstyle = { module = "io.spring.nohttp:nohttp-checkstyle", version.ref = "io-spring-nohttp" }
io-spring-nohttp-nohttp-gradle = { module = "io.spring.nohttp:nohttp-gradle", version.ref = "io-spring-nohttp" }
io-spring-security-release-plugin = "io.spring.gradle:spring-security-release-plugin:1.0.1"
jakarta-annotation-jakarta-annotation-api = "jakarta.annotation:jakarta.annotation-api:2.1.1"
jakarta-inject-jakarta-inject-api = "jakarta.inject:jakarta.inject-api:2.0.1"
jakarta-persistence-jakarta-persistence-api = "jakarta.persistence:jakarta.persistence-api:3.1.0"
@@ -57,7 +58,7 @@ org-apache-maven-maven-resolver-provider = "org.apache.maven:maven-resolver-prov
org-apache-maven-resolver-maven-resolver-connector-basic = { module = "org.apache.maven.resolver:maven-resolver-connector-basic", version.ref = "org-apache-maven-resolver" }
org-apache-maven-resolver-maven-resolver-impl = { module = "org.apache.maven.resolver:maven-resolver-impl", version.ref = "org-apache-maven-resolver" }
org-apache-maven-resolver-maven-resolver-transport-http = { module = "org.apache.maven.resolver:maven-resolver-transport-http", version.ref = "org-apache-maven-resolver" }
org-apereo-cas-client-cas-client-core = "org.apereo.cas.client:cas-client-core:4.0.3"
org-apereo-cas-client-cas-client-core = "org.apereo.cas.client:cas-client-core:4.0.4"
org-aspectj-aspectjrt = { module = "org.aspectj:aspectjrt", version.ref = "org-aspectj" }
org-aspectj-aspectjweaver = { module = "org.aspectj:aspectjweaver", version.ref = "org-aspectj" }
org-assertj-assertj-core = "org.assertj:assertj-core:3.24.2"
@@ -82,7 +83,7 @@ org-seleniumhq-selenium-selenium-java = "org.seleniumhq.selenium:selenium-java:3
org-seleniumhq-selenium-selenium-support = "org.seleniumhq.selenium:selenium-support:3.141.59"
org-skyscreamer-jsonassert = "org.skyscreamer:jsonassert:1.5.1"
org-slf4j-log4j-over-slf4j = "org.slf4j:log4j-over-slf4j:1.7.36"
org-slf4j-slf4j-api = "org.slf4j:slf4j-api:2.0.9"
org-slf4j-slf4j-api = "org.slf4j:slf4j-api:2.0.12"
org-springframework-data-spring-data-bom = "org.springframework.data:spring-data-bom:2022.0.12"
org-springframework-ldap-spring-ldap-core = "org.springframework.ldap:spring-ldap-core:3.0.6"
org-springframework-spring-framework-bom = { module = "org.springframework:spring-framework-bom", version.ref = "org-springframework" }
@@ -95,7 +96,6 @@ org-yaml-snakeyaml = "org.yaml:snakeyaml:1.30"
org-apache-commons-commons-io = "org.apache.commons:commons-io:1.3.2"
io-github-gradle-nexus-publish-plugin = "io.github.gradle-nexus:publish-plugin:1.1.0"
org-gretty-gretty = "org.gretty:gretty:4.0.3"
com-apollographql-apollo-apollo-runtime = "com.apollographql.apollo:apollo-runtime:2.4.5"
com-github-ben-manes-gradle-versions-plugin = "com.github.ben-manes:gradle-versions-plugin:0.38.0"
com-github-spullara-mustache-java-compiler = "com.github.spullara.mustache.java:compiler:0.9.11"
org-hidetake-gradle-ssh-plugin = "org.hidetake:gradle-ssh-plugin:2.10.1"
Binary file not shown.

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