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

Compare commits

...

195 Commits

Author SHA1 Message Date
Rob Winch 38691e6662 Release 5.6.0-RC1 2021-10-18 21:17:52 -05:00
Rob Winch a8c7ac9010 Add Antora based docs in whats-new 2021-10-18 21:11:47 -05:00
Rob Winch c4f07b879a Update org.springframework to 5.3.11
Closes gh-10403
2021-10-18 21:04:51 -05:00
Rob Winch 7c2e2bf75b Update htmlunit-driver to 2.54.0
Closes gh-10402
2021-10-18 21:04:51 -05:00
Rob Winch 1569ae2b75 Update junit-bom to 5.8.1
Closes gh-10400
2021-10-18 21:04:51 -05:00
Rob Winch d2bfe1c1fa Update org.jetbrains.kotlin to 1.5.31
Closes gh-10399
2021-10-18 21:04:51 -05:00
Rob Winch 289ffc3e63 Update hibernate-entitymanager to 5.6.0.Final
Closes gh-10398
2021-10-18 21:04:51 -05:00
Rob Winch ccc1c157a5 Update org.eclipse.jetty to 9.4.44.v20210927
Closes gh-10397
2021-10-18 21:04:51 -05:00
Rob Winch 7cc0c9fb1b Update assertj-core to 3.21.0
Closes gh-10396
2021-10-18 21:04:51 -05:00
Rob Winch 2bb61835a0 Update htmlunit to 2.54.0
Closes gh-10395
2021-10-18 21:04:51 -05:00
Rob Winch 4da15eea18 Update io.spring.javaformat to 0.0.29
Closes gh-10394
2021-10-18 21:04:51 -05:00
Rob Winch f836897190 Checkstyle Fixes
- Javadoc tag ordering
- Private constructors before inner classes

Issue gh-10394
2021-10-18 21:03:35 -05:00
Rob Winch 0c088e278a Update r2dbc-spi-test to 0.8.6.RELEASE
Closes gh-10393
2021-10-18 21:03:12 -05:00
Rob Winch 5f8ded6b5f Update io.projectreactor to 2020.0.12
Closes gh-10391
2021-10-18 21:03:12 -05:00
Rob Winch 8a4e640f9d Update com.nimbusds to 9.18
Closes gh-10390
2021-10-18 21:03:12 -05:00
Rob Winch de5972a062 Update jackson-bom to 2.13.0
Closes gh-10387
2021-10-18 21:03:12 -05:00
Rob Winch e1f4ec1137 Fix Jackson 2021-10-18 21:03:12 -05:00
Rob Winch 7309b8846a updateDependenciesSettings does not process AsciidoctorConventionPlugin 2021-10-18 13:54:26 -05:00
Steve Riesenberg 47087ba9c5 Revamp OAuth 2.0 Client reactive documentation
Related gh-8174
2021-10-14 14:35:25 -05:00
Steve Riesenberg a487ef2d74 Add JwtEncoder to What's New in 5.6 2021-10-14 14:26:48 -05:00
Marcus Da Coregio 7fa39c8807 Deprecate EhCache2 support
Since EhCache 3 is fully JSR-107 compliant, we should remove EhCache2 support and provide JCache implementations

Closes gh-10362
2021-10-14 14:51:27 -03:00
Dávid Kováč 64e9ac995a getClaimAsBoolean() should not be falsy
Closes gh-10148
2021-10-14 11:28:09 -05:00
Marcus Da Coregio 00084cf986 Add saml2.ValidIssuers parameter
Adds the saml2.ValidIssuers parameter into SAML 2.0 Assertion Validators

Closes gh-10335
2021-10-14 09:21:43 -06:00
Marcus Da Coregio c82722c412 Update What's New in 5.6 2021-10-14 09:27:39 -03:00
Marcus Da Coregio faec20bc69 Update DefaultWebInvocationPrivilegeEvaluator to use current ServletContext
Closes gh-10208
2021-10-14 09:27:02 -03:00
Philipp Neuschwander 6db58cbf8a Conditionally resolve bearer token from request parameters
Before this commit, the DefaultBearerTokenResolver unconditionally
resolved the request parameters to check whether multiple tokens
are present in the request and reject those requests as invalid.

This commit changes this behaviour to resolve the request parameters
only if parameter token is supported for the specific request
according to spec (RFC 6750).

Closes gh-10326
2021-10-13 17:10:50 -05:00
Emil Sierżęga 88c64b3b7b Fixed link in .editorconfig 2021-10-13 15:36:10 -06:00
Dávid Kováč 0299808b05 Add ClaimAccessor tests
Add tests for ClaimAccessor#getClaimAsMap and ClaimAccessor#getClaimAsStringList

Issue gh-10117
2021-10-13 12:53:40 -06:00
Dávid Kováč 125d33e3cf Update JavaDoc according to implementation
Update ClaimAccessor#getClaimAsMap and ClaimAccessor#getClaimAsStringList
JavaDoc according to the current implementation

Closes gh-10117
2021-10-13 12:53:40 -06:00
Joe Grandja e3abaf7999 Add OAuth2ErrorCodes.INVALID_REDIRECT_URI
Closes gh-10370
2021-10-13 14:12:44 -04:00
Gaurav Tiwari 33708e61fb Add postProcess support to Saml2LogoutConfigurer
Closes gh-10311
2021-10-13 12:05:48 -06:00
Josh Cummings fbb7691be4 Polish SecurityNamespaceHandler Tests
Issue gh-8974
2021-10-13 11:50:14 -06:00
Emil Sierżęga 8daa6ec1fd SecurityNamespaceHandler: update schema version to 5.6
Closes gh-8974
2021-10-13 11:49:57 -06:00
Steve Riesenberg 00f466a431 Update What's New in 5.6 2021-10-13 12:06:47 -05:00
Eleftheria Stein ba8844a67e Deprecate Kotlin methods that don't use reified types
Closes gh-10365
2021-10-13 10:16:37 +02:00
Josh Cummings 7b98c2ea95 Restructure SwitchUserFilter Logs
Issue gh-6311
2021-10-12 13:32:29 -06:00
Marcus Da Coregio 77399ee2b0 Deprecate remoting technologies support
Closes gh-10361
2021-10-12 14:59:37 -03:00
Marcus Da Coregio 86c24da38b Improve Method Security logging
Closes gh-10247
2021-10-08 14:22:09 -03:00
Marcus Da Coregio ef01124eb9 Add reasons to AuthorizationDecisions
Closes gh-9287
2021-10-08 14:22:09 -03:00
Marcus Da Coregio 570092c467 Remove trace logs for PrePostAnnotationSecurityMetadataSource
Those logs were producing too much noise on the console without adding much value.

Issue gh-10247
2021-10-08 14:22:09 -03:00
Steve Riesenberg 3b564b2026 Add parameters converter support to AbstractWebClientReactiveOAuth2AccessTokenResponseClient
This adds support for configuring NimbusJwtClientAuthenticationParametersConverter to any AbstractWebClientReactiveOAuth2AccessTokenResponseClient as an additional parameters converter, which in turns adds reactive support for jwt client authentication.

Closes gh-10146
2021-10-06 13:09:33 -05:00
Alexander Schwartz f561499683 Fix Antora cross-references that lead to other pages.
Also using AsciiDoc style listings instead of Markdown style listings, and using explicit section IDs on all cross-references.
2021-10-06 09:53:15 -06:00
Steve Riesenberg 9b24f66f1c Implement reactive support for JWT as an Authorization Grant
Closes gh-10147
2021-10-05 16:09:24 -05:00
Marcus Da Coregio 02b2fcc6f0 Restore ManagementConfigurationPlugin
Issue gh-9615
2021-10-05 11:23:29 -03:00
Marcus Da Coregio e2e93887af Update JFrog Build Info plugin
Issue gh-9615
2021-10-04 16:01:40 -03:00
Marcus Da Coregio d2e5f2ae0d Update Gradle to 7.2
Closes gh-9615
2021-10-04 15:19:40 -03:00
Eleftheria Stein 7d81a52780 Allow AuthenticationPrincipal argument type to be primitive
Closes gh-10172
2021-10-04 16:22:21 +02:00
Marcus Da Coregio 7112ee3eaa Allow SAML 2.0 loginProcessingURL without registrationId
Closes gh-10176
2021-10-04 09:54:40 -03:00
Alexander Furer 8c74d6cea5 Fix isAssignable order
Closes gh-10236
2021-09-30 13:56:37 -06:00
Marcus Da Coregio e36e2b2a97 Move Saml2AuthnRequestRepository to web package
Moving to solve package tangles

Issue gh-9185
2021-09-29 14:10:39 -03:00
Rob Winch cff0bde1a3 GitHub Actions uses spring-builds+github user
This is more clear than spring-builds user
2021-09-28 15:09:03 -05:00
Josh Cummings 0f8fa36b93 Fix OAuth2 Error Code
Closes gh-10319
2021-09-28 13:24:51 -06:00
Darren Forsythe 5556b821e3 Check for multiple access tokens per rfc 6750
Check for multiple access tokens on the ServerHttpRequest rather than get get first. If multiples are found throw a OAuth2AuthenticationException.

Closes gh-5708
2021-09-28 08:07:06 -06:00
Josh Cummings 770c57ecc9 Add Structure101 to CI Build
Closes gh-6236
2021-09-27 16:23:19 -06:00
Josh Cummings 64f0102425 Establish Structure101 Baseline
Issue gh-6236
2021-09-27 16:06:43 -06:00
Josh Cummings 4272889dc8 Install Structure101 Plugin
Issue gh-6236
2021-09-27 14:56:03 -06:00
Josh Cummings 6978f51f19 Structure101 Build Plugin
Issue gh-6236
2021-09-27 14:56:03 -06:00
Rob Winch b57caf22af Use GH_ACTIONS_REPO_TOKEN 2021-09-27 13:29:29 -05:00
Daniel Garnier-Moiroux 2fb8e66bc8 Saml2WebSsoAuthenticationFilter adds authentication details
Closes gh-7722
2021-09-27 11:44:30 -03:00
heowc 84d173c310 Fix typo 2021-09-27 10:55:18 -03:00
OllisGit 658aff501c Assert Error-Messages already includes dashes
When the cert-content is not valid, the assert output message is not correct.
Because it outputs too many dashes .The const X509- and PKCS8-PEM_HEADER already includes the dashes.

I took the output message via copy and paste, but it was still not valid ;-(

Only the output is affected, the checks itself is correct.
2021-09-27 09:53:55 -03:00
Rob Winch 3b64cdfc03 Fix XsdDocumentedTests
Issue gh-5835
2021-09-24 10:25:26 -05:00
Joe Grandja 97c949d929 oauth2Login() AuthenticationProvider's preserve root cause exception when rethrown
Closes gh-10228
2021-09-24 10:41:31 -04:00
Joe Grandja 5830fda2fa Introduce JwtEncoder
Closes gh-9208
2021-09-24 05:13:40 -04:00
Rob Winch 339a05312e Use Antora
Closes gh-5835
2021-09-23 16:25:46 -05:00
Rob Winch 14fd213557 Better consistency between reactive and servlet 2021-09-23 15:50:14 -05:00
Rob Winch 77dc3d1a0e Move to servlet/configurations/* 2021-09-23 15:50:14 -05:00
Rob Winch ca2bc958df Create features/integrations 2021-09-23 15:50:14 -05:00
Rob Winch ab63ebbbee Add link to authorization from features. 2021-09-23 15:50:14 -05:00
Rob Winch 176e6b6000 Add additional info to overview 2021-09-23 15:50:14 -05:00
Rob Winch d6ada7fb18 Remove /servlet/hello/index.adoc
This is now /servlet/getting-started.adoc
2021-09-23 15:50:14 -05:00
Rob Winch e368734557 Update "Big Picture" to Architecture 2021-09-23 15:50:14 -05:00
Rob Winch 0d339aeda6 Add Reactive Getting Started Page 2021-09-23 15:50:14 -05:00
Rob Winch 7537aa5124 Add generic authorization page 2021-09-23 15:50:14 -05:00
Rob Winch a1c7a39bd3 Fix Firewall link in nav 2021-09-23 15:50:14 -05:00
Rob Winch d52aab88af Update nav text Project Modules 2021-09-23 15:50:14 -05:00
Rob Winch 7f5b008266 Add spring-security-docs-generated to local-antora-playbook.yml 2021-09-23 15:50:14 -05:00
Rob Winch be9ff16583 Remove Asciidoctor from the build in favor of Antora 2021-09-23 15:50:14 -05:00
Rob Winch 871bc1c42c The next version of Security is 5.6 2021-09-23 15:50:14 -05:00
Rob Winch 7e2eb6894f Use component name of ''
This makes it so that the URL contains security in the root of the deploy
folder. We will do this until we decide to add projects other than the
security related projects.
2021-09-23 15:50:14 -05:00
Rob Winch 52bfae5e27 Servlet Authentication no a link in nav 2021-09-23 15:50:14 -05:00
Rob Winch a7f3f54a33 architecture/index.adoc -> ../architecture.adoc
BASE_DIR=docs/modules/ROOT/pages

git --no-pager diff HEAD~1 --diff-filter=R -M | sed -Ez "s%(\nrename to|rename from |similarity index [^\n]+|diff[^\n]+|$BASE_DIR/)%%g" | grep "\S" | while read rename_from_to; do
  from=$(echo $rename_from_to | cut -f 1 -d " ")
  to=$(echo $rename_from_to | cut -f 2 -d " ")
  echo "processing rename from $from to $to"
  find "$BASE_DIR/../" -name "*.adoc" | while read adoc_file; do
    sed -i -E "s%xref:$from%xref:$to%g" "$adoc_file"
  done
done
2021-09-23 15:50:14 -05:00
Rob Winch 583761f916 unpwd->passwords folder 2021-09-23 15:50:14 -05:00
Rob Winch e4ce7249cc Generate antora.yml and push to spring-security-docs-generated 2021-09-23 15:50:14 -05:00
Rob Winch a0e9c67482 Fix antora version 2021-09-23 15:50:13 -05:00
Rob Winch 0b587dcef1 Reduce folders 2021-09-23 15:50:13 -05:00
Rob Winch 2da377ec88 Fix antora security version 2021-09-23 15:50:13 -05:00
Rob Winch e681e44268 overview/ -> ../ 2021-09-23 15:50:11 -05:00
Rob Winch a64a694c60 Indent testing subsections in nav.adoc 2021-09-23 15:49:45 -05:00
Rob Winch 32331185dc Fix local anchor 2021-09-23 15:49:45 -05:00
Rob Winch 29a7669101 rg "xref:\S+?#\S+\[\]" docs/modules -l -g "*.adoc" | while read adoc_file_to_replace; do
echo "Replacing $adoc_file_to_replace"
  for id_file in build/ids/*.id; do
    id=$(basename $id_file | sed 's/\.id$//')
    xref_page=$(cat $id_file)
    if [[ "$adoc_file_to_replace" -ef "./docs/modules/ROOT/pages/$xref_page" ]]
    then
      echo "  - Skipping same page refid $id "
    else
      text_file=$(echo $id_file | sed 's/\.id$/.text/')
      default_text=$(cat $text_file)
      sed -i -E "s%xref:${xref_page}#${id}\[\]%xref:${xref_page}#${id}[$default_text]%g" $adoc_file_to_replace
    fi
  done
done
2021-09-23 15:49:45 -05:00
Rob Winch 1f90df6a14 mkdir -p build/ids
find -name "*.adoc" |  xargs -I{file} awk -v file={file} '/\[\[/ {  gsub("\[|\]", ""); id=$0; gsub("./docs/modules/ROOT/pages/", "", file); gsub("\[|\]", ""); id=$0;getline;text=$0; sub("^=+ ","", text); print file > "build/ids/"id".id"; print text > "build/ids/"id".text" }' {file}

find docs/modules -name "*.adoc"|while read adoc_file_to_replace; do
  echo "Replacing $adoc_file_to_replace"
  for id_file in build/ids/*.id; do
    id=$(basename $id_file | sed 's/\.id$//')
    xref_page=$(cat $id_file)
    if [[ "$adoc_file_to_replace" -ef "./docs/modules/ROOT/pages/$xref_page" ]]
    then
      echo "  - Skipping same page refid $id "
    else
      sed -i -E "s%<<$id(|,([^,>]+))>>%xref:${xref_page}#${id}[\2]%g" $adoc_file_to_replace
    fi
  done
done
2021-09-23 15:49:43 -05:00
Rob Winch d2affef356 Fix images
- Move images into assets/
- Remove figures form antora.yml
- Add :figures: to each page that uses it
2021-09-23 15:47:21 -05:00
Rob Winch f5274926cf Fix up reactive/oauth2/index.adoc links 2021-09-23 15:47:21 -05:00
Rob Winch 88ac7a5d2e Fixup servlet/authentication/architecture/index.adoc 2021-09-23 15:47:21 -05:00
Rob Winch b8a362a60f Remove include servlet/saml2/index.adoc 2021-09-23 15:47:20 -05:00
Rob Winch c3dfb1711d Remove includes 2021-09-23 15:45:22 -05:00
Rob Winch 59e7a10732 Add dispatch 2021-09-23 15:45:22 -05:00
Rob Winch f01a13aa52 Antora
mkdir -p docs/modules/ROOT/
mkdir -p docs/modules/ROOT/pages/
git checkout antora-2.x docs/antora.yml
git checkout antora-2.x docs/modules/ROOT/nav.adoc
mv docs/manual/src/docs/asciidoc/images docs/modules/ROOT/
mv docs/manual/src/docs/asciidoc/_includes/* docs/modules/ROOT/pages/
cp ~/code/rwinch/spring-reference/*antora* ~/code/spring-projects/spring-security/
mv docs/modules/ROOT/pages/about docs/modules/ROOT/pages/overview
2021-09-23 15:45:22 -05:00
heowc 7b73b94198 Fix typo 2021-09-22 16:29:50 -06:00
bishoy basily 860690491a Add setBodyExtractor
Closes gh-10260
2021-09-22 15:32:19 -06:00
Josh Cummings c3ba2332da Wire BeanResolver into DefaultMethodSecurityExpressionHandler
Closes gh-10305
2021-09-22 14:14:29 -06:00
Josh Cummings 7b599d4770 Share JWKSource Instances
Closes gh-10312
2021-09-22 13:28:08 -06:00
Josh Cummings 4e7c9bee46 Add Supplier JwtDecoders
Closes gh-9991
2021-09-22 10:58:55 -06:00
heqiang 131078dcae Fix typo in digest.adoc
Closes gh-10304
2021-09-21 14:45:43 -04:00
Steve Riesenberg a5e4807912 Next Development Version 2021-09-20 13:13:33 -05:00
Steve Riesenberg e9449beb5f Release 5.6.0-M3 2021-09-20 11:47:26 -05:00
Marcus Da Coregio 220de60142 Update What's New in 5.6 2021-09-20 12:18:27 -03:00
Josh Cummings f0fd09bf79 Update What's New in 5.6 2021-09-20 09:09:45 -06:00
Rob Winch c58fbf0596 Update org.springframework to 5.3.10
Closes gh-10297
2021-09-17 16:40:58 -05:00
Rob Winch f736d4dd76 Update org.mockito to 3.12.4
Closes gh-10296
2021-09-17 16:40:58 -05:00
Rob Winch 0de2baeabb Update junit-bom to 5.8.0
Closes gh-10294
2021-09-17 16:40:58 -05:00
Rob Winch 7f3dee175c Update org.jetbrains.kotlinx to 1.5.2
Closes gh-10293
2021-09-17 16:40:58 -05:00
Rob Winch e846fb43f8 Update org.jetbrains.kotlin to 1.5.30
Closes gh-10292
2021-09-17 16:40:58 -05:00
Rob Winch de6b32ce77 Update hibernate-entitymanager to 5.5.7.Final
Closes gh-10291
2021-09-17 16:40:58 -05:00
Rob Winch d3c5c6add3 Update io.spring.nohttp to 0.0.10
Closes gh-10290
2021-09-17 16:40:58 -05:00
Rob Winch af61fae68a Update io.projectreactor to 2020.0.11
Closes gh-10288
2021-09-17 16:40:58 -05:00
Rob Winch 62db842865 Update com.nimbusds to 9.15
Closes gh-10287
2021-09-17 16:40:58 -05:00
Rob Winch 86c445f491 Update nebula-project-plugin to 8.2.0
Closes gh-10286
2021-09-17 16:40:58 -05:00
Rob Winch 7e334f8abc Update jackson-bom to 2.12.5
Closes gh-10283
2021-09-17 16:40:58 -05:00
Rob Winch 963d40a7cd Update logback-classic to 1.2.6
Closes gh-10282
2021-09-17 16:40:58 -05:00
Steve Riesenberg d207d03bf7 Update What's New for 5.6 2021-09-17 14:40:57 -05:00
Marcus Hert da Coregio ab098f171d Propagate TestSecurityContextHolder to SecurityContextHolder
Create SecurityMockMvcResultHandlers to define security related MockMvc ResultHandlers
Create a method to allow copying the SecurityContext from the TestSecurityContextHolder to SecurityContextHolder

Closes gh-9565
2021-09-17 16:39:53 -03:00
Marcus Da Coregio 017c218bbd Update What's New section
Adds the SAML 2.0 Single Logout Support and the new Saml2AuthenticationRequestRepository
2021-09-17 13:57:23 -03:00
Marcus Da Coregio 0364518b69 Update Saml2LoginConfigurer to pick up Saml2AuthenticationTokenConverter bean
Closes gh-10268
2021-09-17 08:13:19 -03:00
Eleftheria Stein 1e76b11b3c Remove duplicate entry from test LDIF file
Closes gh-10274
2021-09-16 10:26:06 +02:00
Ashley Scopes 171522ebf2 Replace usages of deprecated OAuth2IntrospectionClaimNames
Replace all usages of OAuth2IntrospectionClaimNames with
the suggested OAuth2TokenIntrospectionClaimNames.

There does not appear to be any further usages of OAuth2IntrospectionClaimNames,
so it should be suitable for removal when appropriate in accordance with the
deprecation policy.
2021-09-15 15:05:08 -06:00
Ashley Scopes 7ccc915b2b Ensuring consistency in error handling of opaque providers/managers
The OpaqueTokenAuthenticationProvider now propagates the cause of
introspection exceptions in the same way that the reactive
OpaqueTokenReactiveAuthenticationManager does.

Fixed a final field warning on both OpaqueTokenAuthenticationProvider
and OpaqueTokenReactiveAuthenticationManager.
2021-09-15 15:05:08 -06:00
Ashley Scopes e9d5bbba34 Fixed final field warnings in opaque token introspectors 2021-09-15 15:05:08 -06:00
Ashley Scopes 729418ad7a Fix typo in headers asciidoc 2021-09-15 15:05:08 -06:00
Ashley Scopes 95c2403968 Fixed potential NullPointerException in opaque token introspection
It appears Nimbus does not check the presence of the Content-Type
header before parsing it in some versions, and since prior to this
commit, the code is .toString()-ing the result, a malformed response
(such as that from a misbehaving cloud gateway) that does not include
a Content-Type would currently throw a NullPointerException.

In addition to this, I have added a little more information to the
log output for this module on the standard and reactive implementations
to aid in debugging authorization/authentication issues much more
easily.
2021-09-15 15:05:08 -06:00
Ashley Scopes dd43d9198b Amended treatment of OAuth2 'iss' claim
Prior to this commit, the OAuth2 resource server code is failing any issuer
that is not a valid URL. This does not correspond to
https://datatracker.ietf.org/doc/html/rfc7662#page-7 which redirects to
https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.1, defining an
issuer as being a "StringOrURI", which is defined at
https://datatracker.ietf.org/doc/html/rfc7519#page-5 as being
an "arbitrary string value" that "MUST be a URI" only for
"any value containing a ':'".

The issue currently is that an issuer that is not a valid URL may be
provided, which will automatically result in the request being aborted
due to being invalid.

I have removed the check entirely, since while the claim could be invalid,
it is still a response that the OAuth2 introspection endpoint has provided.
In the liklihood that interpretations of this behaviour are different for
the OAuth2 server implementation in use, this currently stops Spring
Security from being able to be used at all without implementing a custom
introspector from scratch.

It is also worth noting that the spec does not specify whether it is
valid to normalize issuers or not if they are valid URLs. This may cause
other unintended side effects as a result of this change, so it is
safer to disable it entirely.
2021-09-15 15:05:08 -06:00
Dmitriy Bogdanov fe274e7553 Fix some list punctuation and capitalization in docs 2021-09-15 10:49:02 -06:00
Dmitriy Bogdanov 31a8f8c4df Fix the use of "s" with code blocks in docs 2021-09-15 10:49:02 -06:00
Dmitriy Bogdanov af4cc03dec Fix some typos and mistakes in docs 2021-09-15 10:49:02 -06:00
Josh Cummings 194993ad1a Add Saml2ParameterNames
Closes gh-10270
2021-09-14 17:40:12 -06:00
Anthony Lofton 8cba9fbf9d Updated test.adoc SecurityMockServerConfigurers method references
Updated all references to SecurityMockServerConfigurers to refer to
correct methods.
Added documentation for mockJwt to include the
SecurityMockServerConfigurers class.
2021-09-14 15:04:08 -03:00
Josh Cummings 4f06fc6ed1 Add Saml2LogoutConfigurer
Closes gh-9497
2021-09-13 16:39:48 -06:00
Josh Cummings c63d618b26 Add Single Logout Support
Closes gh-8731
2021-09-13 16:39:48 -06:00
Josh Cummings 6488295cad Add RelyingPartyRegistrationResolver
Closes gh-9486
2021-09-13 16:39:48 -06:00
Josh Cummings f5a525e740 Add Registration to Saml2Authentication
Closes gh-9487
2021-09-13 16:39:48 -06:00
Josh Cummings 822e59af45 useJUnitPlatform for SAML 2.0 Tests
Issue gh-9467
2021-09-13 16:39:48 -06:00
Josh Cummings 5da55448f9 Polish SecurityContextChangedEvent
- Changed methods to getOldContext and getNewContext

Closes gh-10249
2021-09-13 16:04:36 -06:00
Josh Cummings 3e87ef84ae Replace SecurityContextHolder#addListener
Closes gh-10226
2021-09-13 15:57:06 -06:00
Derek Van Blerkom 58d50888df Fix return type to allow further security config 2021-09-13 15:31:02 -03:00
heqiang 3443eac829 Fix typo in index.adoc 2021-09-13 16:32:32 +02:00
Yanming Zhou f2b2e6002f Replace static "ROLE_" with customized role prefix
Fix gh-4134
2021-09-09 11:48:25 -06:00
Eleftheria Stein df6ed74303 Compile with parameter names
Closes gh-10240
2021-09-08 10:01:47 +02:00
Marcus Da Coregio 6fae98a6f4 Update docs to point to ACL samples
Closes gh-10110
2021-09-06 11:14:57 -03:00
Josh Cummings 989c1419d5 Clarify OAuth 2.0 Resource Server Multitenancy Snippet
Closes gh-10233
2021-09-03 16:54:41 -06:00
Ayush Kohli f1691370d6 Closes gh-10222 2021-09-03 10:58:01 -06:00
Ayush Kohli 1cfe84922c Add Java examples to session management docs
Closes gh-8979
2021-08-26 10:14:48 +02:00
/usr/local/ΕΨΗΕΛΩΝ 4302a86fad Default principalClaimName to SUB
Closes gh-10214
2021-08-20 15:02:22 -06:00
Rujun Chen 9b4ddd7e0a Make AuthorizationGrantTypeConverter support custom grant type
Closes gh-10155
2021-08-19 13:13:20 -04:00
Marcus Da Coregio d0fbe6b501 Update CI deployments to be dependent on Check Samples
Closes gh-10207
2021-08-19 10:13:38 -03:00
Marcus Da Coregio be91a78781 Update Check Samples job to run in parallel
Issue gh-9846
2021-08-17 11:15:10 -03:00
YevheniiLutsyshyn ac8e912ea1 Update a broken link to Spring Boot documentation 2021-08-17 11:33:49 +02:00
Rob Winch 87ec94a321 Next Development Version 2021-08-16 15:24:29 -05:00
Rob Winch b3cb7b388c Release 5.6.0-M2 2021-08-16 15:24:14 -05:00
Rob Winch 71f1cf1e0b Remove Remaining Sonar Reference
Issue gh-10205
2021-08-16 14:45:33 -05:00
Rob Winch 829733896c Remove unused Sonar from Build
Closes gh-10205
2021-08-16 14:42:38 -05:00
Rob Winch 1d4859a7ee Update org.slf4j to 1.7.32
Closes gh-10204
2021-08-16 14:18:54 -05:00
Rob Winch 0a40ce6181 Update htmlunit-driver to 2.52.0
Closes gh-10203
2021-08-16 14:18:54 -05:00
Rob Winch f8fad9d34d Update hibernate-entitymanager to 5.5.6
Closes gh-10202
2021-08-16 14:18:54 -05:00
Rob Winch f45f0b32b2 Update htmlunit to 2.52.0
Closes gh-10201
2021-08-16 14:18:54 -05:00
Rob Winch 0b068b9b81 Update io.projectreactor to 2020.0.10
Closes gh-10199
2021-08-16 14:18:54 -05:00
Rob Winch dd8048809b Update com.nimbusds to 9.12
Closes gh-10198
2021-08-16 14:18:54 -05:00
Rob Winch ad9da129a8 Update nebula-project-plugin to 8.1.0
Closes gh-10197
2021-08-16 14:18:54 -05:00
Rob Winch 9677ef17f3 Update logback-classic to 1.2.5
Closes gh-10196
2021-08-16 14:18:54 -05:00
Rob Winch 2765cd58f1 Exclude rc without versions from dependencyUpdates 2021-08-16 14:18:54 -05:00
Josh Cummings aed203f367 Docs for WebSessionServerLogoutHandler
Issue gh-4838
2021-08-16 13:09:42 -06:00
Bogdan Ilchyshyn a4c088a3b3 Introducing WebSessionServerLogoutHandler
Closes gh-4838
2021-08-16 13:08:35 -06:00
Josh Cummings cdc902d04d Update SpringOpaqueTokenIntrospector
Issue gh-9647
2021-08-12 16:52:02 -06:00
Dávid Kováč 3ff825576b Move and rename OAuth2IntrospectionClaimAccessor/Names
Introduced OAuth2TokenIntrospectionClaimAccessor and OAuth2TokenIntrospectionClaimNames
with copied implementation from OAuth2IntrospectionClaimAccessor/Names.
OAuth2IntrospectionClaimAccessor and OAuth2IntrospectionClaimNames are
now deprecated.

Also method getScopes() returning list of scopes was introduced
and getScope() is now deprecated.

Closes gh-9647
2021-08-12 16:51:33 -06:00
Josh Cummings b83a4c2985 Polish Preserve Null Claim Values
Preserves the original behavior of ClaimTypeConverter so that its
converters can maintain their default behavior of null meaning that
conversion failed.

Issue gh-10135
2021-08-12 10:22:44 -06:00
Fabio Guenci 30a1c1af7c Preserve Null Claim Values
Prior to this commit ClaimTypeConverter returned the claims with the
original value for all the claims with a null converted value.
The changes allows ClaimTypeConverter to overwrite and return claims
with converted value of null.

Closes gh-10135
2021-08-12 10:09:34 -06:00
Hiroshi Shirosaki 6f3e346b76 Add SecurityContextHolder#addListener
Closes gh-10032
2021-08-11 17:12:13 -06:00
Josh Cummings b8d51725c7 Immutable SecurityContext
Issue gh-10032
2021-08-11 17:12:13 -06:00
Eleftheria Stein 3ab6bee856 Make method static to prevent circular dependency error
Workaround for circular dependency between ServerHttpSecurityConfiguration and WebFluxConfigurationSupport.

Closes gh-10076
2021-08-11 13:46:45 +02:00
Steve Riesenberg 6d6dc113d8 Add converter for authentication result in OAuth2LoginAuthenticationFilter
Closes gh-10033
2021-08-10 16:50:19 -05:00
Steve Riesenberg fc553bf19a Add gh-10130 to tests 2021-08-09 15:33:54 -05:00
Steve Riesenberg acca3dba69 Polish gh-10131 2021-08-09 11:07:12 -05:00
Vincent Boulaye 044157061f Enable customizing headers in token requests
Adds the possibility to customize the headers of the access token request in AbstractWebClientReactiveOAuth2AccessTokenResponseClient, similarly to what is done in the AbstractOAuth2AuthorizationGrantRequestEntityConverter.

Closes gh-10130
2021-08-09 10:50:37 -05:00
Russell Allen 1806cebd64 Fix Assertion
Closes gh-10055
2021-08-09 10:09:06 -03:00
Marcus Da Coregio 87687bf03f Fix Gradle Plugin
Fix IncludeCheckRemotePlugin to add the default tasks even without defining the extension properties

Issue gh-9846
2021-07-30 11:29:53 -03:00
Josh Cummings d5c953b106 Polish Saml2AuthenticationRequestRepository
- Moved docs into AuthnRequest section, changed links to be more
semantically valuable to search engines
- Moved tests to be nearer to similar tests

Issue gh-9185
2021-07-27 14:56:23 -06:00
Marcus Da Coregio 662ab10416 Fix test getting stuck
The tests are getting stuck when running a single test class and the mock is performed in a static variable inside an inner class

Issue gh-6025
2021-07-27 14:55:53 -06:00
Marcus Da Coregio 16e17d242e Add Saml2AuthenticationRequestRepository
Closes gh-9185
2021-07-27 14:55:53 -06:00
Josh Cummings 6b68a6d62b Apply rnc2Xsd
Issue gh-8657
2021-07-27 13:22:42 -06:00
Marcus Da Coregio c562d56ff4 Add checkSamples task
Closes gh-9846
2021-07-27 14:11:51 -03:00
Josh Cummings 6370906ead Add SpringOpaqueTokenIntrospector
Closes gh-9354
2021-07-26 10:50:50 -06:00
Abdul Al-Faraj d1dfb2b9ee Improve OpenSAML Version Check
Closes gh-10077
2021-07-26 10:42:40 -06:00
Steve Riesenberg bfe94f9a80 Update deprecated usage in reference docs
Closes gh-10063
2021-07-21 10:21:02 -05:00
Eleftheria Stein 913f558ff4 Fix release instructions for generating changelog 2021-07-19 13:35:03 +02:00
Eleftheria Stein 21af328349 Next development version 2021-07-19 11:22:06 +02:00
644 changed files with 23069 additions and 5245 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
# EditorConfig for Spring Security
# see https://github.com/spring-projects/spring-security/blob/master/CONTRIBUTING.md#mind-the-whitespace
# see https://github.com/spring-projects/spring-security/blob/master/CONTRIBUTING.adoc#mind-the-whitespace
root = true
+1
View File
@@ -14,3 +14,4 @@
*.odg binary
*.otg binary
*.png binary
*.hsx binary
+5
View File
@@ -0,0 +1,5 @@
REPOSITORY_REF="$1"
TOKEN="$2"
curl -H "Accept: application/vnd.github.everest-preview+json" -H "Authorization: token ${TOKEN}" --request POST --data '{"event_type": "request-build"}' https://api.github.com/repos/${REPOSITORY_REF}/dispatches
echo "Requested Build for $REPOSITORY_REF"
+27
View File
@@ -0,0 +1,27 @@
name: reference
on:
push:
branches-ignore:
- 'gh-pages'
env:
GH_ACTIONS_REPO_TOKEN: ${{ secrets.GH_ACTIONS_REPO_TOKEN }}
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout Source
uses: actions/checkout@v2
- name: Generate antora.yml
run: ./gradlew :spring-security-docs:generateAntora
- name: Push generated antora files to the spring-generated-docs
uses: JamesIves/github-pages-deploy-action@4.1.4
with:
branch: "spring-security/main" # The branch the action should deploy to.
folder: "docs/build/generateAntora" # The folder the action should deploy.
repository-name: "spring-io/spring-generated-docs"
token: ${{ secrets.GH_ACTIONS_REPO_TOKEN }}
- name: Dispatch Build Request
run: ${GITHUB_WORKSPACE}/.github/actions/dispatch.sh 'spring-io/spring-reference' "$GH_ACTIONS_REPO_TOKEN"
@@ -45,7 +45,7 @@ jobs:
- name: Setup gradle user name
run: |
mkdir -p ~/.gradle
echo 'systemProp.user.name=spring-builds' >> ~/.gradle/gradle.properties
echo 'systemProp.user.name=spring-builds+github' >> ~/.gradle/gradle.properties
- name: Cache Gradle packages
uses: actions/cache@v2
with:
@@ -71,21 +71,39 @@ jobs:
- name: Setup gradle user name
run: |
mkdir -p ~/.gradle
echo 'systemProp.user.name=spring-builds' >> ~/.gradle/gradle.properties
echo 'systemProp.user.name=spring-builds+github' >> ~/.gradle/gradle.properties
- name: Snapshot Tests
run: |
export GRADLE_ENTERPRISE_CACHE_USERNAME="$GRADLE_ENTERPRISE_CACHE_USER"
export GRADLE_ENTERPRISE_CACHE_PASSWORD="$GRADLE_ENTERPRISE_CACHE_PASSWORD"
export GRADLE_ENTERPRISE_ACCESS_KEY="$GRADLE_ENTERPRISE_SECRET_ACCESS_KEY"
./gradlew test --refresh-dependencies -PartifactoryUsername="$ARTIFACTORY_USERNAME" -PartifactoryPassword="$ARTIFACTORY_PASSWORD" -PforceMavenRepositories=snapshot -PspringVersion='5.+' -PreactorVersion='20+' -PspringDataVersion='Neumann-BUILD-SNAPSHOT' -PrsocketVersion=1.1.0-SNAPSHOT -PspringBootVersion=2.4.0-SNAPSHOT -PlocksDisabled --stacktrace
sonar_analysis:
name: Static Code Analysis
check_samples:
name: Check Samples project
needs: [prerequisites]
runs-on: ubuntu-latest
if: needs.prerequisites.outputs.runjobs
env:
SONAR_URL: ${{ secrets.SONAR_URL }}
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
steps:
- uses: actions/checkout@v2
- name: Set up JDK
uses: actions/setup-java@v1
with:
java-version: '11'
- name: Setup gradle user name
run: |
mkdir -p ~/.gradle
echo 'systemProp.user.name=spring-builds+github' >> ~/.gradle/gradle.properties
- name: Check samples project
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 checkSamples --stacktrace
check_tangles:
name: Check for Package Tangles
needs: [ prerequisites ]
runs-on: ubuntu-latest
if: needs.prerequisites.outputs.runjobs
steps:
- uses: actions/checkout@v2
- name: Set up JDK
@@ -96,24 +114,15 @@ jobs:
run: |
mkdir -p ~/.gradle
echo 'systemProp.user.name=spring-builds' >> ~/.gradle/gradle.properties
- name: Run Sonar on given (non-main) branch
if: ${{ github.ref != 'refs/heads/main' }}
run: |
export BRANCH=${GITHUB_REF#refs/heads/}
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 sonarqube -PartifactoryUsername="$ARTIFACTORY_USERNAME" -PartifactoryPassword="$ARTIFACTORY_PASSWORD" -PexcludeProjects='**/samples/**' -Dsonar.projectKey="spring-security-${GITHUB_REF#refs/heads/}" -Dsonar.projectName="spring-security-${GITHUB_REF#refs/heads/}" -Dsonar.host.url="$SONAR_URL" -Dsonar.login="$SONAR_TOKEN" --stacktrace
- name: Run Sonar on main
if: ${{ github.ref == 'refs/heads/main' }}
- name: Check for package tangles
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 sonarqube -PartifactoryUsername="$ARTIFACTORY_USERNAME" -PartifactoryPassword="$ARTIFACTORY_PASSWORD" -PexcludeProjects='**/samples/**' -Dsonar.host.url="$SONAR_URL" -Dsonar.login="$SONAR_TOKEN" --stacktrace
./gradlew check s101 --stacktrace
deploy_artifacts:
name: Deploy Artifacts
needs: [build_jdk_11, snapshot_tests, sonar_analysis]
needs: [build_jdk_11, snapshot_tests, check_samples, check_tangles]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
@@ -124,7 +133,7 @@ jobs:
- name: Setup gradle user name
run: |
mkdir -p ~/.gradle
echo 'systemProp.user.name=spring-builds' >> ~/.gradle/gradle.properties
echo 'systemProp.user.name=spring-builds+github' >> ~/.gradle/gradle.properties
- name: Deploy artifacts
run: |
export GRADLE_ENTERPRISE_CACHE_USERNAME="$GRADLE_ENTERPRISE_CACHE_USER"
@@ -140,7 +149,7 @@ jobs:
ARTIFACTORY_PASSWORD: ${{ secrets.ARTIFACTORY_PASSWORD }}
deploy_docs:
name: Deploy Docs
needs: [build_jdk_11, snapshot_tests, sonar_analysis]
needs: [build_jdk_11, snapshot_tests, check_samples, check_tangles]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
@@ -151,7 +160,7 @@ jobs:
- name: Setup gradle user name
run: |
mkdir -p ~/.gradle
echo 'systemProp.user.name=spring-builds' >> ~/.gradle/gradle.properties
echo 'systemProp.user.name=spring-builds+github' >> ~/.gradle/gradle.properties
- name: Deploy Docs
run: |
export GRADLE_ENTERPRISE_CACHE_USERNAME="$GRADLE_ENTERPRISE_CACHE_USER"
@@ -164,7 +173,7 @@ jobs:
DOCS_HOST: ${{ secrets.DOCS_HOST }}
deploy_schema:
name: Deploy Schema
needs: [build_jdk_11, snapshot_tests, sonar_analysis]
needs: [build_jdk_11, snapshot_tests, check_samples, check_tangles]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
@@ -175,7 +184,7 @@ jobs:
- name: Setup gradle user name
run: |
mkdir -p ~/.gradle
echo 'systemProp.user.name=spring-builds' >> ~/.gradle/gradle.properties
echo 'systemProp.user.name=spring-builds+github' >> ~/.gradle/gradle.properties
- name: Deploy Schema
run: |
export GRADLE_ENTERPRISE_CACHE_USERNAME="$GRADLE_ENTERPRISE_CACHE_USER"
@@ -188,7 +197,7 @@ jobs:
DOCS_HOST: ${{ secrets.DOCS_HOST }}
notify_result:
name: Check for failures
needs: [build_jdk_11, snapshot_tests, sonar_analysis, deploy_artifacts, deploy_docs, deploy_schema]
needs: [build_jdk_11, snapshot_tests, check_samples, check_tangles, deploy_artifacts, deploy_docs, deploy_schema]
if: failure()
runs-on: ubuntu-latest
steps:
+1 -1
View File
@@ -143,7 +143,7 @@ Generate the Release Notes replacing:
* <next-version> - Replace with the milestone you are releasing now (i.e. 5.5.0-RC1)
----
$ ./gradlew generateChangelog -P<next-version>
$ ./gradlew generateChangelog -PnextVersion=<next-version>
----
* Copy the release notes to your clipboard (your mileage may vary with
@@ -37,7 +37,9 @@ import org.springframework.util.Assert;
* {@link PermissionGrantingStrategy} and {@link AclAuthorizationStrategy} instances.
*
* @author Ben Alex
* @deprecated since 5.6. In favor of JCache based implementations
*/
@Deprecated
public class EhCacheBasedAclCache implements AclCache {
private final Ehcache cache;
+17 -3
View File
@@ -1,10 +1,10 @@
buildscript {
dependencies {
classpath "io.spring.javaformat:spring-javaformat-gradle-plugin:$springJavaformatVersion"
classpath 'io.spring.nohttp:nohttp-gradle:0.0.8'
classpath 'io.spring.nohttp:nohttp-gradle:0.0.10'
classpath "io.freefair.gradle:aspectj-plugin:5.3.3.3"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
classpath "com.netflix.nebula:nebula-project-plugin:8.0.0"
classpath "com.netflix.nebula:nebula-project-plugin:8.2.0"
}
repositories {
maven { url 'https://repo.spring.io/plugins-snapshot' }
@@ -14,7 +14,9 @@ buildscript {
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.sagan'
@@ -56,7 +58,6 @@ updateDependenciesSettings {
}
addFiles({
return [
project.file("buildSrc/src/main/java/io/spring/gradle/convention/AsciidoctorConventionPlugin.java"),
project.file("buildSrc/src/main/groovy/io/spring/gradle/convention/CheckstylePlugin.groovy")
]
})
@@ -103,6 +104,7 @@ subprojects {
}
tasks.withType(JavaCompile) {
options.encoding = "UTF-8"
options.compilerArgs.add("-parameters")
}
}
@@ -145,3 +147,15 @@ nohttp {
source.exclude "buildSrc/build/**"
}
tasks.register('checkSamples') {
includeCheckRemote {
repository = 'spring-projects/spring-security-samples'
ref = samplesBranch
}
dependsOn checkRemote
}
s101 {
configurationDirectory = project.file("etc/s101")
}
+12 -11
View File
@@ -56,6 +56,10 @@ gradlePlugin {
id = "org.springframework.github.changelog"
implementationClass = "org.springframework.gradle.github.changelog.GitHubChangelogPlugin"
}
s101 {
id = "s101"
implementationClass = "s101.S101Plugin"
}
}
}
@@ -72,29 +76,26 @@ dependencies {
implementation localGroovy()
implementation 'io.github.gradle-nexus:publish-plugin:1.1.0'
implementation 'io.projectreactor:reactor-core:3.4.8'
implementation 'io.projectreactor:reactor-core:3.4.11'
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'
implementation 'io.spring.gradle:docbook-reference-plugin:0.3.1'
implementation 'io.spring.gradle:propdeps-plugin:0.0.10.RELEASE'
implementation 'com.github.spullara.mustache.java:compiler:0.9.4'
implementation 'io.spring.javaformat:spring-javaformat-gradle-plugin:0.0.15'
implementation 'io.spring.nohttp:nohttp-gradle:0.0.8'
implementation 'org.aim42:htmlSanityCheck:1.1.6'
implementation 'org.asciidoctor:asciidoctor-gradle-jvm:3.1.0'
implementation 'org.asciidoctor:asciidoctor-gradle-jvm-pdf:3.1.0'
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.9.10'
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.7.2')
testImplementation platform('org.junit:junit-bom:5.8.1')
testImplementation "org.junit.jupiter:junit-jupiter-api"
testImplementation "org.junit.jupiter:junit-jupiter-params"
testImplementation "org.junit.jupiter:junit-jupiter-engine"
testImplementation 'org.apache.commons:commons-io:1.3.2'
testImplementation 'org.assertj:assertj-core:3.13.2'
testImplementation 'org.mockito:mockito-core:3.11.2'
testImplementation 'org.mockito:mockito-junit-jupiter:3.11.2'
testImplementation 'org.mockito:mockito-core:3.12.4'
testImplementation 'org.mockito:mockito-junit-jupiter:3.12.4'
testImplementation "com.squareup.okhttp3:mockwebserver:3.14.9"
}
+1 -1
View File
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.2-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
@@ -0,0 +1,104 @@
/*
* 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
import groovy.transform.CompileStatic
import groovy.transform.TypeChecked
import groovy.transform.TypeCheckingMode
import org.gradle.api.DefaultTask
import org.gradle.api.Task
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.OutputDirectory
import org.gradle.api.tasks.TaskAction
/**
* Checkout a project template from a git repository.
*
* @author Marcus Da Coregio
*/
@CompileStatic
abstract class IncludeRepoTask extends DefaultTask {
private static final String DEFAULT_URI_PREFIX = 'https://github.com/'
/**
* Git repository to use. Will be prefixed with {@link #DEFAULT_URI_PREFIX} if it isn't already
* @return
*/
@Input
abstract Property<String> getRepository();
/**
* Git reference to use.
*/
@Input
abstract Property<String> getRef()
/**
* Directory where the project template should be copied.
*/
@OutputDirectory
final File outputDirectory = project.file("$project.buildDir/$name")
@TaskAction
void checkoutAndCopy() {
outputDirectory.deleteDir()
File checkoutDir = checkout(this, getRemoteUri(), ref.get())
moveToOutputDir(checkoutDir, outputDirectory)
}
private static File cleanTemporaryDir(Task task, File tmpDir) {
if (tmpDir.exists()) {
task.project.delete(tmpDir)
}
return tmpDir
}
static File checkout(Task task, String remoteUri, String ref) {
checkout(task, remoteUri, ref, task.getTemporaryDir())
}
@TypeChecked(TypeCheckingMode.SKIP)
static File checkout(Task task, String remoteUri, String ref, File checkoutDir) {
cleanTemporaryDir(task, checkoutDir)
task.project.exec {
commandLine = ["git", "clone", "--no-checkout", remoteUri, checkoutDir.absolutePath]
errorOutput = System.err
}
task.project.exec {
commandLine = ["git", "checkout", ref]
workingDir = checkoutDir
errorOutput = System.err
}
return checkoutDir
}
private static void moveToOutputDir(File tmpDir, File outputDirectory) {
File baseDir = tmpDir
baseDir.renameTo(outputDirectory)
}
private String getRemoteUri() {
String remoteUri = this.repository.get()
if (remoteUri.startsWith(DEFAULT_URI_PREFIX)) {
return remoteUri
}
return DEFAULT_URI_PREFIX + remoteUri
}
}
@@ -16,20 +16,17 @@
package io.spring.gradle.convention;
import io.spring.gradle.propdeps.PropDepsMavenPlugin;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.plugins.GroovyPlugin;
import org.gradle.api.plugins.JavaPlugin;
import org.gradle.api.plugins.MavenPlugin;
import org.gradle.api.plugins.JavaPlugin
import org.gradle.api.plugins.PluginManager;
import org.gradle.internal.impldep.org.apache.maven.Maven;
import org.gradle.plugins.ide.eclipse.EclipseWtpPlugin;
import org.gradle.plugins.ide.idea.IdeaPlugin;
import io.spring.gradle.propdeps.PropDepsEclipsePlugin;
import io.spring.gradle.propdeps.PropDepsIdeaPlugin;
import io.spring.gradle.propdeps.PropDepsPlugin
import org.springframework.gradle.CopyPropertiesPlugin;
import org.springframework.gradle.CopyPropertiesPlugin
import org.springframework.gradle.propdeps.PropDepsEclipsePlugin
import org.springframework.gradle.propdeps.PropDepsIdeaPlugin
import org.springframework.gradle.propdeps.PropDepsPlugin;
/**
* @author Rob Winch
@@ -40,7 +37,7 @@ public abstract class AbstractSpringJavaPlugin implements Plugin<Project> {
public final void apply(Project project) {
PluginManager pluginManager = project.getPluginManager();
pluginManager.apply(JavaPlugin.class);
pluginManager.apply(ManagementConfigurationPlugin.class);
pluginManager.apply(ManagementConfigurationPlugin.class)
if (project.file("src/main/groovy").exists()
|| project.file("src/test/groovy").exists()
|| project.file("src/integration-test/groovy").exists()) {
@@ -52,9 +49,6 @@ public abstract class AbstractSpringJavaPlugin implements Plugin<Project> {
pluginManager.apply(PropDepsPlugin);
pluginManager.apply(PropDepsEclipsePlugin);
pluginManager.apply(PropDepsIdeaPlugin);
project.getPlugins().withType(MavenPlugin) {
pluginManager.apply(PropDepsMavenPlugin);
}
pluginManager.apply("io.spring.convention.tests-configuration");
pluginManager.apply("io.spring.convention.integration-test");
pluginManager.apply("io.spring.convention.javadoc-options");
@@ -36,7 +36,7 @@ class CheckstylePlugin implements Plugin<Project> {
if (checkstyleDir.exists() && checkstyleDir.directory) {
project.getPluginManager().apply('checkstyle')
project.dependencies.add('checkstyle', 'io.spring.javaformat:spring-javaformat-checkstyle:0.0.15')
project.dependencies.add('checkstyle', 'io.spring.nohttp:nohttp-checkstyle:0.0.8')
project.dependencies.add('checkstyle', 'io.spring.nohttp:nohttp-checkstyle:0.0.10')
project.checkstyle {
configDirectory = checkstyleDir
@@ -1,12 +1,9 @@
package io.spring.gradle.convention
import org.aim42.htmlsanitycheck.HtmlSanityCheckPlugin
import org.aim42.htmlsanitycheck.HtmlSanityCheckTask
import org.aim42.htmlsanitycheck.check.BrokenHttpLinksChecker
import org.asciidoctor.gradle.jvm.AbstractAsciidoctorTask
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.plugins.BasePlugin
import org.gradle.api.plugins.PluginManager
import org.gradle.api.tasks.bundling.Zip
@@ -19,54 +16,18 @@ public class DocsPlugin implements Plugin<Project> {
public void apply(Project project) {
PluginManager pluginManager = project.getPluginManager();
pluginManager.apply("org.asciidoctor.jvm.convert");
pluginManager.apply("org.asciidoctor.jvm.pdf");
pluginManager.apply(AsciidoctorConventionPlugin);
pluginManager.apply(BasePlugin);
pluginManager.apply(DeployDocsPlugin);
pluginManager.apply(JavadocApiPlugin);
pluginManager.apply(HtmlSanityCheckPlugin)
String projectName = Utils.getProjectName(project);
String pdfFilename = projectName + "-reference.pdf";
project.tasks.withType(AbstractAsciidoctorTask) { t ->
project.configure(t) {
sources {
include "**/*.adoc"
exclude '_*/**'
}
}
}
project.tasks.withType(HtmlSanityCheckTask) { HtmlSanityCheckTask t ->
project.configure(t) {
t.dependsOn 'asciidoctor'
t.checkerClasses = [BrokenHttpLinksChecker]
t.checkingResultsDir = new File(project.getBuildDir(), "/report/htmlchecks")
t.failOnErrors = false
t.httpConnectionTimeout = 3000
t.sourceDir = new File(project.getBuildDir(), "/docs/asciidoc/")
t.sourceDocuments = project.files(new File(project.getBuildDir(), "/docs/asciidoc/index.html"))
}
}
Task docsZip = project.tasks.create('docsZip', Zip) {
dependsOn 'api', 'asciidoctor'
dependsOn 'api'
group = 'Distribution'
archiveBaseName = project.rootProject.name
archiveClassifier = 'docs'
description = "Builds -${classifier} archive containing all " +
"Docs for deployment at docs.spring.io"
from(project.tasks.asciidoctor.outputs) {
into 'reference/html5'
include '**'
}
from(project.tasks.asciidoctorPdf.outputs) {
into 'reference/pdf'
include '**'
rename "index.pdf", pdfFilename
}
from(project.tasks.api.outputs) {
into 'api'
}
@@ -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']
}
}
@@ -15,7 +15,6 @@
*/
package io.spring.gradle.convention
import io.spring.gradle.propdeps.PropDepsPlugin
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.Task
@@ -24,6 +23,7 @@ import org.gradle.api.plugins.JavaPlugin
import org.gradle.api.tasks.testing.Test
import org.gradle.plugins.ide.eclipse.EclipsePlugin
import org.gradle.plugins.ide.idea.IdeaPlugin
import org.springframework.gradle.propdeps.PropDepsPlugin
/**
*
@@ -54,7 +54,7 @@ public class IntegrationTestPlugin implements Plugin<Project> {
}
project.configurations {
integrationTestCompile {
extendsFrom testCompile, testImplementation
extendsFrom testImplementation
}
integrationTestRuntime {
extendsFrom integrationTestCompile, testRuntime, testRuntimeOnly
@@ -1,7 +1,21 @@
/*
* 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.propdeps.PropDepsPlugin;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.artifacts.ConfigurationContainer;
@@ -12,6 +26,8 @@ import org.gradle.api.publish.PublishingExtension;
import org.gradle.api.publish.maven.MavenPublication;
import org.gradle.api.publish.maven.plugins.MavenPublishPlugin;
import org.springframework.gradle.propdeps.PropDepsPlugin;
/**
* Creates a Management configuration that is appropriate for adding a platform to that is not exposed externally. If
* the JavaPlugin is applied, the compileClasspath, runtimeClasspath, testCompileClasspath, and testRuntimeClasspath
@@ -55,5 +71,4 @@ public class ManagementConfigurationPlugin implements Plugin<Project> {
}));
});
}
}
}
@@ -1,26 +0,0 @@
package io.spring.gradle.convention
import org.gradle.api.Plugin
import org.gradle.api.Project
public class OssrhPlugin implements Plugin<Project> {
@Override
public void apply(Project project) {
if(project.hasProperty('ossrhUsername')) {
project.uploadArchives {
repositories {
mavenDeployer {
repository(url: "https://oss.sonatype.org/service/local/staging/deploy/maven2/") {
authentication(userName: project.ossrhUsername, password: project.ossrhPassword)
}
snapshotRepository(url: "https://oss.sonatype.org/content/repositories/snapshots/") {
authentication(userName: project.ossrhUsername, password: project.ossrhPassword)
}
}
}
}
}
}
}
@@ -37,10 +37,7 @@ class SpringModulePlugin extends AbstractSpringJavaPlugin {
def deployArtifacts = project.task("deployArtifacts")
deployArtifacts.group = 'Deploy tasks'
deployArtifacts.description = "Deploys the artifacts to either Artifactory or Maven Central"
if (Utils.isRelease(project)) {
deployArtifacts.dependsOn project.tasks.uploadArchives
}
else {
if (!Utils.isRelease(project)) {
deployArtifacts.dependsOn project.tasks.artifactoryPublish
}
}
@@ -1,208 +0,0 @@
/*
* Copyright 2019-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.spring.gradle.convention;
import org.asciidoctor.gradle.base.AsciidoctorAttributeProvider;
import org.asciidoctor.gradle.jvm.AbstractAsciidoctorTask;
import org.asciidoctor.gradle.jvm.AsciidoctorJExtension;
import org.asciidoctor.gradle.jvm.AsciidoctorJPlugin;
import org.asciidoctor.gradle.jvm.AsciidoctorTask;
import org.gradle.api.Action;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.artifacts.DependencySet;
import org.gradle.api.artifacts.dsl.RepositoryHandler;
import org.gradle.api.file.CopySpec;
import org.gradle.api.file.FileTree;
import org.gradle.api.tasks.Sync;
import java.io.File;
import java.net.URI;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.function.Consumer;
/**
* Conventions that are applied in the presence of the {@link AsciidoctorJPlugin}. When
* the plugin is applied:
*
* <ul>
* <li>All warnings are made fatal.
* <li>A task is created to resolve and unzip our documentation resources (CSS and
* Javascript).
* <li>For each {@link AsciidoctorTask} (HTML only):
* <ul>
* <li>A configuration named asciidoctorExtensions is ued to add the
* <a href="https://github.com/spring-io/spring-asciidoctor-extensions#block-switch">block
* switch</a> extension
* <li>{@code doctype} {@link AsciidoctorTask#options(Map) option} is configured.
* <li>{@link AsciidoctorTask#attributes(Map) Attributes} are configured for syntax
* highlighting, CSS styling, docinfo, etc.
* </ul>
* <li>For each {@link AbstractAsciidoctorTask} (HTML and PDF):
* <ul>
* <li>{@link AsciidoctorTask#attributes(Map) Attributes} are configured to enable
* warnings for references to missing attributes, the year is added as @{code today-year},
* etc
* <li>{@link AbstractAsciidoctorTask#baseDirFollowsSourceDir() baseDirFollowsSourceDir()}
* is enabled.
* </ul>
* </ul>
*
* @author Andy Wilkinson
* @author Rob Winch
*/
public class AsciidoctorConventionPlugin implements Plugin<Project> {
public void apply(Project project) {
project.getPlugins().withType(AsciidoctorJPlugin.class, (asciidoctorPlugin) -> {
createDefaultAsciidoctorRepository(project);
makeAllWarningsFatal(project);
Sync unzipResources = createUnzipDocumentationResourcesTask(project);
project.getTasks().withType(AbstractAsciidoctorTask.class, (asciidoctorTask) -> {
asciidoctorTask.dependsOn(unzipResources);
configureExtensions(project, asciidoctorTask);
configureCommonAttributes(project, asciidoctorTask);
configureOptions(asciidoctorTask);
asciidoctorTask.baseDirFollowsSourceDir();
asciidoctorTask.useIntermediateWorkDir();
asciidoctorTask.resources(new Action<CopySpec>() {
@Override
public void execute(CopySpec resourcesSpec) {
resourcesSpec.from(unzipResources);
resourcesSpec.from(asciidoctorTask.getSourceDir(), new Action<CopySpec>() {
@Override
public void execute(CopySpec resourcesSrcDirSpec) {
// https://github.com/asciidoctor/asciidoctor-gradle-plugin/issues/523
// For now copy the entire sourceDir over so that include files are
// available in the intermediateWorkDir
// resourcesSrcDirSpec.include("images/**");
}
});
}
});
if (asciidoctorTask instanceof AsciidoctorTask) {
configureHtmlOnlyAttributes(project, asciidoctorTask);
}
});
});
}
private void createDefaultAsciidoctorRepository(Project project) {
project.getGradle().afterProject(new Action<Project>() {
@Override
public void execute(Project project) {
RepositoryHandler repositories = project.getRepositories();
if (repositories.isEmpty()) {
repositories.mavenCentral();
repositories.maven(repo -> {
repo.setUrl(URI.create("https://repo.spring.io/release"));
});
}
}
});
}
private void makeAllWarningsFatal(Project project) {
project.getExtensions().getByType(AsciidoctorJExtension.class).fatalWarnings(".*");
}
private void configureExtensions(Project project, AbstractAsciidoctorTask asciidoctorTask) {
Configuration extensionsConfiguration = project.getConfigurations().maybeCreate("asciidoctorExtensions");
extensionsConfiguration.defaultDependencies(new Action<DependencySet>() {
@Override
public void execute(DependencySet dependencies) {
dependencies.add(project.getDependencies().create("io.spring.asciidoctor:spring-asciidoctor-extensions-block-switch:0.4.2.RELEASE"));
}
});
asciidoctorTask.configurations(extensionsConfiguration);
}
private Sync createUnzipDocumentationResourcesTask(Project project) {
Configuration documentationResources = project.getConfigurations().maybeCreate("documentationResources");
documentationResources.getDependencies()
.add(project.getDependencies().create("io.spring.docresources:spring-doc-resources:0.2.5"));
Sync unzipResources = project.getTasks().create("unzipDocumentationResources",
Sync.class, new Action<Sync>() {
@Override
public void execute(Sync sync) {
sync.dependsOn(documentationResources);
sync.from(new Callable<List<FileTree>>() {
@Override
public List<FileTree> call() throws Exception {
List<FileTree> result = new ArrayList<>();
documentationResources.getAsFileTree().forEach(new Consumer<File>() {
@Override
public void accept(File file) {
result.add(project.zipTree(file));
}
});
return result;
}
});
File destination = new File(project.getBuildDir(), "docs/resources");
sync.into(project.relativePath(destination));
}
});
return unzipResources;
}
private void configureOptions(AbstractAsciidoctorTask asciidoctorTask) {
asciidoctorTask.options(Collections.singletonMap("doctype", "book"));
}
private void configureHtmlOnlyAttributes(Project project, AbstractAsciidoctorTask asciidoctorTask) {
Map<String, Object> attributes = new HashMap<>();
attributes.put("source-highlighter", "highlight.js");
attributes.put("highlightjsdir", "js/highlight");
attributes.put("highlightjs-theme", "github");
attributes.put("linkcss", true);
attributes.put("icons", "font");
attributes.put("stylesheet", "css/spring.css");
asciidoctorTask.getAttributeProviders().add(new AsciidoctorAttributeProvider() {
@Override
public Map<String, Object> getAttributes() {
Object version = project.getVersion();
Map<String, Object> attrs = new HashMap<>();
if (version != null && version.toString() != Project.DEFAULT_VERSION) {
attrs.put("revnumber", version);
}
return attrs;
}
});
asciidoctorTask.attributes(attributes);
}
private void configureCommonAttributes(Project project, AbstractAsciidoctorTask asciidoctorTask) {
Map<String, Object> attributes = new HashMap<>();
attributes.put("attribute-missing", "warn");
attributes.put("icons", "font");
attributes.put("idprefix", "");
attributes.put("idseparator", "-");
attributes.put("docinfo", "shared");
attributes.put("sectanchors", "");
attributes.put("sectnums", "");
attributes.put("today-year", LocalDate.now().getYear());
asciidoctorTask.attributes(attributes);
}
}
@@ -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.gradle.propdeps
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.plugins.ide.eclipse.EclipsePlugin
/**
* Plugin to allow optional and provided dependency configurations to work with the
* standard gradle 'eclipse' plugin
*
* @author Phillip Webb
*/
class PropDepsEclipsePlugin implements Plugin<Project> {
public void apply(Project project) {
project.plugins.apply(PropDepsPlugin)
project.plugins.apply(EclipsePlugin)
project.eclipse {
classpath {
plusConfigurations += [project.configurations.provided, project.configurations.optional]
}
}
}
}
@@ -0,0 +1,46 @@
/*
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.gradle.propdeps
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.plugins.ide.idea.IdeaPlugin
/**
* Plugin to allow optional and provided dependency configurations to work with the
* standard gradle 'idea' plugin
*
* @author Phillip Webb
* @author Brian Clozel
* @link https://youtrack.jetbrains.com/issue/IDEA-107046
* @link https://youtrack.jetbrains.com/issue/IDEA-117668
*/
class PropDepsIdeaPlugin implements Plugin<Project> {
public void apply(Project project) {
project.plugins.apply(PropDepsPlugin)
project.plugins.apply(IdeaPlugin)
project.idea.module {
// IDEA internally deals with 4 scopes : COMPILE, TEST, PROVIDED, RUNTIME
// but only PROVIDED seems to be picked up
scopes.PROVIDED.plus += [project.configurations.provided]
scopes.PROVIDED.plus += [project.configurations.optional]
}
}
}
@@ -0,0 +1,76 @@
/*
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.gradle.propdeps
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.artifacts.Configuration
import org.gradle.api.plugins.JavaLibraryPlugin
import org.gradle.api.plugins.JavaPlugin
import org.gradle.api.tasks.javadoc.Javadoc
/**
* Plugin to allow 'optional' and 'provided' dependency configurations
*
* As stated in the maven documentation, provided scope "is only available on the compilation and test classpath,
* and is not transitive".
*
* This plugin creates two new configurations, and each one:
* <ul>
* <li>is a parent of the compile configuration</li>
* <li>is not visible, not transitive</li>
* <li>all dependencies are excluded from the default configuration</li>
* </ul>
*
* @author Phillip Webb
* @author Brian Clozel
* @author Rob Winch
*
* @see <a href="https://www.gradle.org/docs/current/userguide/java_plugin.html#N121CF">Maven documentation</a>
* @see <a href="https://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html#Dependency_Scope">Gradle configurations</a>
* @see PropDepsEclipsePlugin
* @see PropDepsIdeaPlugin
*/
class PropDepsPlugin implements Plugin<Project> {
public void apply(Project project) {
project.plugins.apply(JavaPlugin)
Configuration provided = addConfiguration(project, "provided")
Configuration optional = addConfiguration(project, "optional")
Javadoc javadoc = project.tasks.getByName(JavaPlugin.JAVADOC_TASK_NAME)
javadoc.classpath = javadoc.classpath.plus(provided).plus(optional)
}
private Configuration addConfiguration(Project project, String name) {
Configuration configuration = project.configurations.create(name)
configuration.extendsFrom(project.configurations.implementation)
project.plugins.withType(JavaLibraryPlugin, {
configuration.extendsFrom(project.configurations.api)
})
project.sourceSets.all {
compileClasspath += configuration
runtimeClasspath += configuration
}
return configuration
}
}
@@ -166,7 +166,7 @@ public class UpdateDependenciesExtension {
}
public DependencyExcludes releaseCandidatesVersions() {
this.actions.add(excludeVersionWithRegex("(?i).*?rc\\d+.*", "a release candidate version"));
this.actions.add(excludeVersionWithRegex("(?i).*?rc.*", "a release candidate version"));
return this;
}
@@ -0,0 +1,35 @@
/*
* 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 s101;
import java.io.File;
import org.gradle.api.DefaultTask;
import org.gradle.api.tasks.TaskAction;
public class S101Configure extends DefaultTask {
@TaskAction
public void configure() throws Exception {
S101PluginExtension extension = getProject().getExtensions().getByType(S101PluginExtension.class);
File buildDirectory = extension.getInstallationDirectory().get();
File projectDirectory = extension.getConfigurationDirectory().get();
S101Configurer configurer = new S101Configurer(getProject());
configurer.configure(buildDirectory, projectDirectory);
}
}
@@ -0,0 +1,271 @@
/*
* 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 s101;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.UncheckedIOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import com.gargoylesoftware.htmlunit.WebClient;
import com.gargoylesoftware.htmlunit.html.HtmlAnchor;
import com.gargoylesoftware.htmlunit.html.HtmlPage;
import com.github.mustachejava.DefaultMustacheFactory;
import com.github.mustachejava.Mustache;
import com.github.mustachejava.MustacheFactory;
import org.apache.commons.io.IOUtils;
import org.gradle.api.Project;
import org.gradle.api.logging.Logger;
import org.gradle.api.tasks.SourceSet;
import org.gradle.api.tasks.SourceSetContainer;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
public class S101Configurer {
private static final Pattern VERSION = Pattern.compile("<local-project .* version=\"(.*?)\"");
private static final int BUFFER = 1024;
private static final long TOOBIG = 0x10000000; // ~268M
private static final int TOOMANY = 200;
private final MustacheFactory mustache = new DefaultMustacheFactory();
private final Mustache hspTemplate;
private final Mustache repositoryTemplate;
private final Project project;
private final Logger logger;
public S101Configurer(Project project) {
this.project = project;
this.logger = project.getLogger();
Resource template = new ClassPathResource("s101/project.java.hsp");
try (InputStream is = template.getInputStream()) {
this.hspTemplate = this.mustache.compile(new InputStreamReader(is), "project");
} catch (IOException ex) {
throw new UncheckedIOException(ex);
}
template = new ClassPathResource("s101/repository.xml");
try (InputStream is = template.getInputStream()) {
this.repositoryTemplate = this.mustache.compile(new InputStreamReader(is), "repository");
} catch (IOException ex) {
throw new UncheckedIOException(ex);
}
}
public void install(File installationDirectory, File configurationDirectory) {
deleteDirectory(installationDirectory);
installBuildTool(installationDirectory, configurationDirectory);
}
public void configure(File installationDirectory, File configurationDirectory) {
deleteDirectory(configurationDirectory);
String version = computeVersionFromInstallation(installationDirectory);
configureProject(version, configurationDirectory);
}
private String computeVersionFromInstallation(File installationDirectory) {
File buildJar = new File(installationDirectory, "structure101-java-build.jar");
try (JarInputStream input = new JarInputStream(new FileInputStream(buildJar))) {
JarEntry entry;
while ((entry = input.getNextJarEntry()) != null) {
if (entry.getName().contains("structure101-build.properties")) {
Properties properties = new Properties();
properties.load(input);
return properties.getProperty("s101-build");
}
}
} catch (Exception ex) {
throw new RuntimeException(ex);
}
throw new IllegalStateException("Unable to determine Structure101 version");
}
private boolean deleteDirectory(File directoryToBeDeleted) {
File[] allContents = directoryToBeDeleted.listFiles();
if (allContents != null) {
for (File file : allContents) {
deleteDirectory(file);
}
}
return directoryToBeDeleted.delete();
}
private String installBuildTool(File installationDirectory, File configurationDirectory) {
String source = "https://structure101.com/binaries/v6";
try (final WebClient webClient = new WebClient()) {
HtmlPage page = webClient.getPage(source);
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);
}
}
return null;
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
private void copyZipToFilesystem(String source, File destination, String name) {
try (ZipInputStream in = new ZipInputStream(new URL(source + "/" + name + ".zip").openStream())) {
ZipEntry entry;
String build = destination.getName();
int entries = 0;
long size = 0;
while ((entry = in.getNextEntry()) != null) {
if (entry.getName().equals(name + "/")) {
destination.mkdirs();
} else if (entry.getName().startsWith(name)) {
if (entries++ > TOOMANY) {
throw new IllegalArgumentException("Zip file has more entries than expected");
}
if (size + BUFFER > TOOBIG) {
throw new IllegalArgumentException("Zip file is larger than expected");
}
String filename = entry.getName().replace(name, build);
if (filename.contains("maven")) {
continue;
}
if (filename.contains("jxbrowser")) {
continue;
}
if (filename.contains("jetty")) {
continue;
}
if (filename.contains("jfreechart")) {
continue;
}
if (filename.contains("piccolo2d")) {
continue;
}
if (filename.contains("plexus")) {
continue;
}
if (filename.contains("websocket")) {
continue;
}
validateFilename(filename, build);
this.logger.info("Downloading " + filename);
try (OutputStream out = new FileOutputStream(new File(destination.getParentFile(), filename))) {
byte[] data = new byte[BUFFER];
int read;
while ((read = in.read(data, 0, BUFFER)) != -1 && TOOBIG - size >= read) {
out.write(data, 0, read);
size += read;
}
}
}
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private String validateFilename(String filename, String intendedDir)
throws java.io.IOException {
File f = new File(filename);
String canonicalPath = f.getCanonicalPath();
File iD = new File(intendedDir);
String canonicalID = iD.getCanonicalPath();
if (canonicalPath.startsWith(canonicalID)) {
return canonicalPath;
} else {
throw new IllegalArgumentException("File is outside extraction target directory.");
}
}
private void configureProject(String version, File configurationDirectory) {
configurationDirectory.mkdirs();
Map<String, Object> model = hspTemplateValues(version, configurationDirectory);
copyToProject(this.hspTemplate, model, new File(configurationDirectory, "project.java.hsp"));
copyToProject("s101/config.xml", new File(configurationDirectory, "config.xml"));
File repository = new File(configurationDirectory, "repository");
File snapshots = new File(repository, "snapshots");
if (!snapshots.exists() && !snapshots.mkdirs()) {
throw new IllegalStateException("Unable to create snapshots directory");
}
copyToProject(this.repositoryTemplate, model, new File(repository, "repository.xml"));
}
private void copyToProject(String location, File destination) {
Resource resource = new ClassPathResource(location);
try (InputStream is = resource.getInputStream();
OutputStream os = new FileOutputStream(destination)) {
IOUtils.copy(is, os);
} catch (IOException ex) {
throw new UncheckedIOException(ex);
}
}
private void copyToProject(Mustache view, Map<String, Object> model, File destination) {
try (OutputStream os = new FileOutputStream(destination)) {
view.execute(new OutputStreamWriter(os), model).flush();
} catch (IOException ex) {
throw new UncheckedIOException(ex);
}
}
private Map<String, Object> hspTemplateValues(String version, File configurationDirectory) {
Map<String, Object> values = new LinkedHashMap<>();
values.put("version", version);
values.put("patchVersion", version.split("\\.")[2]);
values.put("relativeTo", "const(THIS_FILE)/" + configurationDirectory.toPath().relativize(this.project.getProjectDir().toPath()));
List<Map<String, Object>> entries = new ArrayList<>();
Set<Project> projects = this.project.getAllprojects();
for (Project p : projects) {
SourceSetContainer sourceSets = (SourceSetContainer) p.getExtensions().findByName("sourceSets");
if (sourceSets == null) {
continue;
}
for (SourceSet source : sourceSets) {
Set<File> classDirs = source.getOutput().getClassesDirs().getFiles();
for (File directory : classDirs) {
Map<String, Object> entry = new HashMap<>();
entry.put("path", this.project.getProjectDir().toPath().relativize(directory.toPath()));
entry.put("module", p.getName());
entries.add(entry);
}
}
}
values.put("entries", entries);
return values;
}
}
@@ -0,0 +1,35 @@
/*
* 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 s101;
import java.io.File;
import org.gradle.api.DefaultTask;
import org.gradle.api.tasks.TaskAction;
public class S101Install extends DefaultTask {
@TaskAction
public void install() throws Exception {
S101PluginExtension extension = getProject().getExtensions().getByType(S101PluginExtension.class);
File installationDirectory = extension.getInstallationDirectory().get();
File configurationDirectory = extension.getConfigurationDirectory().get();
S101Configurer configurer = new S101Configurer(getProject());
configurer.install(installationDirectory, configurationDirectory);
}
}
+158
View File
@@ -0,0 +1,158 @@
/*
* 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 s101;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.provider.Property;
import org.gradle.api.tasks.JavaExec;
public class S101Plugin implements Plugin<Project> {
@Override
public void apply(Project project) {
project.getExtensions().add("s101", new S101PluginExtension(project));
project.getTasks().register("s101Install", S101Install.class, this::configure);
project.getTasks().register("s101Configure", S101Configure.class, this::configure);
project.getTasks().register("s101", JavaExec.class, this::configure);
}
private void configure(S101Install install) {
install.setDescription("Installs Structure101 to your filesystem");
}
private void configure(S101Configure configure) {
configure.setDescription("Applies a default Structure101 configuration to the project");
}
private void configure(JavaExec exec) {
exec.setDescription("Runs Structure101 headless analysis, installing and configuring if necessary");
exec.dependsOn("check");
Project project = exec.getProject();
S101PluginExtension extension = project.getExtensions().getByType(S101PluginExtension.class);
exec
.workingDir(extension.getInstallationDirectory())
.classpath(new File(extension.getInstallationDirectory().get(), "structure101-java-build.jar"))
.args(new File(new File(project.getBuildDir(), "s101"), "config.xml"))
.args("-licensedirectory=" + extension.getLicenseDirectory().get())
.systemProperty("s101.label", computeLabel(extension).get())
.doFirst((task) -> {
installAndConfigureIfNeeded(project);
copyConfigurationToBuildDirectory(extension, project);
})
.doLast((task) -> {
copyResultsBackToConfigurationDirectory(extension, project);
});
}
private Property<String> computeLabel(S101PluginExtension extension) {
boolean hasBaseline = extension.getConfigurationDirectory().get().toPath()
.resolve("repository").resolve("snapshots").resolve("baseline").toFile().exists();
if (!hasBaseline) {
return extension.getLabel().convention("baseline");
}
return extension.getLabel().convention("recent");
}
private void installAndConfigureIfNeeded(Project project) {
S101Configurer configurer = new S101Configurer(project);
S101PluginExtension extension = project.getExtensions().getByType(S101PluginExtension.class);
File installationDirectory = extension.getInstallationDirectory().get();
File configurationDirectory = extension.getConfigurationDirectory().get();
if (!installationDirectory.exists()) {
configurer.install(installationDirectory, configurationDirectory);
}
if (!configurationDirectory.exists()) {
configurer.configure(installationDirectory, configurationDirectory);
}
}
private void copyConfigurationToBuildDirectory(S101PluginExtension extension, Project project) {
Path configurationDirectory = extension.getConfigurationDirectory().get().toPath();
Path buildDirectory = project.getBuildDir().toPath();
copyDirectory(project, configurationDirectory, buildDirectory);
}
private void copyResultsBackToConfigurationDirectory(S101PluginExtension extension, Project project) {
Path buildConfigurationDirectory = project.getBuildDir().toPath().resolve("s101");
String label = extension.getLabel().get();
if ("baseline".equals(label)) { // a new baseline was created
copyDirectory(project, buildConfigurationDirectory.resolve("repository").resolve("snapshots"),
extension.getConfigurationDirectory().get().toPath().resolve("repository"));
copyDirectory(project, buildConfigurationDirectory.resolve("repository"),
extension.getConfigurationDirectory().get().toPath());
}
}
private void copyDirectory(Project project, Path source, Path destination) {
try {
Files.walk(source)
.forEach(each -> {
Path relativeToSource = source.getParent().relativize(each);
Path resolvedDestination = destination.resolve(relativeToSource);
if (each.toFile().isDirectory()) {
resolvedDestination.toFile().mkdirs();
return;
}
InputStream input;
if ("project.java.hsp".equals(each.toFile().getName())) {
Path relativeTo = project.getBuildDir().toPath().resolve("s101").relativize(project.getProjectDir().toPath());
String value = "const(THIS_FILE)/" + relativeTo;
input = replace(each, "<property name=\"relative-to\" value=\"(.*)\" />", "<property name=\"relative-to\" value=\"" + value + "\" />");
} else if (each.toFile().toString().endsWith(".xml")) {
input = replace(each, "\\r\\n", "\n");
} else {
input = input(each);
}
try {
Files.copy(input, resolvedDestination, StandardCopyOption.REPLACE_EXISTING);
} catch (IOException e) {
throw new RuntimeException(e);
}
});
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private InputStream replace(Path file, String search, String replace) {
try {
byte[] b = Files.readAllBytes(file);
String contents = new String(b).replaceAll(search, replace);
return new ByteArrayInputStream(contents.getBytes(StandardCharsets.UTF_8));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private InputStream input(Path file) {
try {
return new FileInputStream(file.toFile());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
@@ -0,0 +1,80 @@
/*
* 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 s101;
import java.io.File;
import org.gradle.api.Project;
import org.gradle.api.provider.Property;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.InputDirectory;
public class S101PluginExtension {
private final Property<File> licenseDirectory;
private final Property<File> installationDirectory;
private final Property<File> configurationDirectory;
private final Property<String> label;
@InputDirectory
public Property<File> getLicenseDirectory() {
return this.licenseDirectory;
}
public void setLicenseDirectory(String licenseDirectory) {
this.licenseDirectory.set(new File(licenseDirectory));
}
@InputDirectory
public Property<File> getInstallationDirectory() {
return this.installationDirectory;
}
public void setInstallationDirectory(String installationDirectory) {
this.installationDirectory.set(new File(installationDirectory));
}
@InputDirectory
public Property<File> getConfigurationDirectory() {
return this.configurationDirectory;
}
public void setConfigurationDirectory(String configurationDirectory) {
this.configurationDirectory.set(new File(configurationDirectory));
}
@Input
public Property<String> getLabel() {
return this.label;
}
public void setLabel(String label) {
this.label.set(label);
}
public S101PluginExtension(Project project) {
this.licenseDirectory = project.getObjects().property(File.class)
.convention(new File(System.getProperty("user.home") + "/.Structure101/java"));
this.installationDirectory = project.getObjects().property(File.class)
.convention(new File(project.getBuildDir(), "s101"));
this.configurationDirectory = project.getObjects().property(File.class)
.convention(new File(project.getProjectDir(), "s101"));
this.label = project.getObjects().property(String.class);
if (project.hasProperty("s101.label")) {
setLabel((String) project.findProperty("s101.label"));
}
}
}
@@ -0,0 +1 @@
implementation-class=io.spring.gradle.convention.IncludeCheckRemotePlugin
@@ -1 +0,0 @@
implementation-class=io.spring.gradle.convention.OssrhPlugin
@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<headless version="1.0">
<operations>
<operation type="publish">
<argument name="overwrite" value="true"/>
<argument name="diagrams" value="true"/>
</operation>
<operation type="check-key-measures">
<argument name="baseline" value="baseline"/>
<argument name="useProjectFileSpec" value="true"/>
<argument name="useProjectFileDiagrams" value="true"/>
<argument name="fail-on-architecture-violations" value="false"/>
<argument name="fail-on-fat-package" value="false"/>
<argument name="fail-on-fat-class" value="false"/>
<argument name="fail-on-fat-method" value="false"/>
<argument name="fail-on-feedback-dependencies" value="true"/>
<argument name="fail-on-spec-violation-dependencies" value="false"/>
<argument name="fail-on-total-problem-dependencies" value="false"/>
<argument name="fail-on-spec-item-violations" value="false"/>
<argument name="fail-on-biggest-class-tangle" value="true"/>
<argument name="fail-on-tangled-package" value="true"/>
<argument name="fail-on-architecture-violations" value="false"/>
<argument name="fail-on-total-problem-dependencies" value="true"/>
<argument name="identifier-on-violation" value="S101 key measure violation"/>
</operation>
</operations>
<arguments>
<argument name="local-project" value="const(THIS_FILE)/project.java.hsp"/>
<argument name="repository" value="const(THIS_FILE)/repository"/>
<argument name="project" value="snapshots"/>
</arguments>
</headless>
@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<local-project language="java" version="{{version}}" xml-version="3" flavor="j2se">
<property name="show-as-module" value="false" />
<property name="publish-architecture-artifacts" value="true" />
<property name="force-classpath" value="false" />
<property name="project-type" value="classpath" />
<property name="hide-externals" value="true" />
<property name="parse-archive-in-archive" value="false" />
<property name="include-injected-dependency" value="false" />
<property name="relative-to" value="{{relativeTo}}" />
<property name="action-set-mod" value="1" />
<property name="detail-mode" value="true" />
<property name="hide-deprecated" value="false" />
<property name="resolve-name-clashes" value="true" />
<property name="project-excluded" />
<property name="show-needs-to-compile" value="false" />
<classpath>
{{#entries}}
<classpathentry kind="lib" path="{{path}}" module="{{module}}" />
{{/entries}}
</classpath>
<pom-root-files />
<modules-in-scope />
<restructuring>
<set version="3" name="Action list 1" hiview="Codemap" active="true" todo="false" list="0" />
</restructuring>
<grid-set sep="." version="{{version}}" />
</local-project>
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<structure101-repository language="java" version="{{patchVersion}}">
<xs-configuration>
<entry metric="Tangled" scope="design" threshold="0" color="153,53,0" />
<entry metric="Fat" scope="design" threshold="120" color="255,153,0" />
<entry metric="Fat" scope="leaf package" threshold="120" color="0,153,153" />
<entry metric="Fat" scope="class" threshold="120" color="255,153,153" />
<entry metric="Fat" scope="method" threshold="15" color="51,255,51" />
</xs-configuration>
<!--Note: All date strings are stored in short US format e.g. 2/1/06 for 1st Feb 2006-->
<project name="snapshots" dir="snapshots" baselineSnapshot="default" version="{{patchVersion}}">
<snapshot label="baseline" location="baseline" timestamp="3/16/21, 4:42 PM" version="{{patchVersion}}" detail="true" good="true" size="20" />
</project>
</structure101-repository>
@@ -1,87 +0,0 @@
package io.spring.gradle.convention;
import io.spring.gradle.TestKit;
import org.codehaus.groovy.runtime.ResourceGroovyMethods;
import org.gradle.testkit.runner.BuildResult;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import java.io.File;
import java.nio.file.Path;
import java.util.Collections;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import static org.assertj.core.api.Assertions.assertThat;
import static org.gradle.testkit.runner.TaskOutcome.FAILED;
import static org.gradle.testkit.runner.TaskOutcome.SUCCESS;
public class DocsPluginITest {
private TestKit testKit;
@BeforeEach
void setup(@TempDir Path tempDir) {
this.testKit = new TestKit(tempDir.toFile());
}
@Test
public void buildTriggersDocs() throws Exception {
BuildResult result = testKit.withProjectResource("samples/docs/simple/")
.withArguments("build")
.build();
assertThat(result.task(":build").getOutcome()).isEqualTo(SUCCESS);
assertThat(result.task(":docs").getOutcome()).isEqualTo(SUCCESS);
assertThat(result.task(":docsZip").getOutcome()).isEqualTo(SUCCESS);
File zip = new File(testKit.getRootDir(), "build/distributions/simple-1.0.0.BUILD-SNAPSHOT-docs.zip");
try (ZipFile file = new ZipFile(zip)) {
List<? extends ZipEntry> entries = Collections.list(file.entries());
assertThat(entries)
.extracting(ZipEntry::getName)
.contains("docs/reference/html5/index.html")
.contains("docs/reference/pdf/simple-reference.pdf");
}
}
@Test
public void asciidocCopiesImages() throws Exception {
BuildResult result = testKit.withProjectResource("samples/docs/simple/").withArguments("asciidoctor").build();
assertThat(result.task(":asciidoctor").getOutcome()).isEqualTo(SUCCESS);
assertThat(new File(testKit.getRootDir(), "build/docs/asciidoc/images")).exists();
}
@Test
public void asciidocDocInfoFromResourcesUsed() throws Exception {
BuildResult result = this.testKit.withProjectResource("samples/docs/simple/")
.withArguments("asciidoctor")
.build();
assertThat(result.task(":asciidoctor").getOutcome()).isEqualTo(SUCCESS);
assertThat(ResourceGroovyMethods.getText(new File(testKit.getRootDir(), "build/docs/asciidoc/index.html")))
.contains("<script type=\"text/javascript\" src=\"js/tocbot/tocbot.min.js\"></script>");
}
@Test
public void missingAttributeFails() throws Exception {
BuildResult result = this.testKit.withProjectResource("samples/docs/missing-attribute/")
.withArguments(":asciidoctor")
.buildAndFail();
assertThat(result.task(":asciidoctor").getOutcome()).isEqualTo(FAILED);
}
@Test
public void missingInclude() throws Exception {
BuildResult result = this.testKit.withProjectResource("samples/docs/missing-include/")
.withArguments(":asciidoctor")
.buildAndFail();
assertThat(result.task(":asciidoctor").getOutcome()).isEqualTo(FAILED);
}
@Test
public void missingCrossReference() throws Exception {
BuildResult result = this.testKit.withProjectResource("samples/docs/missing-cross-reference/")
.withArguments(":asciidoctor")
.buildAndFail();
assertThat(result.task(":asciidoctor").getOutcome()).isEqualTo(FAILED);
}
}
@@ -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();
}
}
@@ -55,19 +55,6 @@ public class SpringMavenPluginITest {
assertThat(signature).exists();
}
@Test
public void upload() throws Exception {
BuildResult result = this.testKit.withProjectResource("samples/maven/upload")
.withArguments("uploadArchives")
.forwardOutput()
.build();
assertThat(result.getOutput()).contains("SUCCESS");
File pom = new File(testKit.getRootDir(), "build/poms/pom-default.xml");
assertThat(pom).exists();
String pomText = new String(Files.readAllBytes(pom.toPath()));
assertThat(pomText.replaceAll("\\s", "")).contains("<dependency>\n <groupId>aopalliance</groupId>\n <artifactId>aopalliance</artifactId>\n <version>1.0</version>\n <scope>compile</scope>\n <optional>true</optional>\n </dependency>".replaceAll("\\s", ""));
}
public String getSigningKey() throws Exception {
return IOUtils.toString(getClass().getResource("/test-private.pgp"));
}
@@ -1,6 +0,0 @@
plugins {
id 'io.spring.convention.docs'
id 'java'
}
version = '1.0.0.BUILD-SNAPSHOT'
@@ -1 +0,0 @@
rootProject.name = 'simple'
@@ -1,3 +0,0 @@
= Example Manual
This will fail due to {missing} attribute
@@ -1,6 +0,0 @@
plugins {
id 'io.spring.convention.docs'
id 'java'
}
version = '1.0.0.BUILD-SNAPSHOT'
@@ -1 +0,0 @@
rootProject.name = 'missing-include'
@@ -1,3 +0,0 @@
= Example Manual
This will fail due to <<missing>> cross reference
@@ -1,6 +0,0 @@
plugins {
id 'io.spring.convention.docs'
id 'java'
}
version = '1.0.0.BUILD-SNAPSHOT'
@@ -1 +0,0 @@
rootProject.name = 'missing-include'
@@ -1,5 +0,0 @@
= Example Manual
This will fail due to missing include
include::missing.adoc[]
@@ -1,13 +0,0 @@
plugins {
id 'io.spring.convention.docs'
id 'java'
}
version = '1.0.0.BUILD-SNAPSHOT'
asciidoctorj {
attributes \
'build-gradle': project.buildFile,
'sourcedir': project.sourceSets.main.java.srcDirs[0],
'endpoint-url': 'https://example.org'
}
@@ -1 +0,0 @@
rootProject.name = 'simple'
@@ -1 +0,0 @@
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.0.3/jquery.js"></script>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 120 KiB

@@ -1,60 +0,0 @@
= Example Manual
Doc Writer <doc.writer@example.org>
2014-09-09
:example-caption!:
ifndef::imagesdir[:imagesdir: images]
ifndef::sourcedir[:sourcedir: ../java]
This is a user manual for an example project.
== Introduction
This project does something.
We just haven't decided what that is yet.
== Source Code
[source,java]
.Java code from project
----
include::{sourcedir}/example/StringUtils.java[tags=contains,indent=0]
----
This page was built by the following command:
$ ./gradlew asciidoctor
== Images
[.thumb]
image::sunset.jpg[scaledwidth=75%]
== Attributes
.Built-in
asciidoctor-version:: {asciidoctor-version}
safe-mode-name:: {safe-mode-name}
docdir:: {docdir}
docfile:: {docfile}
imagesdir:: {imagesdir}
revnumber:: {revnumber}
.Custom
sourcedir:: {sourcedir}
endpoint-url:: {endpoint-url}
== Includes
.include::subdir/_b.adoc[]
====
include::subdir/_b.adoc[]
====
WARNING: Includes can be tricky!
== build.gradle
[source,groovy]
----
include::{build-gradle}[]
----
@@ -1,7 +0,0 @@
content from _src/docs/asciidoc/subdir/_b.adoc_.
.include::_c.adoc[]
[example]
--
include::_c.adoc[]
--
@@ -1 +0,0 @@
content from _src/docs/asciidoc/subdir/c.adoc_.
@@ -1,9 +0,0 @@
package example;
public class StringUtils {
// tag::contains[]
public boolean contains(String haystack, String needle) {
return haystack.contains(needle);
}
// end::contains[]
}
@@ -3,7 +3,6 @@ plugins {
}
apply plugin: 'java'
apply plugin: 'propdeps'
repositories {
mavenCentral()
@@ -2,7 +2,6 @@ plugins {
id 'io.spring.convention.root'
}
apply plugin: 'propdeps-maven'
apply plugin: 'io.spring.convention.maven'
repositories {
@@ -4,7 +4,6 @@ plugins {
version = "1.0.0.RELEASE"
apply plugin: 'propdeps-maven'
apply plugin: 'io.spring.convention.maven'
repositories {
@@ -2,8 +2,6 @@ plugins {
id 'io.spring.convention.root'
}
apply plugin: 'propdeps-maven'
repositories {
mavenCentral()
}
@@ -1,9 +1,9 @@
apply plugin: 'io.spring.convention.spring-module'
dependencies {
management platform('org.springframework.boot:spring-boot-dependencies:2.5.2')
compile 'org.springframework:spring-web'
compile 'org.springframework:spring-core'
api platform('org.springframework.boot:spring-boot-dependencies:2.5.2')
implementation 'org.springframework:spring-web'
implementation 'org.springframework:spring-core'
testImplementation "org.junit.jupiter:junit-jupiter-api"
testImplementation "org.junit.jupiter:junit-jupiter-engine"
}
@@ -1,7 +1,7 @@
apply plugin: 'io.spring.convention.spring-module'
dependencies {
management platform('org.springframework.boot:spring-boot-dependencies:2.5.2')
api platform('org.springframework.boot:spring-boot-dependencies:2.5.2')
optional 'ch.qos.logback:logback-classic'
testImplementation "org.junit.jupiter:junit-jupiter-api"
testImplementation "org.junit.jupiter:junit-jupiter-engine"
@@ -2,10 +2,3 @@ apply plugin: 'java'
apply plugin: 'io.spring.convention.docs'
version = "1.0.0.BUILD-SNAPSHOT"
asciidoctorj {
attributes \
'build-gradle': project.buildFile,
'sourcedir': project.sourceSets.main.java.srcDirs[0],
'endpoint-url': 'https://example.org'
}
@@ -5,6 +5,6 @@ repositories {
}
dependencies {
testCompile project(path: ':core', configuration: 'tests')
testCompile 'junit:junit:4.12'
testImplementation project(path: ':core', configuration: 'tests')
testImplementation 'junit:junit:4.12'
}
@@ -30,7 +30,9 @@ import org.springframework.util.Assert;
* <a href="https://www.ehcache.org/">EHCACHE</a>.
*
* @author Ben Alex
* @deprecated since 5.6. In favor of JCache based implementations
*/
@Deprecated
public class EhCacheBasedTicketCache implements StatelessTicketCache, InitializingBean {
private static final Log logger = LogFactory.getLog(EhCacheBasedTicketCache.class);
@@ -40,9 +40,9 @@ import org.jasig.cas.client.authentication.AttributePrincipal;
* </pre>
*
* @author Jitendra Singh
* @since 4.2
* @see CasJackson2Module
* @see org.springframework.security.jackson2.SecurityJackson2Modules
* @since 4.2
*/
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY)
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE,
@@ -37,9 +37,9 @@ import org.jasig.cas.client.proxy.ProxyRetriever;
* </pre>
*
* @author Jitendra Singh
* @since 4.2
* @see CasJackson2Module
* @see org.springframework.security.jackson2.SecurityJackson2Modules
* @since 4.2
*/
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY)
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE,
@@ -47,9 +47,9 @@ import org.springframework.security.core.userdetails.UserDetails;
* </pre>
*
* @author Jitendra Singh
* @since 4.2
* @see CasJackson2Module
* @see org.springframework.security.jackson2.SecurityJackson2Modules
* @since 4.2
*/
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY)
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, isGetterVisibility = JsonAutoDetect.Visibility.NONE,
@@ -37,6 +37,7 @@ import org.springframework.security.cas.web.authentication.ServiceAuthentication
import org.springframework.security.cas.web.authentication.ServiceAuthenticationDetailsSource;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
@@ -219,7 +220,9 @@ public class CasAuthenticationFilter extends AbstractAuthenticationProcessingFil
}
this.logger.debug(
LogMessage.format("Authentication success. Updating SecurityContextHolder to contain: %s", authResult));
SecurityContextHolder.getContext().setAuthentication(authResult);
SecurityContext context = SecurityContextHolder.createEmptyContext();
context.setAuthentication(authResult);
SecurityContextHolder.setContext(context);
if (this.eventPublisher != null) {
this.eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(authResult, this.getClass()));
}
+1 -1
View File
@@ -49,7 +49,7 @@ dependencies {
testImplementation project(path : ':spring-security-oauth2-client', configuration : 'tests')
testImplementation project(path : ':spring-security-oauth2-resource-server', configuration : 'tests')
testImplementation project(path : ':spring-security-saml2-service-provider', configuration : 'tests')
testImplementation project(path: ':spring-security-saml2-service-provider', configuration: 'opensaml4MainCompile')
testImplementation project(path : ':spring-security-saml2-service-provider', configuration : 'opensaml4MainImplementation')
testImplementation project(path : ':spring-security-web', configuration : 'tests')
testImplementation "org.assertj:assertj-core"
testImplementation "org.junit.jupiter:junit-jupiter-api"
@@ -68,16 +68,6 @@ sn: Mouse
uid: jerry
userPassword: jerryspassword
dn: uid=bcrypt,ou=people,dc=springframework,dc=org
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
cn: BCrypt user
sn: BCrypt
uid: bcrypt
userPassword: $2a$10$lDa0YFNHAt63MjIzK/wUqeM0qjIhzPhp3RNI/MLUQEAUbzhB/SnnS
dn: cn=developers,ou=groups,dc=springframework,dc=org
objectclass: top
objectclass: groupOfNames
@@ -19,8 +19,8 @@ package org.springframework.security.config;
/**
* Callback interface that accepts a single input argument and returns no result.
*
* @author Eleftheria Stein
* @param <T> the type of the input to the operation
* @author Eleftheria Stein
* @since 5.2
*/
@FunctionalInterface
@@ -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.4. Please update your schema declarations to the 5.4 schema.", element);
+ "with Spring Security 5.6. Please update your schema declarations to the 5.6 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\\.4.*.xsd.*")
return schemaLocation.matches("(?m).*spring-security-5\\.6.*.xsd.*")
|| schemaLocation.matches("(?m).*spring-security.xsd.*")
|| !schemaLocation.matches("(?m).*spring-security.*");
}
@@ -27,8 +27,8 @@ import org.springframework.security.config.annotation.authentication.builders.Au
* {@link AuthenticationConfiguration} to configure the global
* {@link AuthenticationManagerBuilder}.
*
* @since 5.0
* @author Rob Winch
* @since 5.0
*/
@Order(100)
public abstract class GlobalAuthenticationConfigurerAdapter
@@ -30,10 +30,10 @@ import org.springframework.security.config.annotation.web.configuration.EnableWe
* class is not intended to be imported manually rather it is imported automatically when
* using {@link EnableWebSecurity} or {@link EnableGlobalMethodSecurity}.
*
* @see EnableWebSecurity
* @see EnableGlobalMethodSecurity
* @author Rob Winch
* @since 3.2
* @see EnableWebSecurity
* @see EnableGlobalMethodSecurity
*/
@Configuration(proxyBeanMethods = false)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
@@ -31,8 +31,8 @@ import org.springframework.security.config.core.GrantedAuthorityDefaults;
*
* @author Evgeniy Cheban
* @author Josh Cummings
* @see EnableMethodSecurity
* @since 5.6
* @see EnableMethodSecurity
*/
@Configuration(proxyBeanMethods = false)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
@@ -17,8 +17,11 @@
package org.springframework.security.config.annotation.method.configuration;
import org.springframework.aop.Advisor;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Role;
@@ -37,12 +40,12 @@ import org.springframework.security.config.core.GrantedAuthorityDefaults;
*
* @author Evgeniy Cheban
* @author Josh Cummings
* @see EnableMethodSecurity
* @since 5.6
* @see EnableMethodSecurity
*/
@Configuration(proxyBeanMethods = false)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
final class PrePostMethodSecurityConfiguration {
final class PrePostMethodSecurityConfiguration implements ApplicationContextAware {
private final PreFilterAuthorizationMethodInterceptor preFilterAuthorizationMethodInterceptor = new PreFilterAuthorizationMethodInterceptor();
@@ -52,29 +55,43 @@ final class PrePostMethodSecurityConfiguration {
private final PostFilterAuthorizationMethodInterceptor postFilterAuthorizationMethodInterceptor = new PostFilterAuthorizationMethodInterceptor();
private final DefaultMethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
private boolean customMethodSecurityExpressionHandler = false;
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
Advisor preFilterAuthorizationMethodInterceptor() {
if (!this.customMethodSecurityExpressionHandler) {
this.preAuthorizeAuthorizationManager.setExpressionHandler(this.expressionHandler);
}
return this.preFilterAuthorizationMethodInterceptor;
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
Advisor preAuthorizeAuthorizationMethodInterceptor() {
if (!this.customMethodSecurityExpressionHandler) {
this.preAuthorizeAuthorizationManager.setExpressionHandler(this.expressionHandler);
}
return AuthorizationManagerBeforeMethodInterceptor.preAuthorize(this.preAuthorizeAuthorizationManager);
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
Advisor postAuthorizeAuthorizationMethodInterceptor() {
if (!this.customMethodSecurityExpressionHandler) {
this.postAuthorizeAuthorizationManager.setExpressionHandler(this.expressionHandler);
}
return AuthorizationManagerAfterMethodInterceptor.postAuthorize(this.postAuthorizeAuthorizationManager);
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
Advisor postFilterAuthorizationMethodInterceptor() {
if (!this.customMethodSecurityExpressionHandler) {
this.postFilterAuthorizationMethodInterceptor.setExpressionHandler(this.expressionHandler);
}
return this.postFilterAuthorizationMethodInterceptor;
}
@@ -89,15 +106,12 @@ final class PrePostMethodSecurityConfiguration {
@Autowired(required = false)
void setGrantedAuthorityDefaults(GrantedAuthorityDefaults grantedAuthorityDefaults) {
if (this.customMethodSecurityExpressionHandler) {
return;
}
DefaultMethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
expressionHandler.setDefaultRolePrefix(grantedAuthorityDefaults.getRolePrefix());
this.preFilterAuthorizationMethodInterceptor.setExpressionHandler(expressionHandler);
this.preAuthorizeAuthorizationManager.setExpressionHandler(expressionHandler);
this.postAuthorizeAuthorizationManager.setExpressionHandler(expressionHandler);
this.postFilterAuthorizationMethodInterceptor.setExpressionHandler(expressionHandler);
this.expressionHandler.setDefaultRolePrefix(grantedAuthorityDefaults.getRolePrefix());
}
@Override
public void setApplicationContext(ApplicationContext context) throws BeansException {
this.expressionHandler.setApplicationContext(context);
}
}
@@ -29,8 +29,8 @@ import org.springframework.security.authorization.method.AuthorizationManagerBef
*
* @author Evgeniy Cheban
* @author Josh Cummings
* @see EnableMethodSecurity
* @since 5.6
* @see EnableMethodSecurity
*/
@Configuration(proxyBeanMethods = false)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
@@ -72,6 +72,7 @@ import org.springframework.security.config.annotation.web.configurers.oauth2.cli
import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer;
import org.springframework.security.config.annotation.web.configurers.openid.OpenIDLoginConfigurer;
import org.springframework.security.config.annotation.web.configurers.saml2.Saml2LoginConfigurer;
import org.springframework.security.config.annotation.web.configurers.saml2.Saml2LogoutConfigurer;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
@@ -2209,6 +2210,143 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<Defaul
return HttpSecurity.this;
}
/**
* Configures logout support for an SAML 2.0 Relying Party. <br>
* <br>
*
* Implements the <b>Single Logout Profile, using POST and REDIRECT bindings</b>, as
* documented in the
* <a target="_blank" href="https://docs.oasis-open.org/security/saml/">SAML V2.0
* Core, Profiles and Bindings</a> specifications. <br>
* <br>
*
* As a prerequisite to using this feature, is that you have a SAML v2.0 Asserting
* Party to sent a logout request to. The representation of the relying party and the
* asserting party is contained within {@link RelyingPartyRegistration}. <br>
* <br>
*
* {@link RelyingPartyRegistration}(s) are composed within a
* {@link RelyingPartyRegistrationRepository}, which is <b>required</b> and must be
* registered with the {@link ApplicationContext} or configured via
* {@link #saml2Login(Customizer)}.<br>
* <br>
*
* The default configuration provides an auto-generated logout endpoint at
* <code>&quot;/logout&quot;</code> and redirects to <code>/login?logout</code> when
* logout completes. <br>
* <br>
*
* <p>
* <h2>Example Configuration</h2>
*
* The following example shows the minimal configuration required, using a
* hypothetical asserting party.
*
* <pre>
* &#064;EnableWebSecurity
* &#064;Configuration
* public class Saml2LogoutSecurityConfig {
* &#064;Bean
* public SecurityFilterChain web(HttpSecurity http) throws Exception {
* http
* .authorizeRequests((authorize) -> authorize
* .anyRequest().authenticated()
* )
* .saml2Login(withDefaults())
* .saml2Logout(withDefaults());
* return http.build();
* }
*
* &#064;Bean
* public RelyingPartyRegistrationRepository relyingPartyRegistrationRepository() {
* RelyingPartyRegistration registration = RelyingPartyRegistrations
* .withMetadataLocation("https://ap.example.org/metadata")
* .registrationId("simple")
* .build();
* return new InMemoryRelyingPartyRegistrationRepository(registration);
* }
* }
* </pre>
*
* <p>
* @return the {@link HttpSecurity} for further customizations
* @throws Exception
* @since 5.6
*/
public HttpSecurity saml2Logout(Customizer<Saml2LogoutConfigurer<HttpSecurity>> saml2LogoutCustomizer)
throws Exception {
saml2LogoutCustomizer.customize(getOrApply(new Saml2LogoutConfigurer<>(getContext())));
return HttpSecurity.this;
}
/**
* Configures logout support for an SAML 2.0 Relying Party. <br>
* <br>
*
* Implements the <b>Single Logout Profile, using POST and REDIRECT bindings</b>, as
* documented in the
* <a target="_blank" href="https://docs.oasis-open.org/security/saml/">SAML V2.0
* Core, Profiles and Bindings</a> specifications. <br>
* <br>
*
* As a prerequisite to using this feature, is that you have a SAML v2.0 Asserting
* Party to sent a logout request to. The representation of the relying party and the
* asserting party is contained within {@link RelyingPartyRegistration}. <br>
* <br>
*
* {@link RelyingPartyRegistration}(s) are composed within a
* {@link RelyingPartyRegistrationRepository}, which is <b>required</b> and must be
* registered with the {@link ApplicationContext} or configured via
* {@link #saml2Login()}.<br>
* <br>
*
* The default configuration provides an auto-generated logout endpoint at
* <code>&quot;/logout&quot;</code> and redirects to <code>/login?logout</code> when
* logout completes. <br>
* <br>
*
* <p>
* <h2>Example Configuration</h2>
*
* The following example shows the minimal configuration required, using a
* hypothetical asserting party.
*
* <pre>
* &#064;EnableWebSecurity
* &#064;Configuration
* public class Saml2LogoutSecurityConfig {
* &#064;Bean
* public SecurityFilterChain web(HttpSecurity http) throws Exception {
* http
* .authorizeRequests()
* .anyRequest().authenticated()
* .and()
* .saml2Login()
* .and()
* .saml2Logout();
* return http.build();
* }
*
* &#064;Bean
* public RelyingPartyRegistrationRepository relyingPartyRegistrationRepository() {
* RelyingPartyRegistration registration = RelyingPartyRegistrations
* .withMetadataLocation("https://ap.example.org/metadata")
* .registrationId("simple")
* .build();
* return new InMemoryRelyingPartyRegistrationRepository(registration);
* }
* }
* </pre>
*
* <p>
* @return the {@link Saml2LoginConfigurer} for further customizations
* @throws Exception
* @since 5.6
*/
public Saml2LogoutConfigurer<HttpSecurity> saml2Logout() throws Exception {
return getOrApply(new Saml2LogoutConfigurer<>(getContext()));
}
/**
* Configures authentication support using an OAuth 2.0 and/or OpenID Connect 1.0
* Provider. <br>
@@ -60,11 +60,11 @@ import org.springframework.util.Assert;
* {@link WebSecurityConfigurer} and exposing it as a {@link Configuration}. This
* configuration is imported when using {@link EnableWebSecurity}.
*
* @see EnableWebSecurity
* @see WebSecurity
* @author Rob Winch
* @author Keesun Baik
* @since 3.2
* @see EnableWebSecurity
* @see WebSecurity
*/
@Configuration(proxyBeanMethods = false)
public class WebSecurityConfiguration implements ImportAware, BeanClassLoaderAware {
@@ -87,8 +87,8 @@ import org.springframework.web.accept.HeaderContentNegotiationStrategy;
* org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer = sample.MyClassThatExtendsAbstractHttpConfigurer, sample.OtherThatExtendsAbstractHttpConfigurer
* </pre>
*
* @see EnableWebSecurity
* @author Rob Winch
* @see EnableWebSecurity
*/
@Order(100)
public abstract class WebSecurityConfigurerAdapter implements WebSecurityConfigurer<WebSecurity> {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 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.
@@ -76,6 +76,7 @@ import org.springframework.util.StringUtils;
*
* @param <H> the type of {@link HttpSecurityBuilder} that is being configured
* @author Rob Winch
* @author Yanming Zhou
* @since 3.2
* @see org.springframework.security.config.annotation.web.builders.HttpSecurity#authorizeRequests()
*/
@@ -94,6 +95,8 @@ public final class ExpressionUrlAuthorizationConfigurer<H extends HttpSecurityBu
private static final String rememberMe = "rememberMe";
private final String rolePrefix;
private final ExpressionInterceptUrlRegistry REGISTRY;
private SecurityExpressionHandler<FilterInvocation> expressionHandler;
@@ -103,6 +106,15 @@ public final class ExpressionUrlAuthorizationConfigurer<H extends HttpSecurityBu
* @see HttpSecurity#authorizeRequests()
*/
public ExpressionUrlAuthorizationConfigurer(ApplicationContext context) {
String[] grantedAuthorityDefaultsBeanNames = context.getBeanNamesForType(GrantedAuthorityDefaults.class);
if (grantedAuthorityDefaultsBeanNames.length == 1) {
GrantedAuthorityDefaults grantedAuthorityDefaults = context.getBean(grantedAuthorityDefaultsBeanNames[0],
GrantedAuthorityDefaults.class);
this.rolePrefix = grantedAuthorityDefaults.getRolePrefix();
}
else {
this.rolePrefix = "ROLE_";
}
this.REGISTRY = new ExpressionInterceptUrlRegistry(context);
}
@@ -176,16 +188,16 @@ public final class ExpressionUrlAuthorizationConfigurer<H extends HttpSecurityBu
return this.expressionHandler;
}
private static String hasAnyRole(String... authorities) {
String anyAuthorities = StringUtils.arrayToDelimitedString(authorities, "','ROLE_");
return "hasAnyRole('ROLE_" + anyAuthorities + "')";
private static String hasAnyRole(String rolePrefix, String... authorities) {
String anyAuthorities = StringUtils.arrayToDelimitedString(authorities, "','" + rolePrefix);
return "hasAnyRole('" + rolePrefix + anyAuthorities + "')";
}
private static String hasRole(String role) {
private static String hasRole(String rolePrefix, String role) {
Assert.notNull(role, "role cannot be null");
Assert.isTrue(!role.startsWith("ROLE_"),
() -> "role should not start with 'ROLE_' since it is automatically inserted. Got '" + role + "'");
return "hasRole('ROLE_" + role + "')";
Assert.isTrue(rolePrefix.isEmpty() || !role.startsWith(rolePrefix), () -> "role should not start with '"
+ rolePrefix + "' since it is automatically inserted. Got '" + role + "'");
return "hasRole('" + rolePrefix + role + "')";
}
private static String hasAuthority(String authority) {
@@ -308,27 +320,30 @@ public final class ExpressionUrlAuthorizationConfigurer<H extends HttpSecurityBu
/**
* Shortcut for specifying URLs require a particular role. If you do not want to
* have "ROLE_" automatically inserted see {@link #hasAuthority(String)}.
* have role prefix (default "ROLE_") automatically inserted see
* {@link #hasAuthority(String)}.
* @param role the role to require (i.e. USER, ADMIN, etc). Note, it should not
* start with "ROLE_" as this is automatically inserted.
* start with role prefix as this is automatically inserted.
* @return the {@link ExpressionUrlAuthorizationConfigurer} for further
* customization
*/
public ExpressionInterceptUrlRegistry hasRole(String role) {
return access(ExpressionUrlAuthorizationConfigurer.hasRole(role));
return access(ExpressionUrlAuthorizationConfigurer
.hasRole(ExpressionUrlAuthorizationConfigurer.this.rolePrefix, role));
}
/**
* Shortcut for specifying URLs require any of a number of roles. If you do not
* want to have "ROLE_" automatically inserted see
* want to have role prefix (default "ROLE_") automatically inserted see
* {@link #hasAnyAuthority(String...)}
* @param roles the roles to require (i.e. USER, ADMIN, etc). Note, it should not
* start with "ROLE_" as this is automatically inserted.
* start with role prefix as this is automatically inserted.
* @return the {@link ExpressionUrlAuthorizationConfigurer} for further
* customization
*/
public ExpressionInterceptUrlRegistry hasAnyRole(String... roles) {
return access(ExpressionUrlAuthorizationConfigurer.hasAnyRole(roles));
return access(ExpressionUrlAuthorizationConfigurer
.hasAnyRole(ExpressionUrlAuthorizationConfigurer.this.rolePrefix, roles));
}
/**
@@ -490,8 +490,8 @@ public class HeadersConfigurer<H extends HttpSecurityBuilder<H>>
* @return the {@link FeaturePolicyConfig} for additional configuration
* @throws IllegalArgumentException if policyDirectives is {@code null} or empty
* @since 5.1
* @see FeaturePolicyHeaderWriter
* @deprecated Use {@link #permissionsPolicy(Customizer)} instead.
* @seeObjectPostProcessorConfiguration FeaturePolicyHeaderWriter
*/
@Deprecated
public FeaturePolicyConfig featurePolicy(String policyDirectives) {
@@ -250,10 +250,11 @@ public final class LogoutConfigurer<H extends HttpSecurityBuilder<H>>
* {@link SimpleUrlLogoutSuccessHandler} using the {@link #logoutSuccessUrl(String)}.
* @return the {@link LogoutSuccessHandler} to use
*/
private LogoutSuccessHandler getLogoutSuccessHandler() {
public LogoutSuccessHandler getLogoutSuccessHandler() {
LogoutSuccessHandler handler = this.logoutSuccessHandler;
if (handler == null) {
handler = createDefaultSuccessHandler();
this.logoutSuccessHandler = handler;
}
return handler;
}
@@ -312,7 +313,7 @@ public final class LogoutConfigurer<H extends HttpSecurityBuilder<H>>
* Gets the {@link LogoutHandler} instances that will be used.
* @return the {@link LogoutHandler} instances. Cannot be null.
*/
List<LogoutHandler> getLogoutHandlers() {
public List<LogoutHandler> getLogoutHandlers() {
return this.logoutHandlers;
}
@@ -49,15 +49,15 @@ import org.springframework.util.Assert;
* <li>{@link ClientRegistrationRepository}</li>
* </ul>
*
* @author Joe Grandja
* @since 5.0
* @see OAuth2AuthorizationRequestRedirectFilter
* @see ClientRegistrationRepository
* @deprecated It is not recommended to use the implicit flow due to the inherent risks of
* returning access tokens in an HTTP redirect without any confirmation that it has been
* received by the client. See reference
* <a target="_blank" href="https://oauth.net/2/grant-types/implicit/">OAuth 2.0 Implicit
* Grant</a>.
* @author Joe Grandja
* @since 5.0
* @see OAuth2AuthorizationRequestRedirectFilter
* @see ClientRegistrationRepository
*/
@Deprecated
public final class ImplicitGrantConfigurer<B extends HttpSecurityBuilder<B>>
@@ -683,10 +683,10 @@ public final class OAuth2LoginConfigurer<B extends HttpSecurityBuilder<B>>
/**
* Sets a custom {@link OAuth2User} type and associates it to the provided client
* {@link ClientRegistration#getRegistrationId() registration identifier}.
* @deprecated See {@link CustomUserTypesOAuth2UserService} for alternative usage.
* @param customUserType a custom {@link OAuth2User} type
* @param clientRegistrationId the client registration identifier
* @return the {@link UserInfoEndpointConfig} for further configuration
* @deprecated See {@link CustomUserTypesOAuth2UserService} for alternative usage.
*/
@Deprecated
public UserInfoEndpointConfig customUserType(Class<? extends OAuth2User> customUserType,
@@ -42,8 +42,8 @@ import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationProvider;
import org.springframework.security.oauth2.server.resource.authentication.OpaqueTokenAuthenticationProvider;
import org.springframework.security.oauth2.server.resource.introspection.NimbusOpaqueTokenIntrospector;
import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector;
import org.springframework.security.oauth2.server.resource.introspection.SpringOpaqueTokenIntrospector;
import org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationEntryPoint;
import org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationFilter;
import org.springframework.security.oauth2.server.resource.web.BearerTokenResolver;
@@ -454,7 +454,7 @@ public final class OAuth2ResourceServerConfigurer<H extends HttpSecurityBuilder<
public OpaqueTokenConfigurer introspectionUri(String introspectionUri) {
Assert.notNull(introspectionUri, "introspectionUri cannot be null");
this.introspectionUri = introspectionUri;
this.introspector = () -> new NimbusOpaqueTokenIntrospector(this.introspectionUri, this.clientId,
this.introspector = () -> new SpringOpaqueTokenIntrospector(this.introspectionUri, this.clientId,
this.clientSecret);
return this;
}
@@ -464,7 +464,7 @@ public final class OAuth2ResourceServerConfigurer<H extends HttpSecurityBuilder<
Assert.notNull(clientSecret, "clientSecret cannot be null");
this.clientId = clientId;
this.clientSecret = clientSecret;
this.introspector = () -> new NimbusOpaqueTokenIntrospector(this.introspectionUri, this.clientId,
this.introspector = () -> new SpringOpaqueTokenIntrospector(this.introspectionUri, this.clientId,
this.clientSecret);
return 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.
@@ -33,6 +33,7 @@ import org.springframework.security.config.annotation.web.configurers.AbstractAu
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.annotation.web.configurers.CsrfConfigurer;
import org.springframework.security.core.Authentication;
import org.springframework.security.saml2.provider.service.authentication.AbstractSaml2AuthenticationRequest;
import org.springframework.security.saml2.provider.service.authentication.OpenSaml4AuthenticationProvider;
import org.springframework.security.saml2.provider.service.authentication.OpenSaml4AuthenticationRequestFactory;
import org.springframework.security.saml2.provider.service.authentication.OpenSamlAuthenticationProvider;
@@ -44,7 +45,10 @@ import org.springframework.security.saml2.provider.service.servlet.filter.Saml2W
import org.springframework.security.saml2.provider.service.servlet.filter.Saml2WebSsoAuthenticationRequestFilter;
import org.springframework.security.saml2.provider.service.web.DefaultRelyingPartyRegistrationResolver;
import org.springframework.security.saml2.provider.service.web.DefaultSaml2AuthenticationRequestContextResolver;
import org.springframework.security.saml2.provider.service.web.HttpSessionSaml2AuthenticationRequestRepository;
import org.springframework.security.saml2.provider.service.web.RelyingPartyRegistrationResolver;
import org.springframework.security.saml2.provider.service.web.Saml2AuthenticationRequestContextResolver;
import org.springframework.security.saml2.provider.service.web.Saml2AuthenticationRequestRepository;
import org.springframework.security.saml2.provider.service.web.Saml2AuthenticationTokenConverter;
import org.springframework.security.web.authentication.AuthenticationConverter;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
@@ -160,7 +164,7 @@ public final class Saml2LoginConfigurer<B extends HttpSecurityBuilder<B>>
* @param repo the repository of relying parties
* @return the {@link Saml2LoginConfigurer} for further configuration
*/
public Saml2LoginConfigurer relyingPartyRegistrationRepository(RelyingPartyRegistrationRepository repo) {
public Saml2LoginConfigurer<B> relyingPartyRegistrationRepository(RelyingPartyRegistrationRepository repo) {
this.relyingPartyRegistrationRepository = repo;
return this;
}
@@ -172,10 +176,19 @@ public final class Saml2LoginConfigurer<B extends HttpSecurityBuilder<B>>
return this;
}
/**
* Specifies the URL to validate the credentials. If specified a custom URL, consider
* specifying a custom {@link AuthenticationConverter} via
* {@link #authenticationConverter(AuthenticationConverter)}, since the default
* {@link AuthenticationConverter} implementation relies on the
* <code>{registrationId}</code> path variable to be present in the URL
* @param loginProcessingUrl the URL to validate the credentials
* @return the {@link Saml2LoginConfigurer} for additional customization
* @see Saml2WebSsoAuthenticationFilter#DEFAULT_FILTER_PROCESSES_URI
*/
@Override
public Saml2LoginConfigurer<B> loginProcessingUrl(String loginProcessingUrl) {
Assert.hasText(loginProcessingUrl, "loginProcessingUrl cannot be empty");
Assert.state(loginProcessingUrl.contains("{registrationId}"), "{registrationId} path variable is required");
this.loginProcessingUrl = loginProcessingUrl;
return this;
}
@@ -201,11 +214,10 @@ public final class Saml2LoginConfigurer<B extends HttpSecurityBuilder<B>>
@Override
public void init(B http) throws Exception {
registerDefaultCsrfOverride(http);
if (this.relyingPartyRegistrationRepository == null) {
this.relyingPartyRegistrationRepository = getSharedOrBean(http, RelyingPartyRegistrationRepository.class);
}
relyingPartyRegistrationRepository(http);
this.saml2WebSsoAuthenticationFilter = new Saml2WebSsoAuthenticationFilter(getAuthenticationConverter(http),
this.loginProcessingUrl);
setAuthenticationRequestRepository(http, this.saml2WebSsoAuthenticationFilter);
setAuthenticationFilter(this.saml2WebSsoAuthenticationFilter);
super.loginProcessingUrl(this.loginProcessingUrl);
if (StringUtils.hasText(this.loginPage)) {
@@ -252,16 +264,45 @@ public final class Saml2LoginConfigurer<B extends HttpSecurityBuilder<B>>
}
}
private AuthenticationConverter getAuthenticationConverter(B http) {
if (this.authenticationConverter == null) {
return new Saml2AuthenticationTokenConverter(
new DefaultRelyingPartyRegistrationResolver(this.relyingPartyRegistrationRepository));
RelyingPartyRegistrationRepository relyingPartyRegistrationRepository(B http) {
if (this.relyingPartyRegistrationRepository == null) {
this.relyingPartyRegistrationRepository = getSharedOrBean(http, RelyingPartyRegistrationRepository.class);
}
return this.authenticationConverter;
return this.relyingPartyRegistrationRepository;
}
private void setAuthenticationRequestRepository(B http,
Saml2WebSsoAuthenticationFilter saml2WebSsoAuthenticationFilter) {
saml2WebSsoAuthenticationFilter.setAuthenticationRequestRepository(getAuthenticationRequestRepository(http));
}
private AuthenticationConverter getAuthenticationConverter(B http) {
if (this.authenticationConverter != null) {
return this.authenticationConverter;
}
AuthenticationConverter authenticationConverterBean = getBeanOrNull(http,
Saml2AuthenticationTokenConverter.class);
if (authenticationConverterBean == null) {
Assert.state(this.loginProcessingUrl.contains("{registrationId}"),
"loginProcessingUrl must contain {registrationId} path variable");
return new Saml2AuthenticationTokenConverter(
(RelyingPartyRegistrationResolver) new DefaultRelyingPartyRegistrationResolver(
this.relyingPartyRegistrationRepository));
}
return authenticationConverterBean;
}
private String version() {
String version = Version.getVersion();
if (version != null) {
return version;
}
return Version.class.getModule().getDescriptor().version().map(Object::toString)
.orElseThrow(() -> new IllegalStateException("cannot determine OpenSAML version"));
}
private void registerDefaultAuthenticationProvider(B http) {
if (Version.getVersion().startsWith("4")) {
if (version().startsWith("4")) {
http.authenticationProvider(postProcess(new OpenSaml4AuthenticationProvider()));
}
else {
@@ -302,6 +343,16 @@ public final class Saml2LoginConfigurer<B extends HttpSecurityBuilder<B>>
return idps;
}
private Saml2AuthenticationRequestRepository<AbstractSaml2AuthenticationRequest> getAuthenticationRequestRepository(
B http) {
Saml2AuthenticationRequestRepository<AbstractSaml2AuthenticationRequest> repository = getBeanOrNull(http,
Saml2AuthenticationRequestRepository.class);
if (repository == null) {
return new HttpSessionSaml2AuthenticationRequestRepository();
}
return repository;
}
private <C> C getSharedOrBean(B http, Class<C> clazz) {
C shared = http.getSharedObject(clazz);
if (shared != null) {
@@ -339,14 +390,18 @@ public final class Saml2LoginConfigurer<B extends HttpSecurityBuilder<B>>
private Filter build(B http) {
Saml2AuthenticationRequestFactory authenticationRequestResolver = getResolver(http);
Saml2AuthenticationRequestContextResolver contextResolver = getContextResolver(http);
return postProcess(
new Saml2WebSsoAuthenticationRequestFilter(contextResolver, authenticationRequestResolver));
Saml2AuthenticationRequestRepository<AbstractSaml2AuthenticationRequest> repository = getAuthenticationRequestRepository(
http);
Saml2WebSsoAuthenticationRequestFilter filter = new Saml2WebSsoAuthenticationRequestFilter(contextResolver,
authenticationRequestResolver);
filter.setAuthenticationRequestRepository(repository);
return postProcess(filter);
}
private Saml2AuthenticationRequestFactory getResolver(B http) {
Saml2AuthenticationRequestFactory resolver = getSharedOrBean(http, Saml2AuthenticationRequestFactory.class);
if (resolver == null) {
if (Version.getVersion().startsWith("4")) {
if (version().startsWith("4")) {
return new OpenSaml4AuthenticationRequestFactory();
}
return new OpenSamlAuthenticationRequestFactory();
@@ -358,8 +413,9 @@ public final class Saml2LoginConfigurer<B extends HttpSecurityBuilder<B>>
Saml2AuthenticationRequestContextResolver resolver = getBeanOrNull(http,
Saml2AuthenticationRequestContextResolver.class);
if (resolver == null) {
return new DefaultSaml2AuthenticationRequestContextResolver(new DefaultRelyingPartyRegistrationResolver(
Saml2LoginConfigurer.this.relyingPartyRegistrationRepository));
RelyingPartyRegistrationResolver relyingPartyRegistrationResolver = new DefaultRelyingPartyRegistrationResolver(
Saml2LoginConfigurer.this.relyingPartyRegistrationRepository);
return new DefaultSaml2AuthenticationRequestContextResolver(relyingPartyRegistrationResolver);
}
return resolver;
}
@@ -0,0 +1,523 @@
/*
* 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.annotation.web.configurers.saml2;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.function.Predicate;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.opensaml.core.Version;
import org.springframework.context.ApplicationContext;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.annotation.web.configurers.LogoutConfigurer;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticatedPrincipal;
import org.springframework.security.saml2.provider.service.authentication.logout.OpenSamlLogoutRequestValidator;
import org.springframework.security.saml2.provider.service.authentication.logout.OpenSamlLogoutResponseValidator;
import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutRequestValidator;
import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutResponseValidator;
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
import org.springframework.security.saml2.provider.service.web.DefaultRelyingPartyRegistrationResolver;
import org.springframework.security.saml2.provider.service.web.RelyingPartyRegistrationResolver;
import org.springframework.security.saml2.provider.service.web.authentication.logout.HttpSessionLogoutRequestRepository;
import org.springframework.security.saml2.provider.service.web.authentication.logout.OpenSaml3LogoutRequestResolver;
import org.springframework.security.saml2.provider.service.web.authentication.logout.OpenSaml3LogoutResponseResolver;
import org.springframework.security.saml2.provider.service.web.authentication.logout.OpenSaml4LogoutRequestResolver;
import org.springframework.security.saml2.provider.service.web.authentication.logout.OpenSaml4LogoutResponseResolver;
import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutRequestFilter;
import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutRequestRepository;
import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutRequestResolver;
import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutResponseFilter;
import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutResponseResolver;
import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2RelyingPartyInitiatedLogoutSuccessHandler;
import org.springframework.security.web.authentication.logout.LogoutFilter;
import org.springframework.security.web.authentication.logout.LogoutHandler;
import org.springframework.security.web.authentication.logout.LogoutSuccessEventPublishingLogoutHandler;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler;
import org.springframework.security.web.csrf.CsrfFilter;
import org.springframework.security.web.csrf.CsrfLogoutHandler;
import org.springframework.security.web.csrf.CsrfTokenRepository;
import org.springframework.security.web.util.matcher.AndRequestMatcher;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
/**
* Adds SAML 2.0 logout support.
*
* <h2>Security Filters</h2>
*
* The following Filters are populated
*
* <ul>
* <li>{@link LogoutFilter}</li>
* <li>{@link Saml2LogoutRequestFilter}</li>
* <li>{@link Saml2LogoutResponseFilter}</li>
* </ul>
*
* <p>
* The following configuration options are available:
*
* <ul>
* <li>{@link #logoutUrl} - The URL to to process SAML 2.0 Logout</li>
* <li>{@link LogoutRequestConfigurer#logoutRequestValidator} - The
* {@link AuthenticationManager} for authenticating SAML 2.0 Logout Requests</li>
* <li>{@link LogoutRequestConfigurer#logoutRequestResolver} - The
* {@link Saml2LogoutRequestResolver} for creating SAML 2.0 Logout Requests</li>
* <li>{@link LogoutRequestConfigurer#logoutRequestRepository} - The
* {@link Saml2LogoutRequestRepository} for storing SAML 2.0 Logout Requests</li>
* <li>{@link LogoutResponseConfigurer#logoutResponseValidator} - The
* {@link AuthenticationManager} for authenticating SAML 2.0 Logout Responses</li>
* <li>{@link LogoutResponseConfigurer#logoutResponseResolver} - The
* {@link Saml2LogoutResponseResolver} for creating SAML 2.0 Logout Responses</li>
* </ul>
*
* <h2>Shared Objects Created</h2>
*
* No shared Objects are created
*
* <h2>Shared Objects Used</h2>
*
* Uses {@link CsrfTokenRepository} to add the {@link CsrfLogoutHandler}.
*
* @author Josh Cummings
* @since 5.6
* @see Saml2LogoutConfigurer
*/
public final class Saml2LogoutConfigurer<H extends HttpSecurityBuilder<H>>
extends AbstractHttpConfigurer<Saml2LogoutConfigurer<H>, H> {
private ApplicationContext context;
private RelyingPartyRegistrationRepository relyingPartyRegistrationRepository;
private String logoutUrl = "/logout";
private List<LogoutHandler> logoutHandlers = new ArrayList<>();
private LogoutSuccessHandler logoutSuccessHandler;
private LogoutRequestConfigurer logoutRequestConfigurer;
private LogoutResponseConfigurer logoutResponseConfigurer;
/**
* Creates a new instance
* @see HttpSecurity#logout()
*/
public Saml2LogoutConfigurer(ApplicationContext context) {
this.context = context;
this.logoutHandlers.add(new SecurityContextLogoutHandler());
this.logoutHandlers.add(new LogoutSuccessEventPublishingLogoutHandler());
SimpleUrlLogoutSuccessHandler logoutSuccessHandler = new SimpleUrlLogoutSuccessHandler();
logoutSuccessHandler.setDefaultTargetUrl("/login?logout");
this.logoutSuccessHandler = logoutSuccessHandler;
this.logoutRequestConfigurer = new LogoutRequestConfigurer();
this.logoutResponseConfigurer = new LogoutResponseConfigurer();
}
/**
* The URL by which the relying or asserting party can trigger logout.
*
* <p>
* The Relying Party triggers logout by POSTing to the endpoint. The Asserting Party
* triggers logout based on what is specified by
* {@link RelyingPartyRegistration#getSingleLogoutServiceBinding()}.
* @param logoutUrl the URL that will invoke logout
* @return the {@link LogoutConfigurer} for further customizations
* @see LogoutConfigurer#logoutUrl(String)
* @see HttpSecurity#csrf()
*/
public Saml2LogoutConfigurer<H> logoutUrl(String logoutUrl) {
this.logoutUrl = logoutUrl;
return this;
}
/**
* Sets the {@link RelyingPartyRegistrationRepository} of relying parties, each party
* representing a service provider, SP and this host, and identity provider, IDP pair
* that communicate with each other.
* @param repo the repository of relying parties
* @return the {@link Saml2LogoutConfigurer} for further customizations
*/
public Saml2LogoutConfigurer<H> relyingPartyRegistrationRepository(RelyingPartyRegistrationRepository repo) {
this.relyingPartyRegistrationRepository = repo;
return this;
}
/**
* Get configurer for SAML 2.0 Logout Request components
* @return the {@link LogoutRequestConfigurer} for further customizations
*/
public LogoutRequestConfigurer logoutRequest() {
return this.logoutRequestConfigurer;
}
/**
* Configures SAML 2.0 Logout Request components
* @param logoutRequestConfigurerCustomizer the {@link Customizer} to provide more
* options for the {@link LogoutRequestConfigurer}
* @return the {@link Saml2LogoutConfigurer} for further customizations
*/
public Saml2LogoutConfigurer<H> logoutRequest(
Customizer<LogoutRequestConfigurer> logoutRequestConfigurerCustomizer) {
logoutRequestConfigurerCustomizer.customize(this.logoutRequestConfigurer);
return this;
}
/**
* Get configurer for SAML 2.0 Logout Response components
* @return the {@link LogoutResponseConfigurer} for further customizations
*/
public LogoutResponseConfigurer logoutResponse() {
return this.logoutResponseConfigurer;
}
/**
* Configures SAML 2.0 Logout Request components
* @param logoutResponseConfigurerCustomizer the {@link Customizer} to provide more
* options for the {@link LogoutResponseConfigurer}
* @return the {@link Saml2LogoutConfigurer} for further customizations
*/
public Saml2LogoutConfigurer<H> logoutResponse(
Customizer<LogoutResponseConfigurer> logoutResponseConfigurerCustomizer) {
logoutResponseConfigurerCustomizer.customize(this.logoutResponseConfigurer);
return this;
}
/**
* {@inheritDoc}
*/
@Override
public void configure(H http) throws Exception {
LogoutConfigurer<H> logout = http.getConfigurer(LogoutConfigurer.class);
if (logout != null) {
this.logoutHandlers = logout.getLogoutHandlers();
this.logoutSuccessHandler = logout.getLogoutSuccessHandler();
}
RelyingPartyRegistrationResolver registrations = relyingPartyRegistrationResolver(http);
http.addFilterBefore(createLogoutRequestProcessingFilter(registrations), CsrfFilter.class);
http.addFilterBefore(createLogoutResponseProcessingFilter(registrations), CsrfFilter.class);
http.addFilterBefore(createRelyingPartyLogoutFilter(registrations), LogoutFilter.class);
}
private RelyingPartyRegistrationResolver relyingPartyRegistrationResolver(H http) {
RelyingPartyRegistrationRepository registrations = getRelyingPartyRegistrationRepository(http);
return new DefaultRelyingPartyRegistrationResolver(registrations);
}
private RelyingPartyRegistrationRepository getRelyingPartyRegistrationRepository(H http) {
if (this.relyingPartyRegistrationRepository != null) {
return this.relyingPartyRegistrationRepository;
}
Saml2LoginConfigurer<H> login = http.getConfigurer(Saml2LoginConfigurer.class);
if (login != null) {
this.relyingPartyRegistrationRepository = login.relyingPartyRegistrationRepository(http);
}
else {
this.relyingPartyRegistrationRepository = getBeanOrNull(RelyingPartyRegistrationRepository.class);
}
return this.relyingPartyRegistrationRepository;
}
private Saml2LogoutRequestFilter createLogoutRequestProcessingFilter(
RelyingPartyRegistrationResolver registrations) {
LogoutHandler[] logoutHandlers = this.logoutHandlers.toArray(new LogoutHandler[0]);
Saml2LogoutResponseResolver logoutResponseResolver = createSaml2LogoutResponseResolver(registrations);
Saml2LogoutRequestFilter filter = new Saml2LogoutRequestFilter(registrations,
this.logoutRequestConfigurer.logoutRequestValidator(), logoutResponseResolver, logoutHandlers);
filter.setLogoutRequestMatcher(createLogoutRequestMatcher());
return postProcess(filter);
}
private Saml2LogoutResponseFilter createLogoutResponseProcessingFilter(
RelyingPartyRegistrationResolver registrations) {
Saml2LogoutResponseFilter logoutResponseFilter = new Saml2LogoutResponseFilter(registrations,
this.logoutResponseConfigurer.logoutResponseValidator(), this.logoutSuccessHandler);
logoutResponseFilter.setLogoutRequestMatcher(createLogoutResponseMatcher());
logoutResponseFilter.setLogoutRequestRepository(this.logoutRequestConfigurer.logoutRequestRepository);
return postProcess(logoutResponseFilter);
}
private LogoutFilter createRelyingPartyLogoutFilter(RelyingPartyRegistrationResolver registrations) {
LogoutHandler[] logoutHandlers = this.logoutHandlers.toArray(new LogoutHandler[0]);
Saml2RelyingPartyInitiatedLogoutSuccessHandler logoutRequestSuccessHandler = createSaml2LogoutRequestSuccessHandler(
registrations);
LogoutFilter logoutFilter = new LogoutFilter(logoutRequestSuccessHandler, logoutHandlers);
logoutFilter.setLogoutRequestMatcher(createLogoutMatcher());
return postProcess(logoutFilter);
}
private RequestMatcher createLogoutMatcher() {
RequestMatcher logout = new AntPathRequestMatcher(this.logoutUrl, "POST");
RequestMatcher saml2 = new Saml2RequestMatcher();
return new AndRequestMatcher(logout, saml2);
}
private RequestMatcher createLogoutRequestMatcher() {
RequestMatcher logout = new AntPathRequestMatcher(this.logoutRequestConfigurer.logoutUrl);
RequestMatcher samlRequest = new ParameterRequestMatcher("SAMLRequest");
return new AndRequestMatcher(logout, samlRequest);
}
private RequestMatcher createLogoutResponseMatcher() {
RequestMatcher logout = new AntPathRequestMatcher(this.logoutResponseConfigurer.logoutUrl);
RequestMatcher samlResponse = new ParameterRequestMatcher("SAMLResponse");
return new AndRequestMatcher(logout, samlResponse);
}
private Saml2RelyingPartyInitiatedLogoutSuccessHandler createSaml2LogoutRequestSuccessHandler(
RelyingPartyRegistrationResolver relyingPartyRegistrationResolver) {
Saml2LogoutRequestResolver logoutRequestResolver = this.logoutRequestConfigurer
.logoutRequestResolver(relyingPartyRegistrationResolver);
return new Saml2RelyingPartyInitiatedLogoutSuccessHandler(logoutRequestResolver);
}
private Saml2LogoutResponseResolver createSaml2LogoutResponseResolver(
RelyingPartyRegistrationResolver relyingPartyRegistrationResolver) {
return this.logoutResponseConfigurer.logoutResponseResolver(relyingPartyRegistrationResolver);
}
private <C> C getBeanOrNull(Class<C> clazz) {
if (this.context == null) {
return null;
}
if (this.context.getBeanNamesForType(clazz).length == 0) {
return null;
}
return this.context.getBean(clazz);
}
private String version() {
String version = Version.getVersion();
if (version != null) {
return version;
}
return Version.class.getModule().getDescriptor().version().map(Object::toString)
.orElseThrow(() -> new IllegalStateException("cannot determine OpenSAML version"));
}
/**
* A configurer for SAML 2.0 LogoutRequest components
*/
public final class LogoutRequestConfigurer {
private String logoutUrl = "/logout/saml2/slo";
private Saml2LogoutRequestValidator logoutRequestValidator;
private Saml2LogoutRequestResolver logoutRequestResolver;
private Saml2LogoutRequestRepository logoutRequestRepository = new HttpSessionLogoutRequestRepository();
LogoutRequestConfigurer() {
}
/**
* The URL by which the asserting party can send a SAML 2.0 Logout Request
*
* <p>
* The Asserting Party should use whatever HTTP method specified in
* {@link RelyingPartyRegistration#getSingleLogoutServiceBinding()}.
* @param logoutUrl the URL that will receive the SAML 2.0 Logout Request
* @return the {@link LogoutRequestConfigurer} for further customizations
* @see Saml2LogoutConfigurer#logoutUrl(String)
*/
public LogoutRequestConfigurer logoutUrl(String logoutUrl) {
this.logoutUrl = logoutUrl;
return this;
}
/**
* Use this {@link LogoutHandler} for processing a logout request from the
* asserting party
* @param authenticator the {@link Saml2LogoutRequestValidator} to use
* @return the {@link LogoutRequestConfigurer} for further customizations
*/
public LogoutRequestConfigurer logoutRequestValidator(Saml2LogoutRequestValidator authenticator) {
this.logoutRequestValidator = authenticator;
return this;
}
/**
* Use this {@link Saml2LogoutRequestResolver} for producing a logout request to
* send to the asserting party
* @param logoutRequestResolver the {@link Saml2LogoutRequestResolver} to use
* @return the {@link LogoutRequestConfigurer} for further customizations
*/
public LogoutRequestConfigurer logoutRequestResolver(Saml2LogoutRequestResolver logoutRequestResolver) {
this.logoutRequestResolver = logoutRequestResolver;
return this;
}
/**
* Use this {@link Saml2LogoutRequestRepository} for storing logout requests
* @param logoutRequestRepository the {@link Saml2LogoutRequestRepository} to use
* @return the {@link LogoutRequestConfigurer} for further customizations
*/
public LogoutRequestConfigurer logoutRequestRepository(Saml2LogoutRequestRepository logoutRequestRepository) {
this.logoutRequestRepository = logoutRequestRepository;
return this;
}
public Saml2LogoutConfigurer<H> and() {
return Saml2LogoutConfigurer.this;
}
private Saml2LogoutRequestValidator logoutRequestValidator() {
if (this.logoutRequestValidator == null) {
return new OpenSamlLogoutRequestValidator();
}
return this.logoutRequestValidator;
}
private Saml2LogoutRequestResolver logoutRequestResolver(
RelyingPartyRegistrationResolver relyingPartyRegistrationResolver) {
if (this.logoutRequestResolver != null) {
return this.logoutRequestResolver;
}
if (version().startsWith("4")) {
return new OpenSaml4LogoutRequestResolver(relyingPartyRegistrationResolver);
}
return new OpenSaml3LogoutRequestResolver(relyingPartyRegistrationResolver);
}
}
public final class LogoutResponseConfigurer {
private String logoutUrl = "/logout/saml2/slo";
private Saml2LogoutResponseValidator logoutResponseValidator;
private Saml2LogoutResponseResolver logoutResponseResolver;
LogoutResponseConfigurer() {
}
/**
* The URL by which the asserting party can send a SAML 2.0 Logout Response
*
* <p>
* The Asserting Party should use whatever HTTP method specified in
* {@link RelyingPartyRegistration#getSingleLogoutServiceBinding()}.
* @param logoutUrl the URL that will receive the SAML 2.0 Logout Response
* @return the {@link LogoutResponseConfigurer} for further customizations
* @see Saml2LogoutConfigurer#logoutUrl(String)
*/
public LogoutResponseConfigurer logoutUrl(String logoutUrl) {
this.logoutUrl = logoutUrl;
return this;
}
/**
* Use this {@link LogoutHandler} for processing a logout response from the
* asserting party
* @param authenticator the {@link AuthenticationManager} to use
* @return the {@link LogoutRequestConfigurer} for further customizations
*/
public LogoutResponseConfigurer logoutResponseValidator(Saml2LogoutResponseValidator authenticator) {
this.logoutResponseValidator = authenticator;
return this;
}
/**
* Use this {@link Saml2LogoutRequestResolver} for producing a logout response to
* send to the asserting party
* @param logoutResponseResolver the {@link Saml2LogoutResponseResolver} to use
* @return the {@link LogoutRequestConfigurer} for further customizations
*/
public LogoutResponseConfigurer logoutResponseResolver(Saml2LogoutResponseResolver logoutResponseResolver) {
this.logoutResponseResolver = logoutResponseResolver;
return this;
}
public Saml2LogoutConfigurer<H> and() {
return Saml2LogoutConfigurer.this;
}
private Saml2LogoutResponseValidator logoutResponseValidator() {
if (this.logoutResponseValidator == null) {
return new OpenSamlLogoutResponseValidator();
}
return this.logoutResponseValidator;
}
private Saml2LogoutResponseResolver logoutResponseResolver(
RelyingPartyRegistrationResolver relyingPartyRegistrationResolver) {
if (this.logoutResponseResolver == null) {
if (version().startsWith("4")) {
return new OpenSaml4LogoutResponseResolver(relyingPartyRegistrationResolver);
}
return new OpenSaml3LogoutResponseResolver(relyingPartyRegistrationResolver);
}
return this.logoutResponseResolver;
}
}
private static class Saml2RequestMatcher implements RequestMatcher {
@Override
public boolean matches(HttpServletRequest request) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null) {
return false;
}
return authentication.getPrincipal() instanceof Saml2AuthenticatedPrincipal;
}
}
private static class ParameterRequestMatcher implements RequestMatcher {
Predicate<String> test = Objects::nonNull;
String name;
ParameterRequestMatcher(String name) {
this.name = name;
}
@Override
public boolean matches(HttpServletRequest request) {
return this.test.test(request.getParameter(this.name));
}
}
private static class NoopLogoutHandler implements LogoutHandler {
@Override
public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
}
}
}
@@ -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.
@@ -89,7 +89,7 @@ class ServerHttpSecurityConfiguration {
}
@Bean
WebFluxConfigurer authenticationPrincipalArgumentResolverConfigurer(
static WebFluxConfigurer authenticationPrincipalArgumentResolverConfigurer(
ObjectProvider<AuthenticationPrincipalArgumentResolver> authenticationPrincipalArgumentResolver) {
return new WebFluxConfigurer() {
@@ -37,9 +37,9 @@ import org.springframework.web.servlet.support.RequestDataValueProcessor;
* {@link AuthenticationPrincipalArgumentResolver} as a
* {@link HandlerMethodArgumentResolver}.
*
* @deprecated This is applied internally using SpringWebMvcImportSelector
* @author Rob Winch
* @since 3.2
* @deprecated This is applied internally using SpringWebMvcImportSelector
*/
@Deprecated
@Configuration(proxyBeanMethods = false)
@@ -79,8 +79,8 @@ import org.springframework.web.socket.sockjs.transport.TransportHandlingSockJsSe
* }
* </pre>
*
* @since 4.0
* @author Rob Winch
* @since 4.0
*/
@Order(Ordered.HIGHEST_PRECEDENCE + 100)
@Import(ObjectPostProcessorConfiguration.class)
@@ -725,6 +725,7 @@ class HttpSecurityDsl(private val http: HttpSecurity, private val init: HttpSecu
* @param atFilter the location of another [Filter] that is already registered
* (i.e. known) with Spring Security.
*/
@Deprecated("Use 'addFilterAt<T>(filter)' instead.")
fun addFilterAt(filter: Filter, atFilter: Class<out Filter>) {
this.http.addFilterAt(filter, atFilter)
}
@@ -751,6 +752,7 @@ class HttpSecurityDsl(private val http: HttpSecurity, private val init: HttpSecu
* @param T the location of another [Filter] that is already registered
* (i.e. known) with Spring Security.
*/
@Suppress("DEPRECATION")
inline fun <reified T: Filter> addFilterAt(filter: Filter) {
this.addFilterAt(filter, T::class.java)
}
@@ -776,6 +778,7 @@ class HttpSecurityDsl(private val http: HttpSecurity, private val init: HttpSecu
* @param afterFilter the location of another [Filter] that is already registered
* (i.e. known) with Spring Security.
*/
@Deprecated("Use 'addFilterAfter<T>(filter)' instead.")
fun addFilterAfter(filter: Filter, afterFilter: Class<out Filter>) {
this.http.addFilterAfter(filter, afterFilter)
}
@@ -802,6 +805,7 @@ class HttpSecurityDsl(private val http: HttpSecurity, private val init: HttpSecu
* @param T the location of another [Filter] that is already registered
* (i.e. known) with Spring Security.
*/
@Suppress("DEPRECATION")
inline fun <reified T: Filter> addFilterAfter(filter: Filter) {
this.addFilterAfter(filter, T::class.java)
}
@@ -827,6 +831,7 @@ class HttpSecurityDsl(private val http: HttpSecurity, private val init: HttpSecu
* @param beforeFilter the location of another [Filter] that is already registered
* (i.e. known) with Spring Security.
*/
@Deprecated("Use 'addFilterBefore<T>(filter)' instead.")
fun addFilterBefore(filter: Filter, beforeFilter: Class<out Filter>) {
this.http.addFilterBefore(filter, beforeFilter)
}
@@ -853,6 +858,7 @@ class HttpSecurityDsl(private val http: HttpSecurity, private val init: HttpSecu
* @param T the location of another [Filter] that is already registered
* (i.e. known) with Spring Security.
*/
@Suppress("DEPRECATION")
inline fun <reified T: Filter> addFilterBefore(filter: Filter) {
this.addFilterBefore(filter, T::class.java)
}
@@ -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.
@@ -53,6 +53,7 @@ class UserInfoEndpointDsl {
* @param customUserType a custom [OAuth2User] type
* @param clientRegistrationId the client registration identifier
*/
@Deprecated("Use 'customUserType<T>(clientRegistrationId)' instead.")
fun customUserType(customUserType: Class<out OAuth2User>, clientRegistrationId: String) {
customUserTypePair = Pair(customUserType, clientRegistrationId)
}
@@ -65,6 +66,7 @@ class UserInfoEndpointDsl {
* @param T a custom [OAuth2User] type
* @param clientRegistrationId the client registration identifier
*/
@Suppress("DEPRECATION")
inline fun <reified T: OAuth2User> customUserType(clientRegistrationId: String) {
customUserType(T::class.java, clientRegistrationId)
}
@@ -1127,4 +1127,4 @@ position =
## The explicit position at which the custom-filter should be placed in the chain. Use if you are replacing a standard filter.
attribute position {named-security-filter}
named-security-filter = "FIRST" | "CHANNEL_FILTER" | "SECURITY_CONTEXT_FILTER" | "CONCURRENT_SESSION_FILTER" | "WEB_ASYNC_MANAGER_FILTER" | "HEADERS_FILTER" | "CORS_FILTER" | "CSRF_FILTER" | "LOGOUT_FILTER" | "OAUTH2_AUTHORIZATION_REQUEST_FILTER" | "X509_FILTER" | "PRE_AUTH_FILTER" | "CAS_FILTER" | "OAUTH2_LOGIN_FILTER" | "FORM_LOGIN_FILTER" | "OPENID_FILTER" | "LOGIN_PAGE_FILTER" |"LOGOUT_PAGE_FILTER" | "DIGEST_AUTH_FILTER" | "BEARER_TOKEN_AUTH_FILTER" | "BASIC_AUTH_FILTER" | "REQUEST_CACHE_FILTER" | "SERVLET_API_SUPPORT_FILTER" | "JAAS_API_SUPPORT_FILTER" | "REMEMBER_ME_FILTER" | "ANONYMOUS_FILTER" | "OAUTH2_AUTHORIZATION_CODE_GRANT_FILTER" | "SESSION_MANAGEMENT_FILTER" | "EXCEPTION_TRANSLATION_FILTER" | "FILTER_SECURITY_INTERCEPTOR" | "SWITCH_USER_FILTER" | "LAST"
named-security-filter = "FIRST" | "CHANNEL_FILTER" | "SECURITY_CONTEXT_FILTER" | "CONCURRENT_SESSION_FILTER" | "WEB_ASYNC_MANAGER_FILTER" | "HEADERS_FILTER" | "CORS_FILTER" | "CSRF_FILTER" | "LOGOUT_FILTER" | "OAUTH2_AUTHORIZATION_REQUEST_FILTER" | "X509_FILTER" | "PRE_AUTH_FILTER" | "CAS_FILTER" | "OAUTH2_LOGIN_FILTER" | "FORM_LOGIN_FILTER" | "OPENID_FILTER" | "LOGIN_PAGE_FILTER" |"LOGOUT_PAGE_FILTER" | "DIGEST_AUTH_FILTER" | "BEARER_TOKEN_AUTH_FILTER" | "BASIC_AUTH_FILTER" | "REQUEST_CACHE_FILTER" | "SERVLET_API_SUPPORT_FILTER" | "JAAS_API_SUPPORT_FILTER" | "REMEMBER_ME_FILTER" | "ANONYMOUS_FILTER" | "OAUTH2_AUTHORIZATION_CODE_GRANT_FILTER" | "WELL_KNOWN_CHANGE_PASSWORD_REDIRECT_FILTER" | "SESSION_MANAGEMENT_FILTER" | "EXCEPTION_TRANSLATION_FILTER" | "FILTER_SECURITY_INTERCEPTOR" | "SWITCH_USER_FILTER" | "LAST"
@@ -1066,7 +1066,7 @@
<xs:attributeGroup ref="security:logout.attlist"/>
</xs:complexType>
</xs:element>
<xs:element ref="security:password-management"/>
<xs:element ref="security:password-management"/>
<xs:element name="session-management">
<xs:annotation>
<xs:documentation>Session-management related functionality is implemented by the addition of a
@@ -2161,16 +2161,15 @@
</xs:annotation>
</xs:attribute>
</xs:attributeGroup>
<xs:element name="password-management">
<xs:annotation>
<xs:documentation>Adds support for the password management.
</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:attributeGroup ref="security:password-management.attlist"/>
</xs:complexType>
</xs:element>
<xs:annotation>
<xs:documentation>Adds support for the password management.
</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:attributeGroup ref="security:password-management.attlist"/>
</xs:complexType>
</xs:element>
<xs:attributeGroup name="password-management.attlist">
<xs:attribute name="change-password-page" type="xs:string">
<xs:annotation>
@@ -2179,6 +2178,7 @@
</xs:annotation>
</xs:attribute>
</xs:attributeGroup>
<xs:attributeGroup name="session-management.attlist">
<xs:attribute name="session-fixation-protection">
<xs:annotation>
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 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.
@@ -26,6 +26,7 @@ import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.beans.factory.parsing.BeanDefinitionParsingException;
import org.springframework.security.config.util.InMemoryXmlApplicationContext;
import org.springframework.security.config.util.SpringSecurityVersions;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.util.ClassUtils;
@@ -133,6 +134,13 @@ public class SecurityNamespaceHandlerTests {
// should load just fine since no websocket block
}
@Test
public void configureWhenOldVersionThenErrorMessageContainsCorrectVersion() {
assertThatExceptionOfType(BeanDefinitionParsingException.class)
.isThrownBy(() -> new InMemoryXmlApplicationContext(XML_AUTHENTICATION_MANAGER, "3.0", null))
.withMessageContaining(SpringSecurityVersions.getCurrentXsdVersionFromSpringSchemas());
}
private void expectClassUtilsForNameThrowsNoClassDefFoundError(String className) {
this.classUtils.when(() -> ClassUtils.forName(eq(FILTER_CHAIN_PROXY_CLASSNAME), any()))
.thenThrow(new NoClassDefFoundError(className));
@@ -350,6 +350,14 @@ public class PrePostMethodSecurityConfigurationTests {
.isThrownBy(() -> this.businessService.repeatedAnnotations());
}
// gh-10305
@WithMockUser
@Test
public void beanInSpelWhenEvaluatedThenLooksUpBean() {
this.spring.register(MethodSecurityServiceConfig.class).autowire();
this.methodSecurityService.preAuthorizeBean(true);
}
@EnableMethodSecurity
static class MethodSecurityServiceConfig {
@@ -358,6 +366,11 @@ public class PrePostMethodSecurityConfigurationTests {
return new MethodSecurityServiceImpl();
}
@Bean
Authz authz() {
return new Authz();
}
}
@EnableMethodSecurity(jsr250Enabled = true)
@@ -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.
@@ -41,6 +41,7 @@ import org.springframework.security.config.annotation.authentication.builders.Au
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.core.GrantedAuthorityDefaults;
import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.config.test.SpringTestContextExtension;
import org.springframework.security.core.Authentication;
@@ -75,6 +76,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
*
* @author Rob Winch
* @author Eleftheria Stein
* @author Yanming Zhou
*/
@ExtendWith(SpringTestContextExtension.class)
public class ExpressionUrlAuthorizationConfigurerTests {
@@ -232,6 +234,28 @@ public class ExpressionUrlAuthorizationConfigurerTests {
this.mvc.perform(requestWithAdmin).andExpect(status().isForbidden());
}
@Test
public void getWhenHasAnyRoleUserWithTestRolePrefixConfiguredAndRoleIsUserThenRespondsWithOk() throws Exception {
this.spring.register(RoleUserWithTestRolePrefixConfig.class, BasicController.class).autowire();
// @formatter:off
MockHttpServletRequestBuilder requestWithUser = get("/")
.with(user("user")
.authorities(new SimpleGrantedAuthority("TEST_USER")));
// @formatter:on
this.mvc.perform(requestWithUser).andExpect(status().isOk());
}
@Test
public void getWhenHasAnyRoleUserWithEmptyRolePrefixConfiguredAndRoleIsUserThenRespondsWithOk() throws Exception {
this.spring.register(RoleUserWithEmptyRolePrefixConfig.class, BasicController.class).autowire();
// @formatter:off
MockHttpServletRequestBuilder requestWithUser = get("/")
.with(user("user")
.authorities(new SimpleGrantedAuthority("USER")));
// @formatter:on
this.mvc.perform(requestWithUser).andExpect(status().isOk());
}
@Test
public void getWhenRoleUserOrAdminConfiguredAndRoleIsUserThenRespondsWithOk() throws Exception {
this.spring.register(RoleUserOrAdminConfig.class, BasicController.class).autowire();
@@ -263,6 +287,28 @@ public class ExpressionUrlAuthorizationConfigurerTests {
this.mvc.perform(requestWithRoleOther).andExpect(status().isForbidden());
}
@Test
public void getWhenRoleUserOrAdminWithTestRolePrefixConfiguredAndRoleIsUserThenRespondsWithOk() throws Exception {
this.spring.register(RoleUserOrAdminWithTestRolePrefixConfig.class, BasicController.class).autowire();
// @formatter:off
MockHttpServletRequestBuilder requestWithUser = get("/")
.with(user("user")
.authorities(new SimpleGrantedAuthority("TEST_USER")));
// @formatter:on
this.mvc.perform(requestWithUser).andExpect(status().isOk());
}
@Test
public void getWhenRoleUserOrAdminWithEmptyRolePrefixConfiguredAndRoleIsUserThenRespondsWithOk() throws Exception {
this.spring.register(RoleUserOrAdminWithEmptyRolePrefixConfig.class, BasicController.class).autowire();
// @formatter:off
MockHttpServletRequestBuilder requestWithUser = get("/")
.with(user("user")
.authorities(new SimpleGrantedAuthority("USER")));
// @formatter:on
this.mvc.perform(requestWithUser).andExpect(status().isOk());
}
@Test
public void getWhenHasIpAddressConfiguredAndIpAddressMatchesThenRespondsWithOk() throws Exception {
this.spring.register(HasIpAddressConfig.class, BasicController.class).autowire();
@@ -628,6 +674,44 @@ public class ExpressionUrlAuthorizationConfigurerTests {
}
@EnableWebSecurity
static class RoleUserWithTestRolePrefixConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// @formatter:off
http
.authorizeRequests()
.anyRequest().hasAnyRole("USER");
// @formatter:on
}
@Bean
GrantedAuthorityDefaults grantedAuthorityDefaults() {
return new GrantedAuthorityDefaults("TEST_");
}
}
@EnableWebSecurity
static class RoleUserWithEmptyRolePrefixConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// @formatter:off
http
.authorizeRequests()
.anyRequest().hasAnyRole("USER");
// @formatter:on
}
@Bean
GrantedAuthorityDefaults grantedAuthorityDefaults() {
return new GrantedAuthorityDefaults("");
}
}
@EnableWebSecurity
static class RoleUserOrAdminConfig extends WebSecurityConfigurerAdapter {
@@ -642,6 +726,44 @@ public class ExpressionUrlAuthorizationConfigurerTests {
}
@EnableWebSecurity
static class RoleUserOrAdminWithTestRolePrefixConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// @formatter:off
http
.authorizeRequests()
.anyRequest().hasAnyRole("USER", "ADMIN");
// @formatter:on
}
@Bean
GrantedAuthorityDefaults grantedAuthorityDefaults() {
return new GrantedAuthorityDefaults("TEST_");
}
}
@EnableWebSecurity
static class RoleUserOrAdminWithEmptyRolePrefixConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// @formatter:off
http
.authorizeRequests()
.anyRequest().hasAnyRole("USER", "ADMIN");
// @formatter:on
}
@Bean
GrantedAuthorityDefaults grantedAuthorityDefaults() {
return new GrantedAuthorityDefaults("");
}
}
@EnableWebSecurity
static class HasIpAddressConfig extends WebSecurityConfigurerAdapter {
@@ -97,7 +97,9 @@ public class LogoutConfigurerTests {
@Test
public void configureWhenRegisteringObjectPostProcessorThenInvokedOnLogoutFilter() {
this.spring.register(ObjectPostProcessorConfig.class).autowire();
verify(ObjectPostProcessorConfig.objectPostProcessor).postProcess(any(LogoutFilter.class));
ObjectPostProcessor<LogoutFilter> objectPostProcessor = this.spring.getContext()
.getBean(ObjectPostProcessor.class);
verify(objectPostProcessor).postProcess(any(LogoutFilter.class));
}
@Test
@@ -361,7 +363,7 @@ public class LogoutConfigurerTests {
@EnableWebSecurity
static class ObjectPostProcessorConfig extends WebSecurityConfigurerAdapter {
static ObjectPostProcessor<Object> objectPostProcessor = spy(ReflectingObjectPostProcessor.class);
ObjectPostProcessor<Object> objectPostProcessor = spy(ReflectingObjectPostProcessor.class);
@Override
protected void configure(HttpSecurity http) throws Exception {
@@ -372,8 +374,8 @@ public class LogoutConfigurerTests {
}
@Bean
static ObjectPostProcessor<Object> objectPostProcessor() {
return objectPostProcessor;
ObjectPostProcessor<Object> objectPostProcessor() {
return this.objectPostProcessor;
}
}
@@ -360,7 +360,7 @@ public class OAuth2ResourceServerConfigurerTests {
this.spring.register(JwkSetUriConfig.class).autowire();
// engage csrf
// @formatter:off
this.mvc.perform(post("/").with(bearerToken("token").asParam()))
this.mvc.perform(post("/").header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE).with(bearerToken("token").asParam()))
.andExpect(status().isForbidden())
.andExpect(header().doesNotExist(HttpHeaders.WWW_AUTHENTICATE));
// @formatter:on
@@ -370,7 +370,7 @@ public class OAuth2ResourceServerConfigurerTests {
public void postWhenCsrfDisabledWithBearerTokenAsFormParameterThenIgnoresToken() throws Exception {
this.spring.register(CsrfDisabledConfig.class).autowire();
// @formatter:off
this.mvc.perform(post("/").with(bearerToken("token").asParam()))
this.mvc.perform(post("/").header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE).with(bearerToken("token").asParam()))
.andExpect(status().isUnauthorized())
.andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, "Bearer"));
// @formatter:on
@@ -536,7 +536,7 @@ public class OAuth2ResourceServerConfigurerTests {
mockRestOperations(jwks("Default"));
String token = this.token("ValidNoScopes");
// @formatter:off
this.mvc.perform(post("/authenticated").with(bearerToken(token)))
this.mvc.perform(post("/authenticated").header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE).with(bearerToken(token)))
.andExpect(status().isOk())
.andExpect(content().string("test-subject"));
// @formatter:on
@@ -558,7 +558,7 @@ public class OAuth2ResourceServerConfigurerTests {
mockRestOperations(jwks("Default"));
String token = this.token("Expired");
// @formatter:off
this.mvc.perform(post("/authenticated").with(bearerToken(token)))
this.mvc.perform(post("/authenticated").header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE).with(bearerToken(token)))
.andExpect(status().isUnauthorized())
.andExpect(invalidTokenHeader("An error occurred while attempting to decode the Jwt"));
// @formatter:on
@@ -626,7 +626,7 @@ public class OAuth2ResourceServerConfigurerTests {
this.mvc.perform(get("/authenticated").with(bearerToken(JWT_TOKEN)))
.andExpect(status().isOk())
.andExpect(content().string(JWT_SUBJECT));
this.mvc.perform(post("/authenticated").param("access_token", JWT_TOKEN))
this.mvc.perform(post("/authenticated").header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE).param("access_token", JWT_TOKEN))
.andExpect(status().isOk())
.andExpect(content().string(JWT_SUBJECT));
// @formatter:on
@@ -659,6 +659,7 @@ public class OAuth2ResourceServerConfigurerTests {
given(decoder.decode(anyString())).willReturn(JWT);
// @formatter:off
MockHttpServletRequestBuilder request = post("/authenticated")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE)
.param("access_token", JWT_TOKEN)
.with(bearerToken(JWT_TOKEN))
.with(csrf());
@@ -1124,7 +1125,7 @@ public class OAuth2ResourceServerConfigurerTests {
opaqueTokenConfigurer.introspector(client);
opaqueTokenConfigurer.introspectionUri(INTROSPECTION_URI);
opaqueTokenConfigurer.introspectionClientCredentials(CLIENT_ID, CLIENT_SECRET);
assertThat(opaqueTokenConfigurer.getIntrospector()).isInstanceOf(NimbusOpaqueTokenIntrospector.class);
assertThat(opaqueTokenConfigurer.getIntrospector()).isNotSameAs(client);
}
@Test
@@ -1250,7 +1251,6 @@ public class OAuth2ResourceServerConfigurerTests {
String jwtThree = jwtFromIssuer(issuerThree);
mockWebServer(String.format(metadata, issuerOne, issuerOne));
mockWebServer(jwkSet);
mockWebServer(jwkSet);
// @formatter:off
this.mvc.perform(get("/authenticated").with(bearerToken(jwtOne)))
.andExpect(status().isOk())
@@ -1258,7 +1258,6 @@ public class OAuth2ResourceServerConfigurerTests {
// @formatter:on
mockWebServer(String.format(metadata, issuerTwo, issuerTwo));
mockWebServer(jwkSet);
mockWebServer(jwkSet);
// @formatter:off
this.mvc.perform(get("/authenticated").with(bearerToken(jwtTwo)))
.andExpect(status().isOk())
@@ -1266,7 +1265,6 @@ public class OAuth2ResourceServerConfigurerTests {
// @formatter:on
mockWebServer(String.format(metadata, issuerThree, issuerThree));
mockWebServer(jwkSet);
mockWebServer(jwkSet);
// @formatter:off
this.mvc.perform(get("/authenticated").with(bearerToken(jwtThree)))
.andExpect(status().isUnauthorized())
@@ -37,6 +37,7 @@ import org.mockito.ArgumentCaptor;
import org.opensaml.saml.saml2.core.Assertion;
import org.opensaml.saml.saml2.core.AuthnRequest;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
@@ -49,6 +50,7 @@ import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
@@ -63,6 +65,7 @@ import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMap
import org.springframework.security.saml2.core.Saml2ErrorCodes;
import org.springframework.security.saml2.core.Saml2Utils;
import org.springframework.security.saml2.core.TestSaml2X509Credentials;
import org.springframework.security.saml2.provider.service.authentication.AbstractSaml2AuthenticationRequest;
import org.springframework.security.saml2.provider.service.authentication.OpenSaml4AuthenticationProvider;
import org.springframework.security.saml2.provider.service.authentication.OpenSaml4AuthenticationRequestFactory;
import org.springframework.security.saml2.provider.service.authentication.OpenSamlAuthenticationProvider;
@@ -78,7 +81,10 @@ import org.springframework.security.saml2.provider.service.registration.RelyingP
import org.springframework.security.saml2.provider.service.registration.TestRelyingPartyRegistrations;
import org.springframework.security.saml2.provider.service.servlet.filter.Saml2WebSsoAuthenticationFilter;
import org.springframework.security.saml2.provider.service.web.Saml2AuthenticationRequestContextResolver;
import org.springframework.security.saml2.provider.service.web.Saml2AuthenticationRequestRepository;
import org.springframework.security.saml2.provider.service.web.Saml2AuthenticationTokenConverter;
import org.springframework.security.web.FilterChainProxy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.AuthenticationConverter;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.context.HttpRequestResponseHolder;
@@ -92,6 +98,7 @@ import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.BDDMockito.given;
@@ -119,6 +126,8 @@ public class Saml2LoginConfigurerTests {
private static final String SIGNED_RESPONSE = "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48c2FtbDJwOlJlc3BvbnNlIHhtbG5zOnNhbWwycD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnByb3RvY29sIiBEZXN0aW5hdGlvbj0iaHR0cHM6Ly9ycC5leGFtcGxlLm9yZy9hY3MiIElEPSJfYzE3MzM2YTAtNTM1My00MTQ5LWI3MmMtMDNkOWY5YWYzMDdlIiBJc3N1ZUluc3RhbnQ9IjIwMjAtMDgtMDRUMjI6MDQ6NDUuMDE2WiIgVmVyc2lvbj0iMi4wIj48c2FtbDI6SXNzdWVyIHhtbG5zOnNhbWwyPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXNzZXJ0aW9uIj5hcC1lbnRpdHktaWQ8L3NhbWwyOklzc3Vlcj48ZHM6U2lnbmF0dXJlIHhtbG5zOmRzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjIj4KPGRzOlNpZ25lZEluZm8+CjxkczpDYW5vbmljYWxpemF0aW9uTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94bWwtZXhjLWMxNG4jIi8+CjxkczpTaWduYXR1cmVNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGRzaWctbW9yZSNyc2Etc2hhMjU2Ii8+CjxkczpSZWZlcmVuY2UgVVJJPSIjX2MxNzMzNmEwLTUzNTMtNDE0OS1iNzJjLTAzZDlmOWFmMzA3ZSI+CjxkczpUcmFuc2Zvcm1zPgo8ZHM6VHJhbnNmb3JtIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnI2VudmVsb3BlZC1zaWduYXR1cmUiLz4KPGRzOlRyYW5zZm9ybSBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMTAveG1sLWV4Yy1jMTRuIyIvPgo8L2RzOlRyYW5zZm9ybXM+CjxkczpEaWdlc3RNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGVuYyNzaGEyNTYiLz4KPGRzOkRpZ2VzdFZhbHVlPjYzTmlyenFzaDVVa0h1a3NuRWUrM0hWWU5aYWFsQW1OQXFMc1lGMlRuRDA9PC9kczpEaWdlc3RWYWx1ZT4KPC9kczpSZWZlcmVuY2U+CjwvZHM6U2lnbmVkSW5mbz4KPGRzOlNpZ25hdHVyZVZhbHVlPgpLMVlvWWJVUjBTclY4RTdVMkhxTTIvZUNTOTNoV25mOExnNnozeGZWMUlyalgzSXhWYkNvMVlYcnRBSGRwRVdvYTJKKzVOMmFNbFBHJiMxMzsKN2VpbDBZRC9xdUVRamRYbTNwQTBjZmEvY25pa2RuKzVhbnM0ZWQwanU1amo2dkpvZ2w2Smt4Q25LWUpwTU9HNzhtampmb0phengrWCYjMTM7CkM2NktQVStBYUdxeGVwUEQ1ZlhRdTFKSy9Jb3lBaitaa3k4Z2Jwc3VyZHFCSEJLRWxjdnVOWS92UGY0OGtBeFZBKzdtRGhNNUMvL1AmIzEzOwp0L084Y3NZYXB2UjZjdjZrdk45QXZ1N3FRdm9qVk1McHVxZWNJZDJwTUVYb0NSSnE2Nkd4MStNTUVPeHVpMWZZQlRoMEhhYjRmK3JyJiMxMzsKOEY2V1NFRC8xZllVeHliRkJqZ1Q4d2lEWHFBRU8wSVY4ZWRQeEE9PQo8L2RzOlNpZ25hdHVyZVZhbHVlPgo8L2RzOlNpZ25hdHVyZT48c2FtbDI6QXNzZXJ0aW9uIHhtbG5zOnNhbWwyPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXNzZXJ0aW9uIiBJRD0iQWUzZjQ5OGI4LTliMTctNDA3OC05ZDM1LTg2YTA4NDA4NDk5NSIgSXNzdWVJbnN0YW50PSIyMDIwLTA4LTA0VDIyOjA0OjQ1LjA3N1oiIFZlcnNpb249IjIuMCI+PHNhbWwyOklzc3Vlcj5hcC1lbnRpdHktaWQ8L3NhbWwyOklzc3Vlcj48c2FtbDI6U3ViamVjdD48c2FtbDI6TmFtZUlEPnRlc3RAc2FtbC51c2VyPC9zYW1sMjpOYW1lSUQ+PHNhbWwyOlN1YmplY3RDb25maXJtYXRpb24gTWV0aG9kPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6Y206YmVhcmVyIj48c2FtbDI6U3ViamVjdENvbmZpcm1hdGlvbkRhdGEgTm90QmVmb3JlPSIyMDIwLTA4LTA0VDIxOjU5OjQ1LjA5MFoiIE5vdE9uT3JBZnRlcj0iMjA0MC0wNy0zMFQyMjowNTowNi4wODhaIiBSZWNpcGllbnQ9Imh0dHBzOi8vcnAuZXhhbXBsZS5vcmcvYWNzIi8+PC9zYW1sMjpTdWJqZWN0Q29uZmlybWF0aW9uPjwvc2FtbDI6U3ViamVjdD48c2FtbDI6Q29uZGl0aW9ucyBOb3RCZWZvcmU9IjIwMjAtMDgtMDRUMjE6NTk6NDUuMDgwWiIgTm90T25PckFmdGVyPSIyMDQwLTA3LTMwVDIyOjA1OjA2LjA4N1oiLz48L3NhbWwyOkFzc2VydGlvbj48L3NhbWwycDpSZXNwb25zZT4=";
private static final AuthenticationConverter AUTHENTICATION_CONVERTER = mock(AuthenticationConverter.class);
@Autowired
private ConfigurableApplicationContext context;
@@ -183,7 +192,8 @@ public class Saml2LoginConfigurerTests {
this.spring.register(CustomAuthenticationRequestContextResolver.class).autowire();
Saml2AuthenticationRequestContext context = TestSaml2AuthenticationRequestContexts
.authenticationRequestContext().build();
Saml2AuthenticationRequestContextResolver resolver = CustomAuthenticationRequestContextResolver.resolver;
Saml2AuthenticationRequestContextResolver resolver = this.spring.getContext()
.getBean(Saml2AuthenticationRequestContextResolver.class);
given(resolver.resolve(any(HttpServletRequest.class))).willReturn(context);
this.mvc.perform(get("/saml2/authenticate/registration-id")).andExpect(status().isFound());
verify(resolver).resolve(any(HttpServletRequest.class));
@@ -219,6 +229,26 @@ public class Saml2LoginConfigurerTests {
verify(CustomAuthenticationConverter.authenticationConverter).convert(any(HttpServletRequest.class));
}
@Test
public void authenticateWhenCustomAuthenticationConverterBeanThenUses() throws Exception {
this.spring.register(CustomAuthenticationConverterBean.class).autowire();
Saml2AuthenticationTokenConverter authenticationConverter = this.spring.getContext()
.getBean(Saml2AuthenticationTokenConverter.class);
RelyingPartyRegistration relyingPartyRegistration = TestRelyingPartyRegistrations.noCredentials()
.assertingPartyDetails((party) -> party.verificationX509Credentials(
(c) -> c.add(TestSaml2X509Credentials.relyingPartyVerifyingCredential())))
.build();
String response = new String(Saml2Utils.samlDecode(SIGNED_RESPONSE));
given(authenticationConverter.convert(any(HttpServletRequest.class)))
.willReturn(new Saml2AuthenticationToken(relyingPartyRegistration, response));
// @formatter:off
MockHttpServletRequestBuilder request = post("/login/saml2/sso/" + relyingPartyRegistration.getRegistrationId())
.param("SAMLResponse", SIGNED_RESPONSE);
// @formatter:on
this.mvc.perform(request).andExpect(redirectedUrl("/"));
verify(authenticationConverter).convert(any(HttpServletRequest.class));
}
@Test
public void authenticateWithInvalidDeflatedSAMLResponseThenFailureHandlerUses() throws Exception {
this.spring.register(CustomAuthenticationFailureHandler.class).autowire();
@@ -237,6 +267,76 @@ public class Saml2LoginConfigurerTests {
assertThat(exception.getCause()).isInstanceOf(IOException.class);
}
@Test
public void authenticationRequestWhenCustomAuthnRequestRepositoryThenUses() throws Exception {
this.spring.register(CustomAuthenticationRequestRepository.class).autowire();
MockHttpServletRequestBuilder request = get("/saml2/authenticate/registration-id");
this.mvc.perform(request).andExpect(status().isFound());
Saml2AuthenticationRequestRepository<AbstractSaml2AuthenticationRequest> repository = this.spring.getContext()
.getBean(Saml2AuthenticationRequestRepository.class);
verify(repository).saveAuthenticationRequest(any(AbstractSaml2AuthenticationRequest.class),
any(HttpServletRequest.class), any(HttpServletResponse.class));
}
@Test
public void authenticateWhenCustomAuthnRequestRepositoryThenUses() throws Exception {
this.spring.register(CustomAuthenticationRequestRepository.class).autowire();
MockHttpServletRequestBuilder request = post("/login/saml2/sso/registration-id").param("SAMLResponse",
SIGNED_RESPONSE);
Saml2AuthenticationRequestRepository<AbstractSaml2AuthenticationRequest> repository = this.spring.getContext()
.getBean(Saml2AuthenticationRequestRepository.class);
this.mvc.perform(request);
verify(repository).loadAuthenticationRequest(any(HttpServletRequest.class));
verify(repository).removeAuthenticationRequest(any(HttpServletRequest.class), any(HttpServletResponse.class));
}
@Test
public void saml2LoginWhenLoginProcessingUrlWithoutRegistrationIdAndDefaultAuthenticationConverterThenValidates() {
assertThatExceptionOfType(BeanCreationException.class)
.isThrownBy(() -> this.spring.register(CustomLoginProcessingUrlDefaultAuthenticationConverter.class)
.autowire())
.havingRootCause().isInstanceOf(IllegalStateException.class)
.withMessage("loginProcessingUrl must contain {registrationId} path variable");
}
@Test
public void authenticateWhenCustomLoginProcessingUrlAndCustomAuthenticationConverterThenAuthenticate()
throws Exception {
this.spring.register(CustomLoginProcessingUrlCustomAuthenticationConverter.class).autowire();
RelyingPartyRegistration relyingPartyRegistration = TestRelyingPartyRegistrations.noCredentials()
.assertingPartyDetails((party) -> party.verificationX509Credentials(
(c) -> c.add(TestSaml2X509Credentials.relyingPartyVerifyingCredential())))
.build();
String response = new String(Saml2Utils.samlDecode(SIGNED_RESPONSE));
given(AUTHENTICATION_CONVERTER.convert(any(HttpServletRequest.class)))
.willReturn(new Saml2AuthenticationToken(relyingPartyRegistration, response));
// @formatter:off
MockHttpServletRequestBuilder request = post("/my/custom/url").param("SAMLResponse", SIGNED_RESPONSE);
// @formatter:on
this.mvc.perform(request).andExpect(redirectedUrl("/"));
verify(AUTHENTICATION_CONVERTER).convert(any(HttpServletRequest.class));
}
@Test
public void authenticateWhenCustomLoginProcessingUrlAndSaml2AuthenticationTokenConverterBeanThenAuthenticate()
throws Exception {
this.spring.register(CustomLoginProcessingUrlSaml2AuthenticationTokenConverterBean.class).autowire();
Saml2AuthenticationTokenConverter authenticationConverter = this.spring.getContext()
.getBean(Saml2AuthenticationTokenConverter.class);
RelyingPartyRegistration relyingPartyRegistration = TestRelyingPartyRegistrations.noCredentials()
.assertingPartyDetails((party) -> party.verificationX509Credentials(
(c) -> c.add(TestSaml2X509Credentials.relyingPartyVerifyingCredential())))
.build();
String response = new String(Saml2Utils.samlDecode(SIGNED_RESPONSE));
given(authenticationConverter.convert(any(HttpServletRequest.class)))
.willReturn(new Saml2AuthenticationToken(relyingPartyRegistration, response));
// @formatter:off
MockHttpServletRequestBuilder request = post("/my/custom/url").param("SAMLResponse", SIGNED_RESPONSE);
// @formatter:on
this.mvc.perform(request).andExpect(redirectedUrl("/"));
verify(authenticationConverter).convert(any(HttpServletRequest.class));
}
private void validateSaml2WebSsoAuthenticationFilterConfiguration() {
// get the OpenSamlAuthenticationProvider
Saml2WebSsoAuthenticationFilter filter = getSaml2SsoFilter(this.springSecurityFilterChain);
@@ -355,7 +455,7 @@ public class Saml2LoginConfigurerTests {
@Import(Saml2LoginConfigBeans.class)
static class CustomAuthenticationRequestContextResolver extends WebSecurityConfigurerAdapter {
private static final Saml2AuthenticationRequestContextResolver resolver = mock(
private final Saml2AuthenticationRequestContextResolver resolver = mock(
Saml2AuthenticationRequestContextResolver.class);
@Override
@@ -371,7 +471,7 @@ public class Saml2LoginConfigurerTests {
@Bean
Saml2AuthenticationRequestContextResolver resolver() {
return resolver;
return this.resolver;
}
}
@@ -420,6 +520,107 @@ public class Saml2LoginConfigurerTests {
}
@EnableWebSecurity
@Import(Saml2LoginConfigBeans.class)
static class CustomAuthenticationConverterBean {
private final Saml2AuthenticationTokenConverter authenticationConverter = mock(
Saml2AuthenticationTokenConverter.class);
@Bean
SecurityFilterChain app(HttpSecurity http) throws Exception {
http.authorizeHttpRequests((authz) -> authz.anyRequest().authenticated())
.saml2Login(Customizer.withDefaults());
return http.build();
}
@Bean
Saml2AuthenticationTokenConverter authenticationConverter() {
return this.authenticationConverter;
}
}
@EnableWebSecurity
@Import(Saml2LoginConfigBeans.class)
static class CustomAuthenticationRequestRepository {
private final Saml2AuthenticationRequestRepository<AbstractSaml2AuthenticationRequest> repository = mock(
Saml2AuthenticationRequestRepository.class);
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.authorizeRequests((authz) -> authz.anyRequest().authenticated());
http.saml2Login(withDefaults());
return http.build();
}
@Bean
Saml2AuthenticationRequestRepository<AbstractSaml2AuthenticationRequest> authenticationRequestRepository() {
return this.repository;
}
}
@EnableWebSecurity
@Import(Saml2LoginConfigBeans.class)
static class CustomLoginProcessingUrlDefaultAuthenticationConverter {
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
// @formatter:off
http
.authorizeRequests((authz) -> authz.anyRequest().authenticated())
.saml2Login((saml2) -> saml2.loginProcessingUrl("/my/custom/url"));
// @formatter:on
return http.build();
}
}
@EnableWebSecurity
@Import(Saml2LoginConfigBeans.class)
static class CustomLoginProcessingUrlCustomAuthenticationConverter {
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
// @formatter:off
http
.authorizeRequests((authz) -> authz.anyRequest().authenticated())
.saml2Login((saml2) -> saml2
.loginProcessingUrl("/my/custom/url")
.authenticationConverter(AUTHENTICATION_CONVERTER)
);
// @formatter:on
return http.build();
}
}
@EnableWebSecurity
@Import(Saml2LoginConfigBeans.class)
static class CustomLoginProcessingUrlSaml2AuthenticationTokenConverterBean {
private final Saml2AuthenticationTokenConverter authenticationConverter = mock(
Saml2AuthenticationTokenConverter.class);
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
// @formatter:off
http
.authorizeRequests((authz) -> authz.anyRequest().authenticated())
.saml2Login((saml2) -> saml2.loginProcessingUrl("/my/custom/url"));
// @formatter:on
return http.build();
}
@Bean
Saml2AuthenticationTokenConverter authenticationTokenConverter() {
return this.authenticationConverter;
}
}
static class Saml2LoginConfigBeans {
@Bean

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