1
0
mirror of synced 2026-07-13 14:45:10 +00:00

Compare commits

..

81 Commits

Author SHA1 Message Date
Steve Riesenberg a8457b518f Release 5.7.0-M1 2022-01-14 12:16:08 -06:00
Steve Riesenberg 4fd5c7ffa3 Update spring-ldap-core to 2.4.0-M1
Closes gh-10732
2022-01-14 11:58:45 -06:00
Steve Riesenberg ce35bee1bd Update org.springframework.data to 2021.2.0-M1
Closes gh-10731
2022-01-14 11:58:45 -06:00
Steve Riesenberg 28ea347b5a Update org.springframework to 5.3.15
Closes gh-10730
2022-01-14 11:58:45 -06:00
Steve Riesenberg 8aa7b5897a Update org.slf4j to 1.7.33
Closes gh-10729
2022-01-14 11:58:45 -06:00
Steve Riesenberg 29bc6745e8 Update htmlunit-driver to 2.56.0
Closes gh-10728
2022-01-14 11:58:45 -06:00
Steve Riesenberg 08bc7a53d6 Update junit-bom to 5.8.2
Closes gh-10726
2022-01-14 11:58:45 -06:00
Steve Riesenberg e5212926a1 Update org.jetbrains.kotlinx to 1.6.0
Closes gh-10725
2022-01-14 11:58:45 -06:00
Marcus Da Coregio 2ed93ec0cf Rename integrationTestCompile/Runtime configurations
The kotlin-gradle-plugin is changing some configuration's properties from configurations that has the same prefix as the sourceSet.
It is enforcing the canBeResolved property to false. See https://youtrack.jetbrains.com/issue/KT-50748.
This commits changes the suffix to compile -> compileClasspath, runtime -> runtimeClasspath to workaround this issue.

Issue gh-10350
2022-01-14 11:58:45 -06:00
Steve Riesenberg bb9d9d7f9d Update org.jetbrains.kotlin to 1.6.10
Closes gh-10724
2022-01-14 11:58:45 -06:00
Steve Riesenberg 815891d6f0 Update cas-client-core to 3.6.4
Closes gh-10723
2022-01-14 11:58:45 -06:00
Steve Riesenberg 5187bea5ea Update hibernate-entitymanager to 5.6.3.Final
Closes gh-10722
2022-01-14 11:58:45 -06:00
Steve Riesenberg c6c27d795b Update org.bouncycastle to 1.70
Closes gh-10721
2022-01-14 11:58:45 -06:00
Steve Riesenberg f8c8d049c3 Update assertj-core to 3.22.0
Closes gh-10720
2022-01-14 11:58:45 -06:00
Steve Riesenberg bb92dd3cc5 Update org.aspectj to 1.9.8.RC3
Closes gh-10719
2022-01-14 11:58:45 -06:00
Steve Riesenberg 60653ddf19 Update htmlunit to 2.56.0
Closes gh-10718
2022-01-14 11:58:45 -06:00
Steve Riesenberg 7c54f98944 Update io.r2dbc to 0.9.0.RELEASE
Closes gh-10717
2022-01-14 11:58:45 -06:00
Steve Riesenberg 5cd7c71923 Update io.projectreactor to 2020.0.15
Closes gh-10715
2022-01-14 11:58:45 -06:00
Steve Riesenberg 7d0185f051 Update mockk to 1.12.2
Closes gh-10714
2022-01-14 11:58:45 -06:00
Steve Riesenberg b91d38752a Update com.nimbusds to 9.22
Closes gh-10713
2022-01-14 11:58:45 -06:00
Steve Riesenberg 9b12616913 Update jackson-bom to 2.13.1
Closes gh-10710
2022-01-14 11:58:45 -06:00
Steve Riesenberg 08139cf9f4 Update logback-classic to 1.2.10
Closes gh-10709
2022-01-14 11:58:45 -06:00
Steve Riesenberg 801dcfdcb4 Allow milestones and release candidates in version upgrades 2022-01-14 11:58:45 -06:00
Steve Riesenberg 4939331501 Fix inconsistency in hasProperty check 2022-01-13 17:39:37 -06:00
Steve Riesenberg 8abd4e999f Add GitHubReleasePlugin with createGitHubRelease task
Closes gh-10456
Closes gh-10457
2022-01-13 17:39:36 -06:00
Josh Cummings 194eaf8491 Pull most recent Structure101 version
Closes gh-10696
2022-01-11 10:03:13 -07:00
Marcus Da Coregio 60ed3602f6 Make source code compatible with JDK 8
Closes gh-10695
2022-01-11 09:19:41 -03:00
Joe Grandja 214cfe807e Allow Jwt assertion to be resolved
Closes gh-9812
2022-01-10 10:42:10 -05:00
heowc 1ab0705b47 Fix typo 2022-01-10 16:17:42 +01:00
Marcus Da Coregio f04cd641b0 Fix @since tag
Issue gh-10590, gh-10554
2022-01-06 13:18:25 -03:00
Rob Winch 3bb82c4449 Antora prerelease: true for milestone and rc 2022-01-05 09:59:48 -06:00
Rob Winch 89366d0874 Update RELEASE.adoc for antora.yml 2022-01-04 15:51:03 -06:00
Rob Winch 6884a16726 Add CheckAntoraVersionPlugin 2022-01-04 15:29:14 -06:00
Josh Cummings 6b54afe9a3 Remove SAML 2.0 Logout Default
Closes gh-10607
2022-01-03 12:54:22 -07:00
Josh Cummings b9453da343 Support No SingleLogoutServiceLocation
Closes gh-10674
2022-01-03 12:54:18 -07:00
Marcus Da Coregio 18427b6411 Configure WebInvocationPrivilegeEvaluator bean for multiple filter chains
Closes gh-10554
2021-12-13 08:57:30 -03:00
Marcus Da Coregio 7e17a00197 Add RequestMatcherEntry 2021-12-13 08:57:30 -03:00
Marcus Da Coregio 53b8cff26f Introduce AuthorizationManagerWebInvocationPrivilegeEvaluator
Closes gh-10590
2021-12-13 08:57:30 -03:00
Josh Cummings cd8983d4e5 Polish enableSessionUrlRewriting Clarification
Closes gh-7644
2021-12-09 12:14:40 -07:00
James Howe 5598688fa6 Clarify behaviour of enableSessionUrlRewriting
See #3087
2021-12-09 12:06:30 -07:00
Guirong Hu 22379e79e7 Fix the bug that the custom GrantedAuthority comparison fails
Closes gh-10566
2021-12-08 08:50:36 -03:00
Marcus Da Coregio 65426a40ec Add Cross Origin Policies headers
Add DSL support for Cross-Origin-Opener-Policy, Cross-Origin-Embedder-Policy and Cross-Origin-Resource-Policy headers

Closes gh-9385, gh-10118
2021-12-07 17:23:06 +01:00
Steve Riesenberg 7ec3b55ab3 Fix Reactive OAuth2 Kotlin DSL examples
Closes gh-10580
2021-12-07 13:48:11 +01:00
Marcus Da Coregio ed3b0fbaad Prevent using both authorizeRequests and authorizeHttpRequests
Closes gh-10573
2021-12-06 15:47:49 -03:00
Steve Riesenberg 62e8799a8d Use BDD in tests 2021-12-02 17:44:47 -06:00
Steve Riesenberg df0f6f83af Polish gh-9597 2021-12-02 17:44:47 -06:00
Karl Tinawi 925d531cbe Set details on authentication token created by HttpServlet3RequestFactory
Currently the login mechanism when triggered by executing HttpServlet3RequestFactory#login does not set any details on the underlying authentication token that is authenticated.

This change adds an AuthenticationDetailsSource on the HttpServlet3RequestFactory, which defaults to a WebAuthenticationDetailsSource.

Closes gh-9579
2021-12-02 17:44:46 -06:00
Steve Riesenberg 074e38d565 Add missing since
Issue gh-7765
2021-12-02 12:09:57 -06:00
Steve Riesenberg 3af619d565 Add hasIpAddress to Reactive Kotlin DSL
Closes gh-10571
2021-12-02 12:01:11 -06:00
Steve Riesenberg bb2d80fea3 Update copyright year
Issue gh-10557
2021-12-01 17:35:43 -06:00
Steve Riesenberg f49c286050 Fix case sensitive headers comparison
Closes gh-10557
2021-12-01 15:05:13 -06:00
Arnaud Mergey dbe4d704f8 Add SP NameIDFormat Support
closes gh-9115
2021-12-01 13:23:30 -07:00
Josh Cummings a68411566e Polish Memory Leak Mitigation
Issue gh-9841
2021-11-30 15:33:47 -07:00
Hiroshi Shirosaki 2bc643d6c8 Address SecurityContextHolder memory leak
To get current context without creating a new context.
Creating a new context may cause ThreadLocal leak.

Closes gh-9841
2021-11-30 15:33:39 -07:00
Josh Cummings 1251cde04c Add Missing Since
Issue gh-10482
2021-11-30 15:17:48 -07:00
Igor Pelesic a3a9de1b9b PermitAllSupport supports AuthorizeHttpRequestsConfigurer
PermitAllSupport supports either an ExpressionUrlAuthorizationConfigurer or an AuthorizeHttpRequestsConfigurer. If none or both are configured an error message is thrown.

Closes gh-10482
2021-11-30 15:17:22 -07:00
Steve Riesenberg f838b7cb1d Polish gh-10081 2021-11-30 15:44:41 -06:00
Jonas Dittrich 23e895f0b1 Add ObjectIdentityGenerator customization to JdbcAclService
Providing the possibility to change, how ObjectIdentitys are created inside the BasicLookupStrategy,JdbcAclService

There was a problem with hard coded object identity creation inside the BasicLookupStrategy and the JdbcAclService. It was overkill to overwrite
these classes only for changing this, so introducing an ObjectIdentityGenerator seems the be the better solution here. At default, the standard
ObjectIdentityRetrievalStrategyImpl is used, but can be customized due to setters.

Closes gh-10079
2021-11-30 15:43:11 -06:00
Steve Riesenberg 204f0b4599 Polish gh-10007 2021-11-30 15:27:58 -06:00
Guirong Hu 43317c5a61 Support IP whitelist for Spring Security Webflux
Closes gh-7765
2021-11-30 15:27:58 -06:00
Jonas Erbe dec858a5b7 Fix JwtClaimValidator wrong error code
Previously JwtClaimValidator returned the invalid_request error on claim validation failure.
But validators have to return invalid_token errors on failure according to:
https://datatracker.ietf.org/doc/html/rfc6750#section-3.1.
Also see gh-10337

Closes gh-10337
2021-11-29 12:04:30 -07:00
Eleftheria Stein bbeca7cd65 Polish LDAP serialization
Closes gh-9263
2021-11-29 18:03:15 +01:00
Markus Heiden 3c18278123 Start with LDAP Jackson2 mixins
Issue gh-9263
2021-11-29 18:03:03 +01:00
Henning Poettker bb99d7d95a Fix return type for NoOpPasswordEncoder bean in documentation 2021-11-23 14:13:12 -03:00
Lars Grefer d736a2b358 Remove usages of Gradle's jcenter() repository
Closes gh-10253
2021-11-22 09:28:47 -03:00
Lars Grefer cf95d3f91e Fix Gradle Deprecation Warnings 2021-11-22 09:28:24 -03:00
Dávid Kováč 17e28fa7aa Update clockSkew javadoc according to implementation
Closes gh-10174
2021-11-19 13:48:32 +01:00
Josh Cummings 739cdc1a4c Polish AuthRequestConverter Sample Doc
Issue gh-10364
2021-11-18 13:36:10 -07:00
Norbert Nowak 02cd1dd3c4 Fix AuthnRequestConverter Sample Typos
Closes gh-10364
2021-11-18 13:26:25 -07:00
Jeff Maxwell b7cc667d21 Fix setJWTClaimSetJWSKeySelector Typo
Closes gh-10504
2021-11-16 15:33:27 -07:00
Jeff Maxwell 3fb1565cc0 Fix jwtDecoder Documentation Usage
Closes gh-10505
2021-11-16 15:17:37 -07:00
Khaled Hamlaoui 00fafd878c Allow custom OAuth2ErrorHttpMessageConverter with OAuth2ErrorResponseErrorHandler
Closes gh-10425
2021-11-16 15:27:48 -06:00
Rob Winch 96a6fef820 Prevent Save @Transient Authentication with existing HttpSession
Previously, @Transient Authentication would get saved if an existing
HttpSession existed but it shouldn't.

This commit always prevents @Transient Authentication from being saved.

Closes gh-9992
2021-11-16 14:44:49 -06:00
« Christophe 4318a51971 Fix CsrfConfigurer default AccessDeniedHandler consistency
Fix when AccessDeniedHandler is specified per RequestMatcher on
ExceptionHandlingConfigurer.

This introduces evolutions on :
- CsrfConfigurer#getDefaultAccessDeniedHandler,
to retrieve an AccessDeniedHandler similar to the one used by
ExceptionHandlingConfigurer.
- OAuth2ResourceServerConfigurer#accessDeniedHandler, to continue to
handle CsrfException with the default AccessDeniedHandler implementation

Fixes: gh-6511
2021-11-16 14:22:35 -06:00
Stephane Nicoll 61ee4e5a76 Avoid using SpEL to change the meaning of the injection point
This commit removes the use of SpEL expression and replaces it with an
explicit call to the underlying method.
2021-11-16 13:53:00 -06:00
Onur Kagan Ozcan aa0f788f59 Add RedirectStrategy customization to ChannelSecurityConfigurer for RetryWith classes 2021-11-16 13:44:18 -06:00
Rob Winch 0c201565fc Fix format DelegatingPasswordEncoder 2021-11-16 13:32:15 -06:00
Rob Winch 582629c087 Rename prefix/suffix in DelegatingPasswordEncoder
Issue gh-10273
2021-11-16 13:16:37 -06:00
heowc 399cf2e59d Support for changing prefix and suffix in DelegatingPasswordEncoder
Closes gh-10273
2021-11-16 13:16:37 -06:00
Lars Grefer ec8912aa47 Update aspectj-plugin to 6.3.0
Version 6.3.0 aligns with the used Gradle 7.3
2021-11-16 12:52:42 -06:00
Josh Cummings 7b15098570 Update Spring Security to 5.7
Closes gh-10509
2021-11-15 17:10:00 -07:00
281 changed files with 10616 additions and 6169 deletions
+20
View File
@@ -0,0 +1,20 @@
{
"index_name": "security-docs",
"start_urls": [
"https://docs.spring.io/spring-security/reference/"
],
"selectors": {
"lvl0": {
"selector": "//nav[@class='crumbs']//li[@class='crumb'][last()-1]",
"type": "xpath",
"global": true,
"default_value": "Home"
},
"lvl1": ".doc h1",
"lvl2": ".doc h2",
"lvl3": ".doc h3",
"lvl4": ".doc h4",
"text": ".doc p, .doc td.content, .doc th.tableblock"
}
}
+20
View File
@@ -0,0 +1,20 @@
#!/bin/bash
HOST="$1"
HOST_PATH="$2"
SSH_PRIVATE_KEY="$3"
SSH_KNOWN_HOST="$4"
if [ "$#" -ne 4 ]; then
echo -e "not enough arguments USAGE:\n\n$0 \$HOST \$HOST_PATH \$SSH_PRIVATE_KEY \$SSH_KNOWN_HOSTS \n\n" >&2
exit 1
fi
# Use a non-default path to avoid overriding when testing locally
SSH_PRIVATE_KEY_PATH=~/.ssh/github-actions-docs
install -m 600 -D /dev/null "$SSH_PRIVATE_KEY_PATH"
echo "$SSH_PRIVATE_KEY" > "$SSH_PRIVATE_KEY_PATH"
echo "$SSH_KNOWN_HOST" > ~/.ssh/known_hosts
rsync --delete -avze "ssh -i $SSH_PRIVATE_KEY_PATH" docs/build/site/ "$HOST:$HOST_PATH"
rm -f "$SSH_PRIVATE_KEY_PATH"
+21
View File
@@ -0,0 +1,21 @@
#!/bin/bash
###
# Docs
# config.json https://docsearch.algolia.com/docs/config-file
# Run the crawler https://docsearch.algolia.com/docs/run-your-own/#run-the-crawl-from-the-docker-image
### USAGE
if [ "$#" -ne 3 ]; then
echo -e "not enough arguments USAGE:\n\n$0 \$ALGOLIA_APPLICATION_ID \$ALGOLIA_API_KEY \$CONFIG_FILE\n\n" >&2
exit 1
fi
# Script Parameters
APPLICATION_ID=$1
API_KEY=$2
CONFIG_FILE=$3
#### Script
script_dir=$(dirname $0)
docker run -e "APPLICATION_ID=$APPLICATION_ID" -e "API_KEY=$API_KEY" -e "CONFIG=$(cat $CONFIG_FILE | jq -r tostring)" algolia/docsearch-scraper
+16
View File
@@ -0,0 +1,16 @@
name: Update Algolia Index
on:
schedule:
- cron: '0 10 * * *' # Once per day at 10am UTC
workflow_dispatch: # Manual trigger
jobs:
update:
name: Update Algolia Index
runs-on: ubuntu-latest
steps:
- name: Checkout Source
uses: actions/checkout@v2
- name: Update Index
run: ${GITHUB_WORKSPACE}/.github/actions/algolia-docsearch-scraper.sh "${{ secrets.ALGOLIA_APPLICATION_ID }}" "${{ secrets.ALGOLIA_WRITE_API_KEY }}" "${GITHUB_WORKSPACE}/.github/actions/algolia-config.json"
-11
View File
@@ -1,30 +1,19 @@
name: Generate Antora Files and Request Build
on:
workflow_dispatch:
push:
branches-ignore:
- 'gh-pages'
tags: '**'
env:
GH_ACTIONS_REPO_TOKEN: ${{ secrets.GH_ACTIONS_REPO_TOKEN }}
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
if: ${{ github.repository == 'spring-projects/spring-security' }}
steps:
- name: Checkout Source
uses: actions/checkout@v2
- name: Set up gradle
uses: spring-io/spring-gradle-build-action@v1
with:
java-version: '11'
distribution: 'adopt'
- name: Generate antora.yml
run: ./gradlew :spring-security-docs:generateAntora
- name: Extract Branch Name
@@ -3,15 +3,9 @@ on:
schedule:
- cron: '0 10 * * *' # Once per day at 10am UTC
permissions:
contents: read
jobs:
main:
runs-on: ubuntu-latest
if: ${{ github.repository == 'spring-projects/spring-security' }}
permissions:
contents: none
steps:
- name: Delete artifacts in cron job
env:
@@ -2,8 +2,6 @@ name: CI
on:
push:
branches:
- '**'
schedule:
- cron: '0 10 * * *' # Once per day at 10am UTC
workflow_dispatch: # Manual trigger
@@ -18,28 +16,19 @@ env:
STRUCTURE101_LICENSEID: ${{ secrets.STRUCTURE101_LICENSEID }}
ARTIFACTORY_USERNAME: ${{ secrets.ARTIFACTORY_USERNAME }}
ARTIFACTORY_PASSWORD: ${{ secrets.ARTIFACTORY_PASSWORD }}
permissions:
contents: read
RUN_JOBS: ${{ github.repository == 'spring-projects/spring-security' }}
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 }}
steps:
- uses: actions/checkout@v2
- id: continue
name: Determine if should continue
run: |
# Run jobs if in upstream repository
echo "::set-output name=runjobs::true"
# Extract version from gradle.properties
version=$(cat gradle.properties | grep "version=" | awk -F'=' '{print $2}')
echo "::set-output name=project_version::$version"
if: env.RUN_JOBS == 'true'
run: echo "::set-output name=runjobs::true"
build_jdk_11:
name: Build JDK 11
needs: [prerequisites]
@@ -51,14 +40,18 @@ jobs:
steps:
- uses: actions/checkout@v2
- name: Set up JDK 11
uses: actions/setup-java@v2
uses: actions/setup-java@v1
with:
java-version: '11'
distribution: 'adopt'
- 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: Setup gradle user name
run: |
mkdir -p ~/.gradle
echo 'systemProp.user.name=spring-builds+github' >> ~/.gradle/gradle.properties
- name: Cache Gradle packages
uses: actions/cache@v2
with:
path: ~/.gradle/caches
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }}
- name: Build with Gradle
env:
GRADLE_ENTERPRISE_CACHE_USERNAME: ${{ secrets.GRADLE_ENTERPRISE_CACHE_USER }}
@@ -72,11 +65,14 @@ jobs:
if: needs.prerequisites.outputs.runjobs
steps:
- uses: actions/checkout@v2
- name: Set up gradle
uses: spring-io/spring-gradle-build-action@v1
- name: Set up JDK
uses: actions/setup-java@v1
with:
java-version: '11'
distribution: 'adopt'
- name: Setup gradle user name
run: |
mkdir -p ~/.gradle
echo 'systemProp.user.name=spring-builds+github' >> ~/.gradle/gradle.properties
- name: Snapshot Tests
run: |
export GRADLE_ENTERPRISE_CACHE_USERNAME="$GRADLE_ENTERPRISE_CACHE_USER"
@@ -90,23 +86,20 @@ jobs:
if: needs.prerequisites.outputs.runjobs
steps:
- uses: actions/checkout@v2
- name: Set up gradle
uses: spring-io/spring-gradle-build-action@v1
- name: Set up JDK
uses: actions/setup-java@v1
with:
java-version: '11'
distribution: 'adopt'
- name: Setup gradle user name
run: |
mkdir -p ~/.gradle
echo 'systemProp.user.name=spring-builds+github' >> ~/.gradle/gradle.properties
- name: Check samples project
env:
LOCAL_REPOSITORY_PATH: ${{ github.workspace }}/build/publications/repos
SAMPLES_DIR: ../spring-security-samples
VERSION: ${{ needs.prerequisites.outputs.project_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"
./gradlew publishMavenJavaPublicationToLocalRepository
./gradlew cloneSamples -PcloneOutputDirectory="$SAMPLES_DIR"
./gradlew --project-dir "$SAMPLES_DIR" --init-script spring-security-ci.gradle -PlocalRepositoryPath="$LOCAL_REPOSITORY_PATH" -PspringSecurityVersion="$VERSION" :runAllTests
./gradlew checkSamples --stacktrace
check_tangles:
name: Check for Package Tangles
needs: [ prerequisites ]
@@ -114,11 +107,14 @@ jobs:
if: needs.prerequisites.outputs.runjobs
steps:
- uses: actions/checkout@v2
- name: Set up gradle
uses: spring-io/spring-gradle-build-action@v1
- name: Set up JDK
uses: actions/setup-java@v1
with:
java-version: '11'
distribution: 'adopt'
- name: Setup gradle user name
run: |
mkdir -p ~/.gradle
echo 'systemProp.user.name=spring-builds+github' >> ~/.gradle/gradle.properties
- name: Check for package tangles
run: |
export GRADLE_ENTERPRISE_CACHE_USERNAME="$GRADLE_ENTERPRISE_CACHE_USER"
@@ -131,11 +127,14 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up gradle
uses: spring-io/spring-gradle-build-action@v1
- name: Set up JDK
uses: actions/setup-java@v1
with:
java-version: '11'
distribution: 'adopt'
- name: Setup gradle user name
run: |
mkdir -p ~/.gradle
echo 'systemProp.user.name=spring-builds+github' >> ~/.gradle/gradle.properties
- name: Deploy artifacts
run: |
export GRADLE_ENTERPRISE_CACHE_USERNAME="$GRADLE_ENTERPRISE_CACHE_USER"
@@ -155,11 +154,14 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up gradle
uses: spring-io/spring-gradle-build-action@v1
- name: Set up JDK
uses: actions/setup-java@v1
with:
java-version: '11'
distribution: 'adopt'
- name: Setup gradle user name
run: |
mkdir -p ~/.gradle
echo 'systemProp.user.name=spring-builds+github' >> ~/.gradle/gradle.properties
- name: Deploy Docs
run: |
export GRADLE_ENTERPRISE_CACHE_USERNAME="$GRADLE_ENTERPRISE_CACHE_USER"
@@ -176,11 +178,14 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up gradle
uses: spring-io/spring-gradle-build-action@v1
- name: Set up JDK
uses: actions/setup-java@v1
with:
java-version: '11'
distribution: 'adopt'
- name: Setup gradle user name
run: |
mkdir -p ~/.gradle
echo 'systemProp.user.name=spring-builds+github' >> ~/.gradle/gradle.properties
- name: Deploy Schema
run: |
export GRADLE_ENTERPRISE_CACHE_USERNAME="$GRADLE_ENTERPRISE_CACHE_USER"
@@ -191,121 +196,14 @@ jobs:
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@v2
with:
token: ${{ secrets.GH_ACTIONS_REPO_TOKEN }}
- name: Set up gradle
uses: spring-io/spring-gradle-build-action@v1
with:
java-version: '11'
distribution: 'adopt'
- name: Wait for Artifactory Artifacts
if: ${{ contains(needs.prerequisites.outputs.project_version, '-RC') || contains(needs.prerequisites.outputs.project_version, '-M') }}
run: |
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@v2
- name: Set up gradle
uses: spring-io/spring-gradle-build-action@v1
with:
java-version: '11'
distribution: 'adopt'
- name: Schedule next release (if not already scheduled)
run: ./gradlew scheduleNextRelease -PnextVersion=$VERSION -PgitHubAccessToken=$TOKEN
notify_result:
name: Check for failures
needs: [perform_release, perform_post_release]
needs: [build_jdk_11, snapshot_tests, check_samples, check_tangles, deploy_artifacts, deploy_docs, deploy_schema]
if: failure()
runs-on: ubuntu-latest
permissions:
actions: read
steps:
- name: Send Slack message
# Workaround while waiting for Gamesight/slack-workflow-status#38 to be fixed
# See https://github.com/Gamesight/slack-workflow-status/issues/38
uses: sjohnr/slack-workflow-status@v1-beta
uses: Gamesight/slack-workflow-status@v1.0.1
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
slack_webhook_url: ${{ secrets.SLACK_WEBHOOK_URL }}
+33
View File
@@ -0,0 +1,33 @@
name: Build & Deploy Reference
on:
repository_dispatch:
types: request-build-reference
schedule:
- cron: '0 10 * * *' # Once per day at 10am UTC
workflow_dispatch: # Manual trigger
jobs:
deploy:
name: deploy
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up JDK 11
uses: actions/setup-java@v2
with:
java-version: '11'
distribution: 'adopt'
cache: gradle
- name: Validate Gradle wrapper
uses: gradle/wrapper-validation-action@e6e38bacfdf1a337459f332974bb2327a31aaf4b
- name: Build with Gradle
run: ./gradlew :spring-security-docs:antora --stacktrace
- name: Cleanup Gradle Cache
# Remove some files from the Gradle cache, so they aren't cached by GitHub Actions.
# Restoring these files from a GitHub Actions cache might cause problems for future builds.
run: |
rm -f ~/.gradle/caches/modules-2/modules-2.lock
rm -f ~/.gradle/caches/modules-2/gc.properties
- name: Deploy
run: ${GITHUB_WORKSPACE}/.github/actions/algolia-deploy.sh "${{ secrets.DOCS_USERNAME }}@${{ secrets.DOCS_HOST }}" "/opt/www/domains/spring.io/docs/htdocs/spring-security/reference/" "${{ secrets.DOCS_SSH_KEY }}" "${{ secrets.DOCS_SSH_HOST_KEY }}"
@@ -5,15 +5,10 @@ on:
env:
DUE_ON: ${{ github.event.milestone.due_on }}
TITLE: ${{ github.event.milestone.title }}
permissions:
contents: read
jobs:
spring-releasetrain-checks:
name: Check DueOn is on a Release Date
runs-on: ubuntu-latest
if: ${{ github.repository == 'spring-projects/spring-security' }}
permissions:
contents: none
steps:
- name: Print Milestone Being Checked
run: echo "Validating DueOn '$DUE_ON' for milestone '$TITLE'"
@@ -28,8 +23,6 @@ jobs:
needs: [spring-releasetrain-checks]
if: failure()
runs-on: ubuntu-latest
permissions:
actions: read
steps:
- name: Send Slack message
uses: Gamesight/slack-workflow-status@v1.0.1
+14 -7
View File
@@ -2,20 +2,27 @@ name: PR Build
on: pull_request
permissions:
contents: read
env:
RUN_JOBS: ${{ github.repository == 'spring-projects/spring-security' }}
jobs:
build:
name: Build
runs-on: ubuntu-latest
if: ${{ github.repository == 'spring-projects/spring-security' }}
steps:
- uses: actions/checkout@v2
- name: Set up gradle
uses: spring-io/spring-gradle-build-action@v1
- if: env.RUN_JOBS == 'true'
uses: actions/checkout@v2
- name: Set up JDK
if: env.RUN_JOBS == 'true'
uses: actions/setup-java@v1
with:
java-version: '11'
distribution: 'adopt'
- name: Cache Gradle packages
if: env.RUN_JOBS == 'true'
uses: actions/cache@v2
with:
path: ~/.gradle/caches
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }}
- name: Build with Gradle
if: env.RUN_JOBS == 'true'
run: ./gradlew clean build --continue --scan
@@ -1,80 +0,0 @@
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@v2
with:
token: ${{ secrets.GH_ACTIONS_REPO_TOKEN }}
- name: Set up gradle
uses: spring-io/spring-gradle-build-action@v1
with:
java-version: '11'
distribution: 'adopt'
- 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 "::set-output name=is_due_today::$(cat build/github/milestones/is-due-today)"
- 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 "::set-output name=is_open_issues::$(cat build/github/milestones/is-open-issues)"
- 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'
-6
View File
@@ -1,6 +0,0 @@
# Use sdkman to run "sdk env" to initialize with correct JDK version
# Enable auto-env through the sdkman_auto_env config
# See https://sdkman.io/usage#config
# A summary is to add the following to ~/.sdkman/etc/config
# sdkman_auto_env=true
java=11.0.14-tem
+2 -9
View File
@@ -43,12 +43,9 @@ If you're considering anything more than correcting a typo or fixing a minor bug
If you have not previously done so, please fill out and submit the https://cla.pivotal.io/sign/spring[Contributor License Agreement].
= Create your branch from oldest maintenance branch
= Create your branch from main
Create your topic branch to be submitted as a pull request from the oldest impacted and supported maintenance branch.
You can find the supported versions by looking at the https://github.com/spring-projects/spring-security/milestones[milestones page].
Switch to a branch named `<major>.<minor>.x` from the smallest milestone in the format of `<major>.<minor>.<patch>(-<prerelease>)`.
The spring team will ensure the code gets merged forward into additional branches.
Create your topic branch to be submitted as a pull request from main. The Spring team will consider your pull request for backporting on a case-by-case basis; you don't need to worry about submitting anything for backporting.
= Use short branch names
@@ -136,10 +133,6 @@ Search the codebase to find related unit tests and add additional `@Test` method
Update the https://www.relaxng.org[RELAX NG] schema `spring-security-x.y.rnc` instead of `spring-security-x.y.xsd` if you contribute changes to supported XML configuration. The XML schema file can be generated the following Gradle task:
----
./gradlew :spring-security-config:rncToXsd
----
Changes to the XML schema will be overwritten by the Gradle build task.
= Squash commits
+1 -1
View File
@@ -135,7 +135,7 @@ git push origin 5.4.0-RC1
The following command will update https://spring.io/projects/spring-security#learn with the new release version using the following parameters
<github-personal-access-token> - Replace with a https://github.com/settings/tokens[GitHub personal access token] that has a scope of `read:org` as https://spring.io/restdocs/index.html#authentication[documented for spring.io api]
<github-personal-access-token> - Replace with a https://github.com/settings/tokens[GitHub personal access token] that has a scope of `public_repo`
<next-version> - Replace with the milestone you are releasing now (i.e. 5.5.0-RC1)
<previous-version> - Replace with the previous release which will be removed from the listed versions (i.e. 5.5.0-M3)
@@ -42,7 +42,7 @@ import org.springframework.security.acls.domain.AuditLogger;
import org.springframework.security.acls.domain.DefaultPermissionFactory;
import org.springframework.security.acls.domain.DefaultPermissionGrantingStrategy;
import org.springframework.security.acls.domain.GrantedAuthoritySid;
import org.springframework.security.acls.domain.ObjectIdentityImpl;
import org.springframework.security.acls.domain.ObjectIdentityRetrievalStrategyImpl;
import org.springframework.security.acls.domain.PermissionFactory;
import org.springframework.security.acls.domain.PrincipalSid;
import org.springframework.security.acls.model.AccessControlEntry;
@@ -51,6 +51,7 @@ import org.springframework.security.acls.model.AclCache;
import org.springframework.security.acls.model.MutableAcl;
import org.springframework.security.acls.model.NotFoundException;
import org.springframework.security.acls.model.ObjectIdentity;
import org.springframework.security.acls.model.ObjectIdentityGenerator;
import org.springframework.security.acls.model.Permission;
import org.springframework.security.acls.model.PermissionGrantingStrategy;
import org.springframework.security.acls.model.Sid;
@@ -73,8 +74,8 @@ import org.springframework.util.Assert;
* one in <tt>lookupObjectIdentities</tt>. These are built from the same select and "order
* by" clause, using a different where clause in each case. In order to use custom schema
* or column names, each of these SQL clauses can be customized, but they must be
* consistent with each other and with the expected result set generated by the default
* values.
* consistent with each other and with the expected result set generated by the the
* default values.
*
* @author Ben Alex
*/
@@ -109,6 +110,8 @@ public class BasicLookupStrategy implements LookupStrategy {
private final AclAuthorizationStrategy aclAuthorizationStrategy;
private ObjectIdentityGenerator objectIdentityGenerator;
private PermissionFactory permissionFactory = new DefaultPermissionFactory();
private final AclCache aclCache;
@@ -162,6 +165,7 @@ public class BasicLookupStrategy implements LookupStrategy {
this.aclCache = aclCache;
this.aclAuthorizationStrategy = aclAuthorizationStrategy;
this.grantingStrategy = grantingStrategy;
this.objectIdentityGenerator = new ObjectIdentityRetrievalStrategyImpl();
this.aclClassIdUtils = new AclClassIdUtils();
this.fieldAces.setAccessible(true);
this.fieldAcl.setAccessible(true);
@@ -488,6 +492,11 @@ public class BasicLookupStrategy implements LookupStrategy {
}
}
public final void setObjectIdentityGenerator(ObjectIdentityGenerator objectIdentityGenerator) {
Assert.notNull(objectIdentityGenerator, "objectIdentityGenerator cannot be null");
this.objectIdentityGenerator = objectIdentityGenerator;
}
public final void setConversionService(ConversionService conversionService) {
this.aclClassIdUtils = new AclClassIdUtils(conversionService);
}
@@ -569,7 +578,8 @@ public class BasicLookupStrategy implements LookupStrategy {
// target id type, e.g. UUID.
Serializable identifier = (Serializable) rs.getObject("object_id_identity");
identifier = BasicLookupStrategy.this.aclClassIdUtils.identifierFrom(identifier, rs);
ObjectIdentity objectIdentity = new ObjectIdentityImpl(rs.getString("class"), identifier);
ObjectIdentity objectIdentity = BasicLookupStrategy.this.objectIdentityGenerator
.createObjectIdentity(identifier, rs.getString("class"));
Acl parentAcl = null;
long parentAclId = rs.getLong("parent_object");
@@ -31,11 +31,12 @@ import org.apache.commons.logging.LogFactory;
import org.springframework.core.convert.ConversionService;
import org.springframework.jdbc.core.JdbcOperations;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.security.acls.domain.ObjectIdentityImpl;
import org.springframework.security.acls.domain.ObjectIdentityRetrievalStrategyImpl;
import org.springframework.security.acls.model.Acl;
import org.springframework.security.acls.model.AclService;
import org.springframework.security.acls.model.NotFoundException;
import org.springframework.security.acls.model.ObjectIdentity;
import org.springframework.security.acls.model.ObjectIdentityGenerator;
import org.springframework.security.acls.model.Sid;
import org.springframework.util.Assert;
@@ -81,6 +82,8 @@ public class JdbcAclService implements AclService {
private AclClassIdUtils aclClassIdUtils;
private ObjectIdentityGenerator objectIdentityGenerator;
public JdbcAclService(DataSource dataSource, LookupStrategy lookupStrategy) {
this(new JdbcTemplate(dataSource), lookupStrategy);
}
@@ -91,6 +94,7 @@ public class JdbcAclService implements AclService {
this.jdbcOperations = jdbcOperations;
this.lookupStrategy = lookupStrategy;
this.aclClassIdUtils = new AclClassIdUtils();
this.objectIdentityGenerator = new ObjectIdentityRetrievalStrategyImpl();
}
@Override
@@ -105,7 +109,7 @@ public class JdbcAclService implements AclService {
String javaType = rs.getString("class");
Serializable identifier = (Serializable) rs.getObject("obj_id");
identifier = this.aclClassIdUtils.identifierFrom(identifier, rs);
return new ObjectIdentityImpl(javaType, identifier);
return this.objectIdentityGenerator.createObjectIdentity(identifier, javaType);
}
@Override
@@ -165,6 +169,11 @@ public class JdbcAclService implements AclService {
this.aclClassIdUtils = new AclClassIdUtils(conversionService);
}
public void setObjectIdentityGenerator(ObjectIdentityGenerator objectIdentityGenerator) {
Assert.notNull(objectIdentityGenerator, "objectIdentityGenerator cannot be null");
this.objectIdentityGenerator = objectIdentityGenerator;
}
protected boolean isAclClassIdSupported() {
return this.aclClassIdSupported;
}
@@ -318,4 +318,13 @@ public abstract class AbstractBasicLookupStrategyTests {
assertThat(((GrantedAuthoritySid) result).getGrantedAuthority()).isEqualTo("sid");
}
@Test
public void setObjectIdentityGeneratorWhenNullThenThrowsIllegalArgumentException() {
// @formatter:off
assertThatIllegalArgumentException()
.isThrownBy(() -> this.strategy.setObjectIdentityGenerator(null))
.withMessage("objectIdentityGenerator cannot be null");
// @formatter:on
}
}
@@ -45,6 +45,7 @@ import org.springframework.security.acls.model.Sid;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyList;
import static org.mockito.ArgumentMatchers.anyString;
@@ -170,6 +171,26 @@ public class JdbcAclServiceTests {
.isEqualTo(UUID.fromString("25d93b3f-c3aa-4814-9d5e-c7c96ced7762"));
}
@Test
public void setObjectIdentityGeneratorWhenNullThenThrowsIllegalArgumentException() {
assertThatIllegalArgumentException()
.isThrownBy(() -> this.aclServiceIntegration.setObjectIdentityGenerator(null))
.withMessage("objectIdentityGenerator cannot be null");
}
@Test
public void findChildrenWhenObjectIdentityGeneratorSetThenUsed() {
this.aclServiceIntegration
.setObjectIdentityGenerator((id, type) -> new ObjectIdentityImpl(type, "prefix:" + id));
ObjectIdentity objectIdentity = new ObjectIdentityImpl("location", "US");
this.aclServiceIntegration.setAclClassIdSupported(true);
List<ObjectIdentity> objectIdentities = this.aclServiceIntegration.findChildren(objectIdentity);
assertThat(objectIdentities.size()).isEqualTo(1);
assertThat(objectIdentities.get(0).getType()).isEqualTo("location");
assertThat(objectIdentities.get(0).getIdentifier()).isEqualTo("prefix:US-PAL");
}
class MockLongIdDomainObject {
private Object id;
-4
View File
@@ -27,7 +27,3 @@ sourceSets.test.aspectj.srcDir "src/test/java"
sourceSets.test.java.srcDirs = files()
compileAspectj.ajcOptions.outxmlfile = "META-INF/aop.xml"
aspectj {
version = aspectjVersion
}
+9 -54
View File
@@ -1,10 +1,8 @@
import io.spring.gradle.IncludeRepoTask
buildscript {
dependencies {
classpath "io.spring.javaformat:spring-javaformat-gradle-plugin:$springJavaformatVersion"
classpath 'io.spring.nohttp:nohttp-gradle:0.0.10'
classpath "io.freefair.gradle:aspectj-plugin:6.2.0"
classpath "io.freefair.gradle:aspectj-plugin:6.3.0"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
classpath "com.netflix.nebula:nebula-project-plugin:8.2.0"
}
@@ -18,9 +16,9 @@ apply plugin: 'io.spring.nohttp'
apply plugin: 'locks'
apply plugin: 's101'
apply plugin: 'io.spring.convention.root'
apply plugin: 'io.spring.convention.include-check-remote'
apply plugin: 'org.jetbrains.kotlin.jvm'
apply plugin: 'org.springframework.security.update-dependencies'
apply plugin: 'org.springframework.security.update-version'
apply plugin: 'org.springframework.security.sagan'
apply plugin: 'org.springframework.github.milestone'
apply plugin: 'org.springframework.github.changelog'
@@ -38,7 +36,7 @@ repositories {
}
tasks.named("saganCreateRelease") {
referenceDocUrl = "https://docs.spring.io/spring-security/reference/{version}/index.html"
referenceDocUrl = "https://docs.spring.io/spring-security/site/docs/{version}/reference/html5/"
apiDocUrl = "https://docs.spring.io/spring-security/site/docs/{version}/api/"
}
@@ -49,29 +47,6 @@ tasks.named("gitHubCheckMilestoneHasNoOpenIssues") {
}
}
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"
}
weekOfMonth = 3
dayOfWeek = 1
}
tasks.named("createGitHubRelease") {
repository {
owner = "spring-projects"
@@ -79,13 +54,6 @@ tasks.named("createGitHubRelease") {
}
}
tasks.named("dispatchGitHubWorkflow") {
repository {
owner = "spring-projects"
name = "spring-security"
}
}
tasks.named("updateDependencies") {
// we aren't Gradle 7 compatible yet
checkForGradleUpdate = false
@@ -103,10 +71,7 @@ updateDependenciesSettings {
})
dependencyExcludes {
majorVersionBump()
minorVersionBump()
alphaBetaVersions()
releaseCandidatesVersions()
milestoneVersions()
snapshotVersions()
addRule { components ->
components.withModule("commons-codec:commons-codec") { selection ->
@@ -135,18 +100,6 @@ updateDependenciesSettings {
selection.reject("org.opensaml maintains two different versions, so it must be updated manually");
}
}
components.withModule("io.spring.javaformat:spring-javaformat-gradle-plugin") { selection ->
ModuleComponentIdentifier candidate = selection.getCandidate();
if (!candidate.getVersion().equals(selection.getCurrentVersion())) {
selection.reject("spring-javaformat-gradle-plugin updates break checkstyle");
}
}
components.withModule("io.spring.javaformat:spring-javaformat-checkstyle") { selection ->
ModuleComponentIdentifier candidate = selection.getCandidate();
if (!candidate.getVersion().equals(selection.getCurrentVersion())) {
selection.reject("spring-javaformat-checkstyle updates break checkstyle");
}
}
}
}
}
@@ -202,10 +155,12 @@ nohttp {
}
tasks.register('cloneSamples', IncludeRepoTask) {
repository = 'spring-projects/spring-security-samples'
ref = samplesBranch
outputDirectory = project.hasProperty("cloneOutputDirectory") ? project.file("$cloneOutputDirectory") : project.file("build/samples")
tasks.register('checkSamples') {
includeCheckRemote {
repository = 'spring-projects/spring-security-samples'
ref = samplesBranch
}
dependsOn checkRemote
}
s101 {
+4 -7
View File
@@ -5,6 +5,7 @@ plugins {
id 'com.apollographql.apollo' version '2.4.5'
}
sourceCompatibility = 1.8
repositories {
@@ -28,7 +29,7 @@ gradlePlugin {
plugins {
checkAntoraVersion {
id = "org.springframework.antora.check-version"
implementationClass = "org.springframework.gradle.antora.AntoraVersionPlugin"
implementationClass = "org.springframework.gradle.antora.CheckAntoraVersionPlugin"
}
trang {
id = "trang"
@@ -46,10 +47,6 @@ gradlePlugin {
id = "org.springframework.security.update-dependencies"
implementationClass = "org.springframework.security.convention.versions.UpdateDependenciesPlugin"
}
updateProjectVersion {
id = "org.springframework.security.update-version"
implementationClass = "org.springframework.security.convention.versions.UpdateProjectVersionPlugin"
}
sagan {
id = "org.springframework.security.sagan"
implementationClass = "org.springframework.gradle.sagan.SaganPlugin"
@@ -87,7 +84,7 @@ dependencies {
implementation localGroovy()
implementation 'io.github.gradle-nexus:publish-plugin:1.1.0'
implementation 'io.projectreactor:reactor-core:3.4.24'
implementation 'io.projectreactor:reactor-core:3.4.14'
implementation 'gradle.plugin.org.gretty:gretty:3.0.1'
implementation 'com.apollographql.apollo:apollo-runtime:2.4.5'
implementation 'com.github.ben-manes:gradle-versions-plugin:0.38.0'
@@ -96,7 +93,7 @@ dependencies {
implementation 'io.spring.nohttp:nohttp-gradle:0.0.10'
implementation 'net.sourceforge.htmlunit:htmlunit:2.37.0'
implementation 'org.hidetake:gradle-ssh-plugin:2.10.1'
implementation 'org.jfrog.buildinfo:build-info-extractor-gradle:4.29.0'
implementation 'org.jfrog.buildinfo:build-info-extractor-gradle:4.24.20'
implementation 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:2.7.1'
testImplementation platform('org.junit:junit-bom:5.8.2')
@@ -54,7 +54,7 @@ abstract class IncludeRepoTask extends DefaultTask {
* Directory where the project template should be copied.
*/
@OutputDirectory
File outputDirectory = project.file("$project.buildDir/$name")
final File outputDirectory = project.file("$project.buildDir/$name")
@TaskAction
void checkoutAndCopy() {
@@ -17,7 +17,6 @@ package io.spring.gradle.convention
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.publish.maven.plugins.MavenPublishPlugin
class ArtifactoryPlugin implements Plugin<Project> {
@@ -37,14 +36,8 @@ class ArtifactoryPlugin implements Plugin<Project> {
password = artifactoryPassword
}
}
}
}
project.plugins.withType(MavenPublishPlugin) {
project.artifactory {
publish {
defaults {
publications('mavenJava')
}
defaults {
publications('mavenJava')
}
}
}
@@ -0,0 +1,65 @@
/*
* 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 io.spring.gradle.convention
import io.spring.gradle.IncludeRepoTask
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.provider.Property
import org.gradle.api.tasks.GradleBuild
import org.gradle.api.tasks.TaskProvider
/**
* Adds a set of tasks that make easy to clone a remote repository and perform some task
*
* @author Marcus Da Coregio
*/
class IncludeCheckRemotePlugin implements Plugin<Project> {
@Override
void apply(Project project) {
IncludeCheckRemoteExtension extension = project.extensions.create('includeCheckRemote', IncludeCheckRemoteExtension)
TaskProvider<IncludeRepoTask> includeRepoTask = project.tasks.register('includeRepo', IncludeRepoTask) { IncludeRepoTask it ->
it.repository = extension.repository
it.ref = extension.ref
}
project.tasks.register('checkRemote', GradleBuild) {
it.dependsOn 'includeRepo'
it.dir = includeRepoTask.get().outputDirectory
it.tasks = extension.getTasks()
}
}
abstract static class IncludeCheckRemoteExtension {
/**
* Git repository to clone
*/
String repository;
/**
* Git ref to checkout
*/
String ref
/**
* Task to run in the repository
*/
List<String> tasks = ['check']
}
}
@@ -59,14 +59,22 @@ public class IntegrationTestPlugin implements Plugin<Project> {
integrationTestRuntime {
extendsFrom integrationTestCompile, testRuntime, testRuntimeOnly
}
integrationTestCompileClasspath {
extendsFrom integrationTestCompile
canBeResolved = true
}
integrationTestRuntimeClasspath {
extendsFrom integrationTestRuntime
canBeResolved = true
}
}
project.sourceSets {
integrationTest {
java.srcDir project.file('src/integration-test/java')
resources.srcDir project.file('src/integration-test/resources')
compileClasspath = project.sourceSets.main.output + project.sourceSets.test.output + project.configurations.integrationTestCompile
runtimeClasspath = output + compileClasspath + project.configurations.integrationTestRuntime
compileClasspath = project.sourceSets.main.output + project.sourceSets.test.output + project.configurations.integrationTestCompileClasspath
runtimeClasspath = output + compileClasspath + project.configurations.integrationTestRuntimeClasspath
}
}
@@ -85,7 +93,7 @@ public class IntegrationTestPlugin implements Plugin<Project> {
project.idea {
module {
testSourceDirs += project.file('src/integration-test/java')
scopes.TEST.plus += [ project.configurations.integrationTestCompile ]
scopes.TEST.plus += [ project.configurations.integrationTestCompileClasspath ]
}
}
}
@@ -115,7 +123,7 @@ public class IntegrationTestPlugin implements Plugin<Project> {
project.plugins.withType(EclipsePlugin) {
project.eclipse.classpath {
plusConfigurations += [ project.configurations.integrationTestCompile ]
plusConfigurations += [ project.configurations.integrationTestCompileClasspath ]
}
}
}
@@ -21,7 +21,6 @@ import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.plugins.BasePlugin
import org.gradle.api.plugins.PluginManager
import org.springframework.gradle.classpath.CheckProhibitedDependenciesLifecyclePlugin
import org.springframework.gradle.maven.SpringNexusPublishPlugin
class RootProjectPlugin implements Plugin<Project> {
@@ -33,8 +32,6 @@ class RootProjectPlugin implements Plugin<Project> {
pluginManager.apply(SchemaPlugin)
pluginManager.apply(NoHttpPlugin)
pluginManager.apply(SpringNexusPublishPlugin)
pluginManager.apply(CheckProhibitedDependenciesLifecyclePlugin)
pluginManager.apply(ArtifactoryPlugin)
pluginManager.apply("org.sonarqube")
project.repositories.mavenCentral()
@@ -20,7 +20,6 @@ import org.gradle.api.Project
import org.gradle.api.plugins.JavaLibraryPlugin;
import org.gradle.api.plugins.MavenPlugin;
import org.gradle.api.plugins.PluginManager
import org.springframework.gradle.classpath.CheckClasspathForProhibitedDependenciesPlugin;
import org.springframework.gradle.maven.SpringMavenPlugin;
/**
@@ -33,7 +32,6 @@ class SpringModulePlugin extends AbstractSpringJavaPlugin {
PluginManager pluginManager = project.getPluginManager();
pluginManager.apply(JavaLibraryPlugin.class)
pluginManager.apply(SpringMavenPlugin.class);
pluginManager.apply(CheckClasspathForProhibitedDependenciesPlugin.class);
pluginManager.apply("io.spring.convention.jacoco");
def deployArtifacts = project.task("deployArtifacts")
@@ -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;
}
}
@@ -8,7 +8,7 @@ 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 class CheckAntoraVersionPlugin implements Plugin<Project> {
public static final String ANTORA_CHECK_VERSION_TASK_NAME = "antoraCheckVersion";
@Override
@@ -20,7 +20,6 @@ public class AntoraVersionPlugin implements Plugin<Project> {
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")));
}
});
@@ -35,29 +34,24 @@ public class AntoraVersionPlugin implements Plugin<Project> {
});
}
});
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);
int preReleaseIndex = getSnapshotIndex(projectVersion);
return isSnapshot(projectVersion) ? projectVersion.substring(0, preReleaseIndex) : 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);
if (isSnapshot(projectVersion)) {
int preReleaseIndex = getSnapshotIndex(projectVersion);
return projectVersion.substring(preReleaseIndex);
}
if (isPreRelease(projectVersion)) {
return Boolean.TRUE.toString();
}
return null;
}
private static String getProjectVersion(Project project) {
@@ -68,4 +62,15 @@ public class AntoraVersionPlugin implements Plugin<Project> {
return String.valueOf(projectVersion);
}
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;
}
}
@@ -6,7 +6,6 @@ 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;
@@ -23,7 +22,6 @@ public abstract class CheckAntoraVersionTask extends DefaultTask {
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);
@@ -34,17 +32,10 @@ public abstract class CheckAntoraVersionTask extends DefaultTask {
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 + "'");
throw new GradleException("The Gradle version of '" + getProject().getVersion() + "' should have version: '" + expectedAntoraVersion + "' and prerelease: '" + expectedAntoraPrerelease + "' defined in " + antoraYmlFile + " but got version: '" + actualAntoraVersion+"' and prerelease: '" + actualAntoraPrerelease + "'");
}
}
@@ -55,20 +46,13 @@ public abstract class CheckAntoraVersionTask extends DefaultTask {
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;
}
@@ -84,13 +68,5 @@ public abstract class CheckAntoraVersionTask extends DefaultTask {
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,99 +0,0 @@
/*
* Copyright 2012-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.classpath;
import org.gradle.api.DefaultTask;
import org.gradle.api.GradleException;
import org.gradle.api.Task;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.artifacts.ModuleVersionIdentifier;
import org.gradle.api.artifacts.ResolvedConfiguration;
import org.gradle.api.file.FileCollection;
import org.gradle.api.tasks.Classpath;
import org.gradle.api.tasks.TaskAction;
import java.io.IOException;
import java.util.TreeSet;
import java.util.stream.Collectors;
/**
* A {@link Task} for checking the classpath for prohibited dependencies.
*
* @author Andy Wilkinson
*/
public class CheckClasspathForProhibitedDependencies extends DefaultTask {
private Configuration classpath;
public CheckClasspathForProhibitedDependencies() {
getOutputs().upToDateWhen((task) -> true);
}
public void setClasspath(Configuration classpath) {
this.classpath = classpath;
}
@Classpath
public FileCollection getClasspath() {
return this.classpath;
}
@TaskAction
public void checkForProhibitedDependencies() throws IOException {
ResolvedConfiguration resolvedConfiguration = this.classpath.getResolvedConfiguration();
TreeSet<String> prohibited = resolvedConfiguration.getResolvedArtifacts().stream()
.map((artifact) -> artifact.getModuleVersion().getId()).filter(this::prohibited)
.map((id) -> id.getGroup() + ":" + id.getName()).collect(Collectors.toCollection(TreeSet::new));
if (!prohibited.isEmpty()) {
StringBuilder message = new StringBuilder(String.format("Found prohibited dependencies in '%s':%n", this.classpath.getName()));
for (String dependency : prohibited) {
message.append(String.format(" %s%n", dependency));
}
throw new GradleException(message.toString());
}
}
private boolean prohibited(ModuleVersionIdentifier id) {
String group = id.getGroup();
if (group.equals("javax.batch")) {
return false;
}
if (group.equals("javax.cache")) {
return false;
}
if (group.equals("javax.money")) {
return false;
}
if (group.startsWith("javax")) {
return true;
}
if (group.equals("commons-logging")) {
return true;
}
if (group.equals("org.slf4j") && id.getName().equals("jcl-over-slf4j")) {
return true;
}
if (group.startsWith("org.jboss.spec")) {
return true;
}
if (group.equals("org.apache.geronimo.specs")) {
return true;
}
return false;
}
}
@@ -1,67 +0,0 @@
/*
* Copyright 2012-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.classpath;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.artifacts.ConfigurationContainer;
import org.gradle.api.plugins.JavaBasePlugin;
import org.gradle.api.tasks.SourceSetContainer;
import org.gradle.api.tasks.TaskProvider;
import org.gradle.language.base.plugins.LifecycleBasePlugin;
import org.springframework.util.StringUtils;
/**
* @author Andy Wilkinson
* @author Rob Winch
*/
public class CheckClasspathForProhibitedDependenciesPlugin implements Plugin<Project> {
@Override
public void apply(Project project) {
project.getPlugins().apply(CheckProhibitedDependenciesLifecyclePlugin.class);
project.getPlugins().withType(JavaBasePlugin.class, javaBasePlugin -> {
configureProhibitedDependencyChecks(project);
});
}
private void configureProhibitedDependencyChecks(Project project) {
SourceSetContainer sourceSets = project.getExtensions().getByType(SourceSetContainer.class);
sourceSets.all((sourceSet) -> createProhibitedDependenciesChecks(project,
sourceSet.getCompileClasspathConfigurationName(), sourceSet.getRuntimeClasspathConfigurationName()));
}
private void createProhibitedDependenciesChecks(Project project, String... configurationNames) {
ConfigurationContainer configurations = project.getConfigurations();
for (String configurationName : configurationNames) {
Configuration configuration = configurations.getByName(configurationName);
createProhibitedDependenciesCheck(configuration, project);
}
}
private void createProhibitedDependenciesCheck(Configuration classpath, Project project) {
String taskName = "check" + StringUtils.capitalize(classpath.getName() + "ForProhibitedDependencies");
TaskProvider<CheckClasspathForProhibitedDependencies> checkClasspathTask = project.getTasks().register(taskName,
CheckClasspathForProhibitedDependencies.class, checkClasspath -> {
checkClasspath.setGroup(LifecycleBasePlugin.CHECK_TASK_NAME);
checkClasspath.setDescription("Checks " + classpath.getName() + " for prohibited dependencies");
checkClasspath.setClasspath(classpath);
});
project.getTasks().named(CheckProhibitedDependenciesLifecyclePlugin.CHECK_PROHIBITED_DEPENDENCIES_TASK_NAME, checkProhibitedTask -> checkProhibitedTask.dependsOn(checkClasspathTask));
}
}
@@ -1,41 +0,0 @@
/*
* Copyright 2012-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.classpath;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.Task;
import org.gradle.api.plugins.JavaBasePlugin;
import org.gradle.api.tasks.TaskProvider;
/**
* @author Rob Winch
*/
public class CheckProhibitedDependenciesLifecyclePlugin implements Plugin<Project> {
public static final String CHECK_PROHIBITED_DEPENDENCIES_TASK_NAME = "checkForProhibitedDependencies";
@Override
public void apply(Project project) {
TaskProvider<Task> checkProhibitedDependencies = project.getTasks().register(CheckProhibitedDependenciesLifecyclePlugin.CHECK_PROHIBITED_DEPENDENCIES_TASK_NAME, task -> {
task.setGroup(JavaBasePlugin.VERIFICATION_GROUP);
task.setDescription("Checks both the compile/runtime classpath of every SourceSet for prohibited dependencies");
});
project.getTasks().named(JavaBasePlugin.CHECK_TASK_NAME, checkTask -> {
checkTask.dependsOn(checkProhibitedDependencies);
});
}
}
@@ -1,11 +1,6 @@
package org.springframework.gradle.github;
import java.io.Serializable;
public class RepositoryRef implements Serializable {
private static final long serialVersionUID = 7151218536746822797L;
public class RepositoryRef {
private String owner;
private String name;
@@ -1,5 +1,5 @@
/*
* Copyright 2019-2022 the original author or authors.
* 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.
@@ -17,21 +17,13 @@
package org.springframework.gradle.github.milestones;
import java.io.IOException;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
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;
@@ -41,10 +33,7 @@ public class GitHubMilestoneApi {
private OkHttpClient client;
private final Gson gson = new GsonBuilder()
.registerTypeAdapter(LocalDate.class, new LocalDateAdapter().nullSafe())
.registerTypeAdapter(LocalDateTime.class, new LocalDateTimeAdapter().nullSafe())
.create();
private Gson gson = new Gson();
public GitHubMilestoneApi() {
this.client = new OkHttpClient.Builder().build();
@@ -61,30 +50,26 @@ public class GitHubMilestoneApi {
}
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);
throw new RuntimeException("Could not find milestone with title " + milestoneTitle + " for repository " + repositoryRef + ". Response " + response);
}
return this.gson.fromJson(response.body().charStream(), new TypeToken<List<Milestone>>(){}.getType());
List<Milestone> milestones = this.gson.fromJson(response.body().charStream(), new TypeToken<List<Milestone>>(){}.getType());
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 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 retrieve milestones for repository " + repositoryRef, e);
throw new RuntimeException("Could not find open milestone with title " + milestoneTitle + " for repository " + repositoryRef, e);
}
}
@@ -104,150 +89,10 @@ public class GitHubMilestoneApi {
}
}
/**
* 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);
}
}
// public boolean isOpenIssuesForMilestoneName(String owner, String repository, String milestoneName) {
//
// }
/**
* 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 {
@@ -1,5 +1,5 @@
/*
* Copyright 2019-2022 the original author or authors.
* 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.
@@ -17,66 +17,32 @@ 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 {
public class GitHubMilestoneHasNoOpenIssuesTask extends DefaultTask {
@Input
private RepositoryRef repository = new RepositoryRef();
@Input @Optional
@Input
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;
}
public void checkHasNoOpenIssues() {
long milestoneNumber = this.milestones.findMilestoneNumberByTitle(this.repository, this.milestoneTitle);
boolean isOpenIssues = this.milestones.isOpenIssuesForMilestoneNumber(this.repository, milestoneNumber);
Path isOpenIssuesPath = getIsOpenIssuesFile().getAsFile().get().toPath();
Files.write(isOpenIssuesPath, String.valueOf(isOpenIssues).getBytes());
if (isOpenIssues) {
System.out.println("The repository " + this.repository + " has open issues for milestone with the title " + this.milestoneTitle + " and number " + milestoneNumber);
}
else {
System.out.println("The repository " + this.repository + " has no open issues for milestone with the title " + this.milestoneTitle + " and number " + milestoneNumber);
throw new IllegalStateException("The repository " + this.repository + " has open issues for milestone with the title " + this.milestoneTitle + " and number " + milestoneNumber);
}
System.out.println("The repository " + this.repository + " has no open issues for milestone with the title " + this.milestoneTitle + " and number " + milestoneNumber);
}
public RepositoryRef getRepository() {
@@ -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,5 +1,5 @@
/*
* Copyright 2019-2022 the original author or authors.
* 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.
@@ -16,55 +16,23 @@
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;
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, new Action<GitHubMilestoneHasNoOpenIssuesTask>() {
@Override
public void execute(GitHubMilestoneHasNoOpenIssuesTask githubCheckMilestoneHasNoOpenIssues) {
githubCheckMilestoneHasNoOpenIssues.setGroup("Release");
githubCheckMilestoneHasNoOpenIssues.setDescription("Checks if there are any open issues for the specified repository and milestone");
githubCheckMilestoneHasNoOpenIssues.setMilestoneTitle((String) project.findProperty("nextVersion"));
if (project.hasProperty("gitHubAccessToken")) {
githubCheckMilestoneHasNoOpenIssues.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,35 +1,9 @@
/*
* 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;
/**
* @author Steve Riesenberg
*/
public class Milestone {
private String title;
private Long number;
@SerializedName("due_on")
private LocalDateTime dueOn;
private long number;
public String getTitle() {
return title;
@@ -39,28 +13,19 @@ public class Milestone {
this.title = title;
}
public Long getNumber() {
public long getNumber() {
return number;
}
public void setNumber(Long 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 + '\'' +
", number=" + number +
'}';
}
}
@@ -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,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);
}
}
}
@@ -17,6 +17,7 @@
package org.springframework.gradle.github.release;
import groovy.lang.MissingPropertyException;
import org.gradle.api.Action;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
@@ -26,29 +27,23 @@ import org.gradle.api.Project;
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");
project.getTasks().register("createGitHubRelease", CreateGitHubReleaseTask.class, new Action<CreateGitHubReleaseTask>() {
@Override
public void execute(CreateGitHubReleaseTask 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.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=...");
}
}
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,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,49 +0,0 @@
/*
* Copyright 2019-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.convention.versions;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.util.Arrays;
import java.util.Scanner;
class CommandLineUtils {
static void runCommand(File dir, String... args) {
try {
Process process = new ProcessBuilder()
.directory(dir)
.command(args)
.start();
writeLinesTo(process.getInputStream(), System.out);
writeLinesTo(process.getErrorStream(), System.out);
if (process.waitFor() != 0) {
new RuntimeException("Failed to run " + Arrays.toString(args));
}
} catch (IOException | InterruptedException e) {
throw new RuntimeException("Failed to run " + Arrays.toString(args), e);
}
}
private static void writeLinesTo(InputStream input, PrintStream out) {
Scanner scanner = new Scanner(input);
while(scanner.hasNextLine()) {
out.println(scanner.nextLine());
}
}
}
@@ -1,49 +0,0 @@
/*
* Copyright 2019-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.convention.versions;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.function.Function;
class FileUtils {
static void replaceFileText(File file, Function<String, String> replaceText) {
String buildFileText = readString(file);
String updatedBuildFileText = replaceText.apply(buildFileText);
writeString(file, updatedBuildFileText);
}
static String readString(File file) {
try {
byte[] bytes = Files.readAllBytes(file.toPath());
return new String(bytes);
}
catch (IOException e) {
throw new RuntimeException(e);
}
}
private static void writeString(File file, String text) {
try {
Files.write(file.toPath(), text.getBytes());
}
catch (IOException e) {
throw new RuntimeException(e);
}
}
}
@@ -33,8 +33,13 @@ import org.gradle.api.artifacts.component.ModuleComponentIdentifier;
import reactor.core.publisher.Mono;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.nio.file.Files;
import java.time.Duration;
import java.util.*;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -163,7 +168,7 @@ public class UpdateDependenciesPlugin implements Plugin<Project> {
Integer issueNumber = gitHubApi.createIssue(createIssueResult.getRepositoryId(), title, createIssueResult.getLabelIds(), createIssueResult.getMilestoneId(), createIssueResult.getAssigneeId()).delayElement(Duration.ofSeconds(1)).block();
commitMessage += "\n\nCloses gh-" + issueNumber;
}
CommandLineUtils.runCommand(rootDir, "git", "commit", "-am", commitMessage);
runCommand(rootDir, "git", "commit", "-am", commitMessage);
}
private Mono<GitHubApi.FindCreateIssueResult> createIssueResultMono(UpdateDependenciesExtension updateDependenciesExtension) {
@@ -182,7 +187,7 @@ public class UpdateDependenciesPlugin implements Plugin<Project> {
if (current.compareTo(running) > 0) {
String title = "Update Gradle to " + current.getVersion();
System.out.println(title);
CommandLineUtils.runCommand(project.getRootDir(), "./gradlew", "wrapper", "--gradle-version", current.getVersion(), "--no-daemon");
runCommand(project.getRootDir(), "./gradlew", "wrapper", "--gradle-version", current.getVersion(), "--no-daemon");
afterGroup(updateDependenciesSettings, project.getRootDir(), title, createIssueResultMono(updateDependenciesSettings));
}
}
@@ -199,6 +204,30 @@ public class UpdateDependenciesPlugin implements Plugin<Project> {
};
}
static void runCommand(File dir, String... args) {
try {
Process process = new ProcessBuilder()
.directory(dir)
.command(args)
.start();
writeLinesTo(process.getInputStream(), System.out);
writeLinesTo(process.getErrorStream(), System.out);
if (process.waitFor() != 0) {
new RuntimeException("Failed to run " + Arrays.toString(args));
}
} catch (IOException | InterruptedException e) {
throw new RuntimeException("Failed to run " + Arrays.toString(args), e);
}
}
static void writeLinesTo(InputStream input, PrintStream out) {
Scanner scanner = new Scanner(input);
while(scanner.hasNextLine()) {
out.println(scanner.nextLine());
}
}
static Action<ComponentSelectionWithCurrent> excludeWithRegex(String regex, String reason) {
Pattern pattern = Pattern.compile(regex);
return (selection) -> {
@@ -213,17 +242,40 @@ public class UpdateDependenciesPlugin implements Plugin<Project> {
String ga = dependency.getGroup() + ":" + dependency.getName() + ":";
String originalDependency = ga + dependency.getVersion();
String replacementDependency = ga + updatedVersion(dependency);
FileUtils.replaceFileText(buildFile, buildFileText -> buildFileText.replace(originalDependency, replacementDependency));
replaceFileText(buildFile, buildFileText -> buildFileText.replace(originalDependency, replacementDependency));
}
static void replaceFileText(File file, Function<String, String> replaceText) {
String buildFileText = readString(file);
String updatedBuildFileText = replaceText.apply(buildFileText);
writeString(file, updatedBuildFileText);
}
private static String readString(File file) {
try {
byte[] bytes = Files.readAllBytes(file.toPath());
return new String(bytes);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private static void writeString(File file, String text) {
try {
Files.write(file.toPath(), text.getBytes());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
static void updateDependencyWithVersionVariable(File scanFile, File gradlePropertiesFile, DependencyOutdated dependency) {
if (!gradlePropertiesFile.exists()) {
return;
}
FileUtils.replaceFileText(gradlePropertiesFile, (gradlePropertiesText) -> {
replaceFileText(gradlePropertiesFile, (gradlePropertiesText) -> {
String ga = dependency.getGroup() + ":" + dependency.getName() + ":";
Pattern pattern = Pattern.compile("\"" + ga + "\\$\\{?([^'\"]+?)\\}?\"");
String buildFileText = FileUtils.readString(scanFile);
String buildFileText = readString(scanFile);
Matcher matcher = pattern.matcher(buildFileText);
while (matcher.find()) {
String versionVariable = matcher.group(1);
@@ -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");
}
}
}
@@ -164,14 +164,18 @@ public class S101Configurer {
String source = "https://structure101.com/binaries/v6";
try (final WebClient webClient = new WebClient()) {
HtmlPage page = webClient.getPage(source);
Matcher matcher = null;
for (HtmlAnchor anchor : page.getAnchors()) {
Matcher matcher = Pattern.compile("(structure101-build-java-all-)(.*).zip").matcher(anchor.getHrefAttribute());
if (matcher.find()) {
copyZipToFilesystem(source, installationDirectory, matcher.group(1) + matcher.group(2));
return matcher.group(2);
Matcher candidate = Pattern.compile("(structure101-build-java-all-)(.*).zip").matcher(anchor.getHrefAttribute());
if (candidate.find()) {
matcher = candidate;
}
}
return null;
if (matcher == null) {
return null;
}
copyZipToFilesystem(source, installationDirectory, matcher.group(1) + matcher.group(2));
return matcher.group(2);
} catch (Exception ex) {
throw new RuntimeException(ex);
}
@@ -0,0 +1 @@
implementation-class=io.spring.gradle.convention.IncludeCheckRemotePlugin
@@ -0,0 +1,111 @@
/*
* 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 io.spring.gradle.convention;
import io.spring.gradle.IncludeRepoTask;
import org.apache.commons.io.FileUtils;
import org.gradle.api.Project;
import org.gradle.api.tasks.GradleBuild;
import org.gradle.testfixtures.ProjectBuilder;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import java.util.Arrays;
import static org.assertj.core.api.Assertions.assertThat;
class IncludeCheckRemotePluginTest {
Project rootProject;
@AfterEach
public void cleanup() throws Exception {
if (rootProject != null) {
FileUtils.deleteDirectory(rootProject.getProjectDir());
}
}
@Test
void applyWhenExtensionPropertiesNoTasksThenCreateCheckRemoteTaskWithDefaultTask() {
this.rootProject = ProjectBuilder.builder().build();
this.rootProject.getPluginManager().apply(IncludeCheckRemotePlugin.class);
this.rootProject.getExtensions().configure(IncludeCheckRemotePlugin.IncludeCheckRemoteExtension.class,
(includeCheckRemoteExtension) -> {
includeCheckRemoteExtension.setProperty("repository", "my-project/my-repository");
includeCheckRemoteExtension.setProperty("ref", "main");
});
GradleBuild checkRemote = (GradleBuild) this.rootProject.getTasks().named("checkRemote").get();
assertThat(checkRemote.getTasks()).containsExactly("check");
}
@Test
void applyWhenExtensionPropertiesTasksThenCreateCheckRemoteWithProvidedTasks() {
this.rootProject = ProjectBuilder.builder().build();
this.rootProject.getPluginManager().apply(IncludeCheckRemotePlugin.class);
this.rootProject.getExtensions().configure(IncludeCheckRemotePlugin.IncludeCheckRemoteExtension.class,
(includeCheckRemoteExtension) -> {
includeCheckRemoteExtension.setProperty("repository", "my-project/my-repository");
includeCheckRemoteExtension.setProperty("ref", "main");
includeCheckRemoteExtension.setProperty("tasks", Arrays.asList("clean", "build", "test"));
});
GradleBuild checkRemote = (GradleBuild) this.rootProject.getTasks().named("checkRemote").get();
assertThat(checkRemote.getTasks()).containsExactly("clean", "build", "test");
}
@Test
void applyWhenExtensionPropertiesThenRegisterIncludeRepoTaskWithExtensionProperties() {
this.rootProject = ProjectBuilder.builder().build();
this.rootProject.getPluginManager().apply(IncludeCheckRemotePlugin.class);
this.rootProject.getExtensions().configure(IncludeCheckRemotePlugin.IncludeCheckRemoteExtension.class,
(includeCheckRemoteExtension) -> {
includeCheckRemoteExtension.setProperty("repository", "my-project/my-repository");
includeCheckRemoteExtension.setProperty("ref", "main");
});
IncludeRepoTask includeRepo = (IncludeRepoTask) this.rootProject.getTasks().named("includeRepo").get();
assertThat(includeRepo).isNotNull();
assertThat(includeRepo.getRepository().get()).isEqualTo("my-project/my-repository");
assertThat(includeRepo.getRef().get()).isEqualTo("main");
}
@Test
void applyWhenRegisterTasksThenCheckRemoteDirSameAsIncludeRepoOutputDir() {
this.rootProject = ProjectBuilder.builder().build();
this.rootProject.getPluginManager().apply(IncludeCheckRemotePlugin.class);
this.rootProject.getExtensions().configure(IncludeCheckRemotePlugin.IncludeCheckRemoteExtension.class,
(includeCheckRemoteExtension) -> {
includeCheckRemoteExtension.setProperty("repository", "my-project/my-repository");
includeCheckRemoteExtension.setProperty("ref", "main");
});
IncludeRepoTask includeRepo = (IncludeRepoTask) this.rootProject.getTasks().named("includeRepo").get();
GradleBuild checkRemote = (GradleBuild) this.rootProject.getTasks().named("checkRemote").get();
assertThat(checkRemote.getDir()).isEqualTo(includeRepo.getOutputDirectory());
}
@Test
void applyWhenNoExtensionPropertiesThenRegisterTasks() {
this.rootProject = ProjectBuilder.builder().build();
this.rootProject.getPluginManager().apply(IncludeCheckRemotePlugin.class);
IncludeRepoTask includeRepo = (IncludeRepoTask) this.rootProject.getTasks().named("includeRepo").get();
GradleBuild checkRemote = (GradleBuild) this.rootProject.getTasks().named("checkRemote").get();
assertThat(includeRepo).isNotNull();
assertThat(checkRemote).isNotNull();
}
}
@@ -58,7 +58,7 @@ public class SaganApiTests {
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");
release.setReferenceDocUrl("https://docs.spring.io/spring-security/site/docs/{version}/reference/html5/");
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");
@@ -67,7 +67,7 @@ public class SaganApiTests {
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" +
" \"referenceDocUrl\":\"https://docs.spring.io/spring-security/site/docs/{version}/reference/html5/\",\n" +
" \"apiDocUrl\":\"https://docs.spring.io/spring-security/site/docs/{version}/api/\"\n" +
"}");
}
@@ -0,0 +1,389 @@
package io.spring.gradle.github.milestones;
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 org.springframework.gradle.github.milestones.GitHubMilestoneApi;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
public class GitHubMilestoneApiTests {
private GitHubMilestoneApi github;
private RepositoryRef repositoryRef = RepositoryRef.owner("spring-projects").repository("spring-security").build();
private MockWebServer server;
private String baseUrl;
@BeforeEach
public void setup() throws Exception {
this.server = new MockWebServer();
this.server.start();
this.github = new GitHubMilestoneApi("mock-oauth-token");
this.baseUrl = this.server.url("/api").toString();
this.github.setBaseUrl(this.baseUrl);
}
@AfterEach
public void cleanup() throws Exception {
this.server.shutdown();
}
@Test
public void findMilestoneNumberByTitleWhenFoundThenSuccess() throws Exception {
String responseJson = "[\n" +
" {\n" +
" \"url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/207\",\n" +
" \"html_url\":\"https://github.com/spring-projects/spring-security/milestone/207\",\n" +
" \"labels_url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/207/labels\",\n" +
" \"id\":6611880,\n" +
" \"node_id\":\"MDk6TWlsZXN0b25lNjYxMTg4MA==\",\n" +
" \"number\":207,\n" +
" \"title\":\"5.6.x\",\n" +
" \"description\":\"\",\n" +
" \"creator\":{\n" +
" \"login\":\"jgrandja\",\n" +
" \"id\":10884212,\n" +
" \"node_id\":\"MDQ6VXNlcjEwODg0MjEy\",\n" +
" \"avatar_url\":\"https://avatars.githubusercontent.com/u/10884212?v=4\",\n" +
" \"gravatar_id\":\"\",\n" +
" \"url\":\"https://api.github.com/users/jgrandja\",\n" +
" \"html_url\":\"https://github.com/jgrandja\",\n" +
" \"followers_url\":\"https://api.github.com/users/jgrandja/followers\",\n" +
" \"following_url\":\"https://api.github.com/users/jgrandja/following{/other_user}\",\n" +
" \"gists_url\":\"https://api.github.com/users/jgrandja/gists{/gist_id}\",\n" +
" \"starred_url\":\"https://api.github.com/users/jgrandja/starred{/owner}{/repo}\",\n" +
" \"subscriptions_url\":\"https://api.github.com/users/jgrandja/subscriptions\",\n" +
" \"organizations_url\":\"https://api.github.com/users/jgrandja/orgs\",\n" +
" \"repos_url\":\"https://api.github.com/users/jgrandja/repos\",\n" +
" \"events_url\":\"https://api.github.com/users/jgrandja/events{/privacy}\",\n" +
" \"received_events_url\":\"https://api.github.com/users/jgrandja/received_events\",\n" +
" \"type\":\"User\",\n" +
" \"site_admin\":false\n" +
" },\n" +
" \"open_issues\":1,\n" +
" \"closed_issues\":0,\n" +
" \"state\":\"open\",\n" +
" \"created_at\":\"2021-03-31T11:29:17Z\",\n" +
" \"updated_at\":\"2021-03-31T11:30:47Z\",\n" +
" \"due_on\":null,\n" +
" \"closed_at\":null\n" +
" },\n" +
" {\n" +
" \"url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/191\",\n" +
" \"html_url\":\"https://github.com/spring-projects/spring-security/milestone/191\",\n" +
" \"labels_url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/191/labels\",\n" +
" \"id\":5884208,\n" +
" \"node_id\":\"MDk6TWlsZXN0b25lNTg4NDIwOA==\",\n" +
" \"number\":191,\n" +
" \"title\":\"5.5.0-RC1\",\n" +
" \"description\":\"\",\n" +
" \"creator\":{\n" +
" \"login\":\"jzheaux\",\n" +
" \"id\":3627351,\n" +
" \"node_id\":\"MDQ6VXNlcjM2MjczNTE=\",\n" +
" \"avatar_url\":\"https://avatars.githubusercontent.com/u/3627351?v=4\",\n" +
" \"gravatar_id\":\"\",\n" +
" \"url\":\"https://api.github.com/users/jzheaux\",\n" +
" \"html_url\":\"https://github.com/jzheaux\",\n" +
" \"followers_url\":\"https://api.github.com/users/jzheaux/followers\",\n" +
" \"following_url\":\"https://api.github.com/users/jzheaux/following{/other_user}\",\n" +
" \"gists_url\":\"https://api.github.com/users/jzheaux/gists{/gist_id}\",\n" +
" \"starred_url\":\"https://api.github.com/users/jzheaux/starred{/owner}{/repo}\",\n" +
" \"subscriptions_url\":\"https://api.github.com/users/jzheaux/subscriptions\",\n" +
" \"organizations_url\":\"https://api.github.com/users/jzheaux/orgs\",\n" +
" \"repos_url\":\"https://api.github.com/users/jzheaux/repos\",\n" +
" \"events_url\":\"https://api.github.com/users/jzheaux/events{/privacy}\",\n" +
" \"received_events_url\":\"https://api.github.com/users/jzheaux/received_events\",\n" +
" \"type\":\"User\",\n" +
" \"site_admin\":false\n" +
" },\n" +
" \"open_issues\":21,\n" +
" \"closed_issues\":23,\n" +
" \"state\":\"open\",\n" +
" \"created_at\":\"2020-09-16T13:28:03Z\",\n" +
" \"updated_at\":\"2021-04-06T23:47:10Z\",\n" +
" \"due_on\":\"2021-04-12T07:00:00Z\",\n" +
" \"closed_at\":null\n" +
" }\n" +
"]";
this.server.enqueue(new MockResponse().setBody(responseJson));
long milestoneNumberByTitle = this.github.findMilestoneNumberByTitle(this.repositoryRef, "5.5.0-RC1");
RecordedRequest recordedRequest = this.server.takeRequest(1, TimeUnit.SECONDS);
assertThat(recordedRequest.getMethod()).isEqualToIgnoringCase("get");
assertThat(recordedRequest.getRequestUrl().toString()).isEqualTo(this.baseUrl + "/repos/spring-projects/spring-security/milestones?per_page=100");
assertThat(milestoneNumberByTitle).isEqualTo(191);
}
@Test
public void findMilestoneNumberByTitleWhenNotFoundThenException() throws Exception {
String responseJson = "[\n" +
" {\n" +
" \"url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/207\",\n" +
" \"html_url\":\"https://github.com/spring-projects/spring-security/milestone/207\",\n" +
" \"labels_url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/207/labels\",\n" +
" \"id\":6611880,\n" +
" \"node_id\":\"MDk6TWlsZXN0b25lNjYxMTg4MA==\",\n" +
" \"number\":207,\n" +
" \"title\":\"5.6.x\",\n" +
" \"description\":\"\",\n" +
" \"creator\":{\n" +
" \"login\":\"jgrandja\",\n" +
" \"id\":10884212,\n" +
" \"node_id\":\"MDQ6VXNlcjEwODg0MjEy\",\n" +
" \"avatar_url\":\"https://avatars.githubusercontent.com/u/10884212?v=4\",\n" +
" \"gravatar_id\":\"\",\n" +
" \"url\":\"https://api.github.com/users/jgrandja\",\n" +
" \"html_url\":\"https://github.com/jgrandja\",\n" +
" \"followers_url\":\"https://api.github.com/users/jgrandja/followers\",\n" +
" \"following_url\":\"https://api.github.com/users/jgrandja/following{/other_user}\",\n" +
" \"gists_url\":\"https://api.github.com/users/jgrandja/gists{/gist_id}\",\n" +
" \"starred_url\":\"https://api.github.com/users/jgrandja/starred{/owner}{/repo}\",\n" +
" \"subscriptions_url\":\"https://api.github.com/users/jgrandja/subscriptions\",\n" +
" \"organizations_url\":\"https://api.github.com/users/jgrandja/orgs\",\n" +
" \"repos_url\":\"https://api.github.com/users/jgrandja/repos\",\n" +
" \"events_url\":\"https://api.github.com/users/jgrandja/events{/privacy}\",\n" +
" \"received_events_url\":\"https://api.github.com/users/jgrandja/received_events\",\n" +
" \"type\":\"User\",\n" +
" \"site_admin\":false\n" +
" },\n" +
" \"open_issues\":1,\n" +
" \"closed_issues\":0,\n" +
" \"state\":\"open\",\n" +
" \"created_at\":\"2021-03-31T11:29:17Z\",\n" +
" \"updated_at\":\"2021-03-31T11:30:47Z\",\n" +
" \"due_on\":null,\n" +
" \"closed_at\":null\n" +
" },\n" +
" {\n" +
" \"url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/191\",\n" +
" \"html_url\":\"https://github.com/spring-projects/spring-security/milestone/191\",\n" +
" \"labels_url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/191/labels\",\n" +
" \"id\":5884208,\n" +
" \"node_id\":\"MDk6TWlsZXN0b25lNTg4NDIwOA==\",\n" +
" \"number\":191,\n" +
" \"title\":\"5.5.0-RC1\",\n" +
" \"description\":\"\",\n" +
" \"creator\":{\n" +
" \"login\":\"jzheaux\",\n" +
" \"id\":3627351,\n" +
" \"node_id\":\"MDQ6VXNlcjM2MjczNTE=\",\n" +
" \"avatar_url\":\"https://avatars.githubusercontent.com/u/3627351?v=4\",\n" +
" \"gravatar_id\":\"\",\n" +
" \"url\":\"https://api.github.com/users/jzheaux\",\n" +
" \"html_url\":\"https://github.com/jzheaux\",\n" +
" \"followers_url\":\"https://api.github.com/users/jzheaux/followers\",\n" +
" \"following_url\":\"https://api.github.com/users/jzheaux/following{/other_user}\",\n" +
" \"gists_url\":\"https://api.github.com/users/jzheaux/gists{/gist_id}\",\n" +
" \"starred_url\":\"https://api.github.com/users/jzheaux/starred{/owner}{/repo}\",\n" +
" \"subscriptions_url\":\"https://api.github.com/users/jzheaux/subscriptions\",\n" +
" \"organizations_url\":\"https://api.github.com/users/jzheaux/orgs\",\n" +
" \"repos_url\":\"https://api.github.com/users/jzheaux/repos\",\n" +
" \"events_url\":\"https://api.github.com/users/jzheaux/events{/privacy}\",\n" +
" \"received_events_url\":\"https://api.github.com/users/jzheaux/received_events\",\n" +
" \"type\":\"User\",\n" +
" \"site_admin\":false\n" +
" },\n" +
" \"open_issues\":21,\n" +
" \"closed_issues\":23,\n" +
" \"state\":\"open\",\n" +
" \"created_at\":\"2020-09-16T13:28:03Z\",\n" +
" \"updated_at\":\"2021-04-06T23:47:10Z\",\n" +
" \"due_on\":\"2021-04-12T07:00:00Z\",\n" +
" \"closed_at\":null\n" +
" }\n" +
"]";
this.server.enqueue(new MockResponse().setBody(responseJson));
assertThatExceptionOfType(RuntimeException.class)
.isThrownBy(() -> this.github.findMilestoneNumberByTitle(this.repositoryRef, "missing"));
}
@Test
public void isOpenIssuesForMilestoneNumberWhenAllClosedThenFalse() throws Exception {
String responseJson = "[]";
long milestoneNumber = 202;
this.server.enqueue(new MockResponse().setBody(responseJson));
assertThat(this.github.isOpenIssuesForMilestoneNumber(this.repositoryRef, milestoneNumber)).isFalse();
RecordedRequest recordedRequest = this.server.takeRequest(1, TimeUnit.SECONDS);
assertThat(recordedRequest.getMethod()).isEqualToIgnoringCase("get");
assertThat(recordedRequest.getRequestUrl().toString()).isEqualTo(this.baseUrl + "/repos/spring-projects/spring-security/issues?per_page=1&milestone=" + milestoneNumber);
}
@Test
public void isOpenIssuesForMilestoneNumberWhenOpenIssuesThenTrue() throws Exception {
String responseJson = "[\n" +
" {\n" +
" \"url\":\"https://api.github.com/repos/spring-projects/spring-security/issues/9562\",\n" +
" \"repository_url\":\"https://api.github.com/repos/spring-projects/spring-security\",\n" +
" \"labels_url\":\"https://api.github.com/repos/spring-projects/spring-security/issues/9562/labels{/name}\",\n" +
" \"comments_url\":\"https://api.github.com/repos/spring-projects/spring-security/issues/9562/comments\",\n" +
" \"events_url\":\"https://api.github.com/repos/spring-projects/spring-security/issues/9562/events\",\n" +
" \"html_url\":\"https://github.com/spring-projects/spring-security/pull/9562\",\n" +
" \"id\":851886504,\n" +
" \"node_id\":\"MDExOlB1bGxSZXF1ZXN0NjEwMjMzMDcw\",\n" +
" \"number\":9562,\n" +
" \"title\":\"Add package-list\",\n" +
" \"user\":{\n" +
" \"login\":\"jzheaux\",\n" +
" \"id\":3627351,\n" +
" \"node_id\":\"MDQ6VXNlcjM2MjczNTE=\",\n" +
" \"avatar_url\":\"https://avatars.githubusercontent.com/u/3627351?v=4\",\n" +
" \"gravatar_id\":\"\",\n" +
" \"url\":\"https://api.github.com/users/jzheaux\",\n" +
" \"html_url\":\"https://github.com/jzheaux\",\n" +
" \"followers_url\":\"https://api.github.com/users/jzheaux/followers\",\n" +
" \"following_url\":\"https://api.github.com/users/jzheaux/following{/other_user}\",\n" +
" \"gists_url\":\"https://api.github.com/users/jzheaux/gists{/gist_id}\",\n" +
" \"starred_url\":\"https://api.github.com/users/jzheaux/starred{/owner}{/repo}\",\n" +
" \"subscriptions_url\":\"https://api.github.com/users/jzheaux/subscriptions\",\n" +
" \"organizations_url\":\"https://api.github.com/users/jzheaux/orgs\",\n" +
" \"repos_url\":\"https://api.github.com/users/jzheaux/repos\",\n" +
" \"events_url\":\"https://api.github.com/users/jzheaux/events{/privacy}\",\n" +
" \"received_events_url\":\"https://api.github.com/users/jzheaux/received_events\",\n" +
" \"type\":\"User\",\n" +
" \"site_admin\":false\n" +
" },\n" +
" \"labels\":[\n" +
" {\n" +
" \"id\":322225043,\n" +
" \"node_id\":\"MDU6TGFiZWwzMjIyMjUwNDM=\",\n" +
" \"url\":\"https://api.github.com/repos/spring-projects/spring-security/labels/in:%20build\",\n" +
" \"name\":\"in: build\",\n" +
" \"color\":\"e8f9de\",\n" +
" \"default\":false,\n" +
" \"description\":\"An issue in the build\"\n" +
" },\n" +
" {\n" +
" \"id\":322225079,\n" +
" \"node_id\":\"MDU6TGFiZWwzMjIyMjUwNzk=\",\n" +
" \"url\":\"https://api.github.com/repos/spring-projects/spring-security/labels/type:%20bug\",\n" +
" \"name\":\"type: bug\",\n" +
" \"color\":\"e3d9fc\",\n" +
" \"default\":false,\n" +
" \"description\":\"A general bug\"\n" +
" }\n" +
" ],\n" +
" \"state\":\"open\",\n" +
" \"locked\":false,\n" +
" \"assignee\":{\n" +
" \"login\":\"rwinch\",\n" +
" \"id\":362503,\n" +
" \"node_id\":\"MDQ6VXNlcjM2MjUwMw==\",\n" +
" \"avatar_url\":\"https://avatars.githubusercontent.com/u/362503?v=4\",\n" +
" \"gravatar_id\":\"\",\n" +
" \"url\":\"https://api.github.com/users/rwinch\",\n" +
" \"html_url\":\"https://github.com/rwinch\",\n" +
" \"followers_url\":\"https://api.github.com/users/rwinch/followers\",\n" +
" \"following_url\":\"https://api.github.com/users/rwinch/following{/other_user}\",\n" +
" \"gists_url\":\"https://api.github.com/users/rwinch/gists{/gist_id}\",\n" +
" \"starred_url\":\"https://api.github.com/users/rwinch/starred{/owner}{/repo}\",\n" +
" \"subscriptions_url\":\"https://api.github.com/users/rwinch/subscriptions\",\n" +
" \"organizations_url\":\"https://api.github.com/users/rwinch/orgs\",\n" +
" \"repos_url\":\"https://api.github.com/users/rwinch/repos\",\n" +
" \"events_url\":\"https://api.github.com/users/rwinch/events{/privacy}\",\n" +
" \"received_events_url\":\"https://api.github.com/users/rwinch/received_events\",\n" +
" \"type\":\"User\",\n" +
" \"site_admin\":false\n" +
" },\n" +
" \"assignees\":[\n" +
" {\n" +
" \"login\":\"rwinch\",\n" +
" \"id\":362503,\n" +
" \"node_id\":\"MDQ6VXNlcjM2MjUwMw==\",\n" +
" \"avatar_url\":\"https://avatars.githubusercontent.com/u/362503?v=4\",\n" +
" \"gravatar_id\":\"\",\n" +
" \"url\":\"https://api.github.com/users/rwinch\",\n" +
" \"html_url\":\"https://github.com/rwinch\",\n" +
" \"followers_url\":\"https://api.github.com/users/rwinch/followers\",\n" +
" \"following_url\":\"https://api.github.com/users/rwinch/following{/other_user}\",\n" +
" \"gists_url\":\"https://api.github.com/users/rwinch/gists{/gist_id}\",\n" +
" \"starred_url\":\"https://api.github.com/users/rwinch/starred{/owner}{/repo}\",\n" +
" \"subscriptions_url\":\"https://api.github.com/users/rwinch/subscriptions\",\n" +
" \"organizations_url\":\"https://api.github.com/users/rwinch/orgs\",\n" +
" \"repos_url\":\"https://api.github.com/users/rwinch/repos\",\n" +
" \"events_url\":\"https://api.github.com/users/rwinch/events{/privacy}\",\n" +
" \"received_events_url\":\"https://api.github.com/users/rwinch/received_events\",\n" +
" \"type\":\"User\",\n" +
" \"site_admin\":false\n" +
" }\n" +
" ],\n" +
" \"milestone\":{\n" +
" \"url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/191\",\n" +
" \"html_url\":\"https://github.com/spring-projects/spring-security/milestone/191\",\n" +
" \"labels_url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/191/labels\",\n" +
" \"id\":5884208,\n" +
" \"node_id\":\"MDk6TWlsZXN0b25lNTg4NDIwOA==\",\n" +
" \"number\":191,\n" +
" \"title\":\"5.5.0-RC1\",\n" +
" \"description\":\"\",\n" +
" \"creator\":{\n" +
" \"login\":\"jzheaux\",\n" +
" \"id\":3627351,\n" +
" \"node_id\":\"MDQ6VXNlcjM2MjczNTE=\",\n" +
" \"avatar_url\":\"https://avatars.githubusercontent.com/u/3627351?v=4\",\n" +
" \"gravatar_id\":\"\",\n" +
" \"url\":\"https://api.github.com/users/jzheaux\",\n" +
" \"html_url\":\"https://github.com/jzheaux\",\n" +
" \"followers_url\":\"https://api.github.com/users/jzheaux/followers\",\n" +
" \"following_url\":\"https://api.github.com/users/jzheaux/following{/other_user}\",\n" +
" \"gists_url\":\"https://api.github.com/users/jzheaux/gists{/gist_id}\",\n" +
" \"starred_url\":\"https://api.github.com/users/jzheaux/starred{/owner}{/repo}\",\n" +
" \"subscriptions_url\":\"https://api.github.com/users/jzheaux/subscriptions\",\n" +
" \"organizations_url\":\"https://api.github.com/users/jzheaux/orgs\",\n" +
" \"repos_url\":\"https://api.github.com/users/jzheaux/repos\",\n" +
" \"events_url\":\"https://api.github.com/users/jzheaux/events{/privacy}\",\n" +
" \"received_events_url\":\"https://api.github.com/users/jzheaux/received_events\",\n" +
" \"type\":\"User\",\n" +
" \"site_admin\":false\n" +
" },\n" +
" \"open_issues\":21,\n" +
" \"closed_issues\":23,\n" +
" \"state\":\"open\",\n" +
" \"created_at\":\"2020-09-16T13:28:03Z\",\n" +
" \"updated_at\":\"2021-04-06T23:47:10Z\",\n" +
" \"due_on\":\"2021-04-12T07:00:00Z\",\n" +
" \"closed_at\":null\n" +
" },\n" +
" \"comments\":0,\n" +
" \"created_at\":\"2021-04-06T23:47:10Z\",\n" +
" \"updated_at\":\"2021-04-07T17:00:00Z\",\n" +
" \"closed_at\":null,\n" +
" \"author_association\":\"MEMBER\",\n" +
" \"active_lock_reason\":null,\n" +
" \"pull_request\":{\n" +
" \"url\":\"https://api.github.com/repos/spring-projects/spring-security/pulls/9562\",\n" +
" \"html_url\":\"https://github.com/spring-projects/spring-security/pull/9562\",\n" +
" \"diff_url\":\"https://github.com/spring-projects/spring-security/pull/9562.diff\",\n" +
" \"patch_url\":\"https://github.com/spring-projects/spring-security/pull/9562.patch\"\n" +
" },\n" +
" \"body\":\"Closes gh-9528\\r\\n\\r\\n<!--\\r\\nFor Security Vulnerabilities, please use https://pivotal.io/security#reporting\\r\\n-->\\r\\n\\r\\n<!--\\r\\nBefore creating new features, we recommend creating an issue to discuss the feature. This ensures that everyone is on the same page before extensive work is done.\\r\\n\\r\\nThanks for contributing to Spring Security. Please provide a brief description of your pull-request and reference any related issue numbers (prefix references with gh-).\\r\\n-->\\r\\n\",\n" +
" \"performed_via_github_app\":null\n" +
" }\n" +
"]";
long milestoneNumber = 191;
this.server.enqueue(new MockResponse().setBody(responseJson));
assertThat(this.github.isOpenIssuesForMilestoneNumber(this.repositoryRef, milestoneNumber)).isTrue();
RecordedRequest recordedRequest = this.server.takeRequest(1, TimeUnit.SECONDS);
assertThat(recordedRequest.getMethod()).isEqualToIgnoringCase("get");
assertThat(recordedRequest.getRequestUrl().toString()).isEqualTo(this.baseUrl + "/repos/spring-projects/spring-security/issues?per_page=1&milestone=" + milestoneNumber);
}
}
@@ -15,23 +15,22 @@ 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 {
class CheckAntoraVersionPluginTests {
@Test
void defaultsPropertiesWhenSnapshot() {
String expectedVersion = "1.0.0-SNAPSHOT";
Project project = ProjectBuilder.builder().build();
project.setVersion(expectedVersion);
project.getPluginManager().apply(AntoraVersionPlugin.class);
project.getPluginManager().apply(CheckAntoraVersionPlugin.class);
Task task = project.getTasks().findByName(AntoraVersionPlugin.ANTORA_CHECK_VERSION_TASK_NAME);
Task task = project.getTasks().findByName(CheckAntoraVersionPlugin.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"));
}
@@ -40,16 +39,15 @@ class AntoraVersionPluginTests {
String expectedVersion = "1.0.0-M1";
Project project = ProjectBuilder.builder().build();
project.setVersion(expectedVersion);
project.getPluginManager().apply(AntoraVersionPlugin.class);
project.getPluginManager().apply(CheckAntoraVersionPlugin.class);
Task task = project.getTasks().findByName(AntoraVersionPlugin.ANTORA_CHECK_VERSION_TASK_NAME);
Task task = project.getTasks().findByName(CheckAntoraVersionPlugin.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"));
}
@@ -58,16 +56,15 @@ class AntoraVersionPluginTests {
String expectedVersion = "1.0.0-RC1";
Project project = ProjectBuilder.builder().build();
project.setVersion(expectedVersion);
project.getPluginManager().apply(AntoraVersionPlugin.class);
project.getPluginManager().apply(CheckAntoraVersionPlugin.class);
Task task = project.getTasks().findByName(AntoraVersionPlugin.ANTORA_CHECK_VERSION_TASK_NAME);
Task task = project.getTasks().findByName(CheckAntoraVersionPlugin.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"));
}
@@ -76,32 +73,30 @@ class AntoraVersionPluginTests {
String expectedVersion = "1.0.0";
Project project = ProjectBuilder.builder().build();
project.setVersion(expectedVersion);
project.getPluginManager().apply(AntoraVersionPlugin.class);
project.getPluginManager().apply(CheckAntoraVersionPlugin.class);
Task task = project.getTasks().findByName(AntoraVersionPlugin.ANTORA_CHECK_VERSION_TASK_NAME);
Task task = project.getTasks().findByName(CheckAntoraVersionPlugin.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);
project.getPluginManager().apply(CheckAntoraVersionPlugin.class);
Task task = project.getTasks().findByName(AntoraVersionPlugin.ANTORA_CHECK_VERSION_TASK_NAME);
Task task = project.getTasks().findByName(CheckAntoraVersionPlugin.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"));
}
@@ -110,9 +105,9 @@ class AntoraVersionPluginTests {
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);
project.getPluginManager().apply(CheckAntoraVersionPlugin.class);
Task task = project.getTasks().findByName(AntoraVersionPlugin.ANTORA_CHECK_VERSION_TASK_NAME);
Task task = project.getTasks().findByName(CheckAntoraVersionPlugin.ANTORA_CHECK_VERSION_TASK_NAME);
assertThat(task).isInstanceOf(CheckAntoraVersionTask.class);
@@ -125,9 +120,9 @@ class AntoraVersionPluginTests {
String expectedVersion = "1.0.0-SNAPSHOT";
Project project = ProjectBuilder.builder().build();
project.setVersion(expectedVersion);
project.getPluginManager().apply(AntoraVersionPlugin.class);
project.getPluginManager().apply(CheckAntoraVersionPlugin.class);
Task task = project.getTasks().findByName(AntoraVersionPlugin.ANTORA_CHECK_VERSION_TASK_NAME);
Task task = project.getTasks().findByName(CheckAntoraVersionPlugin.ANTORA_CHECK_VERSION_TASK_NAME);
assertThat(task).isInstanceOf(CheckAntoraVersionTask.class);
@@ -142,9 +137,9 @@ class AntoraVersionPluginTests {
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);
project.getPluginManager().apply(CheckAntoraVersionPlugin.class);
Task task = project.getTasks().findByName(AntoraVersionPlugin.ANTORA_CHECK_VERSION_TASK_NAME);
Task task = project.getTasks().findByName(CheckAntoraVersionPlugin.ANTORA_CHECK_VERSION_TASK_NAME);
assertThat(task).isInstanceOf(CheckAntoraVersionTask.class);
@@ -160,9 +155,9 @@ class AntoraVersionPluginTests {
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);
project.getPluginManager().apply(CheckAntoraVersionPlugin.class);
Task task = project.getTasks().findByName(AntoraVersionPlugin.ANTORA_CHECK_VERSION_TASK_NAME);
Task task = project.getTasks().findByName(CheckAntoraVersionPlugin.ANTORA_CHECK_VERSION_TASK_NAME);
assertThat(task).isInstanceOf(CheckAntoraVersionTask.class);
@@ -175,11 +170,11 @@ class AntoraVersionPluginTests {
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);
IOUtils.write("version: '1.0.0-M1'\nprerelease: 'true'", new FileOutputStream(new File(rootDir, "antora.yml")), StandardCharsets.UTF_8);
project.setVersion(expectedVersion);
project.getPluginManager().apply(AntoraVersionPlugin.class);
project.getPluginManager().apply(CheckAntoraVersionPlugin.class);
Task task = project.getTasks().findByName(AntoraVersionPlugin.ANTORA_CHECK_VERSION_TASK_NAME);
Task task = project.getTasks().findByName(CheckAntoraVersionPlugin.ANTORA_CHECK_VERSION_TASK_NAME);
assertThat(task).isInstanceOf(CheckAntoraVersionTask.class);
@@ -192,11 +187,11 @@ class AntoraVersionPluginTests {
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);
IOUtils.write("version: '1.0.0-RC1'\nprerelease: 'true'", new FileOutputStream(new File(rootDir, "antora.yml")), StandardCharsets.UTF_8);
project.setVersion(expectedVersion);
project.getPluginManager().apply(AntoraVersionPlugin.class);
project.getPluginManager().apply(CheckAntoraVersionPlugin.class);
Task task = project.getTasks().findByName(AntoraVersionPlugin.ANTORA_CHECK_VERSION_TASK_NAME);
Task task = project.getTasks().findByName(CheckAntoraVersionPlugin.ANTORA_CHECK_VERSION_TASK_NAME);
assertThat(task).isInstanceOf(CheckAntoraVersionTask.class);
@@ -211,9 +206,9 @@ class AntoraVersionPluginTests {
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);
project.getPluginManager().apply(CheckAntoraVersionPlugin.class);
Task task = project.getTasks().findByName(AntoraVersionPlugin.ANTORA_CHECK_VERSION_TASK_NAME);
Task task = project.getTasks().findByName(CheckAntoraVersionPlugin.ANTORA_CHECK_VERSION_TASK_NAME);
assertThat(task).isInstanceOf(CheckAntoraVersionTask.class);
@@ -226,9 +221,9 @@ class AntoraVersionPluginTests {
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);
project.getPluginManager().apply(CheckAntoraVersionPlugin.class);
Task task = project.getTasks().findByName(AntoraVersionPlugin.ANTORA_CHECK_VERSION_TASK_NAME);
Task task = project.getTasks().findByName(CheckAntoraVersionPlugin.ANTORA_CHECK_VERSION_TASK_NAME);
assertThat(task).isInstanceOf(CheckAntoraVersionTask.class);
CheckAntoraVersionTask checkAntoraVersionTask = (CheckAntoraVersionTask) task;
@@ -241,9 +236,9 @@ class AntoraVersionPluginTests {
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);
project.getPluginManager().apply(CheckAntoraVersionPlugin.class);
Task task = project.getTasks().findByName(AntoraVersionPlugin.ANTORA_CHECK_VERSION_TASK_NAME);
Task task = project.getTasks().findByName(CheckAntoraVersionPlugin.ANTORA_CHECK_VERSION_TASK_NAME);
assertThat(task).isInstanceOf(CheckAntoraVersionTask.class);
CheckAntoraVersionTask checkAntoraVersionTask = (CheckAntoraVersionTask) task;
@@ -257,9 +252,9 @@ class AntoraVersionPluginTests {
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);
project.getPluginManager().apply(CheckAntoraVersionPlugin.class);
Task task = project.getTasks().findByName(AntoraVersionPlugin.ANTORA_CHECK_VERSION_TASK_NAME);
Task task = project.getTasks().findByName(CheckAntoraVersionPlugin.ANTORA_CHECK_VERSION_TASK_NAME);
assertThat(task).isInstanceOf(CheckAntoraVersionTask.class);
CheckAntoraVersionTask checkAntoraVersionTask = (CheckAntoraVersionTask) task;
@@ -1,10 +1,5 @@
package org.springframework.gradle.github.milestones;
import java.nio.charset.Charset;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.ZoneId;
import java.util.concurrent.TimeUnit;
import okhttp3.mockwebserver.MockResponse;
@@ -390,836 +385,4 @@ public class GitHubMilestoneApiTests {
assertThat(recordedRequest.getRequestUrl().toString()).isEqualTo(this.baseUrl + "/repos/spring-projects/spring-security/issues?per_page=1&milestone=" + milestoneNumber);
}
@Test
public void isMilestoneDueTodayWhenNotFoundThenException() throws Exception {
String responseJson = "[\n" +
" {\n" +
" \"url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/207\",\n" +
" \"html_url\":\"https://github.com/spring-projects/spring-security/milestone/207\",\n" +
" \"labels_url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/207/labels\",\n" +
" \"id\":6611880,\n" +
" \"node_id\":\"MDk6TWlsZXN0b25lNjYxMTg4MA==\",\n" +
" \"number\":207,\n" +
" \"title\":\"5.6.x\",\n" +
" \"description\":\"\",\n" +
" \"creator\":{\n" +
" \"login\":\"jgrandja\",\n" +
" \"id\":10884212,\n" +
" \"node_id\":\"MDQ6VXNlcjEwODg0MjEy\",\n" +
" \"avatar_url\":\"https://avatars.githubusercontent.com/u/10884212?v=4\",\n" +
" \"gravatar_id\":\"\",\n" +
" \"url\":\"https://api.github.com/users/jgrandja\",\n" +
" \"html_url\":\"https://github.com/jgrandja\",\n" +
" \"followers_url\":\"https://api.github.com/users/jgrandja/followers\",\n" +
" \"following_url\":\"https://api.github.com/users/jgrandja/following{/other_user}\",\n" +
" \"gists_url\":\"https://api.github.com/users/jgrandja/gists{/gist_id}\",\n" +
" \"starred_url\":\"https://api.github.com/users/jgrandja/starred{/owner}{/repo}\",\n" +
" \"subscriptions_url\":\"https://api.github.com/users/jgrandja/subscriptions\",\n" +
" \"organizations_url\":\"https://api.github.com/users/jgrandja/orgs\",\n" +
" \"repos_url\":\"https://api.github.com/users/jgrandja/repos\",\n" +
" \"events_url\":\"https://api.github.com/users/jgrandja/events{/privacy}\",\n" +
" \"received_events_url\":\"https://api.github.com/users/jgrandja/received_events\",\n" +
" \"type\":\"User\",\n" +
" \"site_admin\":false\n" +
" },\n" +
" \"open_issues\":1,\n" +
" \"closed_issues\":0,\n" +
" \"state\":\"open\",\n" +
" \"created_at\":\"2021-03-31T11:29:17Z\",\n" +
" \"updated_at\":\"2021-03-31T11:30:47Z\",\n" +
" \"due_on\":null,\n" +
" \"closed_at\":null\n" +
" },\n" +
" {\n" +
" \"url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/191\",\n" +
" \"html_url\":\"https://github.com/spring-projects/spring-security/milestone/191\",\n" +
" \"labels_url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/191/labels\",\n" +
" \"id\":5884208,\n" +
" \"node_id\":\"MDk6TWlsZXN0b25lNTg4NDIwOA==\",\n" +
" \"number\":191,\n" +
" \"title\":\"5.5.0-RC1\",\n" +
" \"description\":\"\",\n" +
" \"creator\":{\n" +
" \"login\":\"jzheaux\",\n" +
" \"id\":3627351,\n" +
" \"node_id\":\"MDQ6VXNlcjM2MjczNTE=\",\n" +
" \"avatar_url\":\"https://avatars.githubusercontent.com/u/3627351?v=4\",\n" +
" \"gravatar_id\":\"\",\n" +
" \"url\":\"https://api.github.com/users/jzheaux\",\n" +
" \"html_url\":\"https://github.com/jzheaux\",\n" +
" \"followers_url\":\"https://api.github.com/users/jzheaux/followers\",\n" +
" \"following_url\":\"https://api.github.com/users/jzheaux/following{/other_user}\",\n" +
" \"gists_url\":\"https://api.github.com/users/jzheaux/gists{/gist_id}\",\n" +
" \"starred_url\":\"https://api.github.com/users/jzheaux/starred{/owner}{/repo}\",\n" +
" \"subscriptions_url\":\"https://api.github.com/users/jzheaux/subscriptions\",\n" +
" \"organizations_url\":\"https://api.github.com/users/jzheaux/orgs\",\n" +
" \"repos_url\":\"https://api.github.com/users/jzheaux/repos\",\n" +
" \"events_url\":\"https://api.github.com/users/jzheaux/events{/privacy}\",\n" +
" \"received_events_url\":\"https://api.github.com/users/jzheaux/received_events\",\n" +
" \"type\":\"User\",\n" +
" \"site_admin\":false\n" +
" },\n" +
" \"open_issues\":21,\n" +
" \"closed_issues\":23,\n" +
" \"state\":\"open\",\n" +
" \"created_at\":\"2020-09-16T13:28:03Z\",\n" +
" \"updated_at\":\"2021-04-06T23:47:10Z\",\n" +
" \"due_on\":\"2021-04-12T07:00:00Z\",\n" +
" \"closed_at\":null\n" +
" }\n" +
"]";
this.server.enqueue(new MockResponse().setBody(responseJson));
assertThatExceptionOfType(RuntimeException.class)
.isThrownBy(() -> this.github.isMilestoneDueToday(this.repositoryRef, "missing"));
}
@Test
public void isMilestoneDueTodayWhenPastDueThenTrue() throws Exception {
String responseJson = "[\n" +
" {\n" +
" \"url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/207\",\n" +
" \"html_url\":\"https://github.com/spring-projects/spring-security/milestone/207\",\n" +
" \"labels_url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/207/labels\",\n" +
" \"id\":6611880,\n" +
" \"node_id\":\"MDk6TWlsZXN0b25lNjYxMTg4MA==\",\n" +
" \"number\":207,\n" +
" \"title\":\"5.6.x\",\n" +
" \"description\":\"\",\n" +
" \"creator\":{\n" +
" \"login\":\"jgrandja\",\n" +
" \"id\":10884212,\n" +
" \"node_id\":\"MDQ6VXNlcjEwODg0MjEy\",\n" +
" \"avatar_url\":\"https://avatars.githubusercontent.com/u/10884212?v=4\",\n" +
" \"gravatar_id\":\"\",\n" +
" \"url\":\"https://api.github.com/users/jgrandja\",\n" +
" \"html_url\":\"https://github.com/jgrandja\",\n" +
" \"followers_url\":\"https://api.github.com/users/jgrandja/followers\",\n" +
" \"following_url\":\"https://api.github.com/users/jgrandja/following{/other_user}\",\n" +
" \"gists_url\":\"https://api.github.com/users/jgrandja/gists{/gist_id}\",\n" +
" \"starred_url\":\"https://api.github.com/users/jgrandja/starred{/owner}{/repo}\",\n" +
" \"subscriptions_url\":\"https://api.github.com/users/jgrandja/subscriptions\",\n" +
" \"organizations_url\":\"https://api.github.com/users/jgrandja/orgs\",\n" +
" \"repos_url\":\"https://api.github.com/users/jgrandja/repos\",\n" +
" \"events_url\":\"https://api.github.com/users/jgrandja/events{/privacy}\",\n" +
" \"received_events_url\":\"https://api.github.com/users/jgrandja/received_events\",\n" +
" \"type\":\"User\",\n" +
" \"site_admin\":false\n" +
" },\n" +
" \"open_issues\":1,\n" +
" \"closed_issues\":0,\n" +
" \"state\":\"open\",\n" +
" \"created_at\":\"2021-03-31T11:29:17Z\",\n" +
" \"updated_at\":\"2021-03-31T11:30:47Z\",\n" +
" \"due_on\":null,\n" +
" \"closed_at\":null\n" +
" },\n" +
" {\n" +
" \"url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/191\",\n" +
" \"html_url\":\"https://github.com/spring-projects/spring-security/milestone/191\",\n" +
" \"labels_url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/191/labels\",\n" +
" \"id\":5884208,\n" +
" \"node_id\":\"MDk6TWlsZXN0b25lNTg4NDIwOA==\",\n" +
" \"number\":191,\n" +
" \"title\":\"5.5.0-RC1\",\n" +
" \"description\":\"\",\n" +
" \"creator\":{\n" +
" \"login\":\"jzheaux\",\n" +
" \"id\":3627351,\n" +
" \"node_id\":\"MDQ6VXNlcjM2MjczNTE=\",\n" +
" \"avatar_url\":\"https://avatars.githubusercontent.com/u/3627351?v=4\",\n" +
" \"gravatar_id\":\"\",\n" +
" \"url\":\"https://api.github.com/users/jzheaux\",\n" +
" \"html_url\":\"https://github.com/jzheaux\",\n" +
" \"followers_url\":\"https://api.github.com/users/jzheaux/followers\",\n" +
" \"following_url\":\"https://api.github.com/users/jzheaux/following{/other_user}\",\n" +
" \"gists_url\":\"https://api.github.com/users/jzheaux/gists{/gist_id}\",\n" +
" \"starred_url\":\"https://api.github.com/users/jzheaux/starred{/owner}{/repo}\",\n" +
" \"subscriptions_url\":\"https://api.github.com/users/jzheaux/subscriptions\",\n" +
" \"organizations_url\":\"https://api.github.com/users/jzheaux/orgs\",\n" +
" \"repos_url\":\"https://api.github.com/users/jzheaux/repos\",\n" +
" \"events_url\":\"https://api.github.com/users/jzheaux/events{/privacy}\",\n" +
" \"received_events_url\":\"https://api.github.com/users/jzheaux/received_events\",\n" +
" \"type\":\"User\",\n" +
" \"site_admin\":false\n" +
" },\n" +
" \"open_issues\":21,\n" +
" \"closed_issues\":23,\n" +
" \"state\":\"open\",\n" +
" \"created_at\":\"2020-09-16T13:28:03Z\",\n" +
" \"updated_at\":\"2021-04-06T23:47:10Z\",\n" +
" \"due_on\":\"2021-04-12T07:00:00Z\",\n" +
" \"closed_at\":null\n" +
" }\n" +
"]";
this.server.enqueue(new MockResponse().setBody(responseJson));
boolean dueToday = this.github.isMilestoneDueToday(this.repositoryRef, "5.5.0-RC1");
RecordedRequest recordedRequest = this.server.takeRequest(1, TimeUnit.SECONDS);
assertThat(recordedRequest.getMethod()).isEqualToIgnoringCase("get");
assertThat(recordedRequest.getRequestUrl().toString()).isEqualTo(this.baseUrl + "/repos/spring-projects/spring-security/milestones?per_page=100");
assertThat(dueToday).isTrue();
}
@Test
public void isMilestoneDueTodayWhenDueTodayThenTrue() throws Exception {
String responseJson = "[\n" +
" {\n" +
" \"url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/207\",\n" +
" \"html_url\":\"https://github.com/spring-projects/spring-security/milestone/207\",\n" +
" \"labels_url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/207/labels\",\n" +
" \"id\":6611880,\n" +
" \"node_id\":\"MDk6TWlsZXN0b25lNjYxMTg4MA==\",\n" +
" \"number\":207,\n" +
" \"title\":\"5.6.x\",\n" +
" \"description\":\"\",\n" +
" \"creator\":{\n" +
" \"login\":\"jgrandja\",\n" +
" \"id\":10884212,\n" +
" \"node_id\":\"MDQ6VXNlcjEwODg0MjEy\",\n" +
" \"avatar_url\":\"https://avatars.githubusercontent.com/u/10884212?v=4\",\n" +
" \"gravatar_id\":\"\",\n" +
" \"url\":\"https://api.github.com/users/jgrandja\",\n" +
" \"html_url\":\"https://github.com/jgrandja\",\n" +
" \"followers_url\":\"https://api.github.com/users/jgrandja/followers\",\n" +
" \"following_url\":\"https://api.github.com/users/jgrandja/following{/other_user}\",\n" +
" \"gists_url\":\"https://api.github.com/users/jgrandja/gists{/gist_id}\",\n" +
" \"starred_url\":\"https://api.github.com/users/jgrandja/starred{/owner}{/repo}\",\n" +
" \"subscriptions_url\":\"https://api.github.com/users/jgrandja/subscriptions\",\n" +
" \"organizations_url\":\"https://api.github.com/users/jgrandja/orgs\",\n" +
" \"repos_url\":\"https://api.github.com/users/jgrandja/repos\",\n" +
" \"events_url\":\"https://api.github.com/users/jgrandja/events{/privacy}\",\n" +
" \"received_events_url\":\"https://api.github.com/users/jgrandja/received_events\",\n" +
" \"type\":\"User\",\n" +
" \"site_admin\":false\n" +
" },\n" +
" \"open_issues\":1,\n" +
" \"closed_issues\":0,\n" +
" \"state\":\"open\",\n" +
" \"created_at\":\"2021-03-31T11:29:17Z\",\n" +
" \"updated_at\":\"2021-03-31T11:30:47Z\",\n" +
" \"due_on\":null,\n" +
" \"closed_at\":null\n" +
" },\n" +
" {\n" +
" \"url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/191\",\n" +
" \"html_url\":\"https://github.com/spring-projects/spring-security/milestone/191\",\n" +
" \"labels_url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/191/labels\",\n" +
" \"id\":5884208,\n" +
" \"node_id\":\"MDk6TWlsZXN0b25lNTg4NDIwOA==\",\n" +
" \"number\":191,\n" +
" \"title\":\"5.5.0-RC1\",\n" +
" \"description\":\"\",\n" +
" \"creator\":{\n" +
" \"login\":\"jzheaux\",\n" +
" \"id\":3627351,\n" +
" \"node_id\":\"MDQ6VXNlcjM2MjczNTE=\",\n" +
" \"avatar_url\":\"https://avatars.githubusercontent.com/u/3627351?v=4\",\n" +
" \"gravatar_id\":\"\",\n" +
" \"url\":\"https://api.github.com/users/jzheaux\",\n" +
" \"html_url\":\"https://github.com/jzheaux\",\n" +
" \"followers_url\":\"https://api.github.com/users/jzheaux/followers\",\n" +
" \"following_url\":\"https://api.github.com/users/jzheaux/following{/other_user}\",\n" +
" \"gists_url\":\"https://api.github.com/users/jzheaux/gists{/gist_id}\",\n" +
" \"starred_url\":\"https://api.github.com/users/jzheaux/starred{/owner}{/repo}\",\n" +
" \"subscriptions_url\":\"https://api.github.com/users/jzheaux/subscriptions\",\n" +
" \"organizations_url\":\"https://api.github.com/users/jzheaux/orgs\",\n" +
" \"repos_url\":\"https://api.github.com/users/jzheaux/repos\",\n" +
" \"events_url\":\"https://api.github.com/users/jzheaux/events{/privacy}\",\n" +
" \"received_events_url\":\"https://api.github.com/users/jzheaux/received_events\",\n" +
" \"type\":\"User\",\n" +
" \"site_admin\":false\n" +
" },\n" +
" \"open_issues\":21,\n" +
" \"closed_issues\":23,\n" +
" \"state\":\"open\",\n" +
" \"created_at\":\"2020-09-16T13:28:03Z\",\n" +
" \"updated_at\":\"2021-04-06T23:47:10Z\",\n" +
" \"due_on\":\"" + LocalDate.now().atStartOfDay(ZoneId.systemDefault()).toInstant().toString() + "\",\n" +
" \"closed_at\":null\n" +
" }\n" +
"]";
this.server.enqueue(new MockResponse().setBody(responseJson));
boolean dueToday = this.github.isMilestoneDueToday(this.repositoryRef, "5.5.0-RC1");
RecordedRequest recordedRequest = this.server.takeRequest(1, TimeUnit.SECONDS);
assertThat(recordedRequest.getMethod()).isEqualToIgnoringCase("get");
assertThat(recordedRequest.getRequestUrl().toString()).isEqualTo(this.baseUrl + "/repos/spring-projects/spring-security/milestones?per_page=100");
assertThat(dueToday).isTrue();
}
@Test
public void isMilestoneDueTodayWhenNoDueDateThenFalse() throws Exception {
String responseJson = "[\n" +
" {\n" +
" \"url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/207\",\n" +
" \"html_url\":\"https://github.com/spring-projects/spring-security/milestone/207\",\n" +
" \"labels_url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/207/labels\",\n" +
" \"id\":6611880,\n" +
" \"node_id\":\"MDk6TWlsZXN0b25lNjYxMTg4MA==\",\n" +
" \"number\":207,\n" +
" \"title\":\"5.6.x\",\n" +
" \"description\":\"\",\n" +
" \"creator\":{\n" +
" \"login\":\"jgrandja\",\n" +
" \"id\":10884212,\n" +
" \"node_id\":\"MDQ6VXNlcjEwODg0MjEy\",\n" +
" \"avatar_url\":\"https://avatars.githubusercontent.com/u/10884212?v=4\",\n" +
" \"gravatar_id\":\"\",\n" +
" \"url\":\"https://api.github.com/users/jgrandja\",\n" +
" \"html_url\":\"https://github.com/jgrandja\",\n" +
" \"followers_url\":\"https://api.github.com/users/jgrandja/followers\",\n" +
" \"following_url\":\"https://api.github.com/users/jgrandja/following{/other_user}\",\n" +
" \"gists_url\":\"https://api.github.com/users/jgrandja/gists{/gist_id}\",\n" +
" \"starred_url\":\"https://api.github.com/users/jgrandja/starred{/owner}{/repo}\",\n" +
" \"subscriptions_url\":\"https://api.github.com/users/jgrandja/subscriptions\",\n" +
" \"organizations_url\":\"https://api.github.com/users/jgrandja/orgs\",\n" +
" \"repos_url\":\"https://api.github.com/users/jgrandja/repos\",\n" +
" \"events_url\":\"https://api.github.com/users/jgrandja/events{/privacy}\",\n" +
" \"received_events_url\":\"https://api.github.com/users/jgrandja/received_events\",\n" +
" \"type\":\"User\",\n" +
" \"site_admin\":false\n" +
" },\n" +
" \"open_issues\":1,\n" +
" \"closed_issues\":0,\n" +
" \"state\":\"open\",\n" +
" \"created_at\":\"2021-03-31T11:29:17Z\",\n" +
" \"updated_at\":\"2021-03-31T11:30:47Z\",\n" +
" \"due_on\":null,\n" +
" \"closed_at\":null\n" +
" },\n" +
" {\n" +
" \"url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/191\",\n" +
" \"html_url\":\"https://github.com/spring-projects/spring-security/milestone/191\",\n" +
" \"labels_url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/191/labels\",\n" +
" \"id\":5884208,\n" +
" \"node_id\":\"MDk6TWlsZXN0b25lNTg4NDIwOA==\",\n" +
" \"number\":191,\n" +
" \"title\":\"5.5.0-RC1\",\n" +
" \"description\":\"\",\n" +
" \"creator\":{\n" +
" \"login\":\"jzheaux\",\n" +
" \"id\":3627351,\n" +
" \"node_id\":\"MDQ6VXNlcjM2MjczNTE=\",\n" +
" \"avatar_url\":\"https://avatars.githubusercontent.com/u/3627351?v=4\",\n" +
" \"gravatar_id\":\"\",\n" +
" \"url\":\"https://api.github.com/users/jzheaux\",\n" +
" \"html_url\":\"https://github.com/jzheaux\",\n" +
" \"followers_url\":\"https://api.github.com/users/jzheaux/followers\",\n" +
" \"following_url\":\"https://api.github.com/users/jzheaux/following{/other_user}\",\n" +
" \"gists_url\":\"https://api.github.com/users/jzheaux/gists{/gist_id}\",\n" +
" \"starred_url\":\"https://api.github.com/users/jzheaux/starred{/owner}{/repo}\",\n" +
" \"subscriptions_url\":\"https://api.github.com/users/jzheaux/subscriptions\",\n" +
" \"organizations_url\":\"https://api.github.com/users/jzheaux/orgs\",\n" +
" \"repos_url\":\"https://api.github.com/users/jzheaux/repos\",\n" +
" \"events_url\":\"https://api.github.com/users/jzheaux/events{/privacy}\",\n" +
" \"received_events_url\":\"https://api.github.com/users/jzheaux/received_events\",\n" +
" \"type\":\"User\",\n" +
" \"site_admin\":false\n" +
" },\n" +
" \"open_issues\":21,\n" +
" \"closed_issues\":23,\n" +
" \"state\":\"open\",\n" +
" \"created_at\":\"2020-09-16T13:28:03Z\",\n" +
" \"updated_at\":\"2021-04-06T23:47:10Z\",\n" +
" \"due_on\":null,\n" +
" \"closed_at\":null\n" +
" }\n" +
"]";
this.server.enqueue(new MockResponse().setBody(responseJson));
boolean dueToday = this.github.isMilestoneDueToday(this.repositoryRef, "5.5.0-RC1");
RecordedRequest recordedRequest = this.server.takeRequest(1, TimeUnit.SECONDS);
assertThat(recordedRequest.getMethod()).isEqualToIgnoringCase("get");
assertThat(recordedRequest.getRequestUrl().toString()).isEqualTo(this.baseUrl + "/repos/spring-projects/spring-security/milestones?per_page=100");
assertThat(dueToday).isFalse();
}
@Test
public void isMilestoneDueTodayWhenDueDateInFutureThenFalse() throws Exception {
String responseJson = "[\n" +
" {\n" +
" \"url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/207\",\n" +
" \"html_url\":\"https://github.com/spring-projects/spring-security/milestone/207\",\n" +
" \"labels_url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/207/labels\",\n" +
" \"id\":6611880,\n" +
" \"node_id\":\"MDk6TWlsZXN0b25lNjYxMTg4MA==\",\n" +
" \"number\":207,\n" +
" \"title\":\"5.6.x\",\n" +
" \"description\":\"\",\n" +
" \"creator\":{\n" +
" \"login\":\"jgrandja\",\n" +
" \"id\":10884212,\n" +
" \"node_id\":\"MDQ6VXNlcjEwODg0MjEy\",\n" +
" \"avatar_url\":\"https://avatars.githubusercontent.com/u/10884212?v=4\",\n" +
" \"gravatar_id\":\"\",\n" +
" \"url\":\"https://api.github.com/users/jgrandja\",\n" +
" \"html_url\":\"https://github.com/jgrandja\",\n" +
" \"followers_url\":\"https://api.github.com/users/jgrandja/followers\",\n" +
" \"following_url\":\"https://api.github.com/users/jgrandja/following{/other_user}\",\n" +
" \"gists_url\":\"https://api.github.com/users/jgrandja/gists{/gist_id}\",\n" +
" \"starred_url\":\"https://api.github.com/users/jgrandja/starred{/owner}{/repo}\",\n" +
" \"subscriptions_url\":\"https://api.github.com/users/jgrandja/subscriptions\",\n" +
" \"organizations_url\":\"https://api.github.com/users/jgrandja/orgs\",\n" +
" \"repos_url\":\"https://api.github.com/users/jgrandja/repos\",\n" +
" \"events_url\":\"https://api.github.com/users/jgrandja/events{/privacy}\",\n" +
" \"received_events_url\":\"https://api.github.com/users/jgrandja/received_events\",\n" +
" \"type\":\"User\",\n" +
" \"site_admin\":false\n" +
" },\n" +
" \"open_issues\":1,\n" +
" \"closed_issues\":0,\n" +
" \"state\":\"open\",\n" +
" \"created_at\":\"2021-03-31T11:29:17Z\",\n" +
" \"updated_at\":\"2021-03-31T11:30:47Z\",\n" +
" \"due_on\":null,\n" +
" \"closed_at\":null\n" +
" },\n" +
" {\n" +
" \"url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/191\",\n" +
" \"html_url\":\"https://github.com/spring-projects/spring-security/milestone/191\",\n" +
" \"labels_url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/191/labels\",\n" +
" \"id\":5884208,\n" +
" \"node_id\":\"MDk6TWlsZXN0b25lNTg4NDIwOA==\",\n" +
" \"number\":191,\n" +
" \"title\":\"5.5.0-RC1\",\n" +
" \"description\":\"\",\n" +
" \"creator\":{\n" +
" \"login\":\"jzheaux\",\n" +
" \"id\":3627351,\n" +
" \"node_id\":\"MDQ6VXNlcjM2MjczNTE=\",\n" +
" \"avatar_url\":\"https://avatars.githubusercontent.com/u/3627351?v=4\",\n" +
" \"gravatar_id\":\"\",\n" +
" \"url\":\"https://api.github.com/users/jzheaux\",\n" +
" \"html_url\":\"https://github.com/jzheaux\",\n" +
" \"followers_url\":\"https://api.github.com/users/jzheaux/followers\",\n" +
" \"following_url\":\"https://api.github.com/users/jzheaux/following{/other_user}\",\n" +
" \"gists_url\":\"https://api.github.com/users/jzheaux/gists{/gist_id}\",\n" +
" \"starred_url\":\"https://api.github.com/users/jzheaux/starred{/owner}{/repo}\",\n" +
" \"subscriptions_url\":\"https://api.github.com/users/jzheaux/subscriptions\",\n" +
" \"organizations_url\":\"https://api.github.com/users/jzheaux/orgs\",\n" +
" \"repos_url\":\"https://api.github.com/users/jzheaux/repos\",\n" +
" \"events_url\":\"https://api.github.com/users/jzheaux/events{/privacy}\",\n" +
" \"received_events_url\":\"https://api.github.com/users/jzheaux/received_events\",\n" +
" \"type\":\"User\",\n" +
" \"site_admin\":false\n" +
" },\n" +
" \"open_issues\":21,\n" +
" \"closed_issues\":23,\n" +
" \"state\":\"open\",\n" +
" \"created_at\":\"2020-09-16T13:28:03Z\",\n" +
" \"updated_at\":\"2021-04-06T23:47:10Z\",\n" +
" \"due_on\":\"3000-04-12T07:00:00Z\",\n" +
" \"closed_at\":null\n" +
" }\n" +
"]";
this.server.enqueue(new MockResponse().setBody(responseJson));
boolean dueToday = this.github.isMilestoneDueToday(this.repositoryRef, "5.5.0-RC1");
RecordedRequest recordedRequest = this.server.takeRequest(1, TimeUnit.SECONDS);
assertThat(recordedRequest.getMethod()).isEqualToIgnoringCase("get");
assertThat(recordedRequest.getRequestUrl().toString()).isEqualTo(this.baseUrl + "/repos/spring-projects/spring-security/milestones?per_page=100");
assertThat(dueToday).isFalse();
}
@Test
public void calculateNextReleaseMilestoneWhenCurrentVersionIsNotSnapshotThenException() {
assertThatExceptionOfType(RuntimeException.class)
.isThrownBy(() -> this.github.getNextReleaseMilestone(this.repositoryRef, "5.5.0-RC1"));
}
@Test
public void calculateNextReleaseMilestoneWhenPatchSegmentGreaterThan0ThenReturnsVersionWithoutSnapshot() {
String nextVersion = this.github.getNextReleaseMilestone(this.repositoryRef, "5.5.1-SNAPSHOT");
assertThat(nextVersion).isEqualTo("5.5.1");
}
@Test
public void calculateNextReleaseMilestoneWhenMilestoneAndRcExistThenReturnsMilestone() throws Exception {
String responseJson = "[\n" +
" {\n" +
" \"url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/207\",\n" +
" \"html_url\":\"https://github.com/spring-projects/spring-security/milestone/207\",\n" +
" \"labels_url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/207/labels\",\n" +
" \"id\":6611880,\n" +
" \"node_id\":\"MDk6TWlsZXN0b25lNjYxMTg4MA==\",\n" +
" \"number\":207,\n" +
" \"title\":\"5.5.0-M1\",\n" +
" \"description\":\"\",\n" +
" \"creator\":{\n" +
" \"login\":\"jgrandja\",\n" +
" \"id\":10884212,\n" +
" \"node_id\":\"MDQ6VXNlcjEwODg0MjEy\",\n" +
" \"avatar_url\":\"https://avatars.githubusercontent.com/u/10884212?v=4\",\n" +
" \"gravatar_id\":\"\",\n" +
" \"url\":\"https://api.github.com/users/jgrandja\",\n" +
" \"html_url\":\"https://github.com/jgrandja\",\n" +
" \"followers_url\":\"https://api.github.com/users/jgrandja/followers\",\n" +
" \"following_url\":\"https://api.github.com/users/jgrandja/following{/other_user}\",\n" +
" \"gists_url\":\"https://api.github.com/users/jgrandja/gists{/gist_id}\",\n" +
" \"starred_url\":\"https://api.github.com/users/jgrandja/starred{/owner}{/repo}\",\n" +
" \"subscriptions_url\":\"https://api.github.com/users/jgrandja/subscriptions\",\n" +
" \"organizations_url\":\"https://api.github.com/users/jgrandja/orgs\",\n" +
" \"repos_url\":\"https://api.github.com/users/jgrandja/repos\",\n" +
" \"events_url\":\"https://api.github.com/users/jgrandja/events{/privacy}\",\n" +
" \"received_events_url\":\"https://api.github.com/users/jgrandja/received_events\",\n" +
" \"type\":\"User\",\n" +
" \"site_admin\":false\n" +
" },\n" +
" \"open_issues\":1,\n" +
" \"closed_issues\":0,\n" +
" \"state\":\"open\",\n" +
" \"created_at\":\"2021-03-31T11:29:17Z\",\n" +
" \"updated_at\":\"2021-03-31T11:30:47Z\",\n" +
" \"due_on\":null,\n" +
" \"closed_at\":null\n" +
" },\n" +
" {\n" +
" \"url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/191\",\n" +
" \"html_url\":\"https://github.com/spring-projects/spring-security/milestone/191\",\n" +
" \"labels_url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/191/labels\",\n" +
" \"id\":5884208,\n" +
" \"node_id\":\"MDk6TWlsZXN0b25lNTg4NDIwOA==\",\n" +
" \"number\":191,\n" +
" \"title\":\"5.5.0-RC1\",\n" +
" \"description\":\"\",\n" +
" \"creator\":{\n" +
" \"login\":\"jzheaux\",\n" +
" \"id\":3627351,\n" +
" \"node_id\":\"MDQ6VXNlcjM2MjczNTE=\",\n" +
" \"avatar_url\":\"https://avatars.githubusercontent.com/u/3627351?v=4\",\n" +
" \"gravatar_id\":\"\",\n" +
" \"url\":\"https://api.github.com/users/jzheaux\",\n" +
" \"html_url\":\"https://github.com/jzheaux\",\n" +
" \"followers_url\":\"https://api.github.com/users/jzheaux/followers\",\n" +
" \"following_url\":\"https://api.github.com/users/jzheaux/following{/other_user}\",\n" +
" \"gists_url\":\"https://api.github.com/users/jzheaux/gists{/gist_id}\",\n" +
" \"starred_url\":\"https://api.github.com/users/jzheaux/starred{/owner}{/repo}\",\n" +
" \"subscriptions_url\":\"https://api.github.com/users/jzheaux/subscriptions\",\n" +
" \"organizations_url\":\"https://api.github.com/users/jzheaux/orgs\",\n" +
" \"repos_url\":\"https://api.github.com/users/jzheaux/repos\",\n" +
" \"events_url\":\"https://api.github.com/users/jzheaux/events{/privacy}\",\n" +
" \"received_events_url\":\"https://api.github.com/users/jzheaux/received_events\",\n" +
" \"type\":\"User\",\n" +
" \"site_admin\":false\n" +
" },\n" +
" \"open_issues\":21,\n" +
" \"closed_issues\":23,\n" +
" \"state\":\"open\",\n" +
" \"created_at\":\"2020-09-16T13:28:03Z\",\n" +
" \"updated_at\":\"2021-04-06T23:47:10Z\",\n" +
" \"due_on\":\"3000-04-12T07:00:00Z\",\n" +
" \"closed_at\":null\n" +
" }\n" +
"]";
this.server.enqueue(new MockResponse().setBody(responseJson));
String nextVersion = this.github.getNextReleaseMilestone(this.repositoryRef, "5.5.0-SNAPSHOT");
RecordedRequest recordedRequest = this.server.takeRequest(1, TimeUnit.SECONDS);
assertThat(recordedRequest.getMethod()).isEqualToIgnoringCase("get");
assertThat(recordedRequest.getRequestUrl().toString()).isEqualTo(this.baseUrl + "/repos/spring-projects/spring-security/milestones?per_page=100");
assertThat(nextVersion).isEqualTo("5.5.0-M1");
}
@Test
public void calculateNextReleaseMilestoneWhenTwoMilestonesExistThenReturnsSmallerMilestone() throws Exception {
String responseJson = "[\n" +
" {\n" +
" \"url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/207\",\n" +
" \"html_url\":\"https://github.com/spring-projects/spring-security/milestone/207\",\n" +
" \"labels_url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/207/labels\",\n" +
" \"id\":6611880,\n" +
" \"node_id\":\"MDk6TWlsZXN0b25lNjYxMTg4MA==\",\n" +
" \"number\":207,\n" +
" \"title\":\"5.5.0-M9\",\n" +
" \"description\":\"\",\n" +
" \"creator\":{\n" +
" \"login\":\"jgrandja\",\n" +
" \"id\":10884212,\n" +
" \"node_id\":\"MDQ6VXNlcjEwODg0MjEy\",\n" +
" \"avatar_url\":\"https://avatars.githubusercontent.com/u/10884212?v=4\",\n" +
" \"gravatar_id\":\"\",\n" +
" \"url\":\"https://api.github.com/users/jgrandja\",\n" +
" \"html_url\":\"https://github.com/jgrandja\",\n" +
" \"followers_url\":\"https://api.github.com/users/jgrandja/followers\",\n" +
" \"following_url\":\"https://api.github.com/users/jgrandja/following{/other_user}\",\n" +
" \"gists_url\":\"https://api.github.com/users/jgrandja/gists{/gist_id}\",\n" +
" \"starred_url\":\"https://api.github.com/users/jgrandja/starred{/owner}{/repo}\",\n" +
" \"subscriptions_url\":\"https://api.github.com/users/jgrandja/subscriptions\",\n" +
" \"organizations_url\":\"https://api.github.com/users/jgrandja/orgs\",\n" +
" \"repos_url\":\"https://api.github.com/users/jgrandja/repos\",\n" +
" \"events_url\":\"https://api.github.com/users/jgrandja/events{/privacy}\",\n" +
" \"received_events_url\":\"https://api.github.com/users/jgrandja/received_events\",\n" +
" \"type\":\"User\",\n" +
" \"site_admin\":false\n" +
" },\n" +
" \"open_issues\":1,\n" +
" \"closed_issues\":0,\n" +
" \"state\":\"open\",\n" +
" \"created_at\":\"2021-03-31T11:29:17Z\",\n" +
" \"updated_at\":\"2021-03-31T11:30:47Z\",\n" +
" \"due_on\":null,\n" +
" \"closed_at\":null\n" +
" },\n" +
" {\n" +
" \"url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/191\",\n" +
" \"html_url\":\"https://github.com/spring-projects/spring-security/milestone/191\",\n" +
" \"labels_url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/191/labels\",\n" +
" \"id\":5884208,\n" +
" \"node_id\":\"MDk6TWlsZXN0b25lNTg4NDIwOA==\",\n" +
" \"number\":191,\n" +
" \"title\":\"5.5.0-M10\",\n" +
" \"description\":\"\",\n" +
" \"creator\":{\n" +
" \"login\":\"jzheaux\",\n" +
" \"id\":3627351,\n" +
" \"node_id\":\"MDQ6VXNlcjM2MjczNTE=\",\n" +
" \"avatar_url\":\"https://avatars.githubusercontent.com/u/3627351?v=4\",\n" +
" \"gravatar_id\":\"\",\n" +
" \"url\":\"https://api.github.com/users/jzheaux\",\n" +
" \"html_url\":\"https://github.com/jzheaux\",\n" +
" \"followers_url\":\"https://api.github.com/users/jzheaux/followers\",\n" +
" \"following_url\":\"https://api.github.com/users/jzheaux/following{/other_user}\",\n" +
" \"gists_url\":\"https://api.github.com/users/jzheaux/gists{/gist_id}\",\n" +
" \"starred_url\":\"https://api.github.com/users/jzheaux/starred{/owner}{/repo}\",\n" +
" \"subscriptions_url\":\"https://api.github.com/users/jzheaux/subscriptions\",\n" +
" \"organizations_url\":\"https://api.github.com/users/jzheaux/orgs\",\n" +
" \"repos_url\":\"https://api.github.com/users/jzheaux/repos\",\n" +
" \"events_url\":\"https://api.github.com/users/jzheaux/events{/privacy}\",\n" +
" \"received_events_url\":\"https://api.github.com/users/jzheaux/received_events\",\n" +
" \"type\":\"User\",\n" +
" \"site_admin\":false\n" +
" },\n" +
" \"open_issues\":21,\n" +
" \"closed_issues\":23,\n" +
" \"state\":\"open\",\n" +
" \"created_at\":\"2020-09-16T13:28:03Z\",\n" +
" \"updated_at\":\"2021-04-06T23:47:10Z\",\n" +
" \"due_on\":\"3000-04-12T07:00:00Z\",\n" +
" \"closed_at\":null\n" +
" }\n" +
"]";
this.server.enqueue(new MockResponse().setBody(responseJson));
String nextVersion = this.github.getNextReleaseMilestone(this.repositoryRef, "5.5.0-SNAPSHOT");
RecordedRequest recordedRequest = this.server.takeRequest(1, TimeUnit.SECONDS);
assertThat(recordedRequest.getMethod()).isEqualToIgnoringCase("get");
assertThat(recordedRequest.getRequestUrl().toString()).isEqualTo(this.baseUrl + "/repos/spring-projects/spring-security/milestones?per_page=100");
assertThat(nextVersion).isEqualTo("5.5.0-M9");
}
@Test
public void calculateNextReleaseMilestoneWhenTwoRcsExistThenReturnsSmallerRc() throws Exception {
String responseJson = "[\n" +
" {\n" +
" \"url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/207\",\n" +
" \"html_url\":\"https://github.com/spring-projects/spring-security/milestone/207\",\n" +
" \"labels_url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/207/labels\",\n" +
" \"id\":6611880,\n" +
" \"node_id\":\"MDk6TWlsZXN0b25lNjYxMTg4MA==\",\n" +
" \"number\":207,\n" +
" \"title\":\"5.5.0-RC9\",\n" +
" \"description\":\"\",\n" +
" \"creator\":{\n" +
" \"login\":\"jgrandja\",\n" +
" \"id\":10884212,\n" +
" \"node_id\":\"MDQ6VXNlcjEwODg0MjEy\",\n" +
" \"avatar_url\":\"https://avatars.githubusercontent.com/u/10884212?v=4\",\n" +
" \"gravatar_id\":\"\",\n" +
" \"url\":\"https://api.github.com/users/jgrandja\",\n" +
" \"html_url\":\"https://github.com/jgrandja\",\n" +
" \"followers_url\":\"https://api.github.com/users/jgrandja/followers\",\n" +
" \"following_url\":\"https://api.github.com/users/jgrandja/following{/other_user}\",\n" +
" \"gists_url\":\"https://api.github.com/users/jgrandja/gists{/gist_id}\",\n" +
" \"starred_url\":\"https://api.github.com/users/jgrandja/starred{/owner}{/repo}\",\n" +
" \"subscriptions_url\":\"https://api.github.com/users/jgrandja/subscriptions\",\n" +
" \"organizations_url\":\"https://api.github.com/users/jgrandja/orgs\",\n" +
" \"repos_url\":\"https://api.github.com/users/jgrandja/repos\",\n" +
" \"events_url\":\"https://api.github.com/users/jgrandja/events{/privacy}\",\n" +
" \"received_events_url\":\"https://api.github.com/users/jgrandja/received_events\",\n" +
" \"type\":\"User\",\n" +
" \"site_admin\":false\n" +
" },\n" +
" \"open_issues\":1,\n" +
" \"closed_issues\":0,\n" +
" \"state\":\"open\",\n" +
" \"created_at\":\"2021-03-31T11:29:17Z\",\n" +
" \"updated_at\":\"2021-03-31T11:30:47Z\",\n" +
" \"due_on\":null,\n" +
" \"closed_at\":null\n" +
" },\n" +
" {\n" +
" \"url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/191\",\n" +
" \"html_url\":\"https://github.com/spring-projects/spring-security/milestone/191\",\n" +
" \"labels_url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/191/labels\",\n" +
" \"id\":5884208,\n" +
" \"node_id\":\"MDk6TWlsZXN0b25lNTg4NDIwOA==\",\n" +
" \"number\":191,\n" +
" \"title\":\"5.5.0-RC10\",\n" +
" \"description\":\"\",\n" +
" \"creator\":{\n" +
" \"login\":\"jzheaux\",\n" +
" \"id\":3627351,\n" +
" \"node_id\":\"MDQ6VXNlcjM2MjczNTE=\",\n" +
" \"avatar_url\":\"https://avatars.githubusercontent.com/u/3627351?v=4\",\n" +
" \"gravatar_id\":\"\",\n" +
" \"url\":\"https://api.github.com/users/jzheaux\",\n" +
" \"html_url\":\"https://github.com/jzheaux\",\n" +
" \"followers_url\":\"https://api.github.com/users/jzheaux/followers\",\n" +
" \"following_url\":\"https://api.github.com/users/jzheaux/following{/other_user}\",\n" +
" \"gists_url\":\"https://api.github.com/users/jzheaux/gists{/gist_id}\",\n" +
" \"starred_url\":\"https://api.github.com/users/jzheaux/starred{/owner}{/repo}\",\n" +
" \"subscriptions_url\":\"https://api.github.com/users/jzheaux/subscriptions\",\n" +
" \"organizations_url\":\"https://api.github.com/users/jzheaux/orgs\",\n" +
" \"repos_url\":\"https://api.github.com/users/jzheaux/repos\",\n" +
" \"events_url\":\"https://api.github.com/users/jzheaux/events{/privacy}\",\n" +
" \"received_events_url\":\"https://api.github.com/users/jzheaux/received_events\",\n" +
" \"type\":\"User\",\n" +
" \"site_admin\":false\n" +
" },\n" +
" \"open_issues\":21,\n" +
" \"closed_issues\":23,\n" +
" \"state\":\"open\",\n" +
" \"created_at\":\"2020-09-16T13:28:03Z\",\n" +
" \"updated_at\":\"2021-04-06T23:47:10Z\",\n" +
" \"due_on\":\"3000-04-12T07:00:00Z\",\n" +
" \"closed_at\":null\n" +
" }\n" +
"]";
this.server.enqueue(new MockResponse().setBody(responseJson));
String nextVersion = this.github.getNextReleaseMilestone(this.repositoryRef, "5.5.0-SNAPSHOT");
RecordedRequest recordedRequest = this.server.takeRequest(1, TimeUnit.SECONDS);
assertThat(recordedRequest.getMethod()).isEqualToIgnoringCase("get");
assertThat(recordedRequest.getRequestUrl().toString()).isEqualTo(this.baseUrl + "/repos/spring-projects/spring-security/milestones?per_page=100");
assertThat(nextVersion).isEqualTo("5.5.0-RC9");
}
@Test
public void calculateNextReleaseMilestoneWhenNoPreReleaseThenReturnsVersionWithoutSnapshot() throws Exception {
String responseJson = "[\n" +
" {\n" +
" \"url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/207\",\n" +
" \"html_url\":\"https://github.com/spring-projects/spring-security/milestone/207\",\n" +
" \"labels_url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/207/labels\",\n" +
" \"id\":6611880,\n" +
" \"node_id\":\"MDk6TWlsZXN0b25lNjYxMTg4MA==\",\n" +
" \"number\":207,\n" +
" \"title\":\"5.6.x\",\n" +
" \"description\":\"\",\n" +
" \"creator\":{\n" +
" \"login\":\"jgrandja\",\n" +
" \"id\":10884212,\n" +
" \"node_id\":\"MDQ6VXNlcjEwODg0MjEy\",\n" +
" \"avatar_url\":\"https://avatars.githubusercontent.com/u/10884212?v=4\",\n" +
" \"gravatar_id\":\"\",\n" +
" \"url\":\"https://api.github.com/users/jgrandja\",\n" +
" \"html_url\":\"https://github.com/jgrandja\",\n" +
" \"followers_url\":\"https://api.github.com/users/jgrandja/followers\",\n" +
" \"following_url\":\"https://api.github.com/users/jgrandja/following{/other_user}\",\n" +
" \"gists_url\":\"https://api.github.com/users/jgrandja/gists{/gist_id}\",\n" +
" \"starred_url\":\"https://api.github.com/users/jgrandja/starred{/owner}{/repo}\",\n" +
" \"subscriptions_url\":\"https://api.github.com/users/jgrandja/subscriptions\",\n" +
" \"organizations_url\":\"https://api.github.com/users/jgrandja/orgs\",\n" +
" \"repos_url\":\"https://api.github.com/users/jgrandja/repos\",\n" +
" \"events_url\":\"https://api.github.com/users/jgrandja/events{/privacy}\",\n" +
" \"received_events_url\":\"https://api.github.com/users/jgrandja/received_events\",\n" +
" \"type\":\"User\",\n" +
" \"site_admin\":false\n" +
" },\n" +
" \"open_issues\":1,\n" +
" \"closed_issues\":0,\n" +
" \"state\":\"open\",\n" +
" \"created_at\":\"2021-03-31T11:29:17Z\",\n" +
" \"updated_at\":\"2021-03-31T11:30:47Z\",\n" +
" \"due_on\":null,\n" +
" \"closed_at\":null\n" +
" },\n" +
" {\n" +
" \"url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/191\",\n" +
" \"html_url\":\"https://github.com/spring-projects/spring-security/milestone/191\",\n" +
" \"labels_url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/191/labels\",\n" +
" \"id\":5884208,\n" +
" \"node_id\":\"MDk6TWlsZXN0b25lNTg4NDIwOA==\",\n" +
" \"number\":191,\n" +
" \"title\":\"5.4.3\",\n" +
" \"description\":\"\",\n" +
" \"creator\":{\n" +
" \"login\":\"jzheaux\",\n" +
" \"id\":3627351,\n" +
" \"node_id\":\"MDQ6VXNlcjM2MjczNTE=\",\n" +
" \"avatar_url\":\"https://avatars.githubusercontent.com/u/3627351?v=4\",\n" +
" \"gravatar_id\":\"\",\n" +
" \"url\":\"https://api.github.com/users/jzheaux\",\n" +
" \"html_url\":\"https://github.com/jzheaux\",\n" +
" \"followers_url\":\"https://api.github.com/users/jzheaux/followers\",\n" +
" \"following_url\":\"https://api.github.com/users/jzheaux/following{/other_user}\",\n" +
" \"gists_url\":\"https://api.github.com/users/jzheaux/gists{/gist_id}\",\n" +
" \"starred_url\":\"https://api.github.com/users/jzheaux/starred{/owner}{/repo}\",\n" +
" \"subscriptions_url\":\"https://api.github.com/users/jzheaux/subscriptions\",\n" +
" \"organizations_url\":\"https://api.github.com/users/jzheaux/orgs\",\n" +
" \"repos_url\":\"https://api.github.com/users/jzheaux/repos\",\n" +
" \"events_url\":\"https://api.github.com/users/jzheaux/events{/privacy}\",\n" +
" \"received_events_url\":\"https://api.github.com/users/jzheaux/received_events\",\n" +
" \"type\":\"User\",\n" +
" \"site_admin\":false\n" +
" },\n" +
" \"open_issues\":21,\n" +
" \"closed_issues\":23,\n" +
" \"state\":\"open\",\n" +
" \"created_at\":\"2020-09-16T13:28:03Z\",\n" +
" \"updated_at\":\"2021-04-06T23:47:10Z\",\n" +
" \"due_on\":\"2021-04-12T07:00:00Z\",\n" +
" \"closed_at\":null\n" +
" }\n" +
"]";
this.server.enqueue(new MockResponse().setBody(responseJson));
String nextVersion = this.github.getNextReleaseMilestone(this.repositoryRef, "5.5.0-SNAPSHOT");
RecordedRequest recordedRequest = this.server.takeRequest(1, TimeUnit.SECONDS);
assertThat(recordedRequest.getMethod()).isEqualToIgnoringCase("get");
assertThat(recordedRequest.getRequestUrl().toString()).isEqualTo(this.baseUrl + "/repos/spring-projects/spring-security/milestones?per_page=100");
assertThat(nextVersion).isEqualTo("5.5.0");
}
@Test
public void createMilestoneWhenValidParametersThenSuccess() throws Exception {
this.server.enqueue(new MockResponse().setResponseCode(204));
Milestone milestone = new Milestone();
milestone.setTitle("1.0.0");
milestone.setDueOn(LocalDate.of(2022, 5, 4).atTime(LocalTime.NOON));
this.github.createMilestone(this.repositoryRef, milestone);
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/milestones");
assertThat(recordedRequest.getBody().readString(Charset.defaultCharset()))
.isEqualTo("{\"title\":\"1.0.0\",\"due_on\":\"2022-05-04T12:00:00Z\"}");
}
@Test
public void createMilestoneWhenErrorResponseThenException() throws Exception {
this.server.enqueue(new MockResponse().setResponseCode(400));
assertThatExceptionOfType(RuntimeException.class)
.isThrownBy(() -> this.github.createMilestone(this.repositoryRef, new Milestone()));
}
}
@@ -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,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* 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.
@@ -16,15 +16,14 @@
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.Test;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.gradle.github.RepositoryRef;
@@ -35,22 +34,21 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
* @author Steve Riesenberg
*/
public class GitHubReleaseApiTests {
private GitHubReleaseApi gitHubReleaseApi;
private GitHubReleaseApi github;
private RepositoryRef repository = new RepositoryRef("spring-projects", "spring-security");
private MockWebServer server;
private String baseUrl;
private RepositoryRef repository;
@BeforeEach
public void setup() throws Exception {
this.server = new MockWebServer();
this.server.start();
this.github = new GitHubReleaseApi("mock-oauth-token");
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");
this.github.setBaseUrl(this.baseUrl);
}
@AfterEach
@@ -136,20 +134,18 @@ public class GitHubReleaseApiTests {
" ]\n" +
"}";
this.server.enqueue(new MockResponse().setBody(responseJson));
this.gitHubReleaseApi.publishRelease(this.repository, Release.tag("1.0.0").build());
this.github.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}");
assertThat(recordedRequest.getRequestUrl().toString()).isEqualTo(this.baseUrl + "/repos/spring-projects/spring-security/releases");
assertThat(recordedRequest.getBody().toString()).isEqualTo("{\"tag_name\":\"1.0.0\"}");
}
@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()));
.isThrownBy(() -> this.github.publishRelease(this.repository, Release.tag("1.0.0").build()));
}
}
@@ -9,6 +9,6 @@ repositories {
}
dependencies {
optional 'jakarta.servlet:jakarta.servlet-api:3.1.0'
optional 'javax.servlet:javax.servlet-api:3.1.0'
testCompile 'junit:junit:4.12'
}
+2 -5
View File
@@ -4,10 +4,7 @@ dependencies {
management platform(project(":spring-security-dependencies"))
api project(':spring-security-core')
api project(':spring-security-web')
api('org.jasig.cas.client:cas-client-core') {
exclude group: 'org.glassfish.jaxb', module: 'jaxb-core'
exclude group: 'javax.xml.bind', module: 'jaxb-api'
}
api 'org.jasig.cas.client:cas-client-core'
api 'org.springframework:spring-beans'
api 'org.springframework:spring-context'
api 'org.springframework:spring-core'
@@ -16,7 +13,7 @@ dependencies {
optional 'com.fasterxml.jackson.core:jackson-databind'
optional 'net.sf.ehcache:ehcache'
provided 'jakarta.servlet:jakarta.servlet-api'
provided 'javax.servlet:javax.servlet-api'
testImplementation "org.assertj:assertj-core"
testImplementation "org.junit.jupiter:junit-jupiter-api"
+6 -13
View File
@@ -37,9 +37,9 @@ dependencies {
optional'org.springframework:spring-websocket'
optional 'org.jetbrains.kotlin:kotlin-reflect'
optional 'org.jetbrains.kotlin:kotlin-stdlib-jdk8'
optional 'jakarta.annotation:jakarta.annotation-api'
optional 'javax.annotation:jsr250-api'
provided 'jakarta.servlet:jakarta.servlet-api'
provided 'javax.servlet:javax.servlet-api'
testImplementation project(':spring-security-aspects')
testImplementation project(':spring-security-cas')
@@ -62,10 +62,8 @@ dependencies {
testImplementation 'ch.qos.logback:logback-classic'
testImplementation 'io.projectreactor.netty:reactor-netty'
testImplementation 'io.rsocket:rsocket-transport-netty'
testImplementation 'jakarta.annotation:jakarta.annotation-api:1.0'
testImplementation "jakarta.inject:jakarta.inject-api"
testImplementation "jakarta.transaction:jakarta.transaction-api"
testImplementation 'jakarta.xml.bind:jakarta.xml.bind-api'
testImplementation 'javax.annotation:jsr250-api:1.0'
testImplementation 'javax.xml.bind:jaxb-api'
testImplementation 'ldapsdk:ldapsdk:4.1'
testImplementation('net.sourceforge.htmlunit:htmlunit') {
exclude group: 'commons-logging', module: 'commons-logging'
@@ -77,18 +75,12 @@ dependencies {
testImplementation "org.apache.directory.server:apacheds-server-jndi"
testImplementation 'org.apache.directory.shared:shared-ldap'
testImplementation 'org.eclipse.persistence:javax.persistence'
testImplementation('org.hibernate:hibernate-entitymanager') {
exclude group: 'javax.activation', module: 'javax.activation-api'
exclude group: 'javax.persistence', module: 'javax.persistence-api'
exclude group: 'javax.xml.bind', module: 'jaxb-api'
exclude group: 'org.jboss.spec.javax.transaction', module: 'jboss-transaction-api_1.2_spec'
}
testImplementation 'org.hibernate:hibernate-entitymanager'
testImplementation 'org.hsqldb:hsqldb'
testImplementation 'org.mockito:mockito-core'
testImplementation "org.mockito:mockito-inline"
testImplementation ('org.openid4java:openid4java-nodeps') {
exclude group: 'com.google.code.guice', module: 'guice'
exclude group: 'commons-logging', module: 'commons-logging'
}
testImplementation('org.seleniumhq.selenium:htmlunit-driver') {
exclude group: 'commons-logging', module: 'commons-logging'
@@ -97,6 +89,7 @@ dependencies {
exclude group: 'commons-logging', module: 'commons-logging'
exclude group: 'io.netty', module: 'netty'
}
testImplementation 'org.slf4j:jcl-over-slf4j'
testImplementation 'org.springframework.ldap:spring-ldap-core'
testImplementation 'org.springframework:spring-expression'
testImplementation 'org.springframework:spring-jdbc'
@@ -65,7 +65,7 @@ public class NamespaceLdapAuthenticationProviderTestsConfigs {
.ldif("classpath:users.xldif") // ldap-server@ldif
.managerDn("uid=admin,ou=system") // ldap-server@manager-dn
.managerPassword("secret") // ldap-server@manager-password
.port(0) // ldap-server@port
.port(33399) // ldap-server@port
.root("dc=springframework,dc=org"); // ldap-server@root
// .url("ldap://localhost:33389/dc-springframework,dc=org") this overrides root and port and is used for external
// @formatter:on
@@ -94,7 +94,7 @@ public final class SecurityNamespaceHandler implements NamespaceHandler {
if (!namespaceMatchesVersion(element)) {
pc.getReaderContext().fatal("You cannot use a spring-security-2.0.xsd or spring-security-3.0.xsd or "
+ "spring-security-3.1.xsd schema or spring-security-3.2.xsd schema or spring-security-4.0.xsd schema "
+ "with Spring Security 5.6. Please update your schema declarations to the 5.6 schema.", element);
+ "with Spring Security 5.7. Please update your schema declarations to the 5.7 schema.", element);
}
String name = pc.getDelegate().getLocalName(element);
BeanDefinitionParser parser = this.parsers.get(name);
@@ -215,7 +215,7 @@ public final class SecurityNamespaceHandler implements NamespaceHandler {
private boolean matchesVersionInternal(Element element) {
String schemaLocation = element.getAttributeNS("http://www.w3.org/2001/XMLSchema-instance", "schemaLocation");
return schemaLocation.matches("(?m).*spring-security-5\\.6.*.xsd.*")
return schemaLocation.matches("(?m).*spring-security-5\\.7.*.xsd.*")
|| schemaLocation.matches("(?m).*spring-security.xsd.*")
|| !schemaLocation.matches("(?m).*spring-security.*");
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -39,7 +39,6 @@ import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.core.log.LogMessage;
import org.springframework.security.authentication.AuthenticationEventPublisher;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.DefaultAuthenticationEventPublisher;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.authentication.configurers.provisioning.InMemoryUserDetailsManagerConfigurer;
@@ -80,7 +79,8 @@ public class AuthenticationConfiguration {
public AuthenticationManagerBuilder authenticationManagerBuilder(ObjectPostProcessor<Object> objectPostProcessor,
ApplicationContext context) {
LazyPasswordEncoder defaultPasswordEncoder = new LazyPasswordEncoder(context);
AuthenticationEventPublisher authenticationEventPublisher = getAuthenticationEventPublisher(context);
AuthenticationEventPublisher authenticationEventPublisher = getBeanOrNull(context,
AuthenticationEventPublisher.class);
DefaultPasswordEncoderAuthenticationManagerBuilder result = new DefaultPasswordEncoderAuthenticationManagerBuilder(
objectPostProcessor, defaultPasswordEncoder);
if (authenticationEventPublisher != null) {
@@ -142,13 +142,6 @@ public class AuthenticationConfiguration {
this.objectPostProcessor = objectPostProcessor;
}
private AuthenticationEventPublisher getAuthenticationEventPublisher(ApplicationContext context) {
if (context.getBeanNamesForType(AuthenticationEventPublisher.class).length > 0) {
return context.getBean(AuthenticationEventPublisher.class);
}
return this.objectPostProcessor.postProcess(new DefaultAuthenticationEventPublisher());
}
@SuppressWarnings("unchecked")
private <T> T lazyBean(Class<T> interfaceName) {
LazyInitTargetSource lazyTargetSource = new LazyInitTargetSource();
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* 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.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* 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.
@@ -2889,8 +2889,15 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<Defaul
}
}
@SuppressWarnings("unchecked")
@Override
protected DefaultSecurityFilterChain performBuild() {
ExpressionUrlAuthorizationConfigurer<?> expressionConfigurer = getConfigurer(
ExpressionUrlAuthorizationConfigurer.class);
AuthorizeHttpRequestsConfigurer<?> httpConfigurer = getConfigurer(AuthorizeHttpRequestsConfigurer.class);
boolean oneConfigurerPresent = expressionConfigurer == null ^ httpConfigurer == null;
Assert.state((expressionConfigurer == null && httpConfigurer == null) || oneConfigurerPresent,
"authorizeHttpRequests cannot be used in conjunction with authorizeRequests. Please select just one.");
this.filters.sort(OrderComparator.INSTANCE);
List<Filter> sortedFilters = new ArrayList<>(this.filters.size());
for (Filter filter : this.filters) {
@@ -3283,26 +3290,20 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<Defaul
*/
public final class MvcMatchersRequestMatcherConfigurer extends RequestMatcherConfigurer {
private final List<MvcRequestMatcher> mvcMatchers;
/**
* Creates a new instance
* @param context the {@link ApplicationContext} to use
* @param mvcMatchers the {@link MvcRequestMatcher} instances to set the servlet
* path on if {@link #servletPath(String)} is set.
* @param allMatchers the {@link RequestMatcher} instances to continue the
* configuration
* @param matchers the {@link MvcRequestMatcher} instances to set the servlet path
* on if {@link #servletPath(String)} is set.
*/
private MvcMatchersRequestMatcherConfigurer(ApplicationContext context, List<MvcRequestMatcher> mvcMatchers,
List<RequestMatcher> allMatchers) {
private MvcMatchersRequestMatcherConfigurer(ApplicationContext context, List<MvcRequestMatcher> matchers) {
super(context);
this.mvcMatchers = new ArrayList<>(mvcMatchers);
this.matchers = allMatchers;
this.matchers = new ArrayList<>(matchers);
}
public RequestMatcherConfigurer servletPath(String servletPath) {
for (MvcRequestMatcher matcher : this.mvcMatchers) {
matcher.setServletPath(servletPath);
for (RequestMatcher matcher : this.matchers) {
((MvcRequestMatcher) matcher).setServletPath(servletPath);
}
return this;
}
@@ -3327,7 +3328,7 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<Defaul
public MvcMatchersRequestMatcherConfigurer mvcMatchers(HttpMethod method, String... mvcPatterns) {
List<MvcRequestMatcher> mvcMatchers = createMvcMatchers(method, mvcPatterns);
setMatchers(mvcMatchers);
return new MvcMatchersRequestMatcherConfigurer(getContext(), mvcMatchers, this.matchers);
return new MvcMatchersRequestMatcherConfigurer(getContext(), mvcMatchers);
}
@Override
@@ -264,7 +264,6 @@ public final class WebSecurity extends AbstractConfiguredSecurityBuilder<Filter,
* @deprecated Use {@link #privilegeEvaluator(WebInvocationPrivilegeEvaluator)}
* instead
*/
@Deprecated
public WebSecurity securityInterceptor(FilterSecurityInterceptor securityInterceptor) {
this.filterSecurityInterceptor = securityInterceptor;
return this;
@@ -292,8 +291,6 @@ public final class WebSecurity extends AbstractConfiguredSecurityBuilder<Filter,
List<SecurityFilterChain> securityFilterChains = new ArrayList<>(chainSize);
List<RequestMatcherEntry<List<WebInvocationPrivilegeEvaluator>>> requestMatcherPrivilegeEvaluatorsEntries = new ArrayList<>();
for (RequestMatcher ignoredRequest : this.ignoredRequests) {
WebSecurity.this.logger.warn("You are asking Spring Security to ignore " + ignoredRequest
+ ". This is not recommended -- please use permitAll via HttpSecurity#authorizeHttpRequests instead.");
SecurityFilterChain securityFilterChain = new DefaultSecurityFilterChain(ignoredRequest);
securityFilterChains.add(securityFilterChain);
requestMatcherPrivilegeEvaluatorsEntries
@@ -345,10 +342,7 @@ public final class WebSecurity extends AbstractConfiguredSecurityBuilder<Filter,
if (filter instanceof AuthorizationFilter) {
AuthorizationManager<HttpServletRequest> authorizationManager = ((AuthorizationFilter) filter)
.getAuthorizationManager();
AuthorizationManagerWebInvocationPrivilegeEvaluator evaluator = new AuthorizationManagerWebInvocationPrivilegeEvaluator(
authorizationManager);
evaluator.setServletContext(this.servletContext);
privilegeEvaluators.add(evaluator);
privilegeEvaluators.add(new AuthorizationManagerWebInvocationPrivilegeEvaluator(authorizationManager));
}
}
return new RequestMatcherEntry<>(securityFilterChain::matches, privilegeEvaluators);
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -24,9 +24,7 @@ import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
import org.springframework.security.authentication.AuthenticationEventPublisher;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.DefaultAuthenticationEventPublisher;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
@@ -84,7 +82,6 @@ class HttpSecurityConfiguration {
AuthenticationManagerBuilder authenticationBuilder = new WebSecurityConfigurerAdapter.DefaultPasswordEncoderAuthenticationManagerBuilder(
this.objectPostProcessor, passwordEncoder);
authenticationBuilder.parentAuthenticationManager(authenticationManager());
authenticationBuilder.authenticationEventPublisher(getAuthenticationEventPublisher());
HttpSecurity http = new HttpSecurity(this.objectPostProcessor, authenticationBuilder, createSharedObjects());
// @formatter:off
http
@@ -108,13 +105,6 @@ class HttpSecurityConfiguration {
: this.authenticationConfiguration.getAuthenticationManager();
}
private AuthenticationEventPublisher getAuthenticationEventPublisher() {
if (this.context.getBeanNamesForType(AuthenticationEventPublisher.class).length > 0) {
return this.context.getBean(AuthenticationEventPublisher.class);
}
return this.objectPostProcessor.postProcess(new DefaultAuthenticationEventPublisher());
}
private Map<Class<?>, Object> createSharedObjects() {
Map<Class<?>, Object> sharedObjects = new HashMap<>();
sharedObjects.put(ApplicationContext.class, this.context);
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* 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.
@@ -16,10 +16,14 @@
package org.springframework.security.config.annotation.web.configuration;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.function.Supplier;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -67,17 +71,22 @@ class SecurityReactorContextConfiguration {
private static final String SECURITY_REACTOR_CONTEXT_OPERATOR_KEY = "org.springframework.security.SECURITY_REACTOR_CONTEXT_OPERATOR";
private static final Map<Object, Supplier<Object>> CONTEXT_ATTRIBUTE_VALUE_LOADERS = new HashMap<>();
static {
CONTEXT_ATTRIBUTE_VALUE_LOADERS.put(HttpServletRequest.class,
SecurityReactorContextSubscriberRegistrar::getRequest);
CONTEXT_ATTRIBUTE_VALUE_LOADERS.put(HttpServletResponse.class,
SecurityReactorContextSubscriberRegistrar::getResponse);
CONTEXT_ATTRIBUTE_VALUE_LOADERS.put(Authentication.class,
SecurityReactorContextSubscriberRegistrar::getAuthentication);
}
@Override
public void afterPropertiesSet() throws Exception {
Function<? super Publisher<Object>, ? extends Publisher<Object>> lifter = Operators
.liftPublisher((pub, sub) -> createSubscriberIfNecessary(sub));
Hooks.onLastOperator(SECURITY_REACTOR_CONTEXT_OPERATOR_KEY, (pub) -> {
if (!contextAttributesAvailable()) {
// No need to decorate so return original Publisher
return pub;
}
return lifter.apply(pub);
});
Hooks.onLastOperator(SECURITY_REACTOR_CONTEXT_OPERATOR_KEY, lifter::apply);
}
@Override
@@ -93,36 +102,30 @@ class SecurityReactorContextConfiguration {
return new SecurityReactorContextSubscriber<>(delegate, getContextAttributes());
}
private static boolean contextAttributesAvailable() {
return SecurityContextHolder.getContext().getAuthentication() != null
|| RequestContextHolder.getRequestAttributes() instanceof ServletRequestAttributes;
private static Map<Object, Object> getContextAttributes() {
return new LoadingMap<>(CONTEXT_ATTRIBUTE_VALUE_LOADERS);
}
private static Map<Object, Object> getContextAttributes() {
HttpServletRequest servletRequest = null;
HttpServletResponse servletResponse = null;
private static HttpServletRequest getRequest() {
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
if (requestAttributes instanceof ServletRequestAttributes) {
ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) requestAttributes;
servletRequest = servletRequestAttributes.getRequest();
servletResponse = servletRequestAttributes.getResponse(); // possible null
}
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null && servletRequest == null) {
return Collections.emptyMap();
}
Map<Object, Object> contextAttributes = new HashMap<>();
if (servletRequest != null) {
contextAttributes.put(HttpServletRequest.class, servletRequest);
}
if (servletResponse != null) {
contextAttributes.put(HttpServletResponse.class, servletResponse);
}
if (authentication != null) {
contextAttributes.put(Authentication.class, authentication);
return servletRequestAttributes.getRequest();
}
return null;
}
return contextAttributes;
private static HttpServletResponse getResponse() {
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
if (requestAttributes instanceof ServletRequestAttributes) {
ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) requestAttributes;
return servletRequestAttributes.getResponse(); // possible null
}
return null;
}
private static Authentication getAuthentication() {
return SecurityContextHolder.getContext().getAuthentication();
}
}
@@ -175,4 +178,112 @@ class SecurityReactorContextConfiguration {
}
/**
* A map that computes each value when {@link #get} is invoked
*/
static class LoadingMap<K, V> implements Map<K, V> {
private final Map<K, V> loaded = new ConcurrentHashMap<>();
private final Map<K, Supplier<V>> loaders;
LoadingMap(Map<K, Supplier<V>> loaders) {
this.loaders = Collections.unmodifiableMap(new HashMap<>(loaders));
}
@Override
public int size() {
return this.loaders.size();
}
@Override
public boolean isEmpty() {
return this.loaders.isEmpty();
}
@Override
public boolean containsKey(Object key) {
return this.loaders.containsKey(key);
}
@Override
public Set<K> keySet() {
return this.loaders.keySet();
}
@Override
public V get(Object key) {
if (!this.loaders.containsKey(key)) {
throw new IllegalArgumentException(
"This map only supports the following keys: " + this.loaders.keySet());
}
return this.loaded.computeIfAbsent((K) key, (k) -> this.loaders.get(k).get());
}
@Override
public V put(K key, V value) {
if (!this.loaders.containsKey(key)) {
throw new IllegalArgumentException(
"This map only supports the following keys: " + this.loaders.keySet());
}
return this.loaded.put(key, value);
}
@Override
public V remove(Object key) {
if (!this.loaders.containsKey(key)) {
throw new IllegalArgumentException(
"This map only supports the following keys: " + this.loaders.keySet());
}
return this.loaded.remove(key);
}
@Override
public void putAll(Map<? extends K, ? extends V> m) {
for (Map.Entry<? extends K, ? extends V> entry : m.entrySet()) {
put(entry.getKey(), entry.getValue());
}
}
@Override
public void clear() {
this.loaded.clear();
}
@Override
public boolean containsValue(Object value) {
return this.loaded.containsValue(value);
}
@Override
public Collection<V> values() {
return this.loaded.values();
}
@Override
public Set<Entry<K, V>> entrySet() {
return this.loaded.entrySet();
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
LoadingMap<?, ?> that = (LoadingMap<?, ?>) o;
return this.loaded.equals(that.loaded);
}
@Override
public int hashCode() {
return this.loaded.hashCode();
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* 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.
@@ -24,7 +24,6 @@ import javax.servlet.Filter;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.annotation.Bean;
@@ -143,19 +142,20 @@ public class WebSecurityConfiguration implements ImportAware, BeanClassLoaderAwa
* instances used to create the web configuration.
* @param objectPostProcessor the {@link ObjectPostProcessor} used to create a
* {@link WebSecurity} instance
* @param webSecurityConfigurers the
* @param beanFactory the bean factory to use to retrieve the relevant
* {@code <SecurityConfigurer<FilterChainProxy, WebSecurityBuilder>} instances used to
* create the web configuration
* @throws Exception
*/
@Autowired(required = false)
public void setFilterChainProxySecurityConfigurer(ObjectPostProcessor<Object> objectPostProcessor,
@Value("#{@autowiredWebSecurityConfigurersIgnoreParents.getWebSecurityConfigurers()}") List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers)
throws Exception {
ConfigurableListableBeanFactory beanFactory) throws Exception {
this.webSecurity = objectPostProcessor.postProcess(new WebSecurity(objectPostProcessor));
if (this.debugEnabled != null) {
this.webSecurity.debug(this.debugEnabled);
}
List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers = new AutowiredWebSecurityConfigurersIgnoreParents(
beanFactory).getWebSecurityConfigurers();
webSecurityConfigurers.sort(AnnotationAwareOrderComparator.INSTANCE);
Integer previousOrder = null;
Object previousConfig = null;
@@ -189,12 +189,6 @@ public class WebSecurityConfiguration implements ImportAware, BeanClassLoaderAwa
return new RsaKeyConversionServicePostProcessor();
}
@Bean
public static AutowiredWebSecurityConfigurersIgnoreParents autowiredWebSecurityConfigurersIgnoreParents(
ConfigurableListableBeanFactory beanFactory) {
return new AutowiredWebSecurityConfigurersIgnoreParents(beanFactory);
}
@Override
public void setImportMetadata(AnnotationMetadata importMetadata) {
Map<String, Object> enableWebSecurityAttrMap = importMetadata
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* 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.
@@ -16,6 +16,7 @@
package org.springframework.security.config.annotation.web.configurers;
import java.util.LinkedHashMap;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
@@ -46,6 +47,9 @@ import org.springframework.util.Assert;
public final class AuthorizeHttpRequestsConfigurer<H extends HttpSecurityBuilder<H>>
extends AbstractHttpConfigurer<AuthorizeHttpRequestsConfigurer<H>, H> {
static final AuthorizationManager<RequestAuthorizationContext> permitAllAuthorizationManager = (a,
o) -> new AuthorizationDecision(true);
private final AuthorizationManagerRequestMatcherRegistry registry;
/**
@@ -81,6 +85,12 @@ public final class AuthorizeHttpRequestsConfigurer<H extends HttpSecurityBuilder
return this.registry;
}
AuthorizationManagerRequestMatcherRegistry addFirst(RequestMatcher matcher,
AuthorizationManager<RequestAuthorizationContext> manager) {
this.registry.addFirst(matcher, manager);
return this.registry;
}
/**
* Registry for mapping a {@link RequestMatcher} to an {@link AuthorizationManager}.
*
@@ -106,6 +116,19 @@ public final class AuthorizeHttpRequestsConfigurer<H extends HttpSecurityBuilder
this.mappingCount++;
}
private void addFirst(RequestMatcher matcher, AuthorizationManager<RequestAuthorizationContext> manager) {
this.unmappedMatchers = null;
this.managerBuilder.mappings((m) -> {
LinkedHashMap<RequestMatcher, AuthorizationManager<RequestAuthorizationContext>> reorderedMap = new LinkedHashMap<>(
m.size() + 1);
reorderedMap.put(matcher, manager);
reorderedMap.putAll(m);
m.clear();
m.putAll(reorderedMap);
});
this.mappingCount++;
}
private AuthorizationManager<HttpServletRequest> createAuthorizationManager() {
Assert.state(this.unmappedMatchers == null,
() -> "An incomplete mapping was found for " + this.unmappedMatchers
@@ -209,7 +232,7 @@ public final class AuthorizeHttpRequestsConfigurer<H extends HttpSecurityBuilder
* customizations
*/
public AuthorizationManagerRequestMatcherRegistry permitAll() {
return access((a, o) -> new AuthorizationDecision(true));
return access(permitAllAuthorizationManager);
}
/**
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 the original author or authors.
* 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.
@@ -30,7 +30,9 @@ import org.springframework.security.config.annotation.SecurityBuilder;
import org.springframework.security.config.annotation.SecurityConfigurer;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.DefaultRedirectStrategy;
import org.springframework.security.web.PortMapper;
import org.springframework.security.web.RedirectStrategy;
import org.springframework.security.web.access.channel.ChannelDecisionManagerImpl;
import org.springframework.security.web.access.channel.ChannelProcessingFilter;
import org.springframework.security.web.access.channel.ChannelProcessor;
@@ -75,6 +77,7 @@ import org.springframework.security.web.util.matcher.RequestMatcher;
*
* @param <H> the type of {@link HttpSecurityBuilder} that is being configured
* @author Rob Winch
* @author Onur Kagan Ozcan
* @since 3.2
*/
public final class ChannelSecurityConfigurer<H extends HttpSecurityBuilder<H>>
@@ -86,6 +89,8 @@ public final class ChannelSecurityConfigurer<H extends HttpSecurityBuilder<H>>
private List<ChannelProcessor> channelProcessors;
private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
private final ChannelRequestMatcherRegistry REGISTRY;
/**
@@ -123,9 +128,11 @@ public final class ChannelSecurityConfigurer<H extends HttpSecurityBuilder<H>>
if (portMapper != null) {
RetryWithHttpEntryPoint httpEntryPoint = new RetryWithHttpEntryPoint();
httpEntryPoint.setPortMapper(portMapper);
httpEntryPoint.setRedirectStrategy(this.redirectStrategy);
insecureChannelProcessor.setEntryPoint(httpEntryPoint);
RetryWithHttpsEntryPoint httpsEntryPoint = new RetryWithHttpsEntryPoint();
httpsEntryPoint.setPortMapper(portMapper);
httpsEntryPoint.setRedirectStrategy(this.redirectStrategy);
secureChannelProcessor.setEntryPoint(httpsEntryPoint);
}
insecureChannelProcessor = postProcess(insecureChannelProcessor);
@@ -185,6 +192,17 @@ public final class ChannelSecurityConfigurer<H extends HttpSecurityBuilder<H>>
return this;
}
/**
* Sets the {@link RedirectStrategy} instances to use in
* {@link RetryWithHttpEntryPoint} and {@link RetryWithHttpsEntryPoint}
* @param redirectStrategy
* @return the {@link ChannelSecurityConfigurer} for further customizations
*/
public ChannelRequestMatcherRegistry redirectStrategy(RedirectStrategy redirectStrategy) {
ChannelSecurityConfigurer.this.redirectStrategy = redirectStrategy;
return this;
}
/**
* Return the {@link SecurityBuilder} when done using the
* {@link SecurityConfigurer}. This is useful for method chaining.
@@ -237,8 +237,8 @@ public final class CsrfConfigurer<H extends HttpSecurityBuilder<H>>
/**
* Gets the default {@link AccessDeniedHandler} from the
* {@link ExceptionHandlingConfigurer#getAccessDeniedHandler()} or create a
* {@link AccessDeniedHandlerImpl} if not available.
* {@link ExceptionHandlingConfigurer#getAccessDeniedHandler(HttpSecurityBuilder)} or
* create a {@link AccessDeniedHandlerImpl} if not available.
* @param http the {@link HttpSecurityBuilder}
* @return the {@link AccessDeniedHandler}
*/
@@ -247,7 +247,7 @@ public final class CsrfConfigurer<H extends HttpSecurityBuilder<H>>
ExceptionHandlingConfigurer<H> exceptionConfig = http.getConfigurer(ExceptionHandlingConfigurer.class);
AccessDeniedHandler handler = null;
if (exceptionConfig != null) {
handler = exceptionConfig.getAccessDeniedHandler();
handler = exceptionConfig.getAccessDeniedHandler(http);
}
if (handler == null) {
handler = new AccessDeniedHandlerImpl();
@@ -369,9 +369,7 @@ public final class ExpressionUrlAuthorizationConfigurer<H extends HttpSecurityBu
}
/**
* Specify that URLs requires a specific IP Address or <a href=
* "https://forum.spring.io/showthread.php?102783-How-to-use-hasIpAddress&p=343971#post343971"
* >subnet</a>.
* Specify that URLs requires a specific IP Address or subnet.
* @param ipaddressExpression the ipaddress (i.e. 192.168.1.79) or local subnet
* (i.e. 192.168.0/24)
* @return the {@link ExpressionUrlAuthorizationConfigurer} for further
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* 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.
@@ -31,6 +31,9 @@ import org.springframework.security.web.header.HeaderWriter;
import org.springframework.security.web.header.HeaderWriterFilter;
import org.springframework.security.web.header.writers.CacheControlHeadersWriter;
import org.springframework.security.web.header.writers.ContentSecurityPolicyHeaderWriter;
import org.springframework.security.web.header.writers.CrossOriginEmbedderPolicyHeaderWriter;
import org.springframework.security.web.header.writers.CrossOriginOpenerPolicyHeaderWriter;
import org.springframework.security.web.header.writers.CrossOriginResourcePolicyHeaderWriter;
import org.springframework.security.web.header.writers.FeaturePolicyHeaderWriter;
import org.springframework.security.web.header.writers.HpkpHeaderWriter;
import org.springframework.security.web.header.writers.HstsHeaderWriter;
@@ -97,6 +100,12 @@ public class HeadersConfigurer<H extends HttpSecurityBuilder<H>>
private final PermissionsPolicyConfig permissionsPolicy = new PermissionsPolicyConfig();
private final CrossOriginOpenerPolicyConfig crossOriginOpenerPolicy = new CrossOriginOpenerPolicyConfig();
private final CrossOriginEmbedderPolicyConfig crossOriginEmbedderPolicy = new CrossOriginEmbedderPolicyConfig();
private final CrossOriginResourcePolicyConfig crossOriginResourcePolicy = new CrossOriginResourcePolicyConfig();
/**
* Creates a new instance
*
@@ -392,6 +401,9 @@ public class HeadersConfigurer<H extends HttpSecurityBuilder<H>>
addIfNotNull(writers, this.referrerPolicy.writer);
addIfNotNull(writers, this.featurePolicy.writer);
addIfNotNull(writers, this.permissionsPolicy.writer);
addIfNotNull(writers, this.crossOriginOpenerPolicy.writer);
addIfNotNull(writers, this.crossOriginEmbedderPolicy.writer);
addIfNotNull(writers, this.crossOriginResourcePolicy.writer);
writers.addAll(this.headerWriters);
return writers;
}
@@ -544,6 +556,129 @@ public class HeadersConfigurer<H extends HttpSecurityBuilder<H>>
return this.permissionsPolicy;
}
/**
* Allows configuration for <a href=
* "https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Opener-Policy">
* Cross-Origin-Opener-Policy</a> header.
* <p>
* Configuration is provided to the {@link CrossOriginOpenerPolicyHeaderWriter} which
* responsible for writing the header.
* </p>
* @return the {@link CrossOriginOpenerPolicyConfig} for additional confniguration
* @since 5.7
* @see CrossOriginOpenerPolicyHeaderWriter
*/
public CrossOriginOpenerPolicyConfig crossOriginOpenerPolicy() {
this.crossOriginOpenerPolicy.writer = new CrossOriginOpenerPolicyHeaderWriter();
return this.crossOriginOpenerPolicy;
}
/**
* Allows configuration for <a href=
* "https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Opener-Policy">
* Cross-Origin-Opener-Policy</a> header.
* <p>
* Calling this method automatically enables (includes) the
* {@code Cross-Origin-Opener-Policy} header in the response using the supplied
* policy.
* <p>
* <p>
* Configuration is provided to the {@link CrossOriginOpenerPolicyHeaderWriter} which
* responsible for writing the header.
* </p>
* @return the {@link HeadersConfigurer} for additional customizations
* @since 5.7
* @see CrossOriginOpenerPolicyHeaderWriter
*/
public HeadersConfigurer<H> crossOriginOpenerPolicy(
Customizer<CrossOriginOpenerPolicyConfig> crossOriginOpenerPolicyCustomizer) {
this.crossOriginOpenerPolicy.writer = new CrossOriginOpenerPolicyHeaderWriter();
crossOriginOpenerPolicyCustomizer.customize(this.crossOriginOpenerPolicy);
return HeadersConfigurer.this;
}
/**
* Allows configuration for <a href=
* "https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Embedder-Policy">
* Cross-Origin-Embedder-Policy</a> header.
* <p>
* Configuration is provided to the {@link CrossOriginEmbedderPolicyHeaderWriter}
* which is responsible for writing the header.
* </p>
* @return the {@link CrossOriginEmbedderPolicyConfig} for additional customizations
* @since 5.7
* @see CrossOriginEmbedderPolicyHeaderWriter
*/
public CrossOriginEmbedderPolicyConfig crossOriginEmbedderPolicy() {
this.crossOriginEmbedderPolicy.writer = new CrossOriginEmbedderPolicyHeaderWriter();
return this.crossOriginEmbedderPolicy;
}
/**
* Allows configuration for <a href=
* "https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Embedder-Policy">
* Cross-Origin-Embedder-Policy</a> header.
* <p>
* Calling this method automatically enables (includes) the
* {@code Cross-Origin-Embedder-Policy} header in the response using the supplied
* policy.
* <p>
* <p>
* Configuration is provided to the {@link CrossOriginEmbedderPolicyHeaderWriter}
* which is responsible for writing the header.
* </p>
* @return the {@link HeadersConfigurer} for additional customizations
* @since 5.7
* @see CrossOriginEmbedderPolicyHeaderWriter
*/
public HeadersConfigurer<H> crossOriginEmbedderPolicy(
Customizer<CrossOriginEmbedderPolicyConfig> crossOriginEmbedderPolicyCustomizer) {
this.crossOriginEmbedderPolicy.writer = new CrossOriginEmbedderPolicyHeaderWriter();
crossOriginEmbedderPolicyCustomizer.customize(this.crossOriginEmbedderPolicy);
return HeadersConfigurer.this;
}
/**
* Allows configuration for <a href=
* "https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Resource-Policy">
* Cross-Origin-Resource-Policy</a> header.
* <p>
* Configuration is provided to the {@link CrossOriginResourcePolicyHeaderWriter}
* which is responsible for writing the header:
* </p>
* @return the {@link HeadersConfigurer} for additional customizations
* @since 5.7
* @see CrossOriginResourcePolicyHeaderWriter
*/
public CrossOriginResourcePolicyConfig crossOriginResourcePolicy() {
this.crossOriginResourcePolicy.writer = new CrossOriginResourcePolicyHeaderWriter();
return this.crossOriginResourcePolicy;
}
/**
* Allows configuration for <a href=
* "https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Resource-Policy">
* Cross-Origin-Resource-Policy</a> header.
* <p>
* Calling this method automatically enables (includes) the
* {@code Cross-Origin-Resource-Policy} header in the response using the supplied
* policy.
* <p>
* <p>
* Configuration is provided to the {@link CrossOriginResourcePolicyHeaderWriter}
* which is responsible for writing the header:
* </p>
* @return the {@link HeadersConfigurer} for additional customizations
* @since 5.7
* @see CrossOriginResourcePolicyHeaderWriter
*/
public HeadersConfigurer<H> crossOriginResourcePolicy(
Customizer<CrossOriginResourcePolicyConfig> crossOriginResourcePolicyCustomizer) {
this.crossOriginResourcePolicy.writer = new CrossOriginResourcePolicyHeaderWriter();
crossOriginResourcePolicyCustomizer.customize(this.crossOriginResourcePolicy);
return HeadersConfigurer.this;
}
public final class ContentTypeOptionsConfig {
private XContentTypeOptionsHeaderWriter writer;
@@ -1142,4 +1277,96 @@ public class HeadersConfigurer<H extends HttpSecurityBuilder<H>>
}
public final class CrossOriginOpenerPolicyConfig {
private CrossOriginOpenerPolicyHeaderWriter writer;
public CrossOriginOpenerPolicyConfig() {
}
/**
* Sets the policy to be used in the {@code Cross-Origin-Opener-Policy} header
* @param openerPolicy a {@code Cross-Origin-Opener-Policy}
* @return the {@link CrossOriginOpenerPolicyConfig} for additional configuration
* @throws IllegalArgumentException if openerPolicy is null
*/
public CrossOriginOpenerPolicyConfig policy(
CrossOriginOpenerPolicyHeaderWriter.CrossOriginOpenerPolicy openerPolicy) {
this.writer.setPolicy(openerPolicy);
return this;
}
/**
* Allows completing configuration of Cross Origin Opener Policy and continuing
* configuration of headers.
* @return the {@link HeadersConfigurer} for additional configuration
*/
public HeadersConfigurer<H> and() {
return HeadersConfigurer.this;
}
}
public final class CrossOriginEmbedderPolicyConfig {
private CrossOriginEmbedderPolicyHeaderWriter writer;
public CrossOriginEmbedderPolicyConfig() {
}
/**
* Sets the policy to be used in the {@code Cross-Origin-Embedder-Policy} header
* @param embedderPolicy a {@code Cross-Origin-Embedder-Policy}
* @return the {@link CrossOriginEmbedderPolicyConfig} for additional
* configuration
* @throws IllegalArgumentException if embedderPolicy is null
*/
public CrossOriginEmbedderPolicyConfig policy(
CrossOriginEmbedderPolicyHeaderWriter.CrossOriginEmbedderPolicy embedderPolicy) {
this.writer.setPolicy(embedderPolicy);
return this;
}
/**
* Allows completing configuration of Cross-Origin-Embedder-Policy and continuing
* configuration of headers.
* @return the {@link HeadersConfigurer} for additional configuration
*/
public HeadersConfigurer<H> and() {
return HeadersConfigurer.this;
}
}
public final class CrossOriginResourcePolicyConfig {
private CrossOriginResourcePolicyHeaderWriter writer;
public CrossOriginResourcePolicyConfig() {
}
/**
* Sets the policy to be used in the {@code Cross-Origin-Resource-Policy} header
* @param resourcePolicy a {@code Cross-Origin-Resource-Policy}
* @return the {@link CrossOriginResourcePolicyConfig} for additional
* configuration
* @throws IllegalArgumentException if resourcePolicy is null
*/
public CrossOriginResourcePolicyConfig policy(
CrossOriginResourcePolicyHeaderWriter.CrossOriginResourcePolicy resourcePolicy) {
this.writer.setPolicy(resourcePolicy);
return this;
}
/**
* Allows completing configuration of Cross-Origin-Resource-Policy and continuing
* configuration of headers.
* @return the {@link HeadersConfigurer} for additional configuration
*/
public HeadersConfigurer<H> and() {
return HeadersConfigurer.this;
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 the original author or authors.
* 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.
@@ -48,11 +48,22 @@ final class PermitAllSupport {
RequestMatcher... requestMatchers) {
ExpressionUrlAuthorizationConfigurer<?> configurer = http
.getConfigurer(ExpressionUrlAuthorizationConfigurer.class);
Assert.state(configurer != null, "permitAll only works with HttpSecurity.authorizeRequests()");
AuthorizeHttpRequestsConfigurer<?> httpConfigurer = http.getConfigurer(AuthorizeHttpRequestsConfigurer.class);
boolean oneConfigurerPresent = configurer == null ^ httpConfigurer == null;
Assert.state(oneConfigurerPresent,
"permitAll only works with either HttpSecurity.authorizeRequests() or HttpSecurity.authorizeHttpRequests(). "
+ "Please define one or the other but not both.");
for (RequestMatcher matcher : requestMatchers) {
if (matcher != null) {
configurer.getRegistry().addMapping(0, new UrlMapping(matcher,
SecurityConfig.createList(ExpressionUrlAuthorizationConfigurer.permitAll)));
if (configurer != null) {
configurer.getRegistry().addMapping(0, new UrlMapping(matcher,
SecurityConfig.createList(ExpressionUrlAuthorizationConfigurer.permitAll)));
}
else {
httpConfigurer.addFirst(matcher, AuthorizeHttpRequestsConfigurer.permitAllAuthorizationManager);
}
}
}
}
@@ -201,6 +201,12 @@ public final class SessionManagementConfigurer<H extends HttpSecurityBuilder<H>>
* {@link HttpServletResponse#encodeRedirectURL(String)} or
* {@link HttpServletResponse#encodeURL(String)}, otherwise disallows HTTP sessions to
* be included in the URL. This prevents leaking information to external domains.
* <p>
* This is achieved by guarding {@link HttpServletResponse#encodeURL} and
* {@link HttpServletResponse#encodeRedirectURL} invocations. Any code that also
* overrides either of these two methods, like
* {@link org.springframework.web.servlet.resource.ResourceUrlEncodingFilter}, needs
* to come after the security filter chain or risk being skipped.
* @param enableSessionUrlRewriting true if should allow the JSESSIONID to be
* rewritten into the URLs, else false (default)
* @return the {@link SessionManagementConfigurer} for further customization
@@ -18,6 +18,9 @@ package org.springframework.security.config.annotation.web.configurers.oauth2.se
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.function.Supplier;
import javax.servlet.http.HttpServletRequest;
@@ -25,6 +28,7 @@ import javax.servlet.http.HttpServletRequest;
import org.springframework.context.ApplicationContext;
import org.springframework.core.convert.converter.Converter;
import org.springframework.http.MediaType;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationManagerResolver;
@@ -51,6 +55,9 @@ import org.springframework.security.oauth2.server.resource.web.DefaultBearerToke
import org.springframework.security.oauth2.server.resource.web.access.BearerTokenAccessDeniedHandler;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.access.AccessDeniedHandlerImpl;
import org.springframework.security.web.access.DelegatingAccessDeniedHandler;
import org.springframework.security.web.csrf.CsrfException;
import org.springframework.security.web.util.matcher.AndRequestMatcher;
import org.springframework.security.web.util.matcher.MediaTypeRequestMatcher;
import org.springframework.security.web.util.matcher.NegatedRequestMatcher;
@@ -153,12 +160,19 @@ public final class OAuth2ResourceServerConfigurer<H extends HttpSecurityBuilder<
private OpaqueTokenConfigurer opaqueTokenConfigurer;
private AccessDeniedHandler accessDeniedHandler = new BearerTokenAccessDeniedHandler();
private AccessDeniedHandler accessDeniedHandler = new DelegatingAccessDeniedHandler(
new LinkedHashMap<>(createAccessDeniedHandlers()), new BearerTokenAccessDeniedHandler());
private AuthenticationEntryPoint authenticationEntryPoint = new BearerTokenAuthenticationEntryPoint();
private BearerTokenRequestMatcher requestMatcher = new BearerTokenRequestMatcher();
private static Map<Class<? extends AccessDeniedException>, AccessDeniedHandler> createAccessDeniedHandlers() {
Map<Class<? extends AccessDeniedException>, AccessDeniedHandler> handlers = new HashMap<>();
handlers.put(CsrfException.class, new AccessDeniedHandlerImpl());
return handlers;
}
public OAuth2ResourceServerConfigurer(ApplicationContext context) {
Assert.notNull(context, "context cannot be null");
this.context = context;
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* 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.
@@ -36,6 +36,9 @@ import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.security.web.header.HeaderWriterFilter;
import org.springframework.security.web.header.writers.CacheControlHeadersWriter;
import org.springframework.security.web.header.writers.ContentSecurityPolicyHeaderWriter;
import org.springframework.security.web.header.writers.CrossOriginEmbedderPolicyHeaderWriter;
import org.springframework.security.web.header.writers.CrossOriginOpenerPolicyHeaderWriter;
import org.springframework.security.web.header.writers.CrossOriginResourcePolicyHeaderWriter;
import org.springframework.security.web.header.writers.FeaturePolicyHeaderWriter;
import org.springframework.security.web.header.writers.HpkpHeaderWriter;
import org.springframework.security.web.header.writers.HstsHeaderWriter;
@@ -122,6 +125,12 @@ public class HeadersBeanDefinitionParser implements BeanDefinitionParser {
private static final String PERMISSIONS_POLICY_ELEMENT = "permissions-policy";
private static final String CROSS_ORIGIN_OPENER_POLICY_ELEMENT = "cross-origin-opener-policy";
private static final String CROSS_ORIGIN_EMBEDDER_POLICY_ELEMENT = "cross-origin-embedder-policy";
private static final String CROSS_ORIGIN_RESOURCE_POLICY_ELEMENT = "cross-origin-resource-policy";
private static final String ALLOW_FROM = "ALLOW-FROM";
private ManagedList<BeanMetadataElement> headerWriters;
@@ -144,6 +153,9 @@ public class HeadersBeanDefinitionParser implements BeanDefinitionParser {
parseReferrerPolicyElement(element, parserContext);
parseFeaturePolicyElement(element, parserContext);
parsePermissionsPolicyElement(element, parserContext);
parseCrossOriginOpenerPolicy(disabled, element);
parseCrossOriginEmbedderPolicy(disabled, element);
parseCrossOriginResourcePolicy(disabled, element);
parseHeaderElements(element);
boolean noWriters = this.headerWriters.isEmpty();
if (disabled && !noWriters) {
@@ -376,6 +388,75 @@ public class HeadersBeanDefinitionParser implements BeanDefinitionParser {
this.headerWriters.add(headersWriter.getBeanDefinition());
}
private void parseCrossOriginOpenerPolicy(boolean elementDisabled, Element element) {
if (elementDisabled || element == null) {
return;
}
CrossOriginOpenerPolicyHeaderWriter writer = new CrossOriginOpenerPolicyHeaderWriter();
Element crossOriginOpenerPolicyElement = DomUtils.getChildElementByTagName(element,
CROSS_ORIGIN_OPENER_POLICY_ELEMENT);
if (crossOriginOpenerPolicyElement != null) {
addCrossOriginOpenerPolicy(crossOriginOpenerPolicyElement, writer);
}
BeanDefinitionBuilder builder = BeanDefinitionBuilder
.genericBeanDefinition(CrossOriginOpenerPolicyHeaderWriter.class, () -> writer);
this.headerWriters.add(builder.getBeanDefinition());
}
private void parseCrossOriginEmbedderPolicy(boolean elementDisabled, Element element) {
if (elementDisabled || element == null) {
return;
}
CrossOriginEmbedderPolicyHeaderWriter writer = new CrossOriginEmbedderPolicyHeaderWriter();
Element crossOriginEmbedderPolicyElement = DomUtils.getChildElementByTagName(element,
CROSS_ORIGIN_EMBEDDER_POLICY_ELEMENT);
if (crossOriginEmbedderPolicyElement != null) {
addCrossOriginEmbedderPolicy(crossOriginEmbedderPolicyElement, writer);
}
BeanDefinitionBuilder builder = BeanDefinitionBuilder
.genericBeanDefinition(CrossOriginEmbedderPolicyHeaderWriter.class, () -> writer);
this.headerWriters.add(builder.getBeanDefinition());
}
private void parseCrossOriginResourcePolicy(boolean elementDisabled, Element element) {
if (elementDisabled || element == null) {
return;
}
CrossOriginResourcePolicyHeaderWriter writer = new CrossOriginResourcePolicyHeaderWriter();
Element crossOriginResourcePolicyElement = DomUtils.getChildElementByTagName(element,
CROSS_ORIGIN_RESOURCE_POLICY_ELEMENT);
if (crossOriginResourcePolicyElement != null) {
addCrossOriginResourcePolicy(crossOriginResourcePolicyElement, writer);
}
BeanDefinitionBuilder builder = BeanDefinitionBuilder
.genericBeanDefinition(CrossOriginResourcePolicyHeaderWriter.class, () -> writer);
this.headerWriters.add(builder.getBeanDefinition());
}
private void addCrossOriginResourcePolicy(Element crossOriginResourcePolicyElement,
CrossOriginResourcePolicyHeaderWriter writer) {
String policy = crossOriginResourcePolicyElement.getAttribute(ATT_POLICY);
if (StringUtils.hasText(policy)) {
writer.setPolicy(CrossOriginResourcePolicyHeaderWriter.CrossOriginResourcePolicy.from(policy));
}
}
private void addCrossOriginEmbedderPolicy(Element crossOriginEmbedderPolicyElement,
CrossOriginEmbedderPolicyHeaderWriter writer) {
String policy = crossOriginEmbedderPolicyElement.getAttribute(ATT_POLICY);
if (StringUtils.hasText(policy)) {
writer.setPolicy(CrossOriginEmbedderPolicyHeaderWriter.CrossOriginEmbedderPolicy.from(policy));
}
}
private void addCrossOriginOpenerPolicy(Element crossOriginOpenerPolicyElement,
CrossOriginOpenerPolicyHeaderWriter writer) {
String policy = crossOriginOpenerPolicyElement.getAttribute(ATT_POLICY);
if (StringUtils.hasText(policy)) {
writer.setPolicy(CrossOriginOpenerPolicyHeaderWriter.CrossOriginOpenerPolicy.from(policy));
}
}
private void attrNotAllowed(ParserContext context, String attrName, String otherAttrName, Element element) {
context.getReaderContext().error("Only one of '" + attrName + "' or '" + otherAttrName + "' can be set.",
element);
@@ -133,6 +133,7 @@ import org.springframework.security.web.server.authorization.AuthorizationContex
import org.springframework.security.web.server.authorization.AuthorizationWebFilter;
import org.springframework.security.web.server.authorization.DelegatingReactiveAuthorizationManager;
import org.springframework.security.web.server.authorization.ExceptionTranslationWebFilter;
import org.springframework.security.web.server.authorization.IpAddressReactiveAuthorizationManager;
import org.springframework.security.web.server.authorization.ServerAccessDeniedHandler;
import org.springframework.security.web.server.authorization.ServerWebExchangeDelegatingServerAccessDeniedHandler;
import org.springframework.security.web.server.context.NoOpServerSecurityContextRepository;
@@ -148,6 +149,12 @@ import org.springframework.security.web.server.header.CacheControlServerHttpHead
import org.springframework.security.web.server.header.CompositeServerHttpHeadersWriter;
import org.springframework.security.web.server.header.ContentSecurityPolicyServerHttpHeadersWriter;
import org.springframework.security.web.server.header.ContentTypeOptionsServerHttpHeadersWriter;
import org.springframework.security.web.server.header.CrossOriginEmbedderPolicyServerHttpHeadersWriter;
import org.springframework.security.web.server.header.CrossOriginEmbedderPolicyServerHttpHeadersWriter.CrossOriginEmbedderPolicy;
import org.springframework.security.web.server.header.CrossOriginOpenerPolicyServerHttpHeadersWriter;
import org.springframework.security.web.server.header.CrossOriginOpenerPolicyServerHttpHeadersWriter.CrossOriginOpenerPolicy;
import org.springframework.security.web.server.header.CrossOriginResourcePolicyServerHttpHeadersWriter;
import org.springframework.security.web.server.header.CrossOriginResourcePolicyServerHttpHeadersWriter.CrossOriginResourcePolicy;
import org.springframework.security.web.server.header.FeaturePolicyServerHttpHeadersWriter;
import org.springframework.security.web.server.header.HttpHeaderWriterWebFilter;
import org.springframework.security.web.server.header.PermissionsPolicyServerHttpHeadersWriter;
@@ -1682,6 +1689,18 @@ public class ServerHttpSecurity {
return access(AuthenticatedReactiveAuthorizationManager.authenticated());
}
/**
* Require a specific IP address or range using an IP/Netmask (e.g.
* 192.168.1.0/24).
* @param ipAddress the address or range of addresses from which the request
* must come.
* @return the {@link AuthorizeExchangeSpec} to configure
* @since 5.7
*/
public AuthorizeExchangeSpec hasIpAddress(String ipAddress) {
return access(IpAddressReactiveAuthorizationManager.hasIpAddress(ipAddress));
}
/**
* Allows plugging in a custom authorization strategy
* @param manager the authorization manager to use
@@ -2367,10 +2386,17 @@ public class ServerHttpSecurity {
private ReferrerPolicyServerHttpHeadersWriter referrerPolicy = new ReferrerPolicyServerHttpHeadersWriter();
private CrossOriginOpenerPolicyServerHttpHeadersWriter crossOriginOpenerPolicy = new CrossOriginOpenerPolicyServerHttpHeadersWriter();
private CrossOriginEmbedderPolicyServerHttpHeadersWriter crossOriginEmbedderPolicy = new CrossOriginEmbedderPolicyServerHttpHeadersWriter();
private CrossOriginResourcePolicyServerHttpHeadersWriter crossOriginResourcePolicy = new CrossOriginResourcePolicyServerHttpHeadersWriter();
private HeaderSpec() {
this.writers = new ArrayList<>(Arrays.asList(this.cacheControl, this.contentTypeOptions, this.hsts,
this.frameOptions, this.xss, this.featurePolicy, this.permissionsPolicy, this.contentSecurityPolicy,
this.referrerPolicy));
this.referrerPolicy, this.crossOriginOpenerPolicy, this.crossOriginEmbedderPolicy,
this.crossOriginResourcePolicy));
}
/**
@@ -2582,6 +2608,84 @@ public class ServerHttpSecurity {
return this;
}
/**
* Configures the <a href=
* "https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Opener-Policy">
* Cross-Origin-Opener-Policy</a> header.
* @return the {@link CrossOriginOpenerPolicySpec} to configure
* @since 5.7
* @see CrossOriginOpenerPolicyServerHttpHeadersWriter
*/
public CrossOriginOpenerPolicySpec crossOriginOpenerPolicy() {
return new CrossOriginOpenerPolicySpec();
}
/**
* Configures the <a href=
* "https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Opener-Policy">
* Cross-Origin-Opener-Policy</a> header.
* @return the {@link HeaderSpec} to customize
* @since 5.7
* @see CrossOriginOpenerPolicyServerHttpHeadersWriter
*/
public HeaderSpec crossOriginOpenerPolicy(
Customizer<CrossOriginOpenerPolicySpec> crossOriginOpenerPolicyCustomizer) {
crossOriginOpenerPolicyCustomizer.customize(new CrossOriginOpenerPolicySpec());
return this;
}
/**
* Configures the <a href=
* "https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Embedder-Policy">
* Cross-Origin-Embedder-Policy</a> header.
* @return the {@link CrossOriginEmbedderPolicySpec} to configure
* @since 5.7
* @see CrossOriginEmbedderPolicyServerHttpHeadersWriter
*/
public CrossOriginEmbedderPolicySpec crossOriginEmbedderPolicy() {
return new CrossOriginEmbedderPolicySpec();
}
/**
* Configures the <a href=
* "https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Embedder-Policy">
* Cross-Origin-Embedder-Policy</a> header.
* @return the {@link HeaderSpec} to customize
* @since 5.7
* @see CrossOriginEmbedderPolicyServerHttpHeadersWriter
*/
public HeaderSpec crossOriginEmbedderPolicy(
Customizer<CrossOriginEmbedderPolicySpec> crossOriginEmbedderPolicyCustomizer) {
crossOriginEmbedderPolicyCustomizer.customize(new CrossOriginEmbedderPolicySpec());
return this;
}
/**
* Configures the <a href=
* "https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Resource-Policy">
* Cross-Origin-Resource-Policy</a> header.
* @return the {@link CrossOriginResourcePolicySpec} to configure
* @since 5.7
* @see CrossOriginResourcePolicyServerHttpHeadersWriter
*/
public CrossOriginResourcePolicySpec crossOriginResourcePolicy() {
return new CrossOriginResourcePolicySpec();
}
/**
* Configures the <a href=
* "https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Resource-Policy">
* Cross-Origin-Resource-Policy</a> header.
* @return the {@link HeaderSpec} to customize
* @since 5.7
* @see CrossOriginResourcePolicyServerHttpHeadersWriter
*/
public HeaderSpec crossOriginResourcePolicy(
Customizer<CrossOriginResourcePolicySpec> crossOriginResourcePolicyCustomizer) {
crossOriginResourcePolicyCustomizer.customize(new CrossOriginResourcePolicySpec());
return this;
}
/**
* Configures cache control headers
*
@@ -2897,6 +3001,99 @@ public class ServerHttpSecurity {
}
/**
* Configures the Cross-Origin-Opener-Policy header
*
* @since 5.7
*/
public final class CrossOriginOpenerPolicySpec {
private CrossOriginOpenerPolicySpec() {
}
/**
* Sets the value to be used in the `Cross-Origin-Opener-Policy` header
* @param openerPolicy a opener policy
* @return the {@link CrossOriginOpenerPolicySpec} to continue configuring
*/
public CrossOriginOpenerPolicySpec policy(CrossOriginOpenerPolicy openerPolicy) {
HeaderSpec.this.crossOriginOpenerPolicy.setPolicy(openerPolicy);
return this;
}
/**
* Allows method chaining to continue configuring the
* {@link ServerHttpSecurity}.
* @return the {@link HeaderSpec} to continue configuring
*/
public HeaderSpec and() {
return HeaderSpec.this;
}
}
/**
* Configures the Cross-Origin-Embedder-Policy header
*
* @since 5.7
*/
public final class CrossOriginEmbedderPolicySpec {
private CrossOriginEmbedderPolicySpec() {
}
/**
* Sets the value to be used in the `Cross-Origin-Embedder-Policy` header
* @param embedderPolicy a opener policy
* @return the {@link CrossOriginEmbedderPolicySpec} to continue configuring
*/
public CrossOriginEmbedderPolicySpec policy(CrossOriginEmbedderPolicy embedderPolicy) {
HeaderSpec.this.crossOriginEmbedderPolicy.setPolicy(embedderPolicy);
return this;
}
/**
* Allows method chaining to continue configuring the
* {@link ServerHttpSecurity}.
* @return the {@link HeaderSpec} to continue configuring
*/
public HeaderSpec and() {
return HeaderSpec.this;
}
}
/**
* Configures the Cross-Origin-Resource-Policy header
*
* @since 5.7
*/
public final class CrossOriginResourcePolicySpec {
private CrossOriginResourcePolicySpec() {
}
/**
* Sets the value to be used in the `Cross-Origin-Resource-Policy` header
* @param resourcePolicy a opener policy
* @return the {@link CrossOriginResourcePolicySpec} to continue configuring
*/
public CrossOriginResourcePolicySpec policy(CrossOriginResourcePolicy resourcePolicy) {
HeaderSpec.this.crossOriginResourcePolicy.setPolicy(resourcePolicy);
return this;
}
/**
* Allows method chaining to continue configuring the
* {@link ServerHttpSecurity}.
* @return the {@link HeaderSpec} to continue configuring
*/
public HeaderSpec and() {
return HeaderSpec.this;
}
}
}
/**
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* 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.
@@ -22,6 +22,7 @@ import org.springframework.security.authorization.AuthorizationDecision
import org.springframework.security.authorization.ReactiveAuthorizationManager
import org.springframework.security.core.Authentication
import org.springframework.security.web.server.authorization.AuthorizationContext
import org.springframework.security.web.server.authorization.IpAddressReactiveAuthorizationManager
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatchers
import org.springframework.security.web.util.matcher.RequestMatcher
@@ -108,6 +109,13 @@ class AuthorizeExchangeDsl {
fun hasAnyAuthority(vararg authorities: String): ReactiveAuthorizationManager<AuthorizationContext> =
AuthorityReactiveAuthorizationManager.hasAnyAuthority<AuthorizationContext>(*authorities)
/**
* Require a specific IP or range of IP addresses.
* @since 5.7
*/
fun hasIpAddress(ipAddress: String): ReactiveAuthorizationManager<AuthorizationContext> =
IpAddressReactiveAuthorizationManager.hasIpAddress(ipAddress)
/**
* Require an authenticated user.
*/
@@ -0,0 +1,42 @@
/*
* 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.security.config.web.server
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.web.server.header.CrossOriginEmbedderPolicyServerHttpHeadersWriter
/**
* A Kotlin DSL to configure the [HttpSecurity] Cross-Origin-Embedder-Policy header using
* idiomatic Kotlin code.
*
* @author Marcus Da Coregio
* @since 5.7
* @property policy the policy to be used in the response header.
*/
@ServerSecurityMarker
class ServerCrossOriginEmbedderPolicyDsl {
var policy: CrossOriginEmbedderPolicyServerHttpHeadersWriter.CrossOriginEmbedderPolicy? = null
internal fun get(): (ServerHttpSecurity.HeaderSpec.CrossOriginEmbedderPolicySpec) -> Unit {
return { crossOriginEmbedderPolicy ->
policy?.also {
crossOriginEmbedderPolicy.policy(policy)
}
}
}
}
@@ -0,0 +1,42 @@
/*
* 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.security.config.web.server
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.web.server.header.CrossOriginOpenerPolicyServerHttpHeadersWriter
/**
* A Kotlin DSL to configure the [HttpSecurity] Cross-Origin-Opener-Policy header using
* idiomatic Kotlin code.
*
* @author Marcus Da Coregio
* @since 5.7
* @property policy the policy to be used in the response header.
*/
@ServerSecurityMarker
class ServerCrossOriginOpenerPolicyDsl {
var policy: CrossOriginOpenerPolicyServerHttpHeadersWriter.CrossOriginOpenerPolicy? = null
internal fun get(): (ServerHttpSecurity.HeaderSpec.CrossOriginOpenerPolicySpec) -> Unit {
return { crossOriginOpenerPolicy ->
policy?.also {
crossOriginOpenerPolicy.policy(policy)
}
}
}
}
@@ -0,0 +1,42 @@
/*
* 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.security.config.web.server
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.web.server.header.CrossOriginResourcePolicyServerHttpHeadersWriter
/**
* A Kotlin DSL to configure the [HttpSecurity] Cross-Origin-Resource-Policy header using
* idiomatic Kotlin code.
*
* @author Marcus Da Coregio
* @since 5.7
* @property policy the policy to be used in the response header.
*/
@ServerSecurityMarker
class ServerCrossOriginResourcePolicyDsl {
var policy: CrossOriginResourcePolicyServerHttpHeadersWriter.CrossOriginResourcePolicy? = null
internal fun get(): (ServerHttpSecurity.HeaderSpec.CrossOriginResourcePolicySpec) -> Unit {
return { crossOriginResourcePolicy ->
policy?.also {
crossOriginResourcePolicy.policy(policy)
}
}
}
}
@@ -16,7 +16,12 @@
package org.springframework.security.config.web.server
import org.springframework.security.web.server.header.*
import org.springframework.security.web.server.header.CacheControlServerHttpHeadersWriter
import org.springframework.security.web.server.header.ContentTypeOptionsServerHttpHeadersWriter
import org.springframework.security.web.server.header.ReferrerPolicyServerHttpHeadersWriter
import org.springframework.security.web.server.header.StrictTransportSecurityServerHttpHeadersWriter
import org.springframework.security.web.server.header.XFrameOptionsServerHttpHeadersWriter
import org.springframework.security.web.server.header.XXssProtectionServerHttpHeadersWriter
/**
* A Kotlin DSL to configure [ServerHttpSecurity] headers using idiomatic Kotlin code.
@@ -35,6 +40,9 @@ class ServerHeadersDsl {
private var referrerPolicy: ((ServerHttpSecurity.HeaderSpec.ReferrerPolicySpec) -> Unit)? = null
private var featurePolicyDirectives: String? = null
private var permissionsPolicy: ((ServerHttpSecurity.HeaderSpec.PermissionsPolicySpec) -> Unit)? = null
private var crossOriginOpenerPolicy: ((ServerHttpSecurity.HeaderSpec.CrossOriginOpenerPolicySpec) -> Unit)? = null
private var crossOriginEmbedderPolicy: ((ServerHttpSecurity.HeaderSpec.CrossOriginEmbedderPolicySpec) -> Unit)? = null
private var crossOriginResourcePolicy: ((ServerHttpSecurity.HeaderSpec.CrossOriginResourcePolicySpec) -> Unit)? = null
private var disabled = false
@@ -157,6 +165,39 @@ class ServerHeadersDsl {
this.permissionsPolicy = ServerPermissionsPolicyDsl().apply(permissionsPolicyConfig).get()
}
/**
* Allows configuration for <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Opener-Policy">
* Cross-Origin-Opener-Policy</a> header.
*
* @since 5.7
* @param crossOriginOpenerPolicyConfig the customization to apply to the header
*/
fun crossOriginOpenerPolicy(crossOriginOpenerPolicyConfig: ServerCrossOriginOpenerPolicyDsl.() -> Unit) {
this.crossOriginOpenerPolicy = ServerCrossOriginOpenerPolicyDsl().apply(crossOriginOpenerPolicyConfig).get()
}
/**
* Allows configuration for <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Embedder-Policy">
* Cross-Origin-Embedder-Policy</a> header.
*
* @since 5.7
* @param crossOriginEmbedderPolicyConfig the customization to apply to the header
*/
fun crossOriginEmbedderPolicy(crossOriginEmbedderPolicyConfig: ServerCrossOriginEmbedderPolicyDsl.() -> Unit) {
this.crossOriginEmbedderPolicy = ServerCrossOriginEmbedderPolicyDsl().apply(crossOriginEmbedderPolicyConfig).get()
}
/**
* Allows configuration for <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Resource-Policy">
* Cross-Origin-Resource-Policy</a> header.
*
* @since 5.7
* @param crossOriginResourcePolicyConfig the customization to apply to the header
*/
fun crossOriginResourcePolicy(crossOriginResourcePolicyConfig: ServerCrossOriginResourcePolicyDsl.() -> Unit) {
this.crossOriginResourcePolicy = ServerCrossOriginResourcePolicyDsl().apply(crossOriginResourcePolicyConfig).get()
}
/**
* Disables HTTP response headers.
*/
@@ -194,6 +235,15 @@ class ServerHeadersDsl {
referrerPolicy?.also {
headers.referrerPolicy(referrerPolicy)
}
crossOriginOpenerPolicy?.also {
headers.crossOriginOpenerPolicy(crossOriginOpenerPolicy)
}
crossOriginEmbedderPolicy?.also {
headers.crossOriginEmbedderPolicy(crossOriginEmbedderPolicy)
}
crossOriginResourcePolicy?.also {
headers.crossOriginResourcePolicy(crossOriginResourcePolicy)
}
if (disabled) {
headers.disable()
}
@@ -42,6 +42,9 @@ class HeadersDsl {
private var referrerPolicy: ((HeadersConfigurer<HttpSecurity>.ReferrerPolicyConfig) -> Unit)? = null
private var featurePolicyDirectives: String? = null
private var permissionsPolicy: ((HeadersConfigurer<HttpSecurity>.PermissionsPolicyConfig) -> Unit)? = null
private var crossOriginOpenerPolicy: ((HeadersConfigurer<HttpSecurity>.CrossOriginOpenerPolicyConfig) -> Unit)? = null
private var crossOriginEmbedderPolicy: ((HeadersConfigurer<HttpSecurity>.CrossOriginEmbedderPolicyConfig) -> Unit)? = null
private var crossOriginResourcePolicy: ((HeadersConfigurer<HttpSecurity>.CrossOriginResourcePolicyConfig) -> Unit)? = null
private var disabled = false
private var headerWriters = mutableListOf<HeaderWriter>()
@@ -181,6 +184,54 @@ class HeadersDsl {
this.permissionsPolicy = PermissionsPolicyDsl().apply(permissionsPolicyConfig).get()
}
/**
* Allows configuration for <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Opener-Policy">
* Cross-Origin-Opener-Policy</a> header.
*
* <p>
* Calling this method automatically enables (includes) the Cross-Origin-Opener-Policy
* header in the response using the supplied policy.
* <p>
*
* @since 5.7
* @param crossOriginOpenerPolicyConfig the customization to apply to the header
*/
fun crossOriginOpenerPolicy(crossOriginOpenerPolicyConfig: CrossOriginOpenerPolicyDsl.() -> Unit) {
this.crossOriginOpenerPolicy = CrossOriginOpenerPolicyDsl().apply(crossOriginOpenerPolicyConfig).get()
}
/**
* Allows configuration for <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Embedder-Policy">
* Cross-Origin-Embedder-Policy</a> header.
*
* <p>
* Calling this method automatically enables (includes) the Cross-Origin-Embedder-Policy
* header in the response using the supplied policy.
* <p>
*
* @since 5.7
* @param crossOriginEmbedderPolicyConfig the customization to apply to the header
*/
fun crossOriginEmbedderPolicy(crossOriginEmbedderPolicyConfig: CrossOriginEmbedderPolicyDsl.() -> Unit) {
this.crossOriginEmbedderPolicy = CrossOriginEmbedderPolicyDsl().apply(crossOriginEmbedderPolicyConfig).get()
}
/**
* Configures the <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Resource-Policy">
* Cross-Origin-Resource-Policy</a> header.
*
* <p>
* Calling this method automatically enables (includes) the Cross-Origin-Resource-Policy
* header in the response using the supplied policy.
* <p>
*
* @since 5.7
* @param crossOriginResourcePolicyConfig the customization to apply to the header
*/
fun crossOriginResourcePolicy(crossOriginResourcePolicyConfig: CrossOriginResourcePolicyDsl.() -> Unit) {
this.crossOriginResourcePolicy = CrossOriginResourcePolicyDsl().apply(crossOriginResourcePolicyConfig).get()
}
/**
* Adds a [HeaderWriter] instance.
*
@@ -238,6 +289,15 @@ class HeadersDsl {
permissionsPolicy?.also {
headers.permissionsPolicy(permissionsPolicy)
}
crossOriginOpenerPolicy?.also {
headers.crossOriginOpenerPolicy(crossOriginOpenerPolicy)
}
crossOriginEmbedderPolicy?.also {
headers.crossOriginEmbedderPolicy(crossOriginEmbedderPolicy)
}
crossOriginResourcePolicy?.also {
headers.crossOriginResourcePolicy(crossOriginResourcePolicy)
}
headerWriters.forEach { headerWriter ->
headers.addHeaderWriter(headerWriter)
}
@@ -0,0 +1,43 @@
/*
* 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.security.config.web.servlet.headers
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer
import org.springframework.security.web.header.writers.CrossOriginEmbedderPolicyHeaderWriter
/**
* A Kotlin DSL to configure the [HttpSecurity] Cross-Origin-Embedder-Policy header using
* idiomatic Kotlin code.
*
* @author Marcus Da Coregio
* @since 5.7
* @property policy the policy to be used in the response header.
*/
@HeadersSecurityMarker
class CrossOriginEmbedderPolicyDsl {
var policy: CrossOriginEmbedderPolicyHeaderWriter.CrossOriginEmbedderPolicy? = null
internal fun get(): (HeadersConfigurer<HttpSecurity>.CrossOriginEmbedderPolicyConfig) -> Unit {
return { crossOriginEmbedderPolicy ->
policy?.also {
crossOriginEmbedderPolicy.policy(policy)
}
}
}
}
@@ -0,0 +1,43 @@
/*
* 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.security.config.web.servlet.headers
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer
import org.springframework.security.web.header.writers.CrossOriginOpenerPolicyHeaderWriter
/**
* A Kotlin DSL to configure the [HttpSecurity] Cross-Origin-Opener-Policy header using
* idiomatic Kotlin code.
*
* @author Marcus Da Coregio
* @since 5.7
* @property policy the policy to be used in the response header.
*/
@HeadersSecurityMarker
class CrossOriginOpenerPolicyDsl {
var policy: CrossOriginOpenerPolicyHeaderWriter.CrossOriginOpenerPolicy? = null
internal fun get(): (HeadersConfigurer<HttpSecurity>.CrossOriginOpenerPolicyConfig) -> Unit {
return { crossOriginOpenerPolicy ->
policy?.also {
crossOriginOpenerPolicy.policy(policy)
}
}
}
}
@@ -0,0 +1,43 @@
/*
* 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.security.config.web.servlet.headers
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer
import org.springframework.security.web.header.writers.CrossOriginResourcePolicyHeaderWriter
/**
* A Kotlin DSL to configure the [HttpSecurity] Cross-Origin-Resource-Policy header using
* idiomatic Kotlin code.
*
* @author Marcus Da Coregio
* @since 5.7
* @property policy the policy to be used in the response header.
*/
@HeadersSecurityMarker
class CrossOriginResourcePolicyDsl {
var policy: CrossOriginResourcePolicyHeaderWriter.CrossOriginResourcePolicy? = null
internal fun get(): (HeadersConfigurer<HttpSecurity>.CrossOriginResourcePolicyConfig) -> Unit {
return { crossOriginResourcePolicy ->
policy?.also {
crossOriginResourcePolicy.policy(policy)
}
}
}
}
@@ -1,4 +1,5 @@
http\://www.springframework.org/schema/security/spring-security.xsd=org/springframework/security/config/spring-security-5.6.xsd
http\://www.springframework.org/schema/security/spring-security.xsd=org/springframework/security/config/spring-security-5.7.xsd
http\://www.springframework.org/schema/security/spring-security-5.7.xsd=org/springframework/security/config/spring-security-5.7.xsd
http\://www.springframework.org/schema/security/spring-security-5.6.xsd=org/springframework/security/config/spring-security-5.6.xsd
http\://www.springframework.org/schema/security/spring-security-5.5.xsd=org/springframework/security/config/spring-security-5.5.xsd
http\://www.springframework.org/schema/security/spring-security-5.4.xsd=org/springframework/security/config/spring-security-5.4.xsd
@@ -17,7 +18,8 @@ http\://www.springframework.org/schema/security/spring-security-2.0.xsd=org/spri
http\://www.springframework.org/schema/security/spring-security-2.0.1.xsd=org/springframework/security/config/spring-security-2.0.1.xsd
http\://www.springframework.org/schema/security/spring-security-2.0.2.xsd=org/springframework/security/config/spring-security-2.0.2.xsd
http\://www.springframework.org/schema/security/spring-security-2.0.4.xsd=org/springframework/security/config/spring-security-2.0.4.xsd
https\://www.springframework.org/schema/security/spring-security.xsd=org/springframework/security/config/spring-security-5.6.xsd
https\://www.springframework.org/schema/security/spring-security.xsd=org/springframework/security/config/spring-security-5.7.xsd
https\://www.springframework.org/schema/security/spring-security-5.7.xsd=org/springframework/security/config/spring-security-5.7.xsd
https\://www.springframework.org/schema/security/spring-security-5.6.xsd=org/springframework/security/config/spring-security-5.6.xsd
https\://www.springframework.org/schema/security/spring-security-5.5.xsd=org/springframework/security/config/spring-security-5.5.xsd
https\://www.springframework.org/schema/security/spring-security-5.4.xsd=org/springframework/security/config/spring-security-5.4.xsd

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