From dd8528d257712f9c4ead6ee47bd5691adfcf0db1 Mon Sep 17 00:00:00 2001 From: Brian Demers Date: Fri, 12 Oct 2018 09:30:59 -0400 Subject: [PATCH 01/12] Bump version to 0.7.0-SNAPSHOT --- config/pom.xml | 2 +- coverage/pom.xml | 2 +- examples/hosted-login-code-flow/pom.xml | 4 ++-- examples/pom.xml | 2 +- examples/redirect-code-flow/pom.xml | 2 +- examples/siw-jquery/pom.xml | 4 ++-- integration-tests/oauth2-boot2/pom.xml | 2 +- integration-tests/oauth2/pom.xml | 2 +- oauth2/pom.xml | 2 +- okta-spring-boot-starter/pom.xml | 2 +- pom.xml | 12 ++++++------ sdk/pom.xml | 2 +- 12 files changed, 19 insertions(+), 19 deletions(-) diff --git a/config/pom.xml b/config/pom.xml index 0aee43ad4..2006c3cd1 100644 --- a/config/pom.xml +++ b/config/pom.xml @@ -20,7 +20,7 @@ com.okta.spring okta-spring-boot-parent - 0.6.2-SNAPSHOT + 0.7.0-SNAPSHOT okta-spring-config diff --git a/coverage/pom.xml b/coverage/pom.xml index c0047a1e7..95a0317fb 100644 --- a/coverage/pom.xml +++ b/coverage/pom.xml @@ -20,7 +20,7 @@ com.okta.spring okta-spring-boot-parent - 0.6.2-SNAPSHOT + 0.7.0-SNAPSHOT okta-spring-boot-coverage diff --git a/examples/hosted-login-code-flow/pom.xml b/examples/hosted-login-code-flow/pom.xml index df78a1f52..e4cac81c9 100644 --- a/examples/hosted-login-code-flow/pom.xml +++ b/examples/hosted-login-code-flow/pom.xml @@ -20,7 +20,7 @@ com.okta.spring.examples okta-spring-boot-examples - 0.6.2-SNAPSHOT + 0.7.0-SNAPSHOT okta-spring-boot-hosted-code-flow-example @@ -43,7 +43,7 @@ com.okta.spring okta-spring-sdk - 0.6.2-SNAPSHOT + 0.7.0-SNAPSHOT org.springframework.boot diff --git a/examples/pom.xml b/examples/pom.xml index 78b0ec7a4..5de28bbeb 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -21,7 +21,7 @@ com.okta.spring okta-spring-boot-parent - 0.6.2-SNAPSHOT + 0.7.0-SNAPSHOT com.okta.spring.examples diff --git a/examples/redirect-code-flow/pom.xml b/examples/redirect-code-flow/pom.xml index cb8048fe1..fcc066e31 100644 --- a/examples/redirect-code-flow/pom.xml +++ b/examples/redirect-code-flow/pom.xml @@ -20,7 +20,7 @@ com.okta.spring.examples okta-spring-boot-examples - 0.6.2-SNAPSHOT + 0.7.0-SNAPSHOT okta-spring-boot-redirect-code-flow-example diff --git a/examples/siw-jquery/pom.xml b/examples/siw-jquery/pom.xml index ae374d026..51c44c092 100644 --- a/examples/siw-jquery/pom.xml +++ b/examples/siw-jquery/pom.xml @@ -20,7 +20,7 @@ com.okta.spring.examples okta-spring-boot-examples - 0.6.2-SNAPSHOT + 0.7.0-SNAPSHOT okta-spring-boot-siw-jquery-example @@ -59,7 +59,7 @@ com.okta.spring okta-spring-boot-starter - 0.6.2-SNAPSHOT + 0.7.0-SNAPSHOT diff --git a/integration-tests/oauth2-boot2/pom.xml b/integration-tests/oauth2-boot2/pom.xml index 520937958..53da50925 100644 --- a/integration-tests/oauth2-boot2/pom.xml +++ b/integration-tests/oauth2-boot2/pom.xml @@ -20,7 +20,7 @@ com.okta.spring okta-spring-boot-parent - 0.6.2-SNAPSHOT + 0.7.0-SNAPSHOT okta-spring-boot2-integration-tests-oauth2 diff --git a/integration-tests/oauth2/pom.xml b/integration-tests/oauth2/pom.xml index ccb063269..f62cdaee2 100644 --- a/integration-tests/oauth2/pom.xml +++ b/integration-tests/oauth2/pom.xml @@ -20,7 +20,7 @@ com.okta.spring okta-spring-boot-parent - 0.6.2-SNAPSHOT + 0.7.0-SNAPSHOT okta-spring-boot-integration-tests-oauth2 diff --git a/oauth2/pom.xml b/oauth2/pom.xml index 9b2efc493..19ffeffa1 100644 --- a/oauth2/pom.xml +++ b/oauth2/pom.xml @@ -20,7 +20,7 @@ com.okta.spring okta-spring-boot-parent - 0.6.2-SNAPSHOT + 0.7.0-SNAPSHOT okta-spring-security-oauth2 diff --git a/okta-spring-boot-starter/pom.xml b/okta-spring-boot-starter/pom.xml index 97f0a4781..1e6285ffb 100644 --- a/okta-spring-boot-starter/pom.xml +++ b/okta-spring-boot-starter/pom.xml @@ -20,7 +20,7 @@ com.okta.spring okta-spring-boot-parent - 0.6.2-SNAPSHOT + 0.7.0-SNAPSHOT okta-spring-boot-starter diff --git a/pom.xml b/pom.xml index bcea24820..8110bd1dd 100644 --- a/pom.xml +++ b/pom.xml @@ -26,7 +26,7 @@ com.okta.spring okta-spring-boot-parent - 0.6.2-SNAPSHOT + 0.7.0-SNAPSHOT Okta Spring Boot pom @@ -96,27 +96,27 @@ com.okta.spring okta-spring-boot-starter - 0.6.2-SNAPSHOT + 0.7.0-SNAPSHOT com.okta.spring okta-spring-security-oauth2 - 0.6.2-SNAPSHOT + 0.7.0-SNAPSHOT com.okta.spring okta-spring-config - 0.6.2-SNAPSHOT + 0.7.0-SNAPSHOT com.okta.spring okta-spring-sdk - 0.6.2-SNAPSHOT + 0.7.0-SNAPSHOT com.okta.spring okta-spring-boot-integration-tests-oauth2 - 0.6.2-SNAPSHOT + 0.7.0-SNAPSHOT com.okta.sdk diff --git a/sdk/pom.xml b/sdk/pom.xml index 1c3267f3b..ec467fdec 100644 --- a/sdk/pom.xml +++ b/sdk/pom.xml @@ -20,7 +20,7 @@ com.okta.spring okta-spring-boot-parent - 0.6.2-SNAPSHOT + 0.7.0-SNAPSHOT okta-spring-sdk From 52d261c5ec24663a518ab1131e24d1ef6100e80e Mon Sep 17 00:00:00 2001 From: Brian Demers Date: Tue, 30 Oct 2018 15:25:40 -0400 Subject: [PATCH 02/12] Rewrite for Spring Boot 2.1 and Spring Security 5.1 With OIDC support! --- README.md | 40 +- .../config/OktaPropertiesConfiguration.java | 24 - .../main/resources/META-INF/spring.factories | 1 - .../OktaPropertiesConfigurationTest.groovy | 69 --- coverage/pom.xml | 4 - examples/config-server/pom.xml | 40 ++ .../configserver/ConfigServerApplication.java | 14 + .../src/main/resources/application.yml | 11 + .../resources/config-repo/application.yml | 5 + ...HostedLoginCodeFlowExampleApplication.java | 106 +--- .../example/controllers/LoginController.java | 36 +- .../controllers/WelcomeController.java | 2 +- .../src/main/resources/application.yml | 22 +- .../resources/static/{okta => css}/okta.css | 0 .../main/resources/templates/okta/head.html | 8 +- examples/pom.xml | 1 + examples/redirect-code-flow/pom.xml | 15 +- .../example/RedirectCodeFlowApplication.java | 30 +- .../controllers/WelcomeController.java | 1 - .../src/main/resources/application.yml | 11 +- examples/siw-jquery/README.md | 2 +- examples/siw-jquery/pom.xml | 7 - .../example/ImplicitFlowApplication.java | 52 +- .../SignInWidgetConfigController.java | 16 +- .../controllers/WelcomeController.java | 2 +- .../src/main/resources/application.yml | 10 +- .../src/main/resources/public/index.html | 13 +- integration-tests/oauth2-boot2/pom.xml | 151 ------ .../BasicRedirectCodeFlowApplication.java | 56 -- .../BasicImplicitFlowApplication.java | 114 ---- .../src/main/resources/application.yml | 30 -- .../oauth2/SpringApplicationUnderTest.groovy | 48 -- .../ImplicitLocalValidationGroupIT.groovy | 40 -- .../ImplicitRemoteValidationGroupIT.groovy | 58 --- .../src/test/resources/testRunner.yml | 60 --- .../src/test/resources/versions.properties | 17 - integration-tests/oauth2/pom.xml | 9 +- .../BasicRedirectCodeFlowApplication.java | 32 +- .../BasicImplicitFlowApplication.java | 38 +- .../oauth2/src/main/resources/application.yml | 17 +- .../oauth2/SpringApplicationUnderTest.groovy | 9 + .../ImplicitRemoteValidationGroupIT.groovy | 58 --- .../oauth2/src/test/resources/testRunner.yml | 35 +- oauth2/pom.xml | 37 +- .../oauth/OktaJwtAuthenticationConverter.java | 26 + .../boot/oauth/OktaOAuth2AutoConfig.java | 19 + .../boot/oauth/OktaOAuth2Configurer.java | 48 ++ .../OktaOAuth2ResourceServerAutoConfig.java | 67 +++ .../boot/oauth/OktaOAuth2UserService.java | 42 ++ .../boot/oauth/OktaOidcUserService.java | 45 ++ .../com/okta/spring/boot/oauth/TokenUtil.java | 48 ++ .../oauth}/config/OktaOAuth2Properties.java | 114 ++-- ...ertiesMappingEnvironmentPostProcessor.java | 168 ++++++ .../oauth/env/RemappedPropertySource.java | 4 +- .../oauth/ClaimsAuthoritiesExtractor.java | 57 -- .../oauth/ClaimsPrincipalExtractor.java | 39 -- .../ConfigurableAccessTokenConverter.java | 96 ---- .../OAuth2AccessTokenValidationException.java | 28 - .../spring/oauth/OktaTokenServicesConfig.java | 138 ----- .../oauth/OktaUserInfoTokenServices.java | 46 -- .../oauth/ScopeSupportedOAuth2Request.java | 35 -- ...deFlowAudienceValidatingTokenServices.java | 47 -- .../code/OktaOAuthCodeFlowConfiguration.java | 53 -- .../OktaOAuthCodeFlowCustomConfiguration.java | 33 -- ...OktaOAuthCodeFlowDefaultConfiguration.java | 33 -- .../discovery/DiscoveryPropertySource.java | 133 ----- .../oauth/discovery/OidcDiscoveryClient.java | 72 --- .../discovery/OidcDiscoveryMetadata.java | 268 ---------- ...ertiesMappingEnvironmentPostProcessor.java | 203 -------- .../com/okta/spring/oauth/http/UserAgent.java | 485 ------------------ .../com/okta/spring/oauth/http/Version.java | 74 --- ...plicitAudienceValidatingTokenServices.java | 53 -- .../oauth/implicit/ResourceServerConfig.java | 72 --- .../main/resources/META-INF/spring.factories | 10 +- .../main/resources/com/okta/spring/okta.yml | 24 - .../OktaJwtAuthenticationConverterTest.groovy | 40 ++ .../spring/boot/oauth/TokenUtilTest.groovy | 94 ++++ .../config/OktaOAuth2PropertiesTest.groovy | 76 +++ ...MappingEnvironmentPostProcessorTest.groovy | 84 +++ .../ClaimsAuthoritiesExtractorTest.groovy | 99 ---- .../oauth/ClaimsPrincipalExtractorTest.groovy | 67 --- ...onfigurableAccessTokenConverterTest.groovy | 137 ----- ...MappingEnvironmentPostProcessorTest.groovy | 47 -- .../spring/oauth/code/MockCodeFlowApp.groovy | 36 -- .../code/MockCustomSsoCodeFlowApp.groovy | 37 -- ...AuthCodeFlowCustomConfigurationTest.groovy | 75 --- ...uthCodeFlowDefaultConfigurationTest.groovy | 72 --- .../code/OktaUserInfoTokenServicesTest.groovy | 62 --- .../DiscoveryPropertySourceTest.groovy | 116 ----- .../discovery/OidcDiscoveryClientTest.groovy | 46 -- .../oauth/discovery/OidcDiscoveryTest.groovy | 147 ------ .../implicit/ResourceServerConfigTest.groovy | 65 --- .../okta/spring/oauth/implicit/StubApp.groovy | 24 - .../spring/oauth/http/UserAgentTest.groovy | 51 -- pom.xml | 93 ++-- sdk/pom.xml | 4 - .../spring/{ => boot}/sdk/OktaSdkConfig.java | 8 +- .../{ => boot}/sdk/cache/SpringCache.java | 2 +- .../sdk/cache/SpringCacheManager.java | 2 +- .../sdk}/config/OktaClientProperties.java | 2 +- ...SdkPropertiesEnvironmentPostProcessor.java | 67 +++ .../main/resources/META-INF/spring.factories | 4 +- .../spring/{ => boot}/sdk/MockSdkApp.groovy | 2 +- .../{ => boot}/sdk/MockSdkAppWithCache.groovy | 2 +- .../{ => boot}/sdk/OktaSdkConfigTest.groovy | 2 +- .../sdk/OktaSdkConfigWithCacheTest.groovy | 4 +- .../sdk/OktaSdkConfigWithProxyTest.groovy | 4 +- .../sdk/cache/SpringCacheManagerTest.groovy | 2 +- .../sdk/cache/SpringCacheTest.groovy | 2 +- {config => starter}/pom.xml | 61 ++- 110 files changed, 1272 insertions(+), 4295 deletions(-) delete mode 100644 config/src/main/java/com/okta/spring/config/OktaPropertiesConfiguration.java delete mode 100644 config/src/main/resources/META-INF/spring.factories delete mode 100644 config/src/test/groovy/com/okta/spring/config/OktaPropertiesConfigurationTest.groovy create mode 100644 examples/config-server/pom.xml create mode 100644 examples/config-server/src/main/java/com/okta/example/cloud/configserver/ConfigServerApplication.java create mode 100644 examples/config-server/src/main/resources/application.yml create mode 100644 examples/config-server/src/main/resources/config-repo/application.yml rename examples/hosted-login-code-flow/src/main/resources/static/{okta => css}/okta.css (100%) delete mode 100644 integration-tests/oauth2-boot2/pom.xml delete mode 100644 integration-tests/oauth2-boot2/src/main/java/com/okta/spring/tests/oauth2/code/BasicRedirectCodeFlowApplication.java delete mode 100644 integration-tests/oauth2-boot2/src/main/java/com/okta/spring/tests/oauth2/implicit/BasicImplicitFlowApplication.java delete mode 100644 integration-tests/oauth2-boot2/src/main/resources/application.yml delete mode 100644 integration-tests/oauth2-boot2/src/test/groovy/com/okta/spring/tests/oauth2/SpringApplicationUnderTest.groovy delete mode 100644 integration-tests/oauth2-boot2/src/test/groovy/com/okta/spring/tests/oauth2/implicit/ImplicitLocalValidationGroupIT.groovy delete mode 100644 integration-tests/oauth2-boot2/src/test/groovy/com/okta/spring/tests/oauth2/implicit/ImplicitRemoteValidationGroupIT.groovy delete mode 100644 integration-tests/oauth2-boot2/src/test/resources/testRunner.yml delete mode 100644 integration-tests/oauth2-boot2/src/test/resources/versions.properties delete mode 100644 integration-tests/oauth2/src/test/groovy/com/okta/spring/tests/oauth2/implicit/ImplicitRemoteValidationGroupIT.groovy create mode 100644 oauth2/src/main/java/com/okta/spring/boot/oauth/OktaJwtAuthenticationConverter.java create mode 100644 oauth2/src/main/java/com/okta/spring/boot/oauth/OktaOAuth2AutoConfig.java create mode 100644 oauth2/src/main/java/com/okta/spring/boot/oauth/OktaOAuth2Configurer.java create mode 100644 oauth2/src/main/java/com/okta/spring/boot/oauth/OktaOAuth2ResourceServerAutoConfig.java create mode 100644 oauth2/src/main/java/com/okta/spring/boot/oauth/OktaOAuth2UserService.java create mode 100644 oauth2/src/main/java/com/okta/spring/boot/oauth/OktaOidcUserService.java create mode 100644 oauth2/src/main/java/com/okta/spring/boot/oauth/TokenUtil.java rename {config/src/main/java/com/okta/spring => oauth2/src/main/java/com/okta/spring/boot/oauth}/config/OktaOAuth2Properties.java (60%) create mode 100644 oauth2/src/main/java/com/okta/spring/boot/oauth/env/OktaOAuth2PropertiesMappingEnvironmentPostProcessor.java rename oauth2/src/main/java/com/okta/spring/{ => boot}/oauth/env/RemappedPropertySource.java (95%) delete mode 100644 oauth2/src/main/java/com/okta/spring/oauth/ClaimsAuthoritiesExtractor.java delete mode 100644 oauth2/src/main/java/com/okta/spring/oauth/ClaimsPrincipalExtractor.java delete mode 100644 oauth2/src/main/java/com/okta/spring/oauth/ConfigurableAccessTokenConverter.java delete mode 100644 oauth2/src/main/java/com/okta/spring/oauth/OAuth2AccessTokenValidationException.java delete mode 100644 oauth2/src/main/java/com/okta/spring/oauth/OktaTokenServicesConfig.java delete mode 100644 oauth2/src/main/java/com/okta/spring/oauth/OktaUserInfoTokenServices.java delete mode 100644 oauth2/src/main/java/com/okta/spring/oauth/ScopeSupportedOAuth2Request.java delete mode 100644 oauth2/src/main/java/com/okta/spring/oauth/code/CodeFlowAudienceValidatingTokenServices.java delete mode 100644 oauth2/src/main/java/com/okta/spring/oauth/code/OktaOAuthCodeFlowConfiguration.java delete mode 100644 oauth2/src/main/java/com/okta/spring/oauth/code/OktaOAuthCodeFlowCustomConfiguration.java delete mode 100644 oauth2/src/main/java/com/okta/spring/oauth/code/OktaOAuthCodeFlowDefaultConfiguration.java delete mode 100644 oauth2/src/main/java/com/okta/spring/oauth/discovery/DiscoveryPropertySource.java delete mode 100644 oauth2/src/main/java/com/okta/spring/oauth/discovery/OidcDiscoveryClient.java delete mode 100644 oauth2/src/main/java/com/okta/spring/oauth/discovery/OidcDiscoveryMetadata.java delete mode 100644 oauth2/src/main/java/com/okta/spring/oauth/env/OktaPropertiesMappingEnvironmentPostProcessor.java delete mode 100644 oauth2/src/main/java/com/okta/spring/oauth/http/UserAgent.java delete mode 100644 oauth2/src/main/java/com/okta/spring/oauth/http/Version.java delete mode 100644 oauth2/src/main/java/com/okta/spring/oauth/implicit/ImplicitAudienceValidatingTokenServices.java delete mode 100644 oauth2/src/main/java/com/okta/spring/oauth/implicit/ResourceServerConfig.java delete mode 100644 oauth2/src/main/resources/com/okta/spring/okta.yml create mode 100644 oauth2/src/test/groovy/com/okta/spring/boot/oauth/OktaJwtAuthenticationConverterTest.groovy create mode 100644 oauth2/src/test/groovy/com/okta/spring/boot/oauth/TokenUtilTest.groovy create mode 100644 oauth2/src/test/groovy/com/okta/spring/boot/oauth/config/OktaOAuth2PropertiesTest.groovy create mode 100644 oauth2/src/test/groovy/com/okta/spring/boot/oauth/env/OktaOAuth2PropertiesMappingEnvironmentPostProcessorTest.groovy delete mode 100644 oauth2/src/test/groovy/com/okta/spring/oauth/ClaimsAuthoritiesExtractorTest.groovy delete mode 100644 oauth2/src/test/groovy/com/okta/spring/oauth/ClaimsPrincipalExtractorTest.groovy delete mode 100644 oauth2/src/test/groovy/com/okta/spring/oauth/ConfigurableAccessTokenConverterTest.groovy delete mode 100644 oauth2/src/test/groovy/com/okta/spring/oauth/OktaPropertiesMappingEnvironmentPostProcessorTest.groovy delete mode 100644 oauth2/src/test/groovy/com/okta/spring/oauth/code/MockCodeFlowApp.groovy delete mode 100644 oauth2/src/test/groovy/com/okta/spring/oauth/code/MockCustomSsoCodeFlowApp.groovy delete mode 100644 oauth2/src/test/groovy/com/okta/spring/oauth/code/OktaOAuthCodeFlowCustomConfigurationTest.groovy delete mode 100644 oauth2/src/test/groovy/com/okta/spring/oauth/code/OktaOAuthCodeFlowDefaultConfigurationTest.groovy delete mode 100644 oauth2/src/test/groovy/com/okta/spring/oauth/code/OktaUserInfoTokenServicesTest.groovy delete mode 100644 oauth2/src/test/groovy/com/okta/spring/oauth/discovery/DiscoveryPropertySourceTest.groovy delete mode 100644 oauth2/src/test/groovy/com/okta/spring/oauth/discovery/OidcDiscoveryClientTest.groovy delete mode 100644 oauth2/src/test/groovy/com/okta/spring/oauth/discovery/OidcDiscoveryTest.groovy delete mode 100644 oauth2/src/test/groovy/com/okta/spring/oauth/implicit/ResourceServerConfigTest.groovy delete mode 100644 oauth2/src/test/groovy/com/okta/spring/oauth/implicit/StubApp.groovy delete mode 100644 okta-spring-boot-starter/src/test/groovy/com/okta/spring/oauth/http/UserAgentTest.groovy rename sdk/src/main/java/com/okta/spring/{ => boot}/sdk/OktaSdkConfig.java (94%) rename sdk/src/main/java/com/okta/spring/{ => boot}/sdk/cache/SpringCache.java (98%) rename sdk/src/main/java/com/okta/spring/{ => boot}/sdk/cache/SpringCacheManager.java (98%) rename {config/src/main/java/com/okta/spring => sdk/src/main/java/com/okta/spring/boot/sdk}/config/OktaClientProperties.java (98%) create mode 100644 sdk/src/main/java/com/okta/spring/boot/sdk/env/OktaSdkPropertiesEnvironmentPostProcessor.java rename sdk/src/test/groovy/com/okta/spring/{ => boot}/sdk/MockSdkApp.groovy (96%) rename sdk/src/test/groovy/com/okta/spring/{ => boot}/sdk/MockSdkAppWithCache.groovy (97%) rename sdk/src/test/groovy/com/okta/spring/{ => boot}/sdk/OktaSdkConfigTest.groovy (98%) rename sdk/src/test/groovy/com/okta/spring/{ => boot}/sdk/OktaSdkConfigWithCacheTest.groovy (94%) rename sdk/src/test/groovy/com/okta/spring/{ => boot}/sdk/OktaSdkConfigWithProxyTest.groovy (97%) rename sdk/src/test/groovy/com/okta/spring/{ => boot}/sdk/cache/SpringCacheManagerTest.groovy (97%) rename sdk/src/test/groovy/com/okta/spring/{ => boot}/sdk/cache/SpringCacheTest.groovy (98%) rename {config => starter}/pom.xml (66%) diff --git a/README.md b/README.md index 2707dafd4..a23677c15 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,9 @@ Okta Spring Boot Starter ======================== -Okta's Spring Boot Starter will enable your Spring Boot application to work with Okta via OAuth 2.0. Jump to our [quickstart](https://developer.okta.com/quickstart/#/angular/java/spring) to see how to configure various clients or follow along below to use curl. +Okta's Spring Boot Starter will enable your Spring Boot application to work with Okta via OAuth 2.0/OIDC. Jump to our [quickstart](https://developer.okta.com/quickstart/#/angular/java/spring) to see how to configure various clients or follow along below to use curl. + +**NOTE:** If you need support for Spring Boot 1.5.x, use version version 0.6. ## What you need @@ -42,17 +44,15 @@ You can configure your applications properties with environment variables, syste | okta.oauth2.issuer | N/A | [Authorization Server](/docs/how-to/set-up-auth-server.html) issuer URL, i.e.: https://{yourOktaDomain}/oauth2/default | | okta.oauth2.clientId | N/A | The Client Id of your Okta OIDC application | | okta.oauth2.audience | api://default | The audience of your [Authorization Server](/docs/how-to/set-up-auth-server.html) | -| okta.oauth2.scopeClaim | scp | The scope claim key in the Access Token's JWT | -| okta.oauth2.rolesClaim | groups | The claim key in the Access Token's JWT that corresponds to an array of the users groups. | +| okta.oauth2.groupsClaim | groups | The claim key in the Access Token's JWT that corresponds to an array of the users groups. | ### Create a Controller The above client makes a request to `/hello-oauth`, you simply need to create a Spring Boot application and `Controller` to handle the response: ```java -@EnableResourceServer -@SpringBootApplication @RestController +@SpringBootApplication public class ExampleApplication { public static void main(String[] args) { @@ -63,10 +63,19 @@ public class ExampleApplication { public String sayHello(Principal principal) { return "Hello, " + principal.getName(); } + + @Configuration + static class OktaOAuth2WebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter { + + @Override + protected void configure(HttpSecurity http) throws Exception { + http.oauth2ResourceServer().jwt(); + } + } } ``` -Make sure to mark the application with Spring Security's `@EnableResourceServer` annotation, to enable handling of access tokens. +Make sure to configure the `WebSecurityConfigurerAdaptor` with `http.oauth2ResourceServer().jwt()` to enable handling of access tokens. ### That's it! @@ -105,9 +114,8 @@ You can configure your applications properties with environment variables, syste Create a minimal Spring Boot application: ```java -@EnableOAuth2Sso -@SpringBootApplication @RestController +@SpringBootApplication public class ExampleApplication { public static void main(String[] args) { @@ -121,6 +129,22 @@ public class ExampleApplication { } ``` +If you want to allow anonymous access to specific routes you can add a `WebSecurityConfigurerAdapter`: + +```java +@Configuration +static class WebConfig extends WebSecurityConfigurerAdapter { + @Override + protected void configure(HttpSecurity http) throws Exception { + http.authorizeRequests() + .antMatchers("/my-anon-page").permitAll() + .anyRequest().authenticated() + .and().oauth2Client() + .and().oauth2Login(); + } +} +``` + ### That's it! Open up the this link in your browser: [http://localhost:8080/](http://localhost:8080/) diff --git a/config/src/main/java/com/okta/spring/config/OktaPropertiesConfiguration.java b/config/src/main/java/com/okta/spring/config/OktaPropertiesConfiguration.java deleted file mode 100644 index 001088333..000000000 --- a/config/src/main/java/com/okta/spring/config/OktaPropertiesConfiguration.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright 2017 Okta, Inc. - * - * 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 - * - * http://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 com.okta.spring.config; - -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Configuration; - -@Configuration -@EnableConfigurationProperties({OktaOAuth2Properties.class, OktaClientProperties.class}) -public class OktaPropertiesConfiguration { -} diff --git a/config/src/main/resources/META-INF/spring.factories b/config/src/main/resources/META-INF/spring.factories deleted file mode 100644 index 6bbfdbe36..000000000 --- a/config/src/main/resources/META-INF/spring.factories +++ /dev/null @@ -1 +0,0 @@ -org.springframework.boot.autoconfigure.EnableAutoConfiguration = com.okta.spring.config.OktaPropertiesConfiguration \ No newline at end of file diff --git a/config/src/test/groovy/com/okta/spring/config/OktaPropertiesConfigurationTest.groovy b/config/src/test/groovy/com/okta/spring/config/OktaPropertiesConfigurationTest.groovy deleted file mode 100644 index affb470c6..000000000 --- a/config/src/test/groovy/com/okta/spring/config/OktaPropertiesConfigurationTest.groovy +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright 2018-Present Okta, Inc. - * - * 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 - * - * http://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 com.okta.spring.config - -import org.springframework.beans.factory.BeanCreationException -import org.springframework.boot.test.util.EnvironmentTestUtils -import org.springframework.context.annotation.AnnotationConfigApplicationContext -import org.testng.Assert -import org.testng.annotations.Test - -import static org.hamcrest.MatcherAssert.assertThat -import static org.hamcrest.Matchers.allOf -import static org.hamcrest.Matchers.containsString -import static org.hamcrest.Matchers.instanceOf - -class OktaPropertiesConfigurationTest { - - @Test - void issuerIsNotAUrl() { - createAndValidateContext({ - assertThat(it.message, allOf(containsString("foobar"), - containsString("It looks like there's a typo in your Okta Issuer URL"))) - }, "okta.oauth2.issuer:foobar") - } - - @Test - void nonHttpsIssuerUrl() { - createAndValidateContext({ - assertThat(it.message, allOf(containsString("http://okta.example.com"), - containsString("Your Okta Issuer URL must start with https"))) - }, "okta.oauth2.issuer:http://okta.example.com") - } - - static void createAndValidateContext(Closure validation, String... pairs) { - - def context = new AnnotationConfigApplicationContext() - context.register(OktaPropertiesConfiguration.class) - try { - EnvironmentTestUtils.addEnvironment(context, pairs) - def e = expect(BeanCreationException, {context.refresh()}) - validation.call(e) - } finally { - context.close() - } - } - - static E expect(Class catchMe, Closure callMe) { - try { - callMe.call() - Assert.fail("Expected ${catchMe.getName()} to be thrown.") - } catch(e) { - assertThat(e, instanceOf(catchMe)) - return e - } - } -} diff --git a/coverage/pom.xml b/coverage/pom.xml index 95a0317fb..8eb472f8e 100644 --- a/coverage/pom.xml +++ b/coverage/pom.xml @@ -28,10 +28,6 @@ pom - - com.okta.spring - okta-spring-config - com.okta.spring okta-spring-security-oauth2 diff --git a/examples/config-server/pom.xml b/examples/config-server/pom.xml new file mode 100644 index 000000000..5dc601c94 --- /dev/null +++ b/examples/config-server/pom.xml @@ -0,0 +1,40 @@ + + + + 4.0.0 + + + com.okta.spring.examples + okta-spring-boot-examples + 0.7.0-SNAPSHOT + + + okta-spring-boot-cloud-config-example + Okta Spring Boot :: Examples :: Hosted Code Flow + + + + org.springframework.cloud + spring-cloud-config-server + ${spring-cloud.version} + + + + + spring-boot:run + + diff --git a/examples/config-server/src/main/java/com/okta/example/cloud/configserver/ConfigServerApplication.java b/examples/config-server/src/main/java/com/okta/example/cloud/configserver/ConfigServerApplication.java new file mode 100644 index 000000000..226865719 --- /dev/null +++ b/examples/config-server/src/main/java/com/okta/example/cloud/configserver/ConfigServerApplication.java @@ -0,0 +1,14 @@ +package com.okta.example.cloud.configserver; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.config.server.EnableConfigServer; + +@EnableConfigServer +@SpringBootApplication +public class ConfigServerApplication { + + public static void main(String[] args) { + SpringApplication.run(ConfigServerApplication.class, args); + } +} diff --git a/examples/config-server/src/main/resources/application.yml b/examples/config-server/src/main/resources/application.yml new file mode 100644 index 000000000..7a05a5f10 --- /dev/null +++ b/examples/config-server/src/main/resources/application.yml @@ -0,0 +1,11 @@ +server: + port: 8888 + +spring: + profiles: + active: native + cloud: + config: + server: + native: + searchLocations: classpath:/config-repo/ diff --git a/examples/config-server/src/main/resources/config-repo/application.yml b/examples/config-server/src/main/resources/config-repo/application.yml new file mode 100644 index 000000000..6c7096223 --- /dev/null +++ b/examples/config-server/src/main/resources/config-repo/application.yml @@ -0,0 +1,5 @@ +okta: + oauth2: + issuer: "https://{yourOktaDomain}/oauth2/default" + client-id: "{clientId}" + client-secret: "{clientSecret}" diff --git a/examples/hosted-login-code-flow/src/main/java/com/okta/spring/example/HostedLoginCodeFlowExampleApplication.java b/examples/hosted-login-code-flow/src/main/java/com/okta/spring/example/HostedLoginCodeFlowExampleApplication.java index 26343e1f4..191bd8e8a 100644 --- a/examples/hosted-login-code-flow/src/main/java/com/okta/spring/example/HostedLoginCodeFlowExampleApplication.java +++ b/examples/hosted-login-code-flow/src/main/java/com/okta/spring/example/HostedLoginCodeFlowExampleApplication.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Okta, Inc. + * Copyright 2017-Present Okta, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,122 +15,32 @@ */ package com.okta.spring.example; -import com.okta.spring.config.OktaOAuth2Properties; -import com.okta.spring.oauth.OktaUserInfoTokenServices; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.boot.autoconfigure.security.oauth2.client.EnableOAuth2Sso; -import org.springframework.boot.autoconfigure.security.oauth2.resource.AuthoritiesExtractor; -import org.springframework.boot.autoconfigure.security.oauth2.resource.PrincipalExtractor; -import org.springframework.boot.autoconfigure.security.oauth2.resource.ResourceServerProperties; -import org.springframework.boot.autoconfigure.security.oauth2.resource.UserInfoTokenServices; -import org.springframework.context.ApplicationEventPublisher; -import org.springframework.context.ApplicationListener; -import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpMethod; -import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler; -import org.springframework.security.authentication.event.AuthenticationSuccessEvent; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; -import org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; -import org.springframework.security.oauth2.client.OAuth2ClientContext; -import org.springframework.security.oauth2.client.OAuth2RestTemplate; -import org.springframework.security.oauth2.client.filter.OAuth2ClientAuthenticationProcessingFilter; -import org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeResourceDetails; -import org.springframework.security.oauth2.provider.expression.OAuth2MethodSecurityExpressionHandler; -import org.springframework.security.web.AuthenticationEntryPoint; -import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint; -import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter; - -import javax.servlet.Filter; @SpringBootApplication -@EnableOAuth2Sso +@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true) public class HostedLoginCodeFlowExampleApplication { - private final Logger logger = LoggerFactory.getLogger(HostedLoginCodeFlowExampleApplication.class); - - private final OktaOAuth2Properties oktaOAuth2Properties; - - public HostedLoginCodeFlowExampleApplication(OktaOAuth2Properties oktaOAuth2Properties) { - this.oktaOAuth2Properties = oktaOAuth2Properties; - } - public static void main(String[] args) { SpringApplication.run(HostedLoginCodeFlowExampleApplication.class, args); } - /** - * Enable OAuth claim checking from @PreAuthorize annotation. - * - * @see com.okta.spring.example.controllers.WelcomeController - */ - @EnableGlobalMethodSecurity(prePostEnabled = true) - protected static class GlobalSecurityConfiguration extends GlobalMethodSecurityConfiguration { - @Override - protected MethodSecurityExpressionHandler createExpressionHandler() { - return new OAuth2MethodSecurityExpressionHandler(); - } - } - - /** - * Create an ApplicationListener that listens for successful logins and simply just logs the principal name. - * @return a new listener - */ - @Bean - protected ApplicationListener authenticationSuccessEventApplicationListener() { - return event -> logger.info("Authentication Success with principal: {}", event.getAuthentication().getPrincipal()); - } - - @Bean - protected Filter oktaSsoFilter(ApplicationEventPublisher applicationEventPublisher, - OAuth2ClientContext oauth2ClientContext, - PrincipalExtractor principalExtractor, - AuthoritiesExtractor authoritiesExtractor, - AuthorizationCodeResourceDetails authorizationCodeResourceDetails, - ResourceServerProperties resourceServerProperties) { - - OAuth2ClientAuthenticationProcessingFilter oktaFilter = new OAuth2ClientAuthenticationProcessingFilter(oktaOAuth2Properties.getRedirectUri()); - oktaFilter.setApplicationEventPublisher(applicationEventPublisher); - OAuth2RestTemplate oktaTemplate = new OAuth2RestTemplate(authorizationCodeResourceDetails, oauth2ClientContext); - oktaFilter.setRestTemplate(oktaTemplate); - UserInfoTokenServices tokenServices = new OktaUserInfoTokenServices(resourceServerProperties.getUserInfoUri(), authorizationCodeResourceDetails.getClientId(), oauth2ClientContext); - tokenServices.setRestTemplate(oktaTemplate); - tokenServices.setPrincipalExtractor(principalExtractor); - tokenServices.setAuthoritiesExtractor(authoritiesExtractor); - oktaFilter.setTokenServices(tokenServices); - return oktaFilter; - } - @Configuration static class OAuth2SecurityConfigurerAdapter extends WebSecurityConfigurerAdapter { - private final Filter oktaSsoFilter; - - private final OktaOAuth2Properties oktaOAuth2Properties; - - OAuth2SecurityConfigurerAdapter(Filter oktaSsoFilter, OktaOAuth2Properties oktaOAuth2Properties) { - this.oktaSsoFilter = oktaSsoFilter; - this.oktaOAuth2Properties = oktaOAuth2Properties; - } - - @Bean - protected AuthenticationEntryPoint authenticationEntryPoint() { - return new LoginUrlAuthenticationEntryPoint(oktaOAuth2Properties.getRedirectUri()); - } - @Override protected void configure(HttpSecurity http) throws Exception { - http - .addFilterAfter(oktaSsoFilter, AbstractPreAuthenticatedProcessingFilter.class) - .exceptionHandling().authenticationEntryPoint(authenticationEntryPoint()) - .and() - .authorizeRequests() - .antMatchers(HttpMethod.GET, oktaOAuth2Properties.getRedirectUri()).authenticated(); + http.authorizeRequests() + .antMatchers(HttpMethod.GET,"/okta-custom-login", "/css/okta.css").permitAll() + .anyRequest().authenticated() + .and().oauth2Client() + .and().oauth2Login(); } } -} +} \ No newline at end of file diff --git a/examples/hosted-login-code-flow/src/main/java/com/okta/spring/example/controllers/LoginController.java b/examples/hosted-login-code-flow/src/main/java/com/okta/spring/example/controllers/LoginController.java index c339c1de1..182c0afba 100644 --- a/examples/hosted-login-code-flow/src/main/java/com/okta/spring/example/controllers/LoginController.java +++ b/examples/hosted-login-code-flow/src/main/java/com/okta/spring/example/controllers/LoginController.java @@ -15,15 +15,15 @@ */ package com.okta.spring.example.controllers; -import com.okta.spring.config.OktaClientProperties; -import com.okta.spring.config.OktaOAuth2Properties; -import org.springframework.beans.factory.annotation.Autowired; +import com.okta.spring.boot.oauth.config.OktaOAuth2Properties; import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.servlet.ModelAndView; +import java.net.MalformedURLException; +import java.net.URL; + @Controller public class LoginController { @@ -34,21 +34,27 @@ public class LoginController { private static final String REDIRECT_URI = "redirectUri"; private static final String ISSUER_URI = "issuerUri"; - @Autowired - private OktaOAuth2Properties oktaOAuth2Properties; + private final OktaOAuth2Properties oktaOAuth2Properties; + + public LoginController(OktaOAuth2Properties oktaOAuth2Properties) { + this.oktaOAuth2Properties = oktaOAuth2Properties; + } + + @GetMapping(value = "/okta-custom-login") + public ModelAndView login(@RequestParam("state") String state) throws MalformedURLException { - @Autowired - private OktaClientProperties oktaClientProperties; + String issuer = oktaOAuth2Properties.getIssuer(); + // the widget needs the base url, just grab the root of the issuer + String orgUrl = new URL(new URL(issuer), "/").toString(); - @RequestMapping(value = "/login", method = RequestMethod.GET) - public ModelAndView login(@RequestParam("state") String state) { ModelAndView mav = new ModelAndView("okta/login"); mav.addObject(STATE, state); mav.addObject(SCOPES, oktaOAuth2Properties.getScopes()); - mav.addObject(OKTA_BASE_URL, oktaClientProperties.getOrgUrl()); + mav.addObject(OKTA_BASE_URL, orgUrl); mav.addObject(OKTA_CLIENT_ID, oktaOAuth2Properties.getClientId()); - mav.addObject(REDIRECT_URI, oktaOAuth2Properties.getRedirectUri()); - mav.addObject(ISSUER_URI, oktaOAuth2Properties.getIssuer()); + // from ClientRegistration.redirectUriTemplate, if the template is change you must update this + mav.addObject(REDIRECT_URI, "/login/oauth2/code/okta"); + mav.addObject(ISSUER_URI, issuer); return mav; } -} +} \ No newline at end of file diff --git a/examples/hosted-login-code-flow/src/main/java/com/okta/spring/example/controllers/WelcomeController.java b/examples/hosted-login-code-flow/src/main/java/com/okta/spring/example/controllers/WelcomeController.java index 81f29e526..fefb3bd8e 100644 --- a/examples/hosted-login-code-flow/src/main/java/com/okta/spring/example/controllers/WelcomeController.java +++ b/examples/hosted-login-code-flow/src/main/java/com/okta/spring/example/controllers/WelcomeController.java @@ -30,7 +30,7 @@ public class WelcomeController { * @return a static welcome message */ @GetMapping("/") - @PreAuthorize("#oauth2.clientHasRole('Everyone') || #oauth2.hasScope('email')") + @PreAuthorize("hasAuthority('Everyone')") public Welcome getMessageOfTheDay(Principal principal) { return new Welcome("The message of the day is boring.", principal.getName()); } diff --git a/examples/hosted-login-code-flow/src/main/resources/application.yml b/examples/hosted-login-code-flow/src/main/resources/application.yml index 4de7a91ae..4b0959258 100644 --- a/examples/hosted-login-code-flow/src/main/resources/application.yml +++ b/examples/hosted-login-code-flow/src/main/resources/application.yml @@ -14,18 +14,16 @@ # limitations under the License. # -logging: - level: - root: INFO - org.springframework.web: DEBUG - org.springframework.security: DEBUG - okta: oauth2: - redirectUri: /login/okta + issuer: "https://{yourOktaDomain}/oauth2/default" + client-id: "{clientId}" + client-secret: "{clientSecret}" -security: - oauth2: - client: - # Redirect to your local page and render the widget - userAuthorizationUri: http://localhost:8080/login \ No newline at end of file +spring: + security: + oauth2: + client: + provider: + okta: + authorization-uri: http://localhost:8080/okta-custom-login \ No newline at end of file diff --git a/examples/hosted-login-code-flow/src/main/resources/static/okta/okta.css b/examples/hosted-login-code-flow/src/main/resources/static/css/okta.css similarity index 100% rename from examples/hosted-login-code-flow/src/main/resources/static/okta/okta.css rename to examples/hosted-login-code-flow/src/main/resources/static/css/okta.css diff --git a/examples/hosted-login-code-flow/src/main/resources/templates/okta/head.html b/examples/hosted-login-code-flow/src/main/resources/templates/okta/head.html index 30f31ae53..6ff2367d3 100644 --- a/examples/hosted-login-code-flow/src/main/resources/templates/okta/head.html +++ b/examples/hosted-login-code-flow/src/main/resources/templates/okta/head.html @@ -25,10 +25,10 @@ - - - - + + + +

Nothing to see here, move along.

diff --git a/examples/pom.xml b/examples/pom.xml index 5de28bbeb..520ac4d0e 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -33,6 +33,7 @@ siw-jquery redirect-code-flow hosted-login-code-flow + config-server diff --git a/examples/redirect-code-flow/pom.xml b/examples/redirect-code-flow/pom.xml index fcc066e31..3ac68c795 100644 --- a/examples/redirect-code-flow/pom.xml +++ b/examples/redirect-code-flow/pom.xml @@ -34,6 +34,11 @@ + + org.springframework.security + spring-security-config + + com.okta.spring okta-spring-boot-starter @@ -62,13 +67,13 @@ runtime - - - org.slf4j - jcl-over-slf4j - runtime + org.springframework.cloud + spring-cloud-starter-config + ${spring-cloud.version} + + ch.qos.logback logback-classic diff --git a/examples/redirect-code-flow/src/main/java/com/okta/spring/example/RedirectCodeFlowApplication.java b/examples/redirect-code-flow/src/main/java/com/okta/spring/example/RedirectCodeFlowApplication.java index 645ea5f91..5c29fa20d 100644 --- a/examples/redirect-code-flow/src/main/java/com/okta/spring/example/RedirectCodeFlowApplication.java +++ b/examples/redirect-code-flow/src/main/java/com/okta/spring/example/RedirectCodeFlowApplication.java @@ -17,29 +17,25 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.boot.autoconfigure.security.oauth2.client.EnableOAuth2Sso; -import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; -import org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration; -import org.springframework.security.oauth2.provider.expression.OAuth2MethodSecurityExpressionHandler; @SpringBootApplication -@EnableOAuth2Sso +@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true) public class RedirectCodeFlowApplication { - /** - * Enable OAuth claim checking from @PreAuthorize annotation. - * @see com.okta.spring.example.controllers.WelcomeController - */ - @EnableGlobalMethodSecurity(prePostEnabled = true) - protected static class GlobalSecurityConfiguration extends GlobalMethodSecurityConfiguration { - @Override - protected MethodSecurityExpressionHandler createExpressionHandler() { - return new OAuth2MethodSecurityExpressionHandler(); - } - } - public static void main(String[] args) { SpringApplication.run(RedirectCodeFlowApplication.class, args); } + +// By default Spring configures the equivalent for you. Secure by default. + +// @Configuration +// static class WebConfig extends WebSecurityConfigurerAdapter { +// @Override +// protected void configure(HttpSecurity http) throws Exception { +// http.authorizeRequests().anyRequest().authenticated() +// .and().oauth2Client() +// .and().oauth2Login(); +// } +// } } \ No newline at end of file diff --git a/examples/redirect-code-flow/src/main/java/com/okta/spring/example/controllers/WelcomeController.java b/examples/redirect-code-flow/src/main/java/com/okta/spring/example/controllers/WelcomeController.java index c1792015e..6135d2d44 100644 --- a/examples/redirect-code-flow/src/main/java/com/okta/spring/example/controllers/WelcomeController.java +++ b/examples/redirect-code-flow/src/main/java/com/okta/spring/example/controllers/WelcomeController.java @@ -30,7 +30,6 @@ public class WelcomeController { * @return a static welcome message */ @GetMapping("/") - @PreAuthorize("#oauth2.hasScope('email')") public Welcome getMessageOfTheDay(Principal principal) { return new Welcome("The message of the day is boring.", principal.getName()); } diff --git a/examples/redirect-code-flow/src/main/resources/application.yml b/examples/redirect-code-flow/src/main/resources/application.yml index 5667ce379..5a319ff7c 100644 --- a/examples/redirect-code-flow/src/main/resources/application.yml +++ b/examples/redirect-code-flow/src/main/resources/application.yml @@ -14,9 +14,8 @@ # limitations under the License. # -logging: - level: - root: INFO - org.springframework.web: INFO - org.springframework.security: DEBUG - +okta: + oauth2: + issuer: "https://{yourOktaDomain}/oauth2/default" + client-id: "{clientId}" + client-secret: "{clientSecret}" diff --git a/examples/siw-jquery/README.md b/examples/siw-jquery/README.md index 108700b36..d744692e1 100644 --- a/examples/siw-jquery/README.md +++ b/examples/siw-jquery/README.md @@ -4,7 +4,7 @@ mvn spring-boot:run \ -Dokta.oauth2.issuer=https://{yourOktaDomain}/oauth2/{yourAuthorizationServerId} \ -Dokta.oauth2.audience={yourAuthorizationServerAudience} \ -Dokta.oauth2.clientId={oauthClientId} \ - -Dokta.oauth2.rolesClaim={customRoleClaim) # defaults to 'groups' + -Dokta.oauth2.groupsClaim={customRoleClaim) # defaults to 'groups' ``` Browse to: http://localhost:8080 diff --git a/examples/siw-jquery/pom.xml b/examples/siw-jquery/pom.xml index 51c44c092..69708d317 100644 --- a/examples/siw-jquery/pom.xml +++ b/examples/siw-jquery/pom.xml @@ -43,13 +43,6 @@ import - - - org.springframework.security.oauth - spring-security-oauth2 - 2.2.0.RELEASE - - diff --git a/examples/siw-jquery/src/main/java/com/okta/spring/example/ImplicitFlowApplication.java b/examples/siw-jquery/src/main/java/com/okta/spring/example/ImplicitFlowApplication.java index 199220007..f2088fb34 100644 --- a/examples/siw-jquery/src/main/java/com/okta/spring/example/ImplicitFlowApplication.java +++ b/examples/siw-jquery/src/main/java/com/okta/spring/example/ImplicitFlowApplication.java @@ -17,64 +17,30 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.boot.autoconfigure.security.oauth2.resource.ResourceServerProperties; -import org.springframework.context.annotation.Bean; -import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler; +import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; -import org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer; -import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter; -import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer; -import org.springframework.security.oauth2.provider.expression.OAuth2MethodSecurityExpressionHandler; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; -@EnableResourceServer @SpringBootApplication +@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true) public class ImplicitFlowApplication { - /** - * Enable OAuth claim checking from @PreAuthorize annotation. - * @see com.okta.spring.example.controllers.WelcomeController - */ - @EnableGlobalMethodSecurity(prePostEnabled = true) - protected static class GlobalSecurityConfiguration extends GlobalMethodSecurityConfiguration { - @Override - protected MethodSecurityExpressionHandler createExpressionHandler() { - return new OAuth2MethodSecurityExpressionHandler(); - } - } - - @Bean - protected ResourceServerConfigurerAdapter securityConfigurer(ResourceServerProperties rsp) { - return new ResourceSecurityConfigurer(rsp); - } - - public static void main(String[] args) { SpringApplication.run(ImplicitFlowApplication.class, args); } - - private static class ResourceSecurityConfigurer - extends ResourceServerConfigurerAdapter { - - private ResourceServerProperties resource; - - ResourceSecurityConfigurer(ResourceServerProperties resource) { - this.resource = resource; - } + @Configuration + static class OktaOAuth2WebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter { @Override - public void configure(ResourceServerSecurityConfigurer resources) - throws Exception { - resources.resourceId(this.resource.getResourceId()); - } + protected void configure(HttpSecurity http) throws Exception { - @Override - public void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/", "/index.html", "/sign-in-widget-config").permitAll() .anyRequest().authenticated(); + + http.oauth2ResourceServer().jwt(); } } -} +} \ No newline at end of file diff --git a/examples/siw-jquery/src/main/java/com/okta/spring/example/controllers/SignInWidgetConfigController.java b/examples/siw-jquery/src/main/java/com/okta/spring/example/controllers/SignInWidgetConfigController.java index 0b64ef4c1..6122e4d59 100644 --- a/examples/siw-jquery/src/main/java/com/okta/spring/example/controllers/SignInWidgetConfigController.java +++ b/examples/siw-jquery/src/main/java/com/okta/spring/example/controllers/SignInWidgetConfigController.java @@ -15,8 +15,7 @@ */ package com.okta.spring.example.controllers; -import com.okta.spring.config.OktaOAuth2Properties; -import org.springframework.util.Assert; +import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @@ -26,20 +25,19 @@ @RestController public class SignInWidgetConfigController { - private final String issuerUrl; + private final String issuer; private final String clientId; - public SignInWidgetConfigController(OktaOAuth2Properties oktaOAuth2Properties) { - - Assert.notNull(oktaOAuth2Properties.getClientId(), "Property 'okta.oauth.clientId' is required."); - this.issuerUrl = oktaOAuth2Properties.getIssuer(); - this.clientId = oktaOAuth2Properties.getClientId(); + public SignInWidgetConfigController(@Value("okta.oauth2.issuer") String issuer, + @Value("okta.oauth2.client-id") String clientId) { + this.issuer = issuer; + this.clientId = clientId; } @GetMapping("/sign-in-widget-config") public WidgetConfig getWidgetConfig() { - return new WidgetConfig(issuerUrl, clientId); + return new WidgetConfig(issuer, clientId); } public static class WidgetConfig { diff --git a/examples/siw-jquery/src/main/java/com/okta/spring/example/controllers/WelcomeController.java b/examples/siw-jquery/src/main/java/com/okta/spring/example/controllers/WelcomeController.java index 1f3f66611..adc9ac6f0 100644 --- a/examples/siw-jquery/src/main/java/com/okta/spring/example/controllers/WelcomeController.java +++ b/examples/siw-jquery/src/main/java/com/okta/spring/example/controllers/WelcomeController.java @@ -30,7 +30,7 @@ public class WelcomeController { * @return a static welcome message */ @GetMapping("/welcome") - @PreAuthorize("#oauth2.clientHasRole('Everyone') || #oauth2.hasScope('email')") + @PreAuthorize("hasAuthority('SCOPE_email')") public Welcome getMessageOfTheDay(Principal principal) { return new Welcome("The message of the day is boring.", principal.getName()); } diff --git a/examples/siw-jquery/src/main/resources/application.yml b/examples/siw-jquery/src/main/resources/application.yml index b924e30ff..31d575421 100644 --- a/examples/siw-jquery/src/main/resources/application.yml +++ b/examples/siw-jquery/src/main/resources/application.yml @@ -14,8 +14,8 @@ # limitations under the License. # -logging: - level: - root: INFO - org.springframework.web: INFO - org.springframework.security: INFO +okta: + oauth2: + issuer: "{yourOktaDomain}/oauth2/default" + client-id: "{clientId}" + diff --git a/examples/siw-jquery/src/main/resources/public/index.html b/examples/siw-jquery/src/main/resources/public/index.html index 34c8b6538..cd33e51f3 100644 --- a/examples/siw-jquery/src/main/resources/public/index.html +++ b/examples/siw-jquery/src/main/resources/public/index.html @@ -26,9 +26,9 @@ - - - + + + @@ -68,6 +68,7 @@ // we want the access token so include 'token' data.authParams.responseType = ['id_token', 'token']; data.redirectUri = window.location.href; // simple single page app + data.logo = 'https://ok1static.oktacdn.com/assets/img/logos/okta-logo.png'; // setup the widget window.oktaSignIn = new OktaSignIn(data); @@ -91,12 +92,6 @@ }).then(function(data) { // Render the message of the day $('#cool-stuff-here').append(data.messageOfTheDay + "
User: " + data.username); - }) - .fail(function(data) { - // handle any errors - console.log("ERROR!!"); - console.log(data.responseJSON.error); - console.log(data.responseJSON.error_description); }); // show the logout button diff --git a/integration-tests/oauth2-boot2/pom.xml b/integration-tests/oauth2-boot2/pom.xml deleted file mode 100644 index 53da50925..000000000 --- a/integration-tests/oauth2-boot2/pom.xml +++ /dev/null @@ -1,151 +0,0 @@ - - - - 4.0.0 - - - com.okta.spring - okta-spring-boot-parent - 0.7.0-SNAPSHOT - - - okta-spring-boot2-integration-tests-oauth2 - Okta Spring Boot 2.x :: ITs :: OAuth2 - - - com.okta.spring.tests.oauth2.code.BasicRedirectCodeFlowApplication - - ${spring-boot2.version} - - - - - - - org.springframework.security.oauth.boot - spring-security-oauth2-autoconfigure - - 2.0.1.RELEASE - - - com.okta.spring - okta-spring-boot-starter - - - org.springframework.boot - spring-boot-starter-web - - - org.springframework.boot - spring-boot-starter-security - - - com.github.tomakehurst - wiremock-standalone - test - - - io.rest-assured - rest-assured - test - - - org.hamcrest - java-hamcrest - test - - - - - - org.slf4j - jcl-over-slf4j - runtime - - - ch.qos.logback - logback-classic - runtime - - - - - com.okta.oidc.tck - okta-oidc-tck - test - - - org.springframework.boot - spring-boot-test - test - - - org.springframework - spring-test - test - - - - - - - src/test/resources - true - - versions.properties - - - - src/test/resources - false - - versions.properties - - - - - - - - org.springframework.boot - spring-boot-maven-plugin - - ${test.mainClass} - - - - org.apache.maven.plugins - maven-failsafe-plugin - 2.20.1 - - - com.okta.oidc.tck:okta-oidc-tck - - false - - **/OIDCCodeFlowLocalValidationIT.* - - - true - - - - - - - - diff --git a/integration-tests/oauth2-boot2/src/main/java/com/okta/spring/tests/oauth2/code/BasicRedirectCodeFlowApplication.java b/integration-tests/oauth2-boot2/src/main/java/com/okta/spring/tests/oauth2/code/BasicRedirectCodeFlowApplication.java deleted file mode 100644 index 4db3a1076..000000000 --- a/integration-tests/oauth2-boot2/src/main/java/com/okta/spring/tests/oauth2/code/BasicRedirectCodeFlowApplication.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2017 Okta, Inc. - * - * 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 - * - * http://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 com.okta.spring.tests.oauth2.code; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass; -import org.springframework.boot.autoconfigure.security.oauth2.client.EnableOAuth2Sso; -import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; -import org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration; -import org.springframework.security.oauth2.provider.expression.OAuth2MethodSecurityExpressionHandler; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; - -import java.security.Principal; - -@SpringBootApplication -@RestController -@EnableOAuth2Sso -// fail loading this config if the SDK 'Client' is found. It should NOT exist on the classpath by default -@ConditionalOnMissingClass("com.okta.sdk.client.Client") -public class BasicRedirectCodeFlowApplication { - - @EnableGlobalMethodSecurity(prePostEnabled = true) - protected static class GlobalSecurityConfiguration extends GlobalMethodSecurityConfiguration { - @Override - protected MethodSecurityExpressionHandler createExpressionHandler() { - return new OAuth2MethodSecurityExpressionHandler(); - } - } - - @GetMapping("/") - @PreAuthorize("#oauth2.hasScope('email')") - public String getMessageOfTheDay(Principal principal) { - return "The message of the day is boring: " + principal.getName(); - } - - public static void main(String[] args) { - SpringApplication.run(BasicRedirectCodeFlowApplication.class, args); - } -} \ No newline at end of file diff --git a/integration-tests/oauth2-boot2/src/main/java/com/okta/spring/tests/oauth2/implicit/BasicImplicitFlowApplication.java b/integration-tests/oauth2-boot2/src/main/java/com/okta/spring/tests/oauth2/implicit/BasicImplicitFlowApplication.java deleted file mode 100644 index 0e31861a3..000000000 --- a/integration-tests/oauth2-boot2/src/main/java/com/okta/spring/tests/oauth2/implicit/BasicImplicitFlowApplication.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright 2017 Okta, Inc. - * - * 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 - * - * http://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 com.okta.spring.tests.oauth2.implicit; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.context.annotation.Configuration; -import org.springframework.core.annotation.Order; -import org.springframework.http.HttpMethod; -import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; -import org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer; -import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter; -import org.springframework.security.oauth2.provider.OAuth2Authentication; -import org.springframework.security.oauth2.provider.expression.OAuth2MethodSecurityExpressionHandler; -import org.springframework.web.bind.annotation.CrossOrigin; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; - -import java.security.Principal; -import java.util.Arrays; -import java.util.Date; -import java.util.HashMap; -import java.util.Map; - -@EnableResourceServer -@SpringBootApplication -public class BasicImplicitFlowApplication { - - public static void main(String[] args) { - SpringApplication.run(BasicImplicitFlowApplication.class, args); - } - - @EnableGlobalMethodSecurity(prePostEnabled = true) - protected static class GlobalSecurityConfiguration extends GlobalMethodSecurityConfiguration { - @Override - protected MethodSecurityExpressionHandler createExpressionHandler() { - return new OAuth2MethodSecurityExpressionHandler(); - } - } - - @Configuration - @Order(0) - static class ResourceSecurityConfigurer extends ResourceServerConfigurerAdapter { - - @Override - public void configure(HttpSecurity http) throws Exception { - http.authorizeRequests() - .antMatchers(HttpMethod.OPTIONS,"/**").permitAll() - .anyRequest().authenticated(); - } - } - - @RestController - @CrossOrigin(origins = "http://localhost:8080") - public static class MessageOfTheDayController { - - @GetMapping("/api/userProfile") - @PreAuthorize("#oauth2.hasScope('profile')") - public Map getUserDetails(OAuth2Authentication authentication) { - return (Map) authentication.getUserAuthentication().getDetails(); - } - - @GetMapping("/api/messages") - @PreAuthorize("#oauth2.hasScope('email')") - public Map messages() { - - Map result = new HashMap<>(); - result.put("messages", Arrays.asList( - new Message("I am a robot."), - new Message("Hello, word!") - )); - - return result; - } - - @GetMapping("/") - @PreAuthorize("#oauth2.hasScope('email')") - public String getMessageOfTheDay(Principal principal) { - return "The message of the day is boring: " + principal.getName(); - } - - @GetMapping("/everyone") - @PreAuthorize("hasAuthority('Everyone')") - public String everyoneAccess(Principal principal) { - return "Everyone has Access: " + principal.getName(); - } - } - - static class Message { - public Date date = new Date(); - public String text; - - Message(String text) { - this.text = text; - } - } -} \ No newline at end of file diff --git a/integration-tests/oauth2-boot2/src/main/resources/application.yml b/integration-tests/oauth2-boot2/src/main/resources/application.yml deleted file mode 100644 index cfe5138d0..000000000 --- a/integration-tests/oauth2-boot2/src/main/resources/application.yml +++ /dev/null @@ -1,30 +0,0 @@ -# -# Copyright 2017 Okta, Inc. -# -# 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 -# -# http://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. -# - -security: - oauth2: - sso: - loginPath: /authorization-code/callback - client: - clientAuthenticationScheme: header - -logging: - level: - root: INFO - org.springframework.web: DEBUG - org.springframework.security: DEBUG - - diff --git a/integration-tests/oauth2-boot2/src/test/groovy/com/okta/spring/tests/oauth2/SpringApplicationUnderTest.groovy b/integration-tests/oauth2-boot2/src/test/groovy/com/okta/spring/tests/oauth2/SpringApplicationUnderTest.groovy deleted file mode 100644 index ac6b69434..000000000 --- a/integration-tests/oauth2-boot2/src/test/groovy/com/okta/spring/tests/oauth2/SpringApplicationUnderTest.groovy +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2017 Okta, Inc. - * - * 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 - * - * http://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 com.okta.spring.tests.oauth2 - -import com.okta.test.mock.application.ApplicationUnderTest -import org.slf4j.Logger -import org.slf4j.LoggerFactory -import org.springframework.boot.SpringApplication -import org.springframework.context.ConfigurableApplicationContext - -class SpringApplicationUnderTest implements ApplicationUnderTest { - - private final Logger logger = LoggerFactory.getLogger(SpringApplicationUnderTest) - - private ConfigurableApplicationContext applicationContext - - @Override - void start() { - applicationContext = SpringApplication.run( - Class.forName(testScenario.command), - testScenario.args.toArray(new String[testScenario.args.size()])) - } - - @Override - int stop() { - - if (applicationContext != null && applicationContext.isRunning()) { - applicationContext.stop() - return 0 - } else { - logger.warn("Spring Application was not running") - return 1 - } - } -} diff --git a/integration-tests/oauth2-boot2/src/test/groovy/com/okta/spring/tests/oauth2/implicit/ImplicitLocalValidationGroupIT.groovy b/integration-tests/oauth2-boot2/src/test/groovy/com/okta/spring/tests/oauth2/implicit/ImplicitLocalValidationGroupIT.groovy deleted file mode 100644 index f312db5bd..000000000 --- a/integration-tests/oauth2-boot2/src/test/groovy/com/okta/spring/tests/oauth2/implicit/ImplicitLocalValidationGroupIT.groovy +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2017 Okta, Inc. - * - * 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 - * - * http://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 com.okta.spring.tests.oauth2.implicit - -import com.okta.test.mock.Scenario -import com.okta.test.mock.application.ApplicationTestRunner -import org.hamcrest.Matchers -import org.testng.annotations.Test - -import static io.restassured.RestAssured.given -import static com.okta.test.mock.scenarios.Scenario.IMPLICIT_FLOW_LOCAL_VALIDATION - -@Scenario(IMPLICIT_FLOW_LOCAL_VALIDATION) -class ImplicitLocalValidationGroupIT extends ApplicationTestRunner { - - @Test - void groupAccessTest() { - given() - .header("Authorization", "Bearer ${IMPLICIT_FLOW_LOCAL_VALIDATION.definition.accessTokenJwt}") - .redirects() - .follow(false) - .when() - .get("http://localhost:${applicationPort}/everyone") - .then() - .body(Matchers.equalTo("Everyone has Access: joe.coder@example.com")) - } -} diff --git a/integration-tests/oauth2-boot2/src/test/groovy/com/okta/spring/tests/oauth2/implicit/ImplicitRemoteValidationGroupIT.groovy b/integration-tests/oauth2-boot2/src/test/groovy/com/okta/spring/tests/oauth2/implicit/ImplicitRemoteValidationGroupIT.groovy deleted file mode 100644 index 4b12718da..000000000 --- a/integration-tests/oauth2-boot2/src/test/groovy/com/okta/spring/tests/oauth2/implicit/ImplicitRemoteValidationGroupIT.groovy +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2017 Okta, Inc. - * - * 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 - * - * http://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 com.okta.spring.tests.oauth2.implicit - -import com.github.tomakehurst.wiremock.http.RequestMethod -import com.okta.test.mock.Scenario -import com.okta.test.mock.application.ApplicationTestRunner -import org.hamcrest.Matchers -import org.testng.annotations.Test - -import static com.github.tomakehurst.wiremock.client.WireMock.containing -import static com.github.tomakehurst.wiremock.client.WireMock.urlMatching -import static com.github.tomakehurst.wiremock.matching.RequestPatternBuilder.newRequestPattern -import static io.restassured.RestAssured.given -import static com.okta.test.mock.scenarios.Scenario.IMPLICIT_FLOW_REMOTE_VALIDATION - -@Scenario(IMPLICIT_FLOW_REMOTE_VALIDATION) -class ImplicitRemoteValidationGroupIT extends ApplicationTestRunner { - - @Test - void groupAccessTest() { - - given() - .header("Authorization", "Bearer some.random.jwt") - .redirects() - .follow(false) - .when() - .get("http://localhost:${applicationPort}/everyone") - .then() - .body(Matchers.equalTo("Everyone has Access: joe.coder@example.com")) - } - - @Test - void validateUserAgent() { - - ResourceBundle versions = ResourceBundle.getBundle("versions") - def springIntegrationVersion = versions.getString("project.version") - - // discovery - wireMockServer.verify( - newRequestPattern(RequestMethod.GET, urlMatching("/oauth2/default/.well-known/openid-configuration")) - .withHeader("User-Agent", containing("okta-spring-security/${springIntegrationVersion}")) - ) - } -} \ No newline at end of file diff --git a/integration-tests/oauth2-boot2/src/test/resources/testRunner.yml b/integration-tests/oauth2-boot2/src/test/resources/testRunner.yml deleted file mode 100644 index b987c4ad6..000000000 --- a/integration-tests/oauth2-boot2/src/test/resources/testRunner.yml +++ /dev/null @@ -1,60 +0,0 @@ -# -# Copyright 2017 Okta, Inc. -# -# 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 -# -# http://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. -# - -implementation: com.okta.spring.tests.oauth2.SpringApplicationUnderTest -scenarios: - code-flow-local-validation: - command: com.okta.spring.tests.oauth2.code.BasicRedirectCodeFlowApplication - args: - - --server.port=${applicationPort} - - --okta.oauth2.issuer=https://localhost:${mockHttpsPort}/oauth2/default - - --okta.oauth2.clientId=OOICU812 - - --okta.oauth2.clientSecret=VERY_SECRET - - --server.session.trackingModes=cookie - - code-flow-remote-validation: - command: com.okta.spring.tests.oauth2.code.BasicRedirectCodeFlowApplication - args: - - --server.port=${applicationPort} - - --okta.oauth2.issuer=https://localhost:${mockHttpsPort}/oauth2/default - - --okta.oauth2.clientId=OOICU812 - - --okta.oauth2.clientSecret=VERY_SECRET - - --server.session.trackingModes=cookie - - --okta.oauth2.localTokenValidation=false - - - implicit-flow-local-validation: - disabledTests: - # Spring JWT parsing does NOT check the 'not-before' claim, (the 'exp' claim is validated) - - inactiveAccessTokenTest - command: com.okta.spring.tests.oauth2.implicit.BasicImplicitFlowApplication - args: - - --server.port=${applicationPort} - - --okta.oauth2.issuer=https://localhost:${mockHttpsPort}/oauth2/default - - --okta.oauth2.clientId=OOICU812 - - --server.session.trackingModes=cookie - - implicit-flow-remote-validation: - command: com.okta.spring.tests.oauth2.implicit.BasicImplicitFlowApplication - args: - - --server.port=${applicationPort} - - --okta.oauth2.issuer=https://localhost:${mockHttpsPort}/oauth2/default - - --okta.oauth2.clientId=OOICU812 - - --server.session.trackingModes=cookie - - --okta.oauth2.localTokenValidation=false - - oidc-code-flow-local-validation: - enabled: false \ No newline at end of file diff --git a/integration-tests/oauth2-boot2/src/test/resources/versions.properties b/integration-tests/oauth2-boot2/src/test/resources/versions.properties deleted file mode 100644 index 959117968..000000000 --- a/integration-tests/oauth2-boot2/src/test/resources/versions.properties +++ /dev/null @@ -1,17 +0,0 @@ -# -# Copyright 2018 Okta, Inc. -# -# 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 -# -# http://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. -# - -project.version=${project.version} \ No newline at end of file diff --git a/integration-tests/oauth2/pom.xml b/integration-tests/oauth2/pom.xml index f62cdaee2..7d633adb8 100644 --- a/integration-tests/oauth2/pom.xml +++ b/integration-tests/oauth2/pom.xml @@ -120,15 +120,14 @@ org.apache.maven.plugins maven-failsafe-plugin - 2.20.1 + 2.22.1 com.okta.oidc.tck:okta-oidc-tck - false - - **/OIDCCodeFlowLocalValidationIT.* - + true + classesAndMethods + 1 diff --git a/integration-tests/oauth2/src/main/java/com/okta/spring/tests/oauth2/code/BasicRedirectCodeFlowApplication.java b/integration-tests/oauth2/src/main/java/com/okta/spring/tests/oauth2/code/BasicRedirectCodeFlowApplication.java index 4db3a1076..93b3c0f48 100644 --- a/integration-tests/oauth2/src/main/java/com/okta/spring/tests/oauth2/code/BasicRedirectCodeFlowApplication.java +++ b/integration-tests/oauth2/src/main/java/com/okta/spring/tests/oauth2/code/BasicRedirectCodeFlowApplication.java @@ -18,12 +18,11 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass; -import org.springframework.boot.autoconfigure.security.oauth2.client.EnableOAuth2Sso; -import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler; +import org.springframework.context.annotation.Configuration; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; -import org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration; -import org.springframework.security.oauth2.provider.expression.OAuth2MethodSecurityExpressionHandler; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @@ -31,23 +30,26 @@ @SpringBootApplication @RestController -@EnableOAuth2Sso // fail loading this config if the SDK 'Client' is found. It should NOT exist on the classpath by default @ConditionalOnMissingClass("com.okta.sdk.client.Client") +@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true) public class BasicRedirectCodeFlowApplication { - @EnableGlobalMethodSecurity(prePostEnabled = true) - protected static class GlobalSecurityConfiguration extends GlobalMethodSecurityConfiguration { - @Override - protected MethodSecurityExpressionHandler createExpressionHandler() { - return new OAuth2MethodSecurityExpressionHandler(); - } - } - @GetMapping("/") - @PreAuthorize("#oauth2.hasScope('email')") + @PreAuthorize("hasAuthority('SCOPE_email')") public String getMessageOfTheDay(Principal principal) { - return "The message of the day is boring: " + principal.getName(); + return "Welcome home, The message of the day is boring: " + principal.getName(); + } + +// The following isn't needed as the equivalent is provided by Spring Boot Security by default + @Configuration + static class WebConfig extends WebSecurityConfigurerAdapter { + @Override + protected void configure(HttpSecurity http) throws Exception { + http.authorizeRequests().anyRequest().authenticated() + .and().oauth2Client() + .and().oauth2Login(); + } } public static void main(String[] args) { diff --git a/integration-tests/oauth2/src/main/java/com/okta/spring/tests/oauth2/implicit/BasicImplicitFlowApplication.java b/integration-tests/oauth2/src/main/java/com/okta/spring/tests/oauth2/implicit/BasicImplicitFlowApplication.java index 0e31861a3..9e6382ef9 100644 --- a/integration-tests/oauth2/src/main/java/com/okta/spring/tests/oauth2/implicit/BasicImplicitFlowApplication.java +++ b/integration-tests/oauth2/src/main/java/com/okta/spring/tests/oauth2/implicit/BasicImplicitFlowApplication.java @@ -18,17 +18,12 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Configuration; -import org.springframework.core.annotation.Order; import org.springframework.http.HttpMethod; -import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; -import org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer; -import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter; -import org.springframework.security.oauth2.provider.OAuth2Authentication; -import org.springframework.security.oauth2.provider.expression.OAuth2MethodSecurityExpressionHandler; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.core.Authentication; import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @@ -39,31 +34,23 @@ import java.util.HashMap; import java.util.Map; -@EnableResourceServer @SpringBootApplication +@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true) public class BasicImplicitFlowApplication { public static void main(String[] args) { SpringApplication.run(BasicImplicitFlowApplication.class, args); } - @EnableGlobalMethodSecurity(prePostEnabled = true) - protected static class GlobalSecurityConfiguration extends GlobalMethodSecurityConfiguration { - @Override - protected MethodSecurityExpressionHandler createExpressionHandler() { - return new OAuth2MethodSecurityExpressionHandler(); - } - } - @Configuration - @Order(0) - static class ResourceSecurityConfigurer extends ResourceServerConfigurerAdapter { + static class ResourceSecurityConfigurer extends WebSecurityConfigurerAdapter { @Override public void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers(HttpMethod.OPTIONS,"/**").permitAll() - .anyRequest().authenticated(); + .anyRequest().authenticated() + .and().oauth2ResourceServer().jwt(); } } @@ -72,26 +59,27 @@ public void configure(HttpSecurity http) throws Exception { public static class MessageOfTheDayController { @GetMapping("/api/userProfile") - @PreAuthorize("#oauth2.hasScope('profile')") - public Map getUserDetails(OAuth2Authentication authentication) { - return (Map) authentication.getUserAuthentication().getDetails(); + @PreAuthorize("hasAuthority('SCOPE_profile')") + public Map getUserDetails(Authentication authentication) { + // TODO: validate this is the correct way to get the details + return (Map) authentication.getDetails(); } @GetMapping("/api/messages") - @PreAuthorize("#oauth2.hasScope('email')") + @PreAuthorize("hasAuthority('SCOPE_email')") public Map messages() { Map result = new HashMap<>(); result.put("messages", Arrays.asList( new Message("I am a robot."), - new Message("Hello, word!") + new Message("Hello, world!") )); return result; } @GetMapping("/") - @PreAuthorize("#oauth2.hasScope('email')") + @PreAuthorize("hasAuthority('SCOPE_email')") public String getMessageOfTheDay(Principal principal) { return "The message of the day is boring: " + principal.getName(); } diff --git a/integration-tests/oauth2/src/main/resources/application.yml b/integration-tests/oauth2/src/main/resources/application.yml index eb4bb0777..5ed5dc1a4 100644 --- a/integration-tests/oauth2/src/main/resources/application.yml +++ b/integration-tests/oauth2/src/main/resources/application.yml @@ -1,5 +1,5 @@ # -# Copyright 2017 Okta, Inc. +# Copyright 2017-Present Okta, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -13,18 +13,3 @@ # See the License for the specific language governing permissions and # limitations under the License. # - -security: - oauth2: - sso: - loginPath: /authorization-code/callback - client: - clientAuthenticationScheme: header - -logging: - level: - root: INFO - org.springframework.web: INFO - org.springframework.security: INFO - - diff --git a/integration-tests/oauth2/src/test/groovy/com/okta/spring/tests/oauth2/SpringApplicationUnderTest.groovy b/integration-tests/oauth2/src/test/groovy/com/okta/spring/tests/oauth2/SpringApplicationUnderTest.groovy index ac6b69434..880f6c936 100644 --- a/integration-tests/oauth2/src/test/groovy/com/okta/spring/tests/oauth2/SpringApplicationUnderTest.groovy +++ b/integration-tests/oauth2/src/test/groovy/com/okta/spring/tests/oauth2/SpringApplicationUnderTest.groovy @@ -29,6 +29,15 @@ class SpringApplicationUnderTest implements ApplicationUnderTest { @Override void start() { + + testScenario.args.stream() + .filter {it.startsWith("-D")} + .map {it.substring(2)} + .map {it.split("=")} + .forEach { + System.setProperty(it[0], it[1]) + } + applicationContext = SpringApplication.run( Class.forName(testScenario.command), testScenario.args.toArray(new String[testScenario.args.size()])) diff --git a/integration-tests/oauth2/src/test/groovy/com/okta/spring/tests/oauth2/implicit/ImplicitRemoteValidationGroupIT.groovy b/integration-tests/oauth2/src/test/groovy/com/okta/spring/tests/oauth2/implicit/ImplicitRemoteValidationGroupIT.groovy deleted file mode 100644 index 4b12718da..000000000 --- a/integration-tests/oauth2/src/test/groovy/com/okta/spring/tests/oauth2/implicit/ImplicitRemoteValidationGroupIT.groovy +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2017 Okta, Inc. - * - * 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 - * - * http://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 com.okta.spring.tests.oauth2.implicit - -import com.github.tomakehurst.wiremock.http.RequestMethod -import com.okta.test.mock.Scenario -import com.okta.test.mock.application.ApplicationTestRunner -import org.hamcrest.Matchers -import org.testng.annotations.Test - -import static com.github.tomakehurst.wiremock.client.WireMock.containing -import static com.github.tomakehurst.wiremock.client.WireMock.urlMatching -import static com.github.tomakehurst.wiremock.matching.RequestPatternBuilder.newRequestPattern -import static io.restassured.RestAssured.given -import static com.okta.test.mock.scenarios.Scenario.IMPLICIT_FLOW_REMOTE_VALIDATION - -@Scenario(IMPLICIT_FLOW_REMOTE_VALIDATION) -class ImplicitRemoteValidationGroupIT extends ApplicationTestRunner { - - @Test - void groupAccessTest() { - - given() - .header("Authorization", "Bearer some.random.jwt") - .redirects() - .follow(false) - .when() - .get("http://localhost:${applicationPort}/everyone") - .then() - .body(Matchers.equalTo("Everyone has Access: joe.coder@example.com")) - } - - @Test - void validateUserAgent() { - - ResourceBundle versions = ResourceBundle.getBundle("versions") - def springIntegrationVersion = versions.getString("project.version") - - // discovery - wireMockServer.verify( - newRequestPattern(RequestMethod.GET, urlMatching("/oauth2/default/.well-known/openid-configuration")) - .withHeader("User-Agent", containing("okta-spring-security/${springIntegrationVersion}")) - ) - } -} \ No newline at end of file diff --git a/integration-tests/oauth2/src/test/resources/testRunner.yml b/integration-tests/oauth2/src/test/resources/testRunner.yml index b987c4ad6..a88963029 100644 --- a/integration-tests/oauth2/src/test/resources/testRunner.yml +++ b/integration-tests/oauth2/src/test/resources/testRunner.yml @@ -16,14 +16,6 @@ implementation: com.okta.spring.tests.oauth2.SpringApplicationUnderTest scenarios: - code-flow-local-validation: - command: com.okta.spring.tests.oauth2.code.BasicRedirectCodeFlowApplication - args: - - --server.port=${applicationPort} - - --okta.oauth2.issuer=https://localhost:${mockHttpsPort}/oauth2/default - - --okta.oauth2.clientId=OOICU812 - - --okta.oauth2.clientSecret=VERY_SECRET - - --server.session.trackingModes=cookie code-flow-remote-validation: command: com.okta.spring.tests.oauth2.code.BasicRedirectCodeFlowApplication @@ -32,14 +24,12 @@ scenarios: - --okta.oauth2.issuer=https://localhost:${mockHttpsPort}/oauth2/default - --okta.oauth2.clientId=OOICU812 - --okta.oauth2.clientSecret=VERY_SECRET - - --server.session.trackingModes=cookie - - --okta.oauth2.localTokenValidation=false - + - --okta.oauth2.scopes=offline_access + - --okta.oauth2.redirectUri=/authorization-code/callback + - --server.servlet.session.tracking-modes=cookie + protectedPath: /oauth2/authorization/okta implicit-flow-local-validation: - disabledTests: - # Spring JWT parsing does NOT check the 'not-before' claim, (the 'exp' claim is validated) - - inactiveAccessTokenTest command: com.okta.spring.tests.oauth2.implicit.BasicImplicitFlowApplication args: - --server.port=${applicationPort} @@ -47,14 +37,17 @@ scenarios: - --okta.oauth2.clientId=OOICU812 - --server.session.trackingModes=cookie - implicit-flow-remote-validation: - command: com.okta.spring.tests.oauth2.implicit.BasicImplicitFlowApplication + oidc-code-flow-local-validation: + disabledTests: + # Spring does remote validation of the token + - invalidIssuerIdTokenJwtTest + command: com.okta.spring.tests.oauth2.code.BasicRedirectCodeFlowApplication args: - --server.port=${applicationPort} - --okta.oauth2.issuer=https://localhost:${mockHttpsPort}/oauth2/default - --okta.oauth2.clientId=OOICU812 - - --server.session.trackingModes=cookie - - --okta.oauth2.localTokenValidation=false - - oidc-code-flow-local-validation: - enabled: false \ No newline at end of file + - --okta.oauth2.clientSecret=VERY_SECRET + - --okta.oauth2.scopes=profile,email,openid + - --okta.oauth2.redirectUri=/authorization-code/callback + - --server.servlet.session.tracking-modes=cookie + protectedPath: /oauth2/authorization/okta \ No newline at end of file diff --git a/oauth2/pom.xml b/oauth2/pom.xml index 19ffeffa1..d5e4cc326 100644 --- a/oauth2/pom.xml +++ b/oauth2/pom.xml @@ -28,31 +28,48 @@ - com.okta.spring - okta-spring-config + javax.servlet + javax.servlet-api + provided + - javax.servlet - javax.servlet-api + com.okta.commons + okta-config-check - + + + org.springframework.security + spring-security-config + org.springframework.boot spring-boot-starter-security org.springframework.security - spring-security-jwt + spring-security-oauth2-client - org.springframework.security.oauth - spring-security-oauth2 + org.springframework.security + spring-security-oauth2-jose - org.springframework - spring-webmvc + org.springframework.security + spring-security-oauth2-resource-server + + + + + org.springframework.boot + spring-boot-starter-security + + + org.springframework.boot + spring-boot-configuration-processor + true diff --git a/oauth2/src/main/java/com/okta/spring/boot/oauth/OktaJwtAuthenticationConverter.java b/oauth2/src/main/java/com/okta/spring/boot/oauth/OktaJwtAuthenticationConverter.java new file mode 100644 index 000000000..a917f3b70 --- /dev/null +++ b/oauth2/src/main/java/com/okta/spring/boot/oauth/OktaJwtAuthenticationConverter.java @@ -0,0 +1,26 @@ +package com.okta.spring.boot.oauth; + +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.oauth2.jwt.Jwt; +import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter; + +import java.util.Collection; +import java.util.HashSet; + +final class OktaJwtAuthenticationConverter extends JwtAuthenticationConverter { + + private final String groupClaim; + + public OktaJwtAuthenticationConverter(String groupClaim) { + this.groupClaim = groupClaim; + } + + @Override + protected Collection extractAuthorities(Jwt jwt) { + + Collection result = new HashSet<>(super.extractAuthorities(jwt)); + result.addAll(TokenUtil.tokenClaimsToAuthorities(jwt.getClaims(), groupClaim)); + + return result; + } +} diff --git a/oauth2/src/main/java/com/okta/spring/boot/oauth/OktaOAuth2AutoConfig.java b/oauth2/src/main/java/com/okta/spring/boot/oauth/OktaOAuth2AutoConfig.java new file mode 100644 index 000000000..f58283b88 --- /dev/null +++ b/oauth2/src/main/java/com/okta/spring/boot/oauth/OktaOAuth2AutoConfig.java @@ -0,0 +1,19 @@ +package com.okta.spring.boot.oauth; + +import com.okta.spring.boot.oauth.config.OktaOAuth2Properties; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; +import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientProperties; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.oauth2.client.registration.ClientRegistration; + +@Configuration +@ConditionalOnBean(OAuth2ClientProperties.class) +@EnableConfigurationProperties(OktaOAuth2Properties.class) +@ConditionalOnClass({ EnableWebSecurity.class, ClientRegistration.class }) +@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET) +class OktaOAuth2AutoConfig { +} \ No newline at end of file diff --git a/oauth2/src/main/java/com/okta/spring/boot/oauth/OktaOAuth2Configurer.java b/oauth2/src/main/java/com/okta/spring/boot/oauth/OktaOAuth2Configurer.java new file mode 100644 index 000000000..976830f9e --- /dev/null +++ b/oauth2/src/main/java/com/okta/spring/boot/oauth/OktaOAuth2Configurer.java @@ -0,0 +1,48 @@ +package com.okta.spring.boot.oauth; + +import com.okta.spring.boot.oauth.config.OktaOAuth2Properties; +import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientProperties; +import org.springframework.boot.autoconfigure.security.oauth2.resource.OAuth2ResourceServerProperties; +import org.springframework.context.ApplicationContext; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; + +final class OktaOAuth2Configurer extends AbstractHttpConfigurer { + + @Override + public void init(HttpSecurity http) throws Exception { + + ApplicationContext context = http.getSharedObject(ApplicationContext.class); + + // make sure OktaOAuth2Properties are available + if (!context.getBeansOfType(OktaOAuth2Properties.class).isEmpty()) { + OktaOAuth2Properties oktaOAuth2Properties = context.getBean(OktaOAuth2Properties.class); + + // if OAuth2ClientProperties bean is not available do NOT configure + if (!context.getBeansOfType(OAuth2ClientProperties.class).isEmpty()) { + // configure Okta user services + configureLogin(http, oktaOAuth2Properties); + } + + if (!context.getBeansOfType(OAuth2ResourceServerProperties.class).isEmpty()) { + // configure Okta specific auth converter (extracts authorities from `groupsClaim` + configureResourceServer(http, oktaOAuth2Properties); + } + } + } + + private void configureLogin(HttpSecurity http, OktaOAuth2Properties oktaOAuth2Properties) throws Exception { + http.oauth2Login() + .userInfoEndpoint() + .userService(new OktaOAuth2UserService(oktaOAuth2Properties.getGroupsClaim())) + .oidcUserService(new OktaOidcUserService(oktaOAuth2Properties.getGroupsClaim())); + + if (oktaOAuth2Properties.getRedirectUri() != null) { + http.oauth2Login().redirectionEndpoint().baseUri(oktaOAuth2Properties.getRedirectUri()); + } + } + + private void configureResourceServer(HttpSecurity http, OktaOAuth2Properties oktaOAuth2Properties) throws Exception { + http.oauth2ResourceServer().jwt().jwtAuthenticationConverter(new OktaJwtAuthenticationConverter(oktaOAuth2Properties.getGroupsClaim())); + } +} diff --git a/oauth2/src/main/java/com/okta/spring/boot/oauth/OktaOAuth2ResourceServerAutoConfig.java b/oauth2/src/main/java/com/okta/spring/boot/oauth/OktaOAuth2ResourceServerAutoConfig.java new file mode 100644 index 000000000..85a749db4 --- /dev/null +++ b/oauth2/src/main/java/com/okta/spring/boot/oauth/OktaOAuth2ResourceServerAutoConfig.java @@ -0,0 +1,67 @@ +package com.okta.spring.boot.oauth; + +import com.okta.spring.boot.oauth.config.OktaOAuth2Properties; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.AutoConfigureBefore; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; +import org.springframework.boot.autoconfigure.security.oauth2.resource.OAuth2ResourceServerProperties; +import org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerAutoConfiguration; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator; +import org.springframework.security.oauth2.core.OAuth2Error; +import org.springframework.security.oauth2.core.OAuth2ErrorCodes; +import org.springframework.security.oauth2.core.OAuth2TokenValidator; +import org.springframework.security.oauth2.core.OAuth2TokenValidatorResult; +import org.springframework.security.oauth2.jwt.Jwt; +import org.springframework.security.oauth2.jwt.JwtDecoder; +import org.springframework.security.oauth2.jwt.JwtIssuerValidator; +import org.springframework.security.oauth2.jwt.JwtTimestampValidator; +import org.springframework.security.oauth2.jwt.NimbusJwtDecoderJwkSupport; +import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +@Configuration +@AutoConfigureBefore(OAuth2ResourceServerAutoConfiguration.class) +@ConditionalOnClass(JwtAuthenticationToken.class) +@ConditionalOnProperty(name = "spring.security.oauth2.resourceserver.jwt.jwk-set-uri") +@EnableConfigurationProperties(OktaOAuth2Properties.class) +@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET) +class OktaOAuth2ResourceServerAutoConfig { + + private static final OAuth2Error INVALID_AUDIENCE = new OAuth2Error( + OAuth2ErrorCodes.INVALID_REQUEST, + "This aud claim is not equal to the configured audience", + "https://tools.ietf.org/html/rfc6750#section-3.1"); + @Bean + @ConditionalOnMissingBean + JwtDecoder jwtDecoder(OAuth2ResourceServerProperties oAuth2ResourceServerProperties, + OktaOAuth2Properties oktaOAuth2Properties) { + + List> validators = new ArrayList<>(); + validators.add(new JwtTimestampValidator()); + validators.add(new JwtIssuerValidator(oAuth2ResourceServerProperties.getJwt().getIssuerUri())); + validators.add(token -> { + Set expectedAudience = new HashSet<>(); + expectedAudience.add(oktaOAuth2Properties.getAudience()); + return (!Collections.disjoint(token.getAudience(), expectedAudience)) + ? OAuth2TokenValidatorResult.success() + : OAuth2TokenValidatorResult.failure(INVALID_AUDIENCE); + }); + OAuth2TokenValidator validator = new DelegatingOAuth2TokenValidator<>(validators); + + NimbusJwtDecoderJwkSupport decoder = new NimbusJwtDecoderJwkSupport(oAuth2ResourceServerProperties.getJwt().getJwkSetUri()); + decoder.setJwtValidator(validator); + + return decoder; + } +} \ No newline at end of file diff --git a/oauth2/src/main/java/com/okta/spring/boot/oauth/OktaOAuth2UserService.java b/oauth2/src/main/java/com/okta/spring/boot/oauth/OktaOAuth2UserService.java new file mode 100644 index 000000000..6b938abd6 --- /dev/null +++ b/oauth2/src/main/java/com/okta/spring/boot/oauth/OktaOAuth2UserService.java @@ -0,0 +1,42 @@ +package com.okta.spring.boot.oauth; + +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService; +import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; +import org.springframework.security.oauth2.core.user.DefaultOAuth2User; +import org.springframework.security.oauth2.core.user.OAuth2User; + +import java.util.HashSet; +import java.util.Set; + +final class OktaOAuth2UserService extends DefaultOAuth2UserService { + + private final String groupClaim; + + OktaOAuth2UserService(String groupClaim) { + this.groupClaim = groupClaim; + } + + @Override + public OAuth2User loadUser(OAuth2UserRequest userRequest) { + + OAuth2User user = super.loadUser(userRequest); + + // Only post process requests from the "Okta" reg + if (!"Okta".equals(userRequest.getClientRegistration().getClientName())) { + return user; + } + + // start with authorities from super + Set authorities = new HashSet<>(user.getAuthorities()); + // add 'SCOPE_' authorities + authorities.addAll(TokenUtil.tokenScopesToAuthorities(userRequest.getAccessToken())); + // add any authorities extracted from the 'group' claim + authorities.addAll(TokenUtil.tokenClaimsToAuthorities(user.getAttributes(), groupClaim)); + + String userNameAttributeName = userRequest.getClientRegistration().getProviderDetails() + .getUserInfoEndpoint().getUserNameAttributeName(); + + return new DefaultOAuth2User(authorities, user.getAttributes(), userNameAttributeName); + } +} \ No newline at end of file diff --git a/oauth2/src/main/java/com/okta/spring/boot/oauth/OktaOidcUserService.java b/oauth2/src/main/java/com/okta/spring/boot/oauth/OktaOidcUserService.java new file mode 100644 index 000000000..dbec5150d --- /dev/null +++ b/oauth2/src/main/java/com/okta/spring/boot/oauth/OktaOidcUserService.java @@ -0,0 +1,45 @@ +package com.okta.spring.boot.oauth; + +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest; +import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserService; +import org.springframework.security.oauth2.core.OAuth2AuthenticationException; +import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser; +import org.springframework.security.oauth2.core.oidc.user.OidcUser; +import org.springframework.util.StringUtils; + +import java.util.HashSet; +import java.util.Set; + +final class OktaOidcUserService extends OidcUserService { + + private final String groupClaim; + + OktaOidcUserService(String groupClaim) { + this.groupClaim = groupClaim; + } + + @Override + public OidcUser loadUser(OidcUserRequest userRequest) throws OAuth2AuthenticationException { + OidcUser user = super.loadUser(userRequest); + + // Only post process requests from the "Okta" reg + if (!"Okta".equals(userRequest.getClientRegistration().getClientName())) { + return user; + } + + // start with authorities from super + Set authorities = new HashSet<>(user.getAuthorities()); + // add 'SCOPE_' authorities + authorities.addAll(TokenUtil.tokenScopesToAuthorities(userRequest.getAccessToken())); + // add any authorities extracted from the 'group' claim + authorities.addAll(TokenUtil.tokenClaimsToAuthorities(user.getAttributes(), groupClaim)); + + String userNameAttributeName = userRequest.getClientRegistration() + .getProviderDetails().getUserInfoEndpoint().getUserNameAttributeName(); + + return StringUtils.hasText(userNameAttributeName) + ? new DefaultOidcUser(authorities, user.getIdToken(), user.getUserInfo(), userNameAttributeName) + : new DefaultOidcUser(authorities, user.getIdToken(), user.getUserInfo()); + } +} diff --git a/oauth2/src/main/java/com/okta/spring/boot/oauth/TokenUtil.java b/oauth2/src/main/java/com/okta/spring/boot/oauth/TokenUtil.java new file mode 100644 index 000000000..0acc5f623 --- /dev/null +++ b/oauth2/src/main/java/com/okta/spring/boot/oauth/TokenUtil.java @@ -0,0 +1,48 @@ +package com.okta.spring.boot.oauth; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.oauth2.core.OAuth2AccessToken; +import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.stream.Collectors; + +final class TokenUtil { + + private static final Logger log = LoggerFactory.getLogger(TokenUtil.class); + + private TokenUtil(){} + + static Collection tokenScopesToAuthorities(OAuth2AccessToken accessToken) { + + if (accessToken == null || accessToken.getScopes() == null) { + return Collections.emptySet(); + } + + return accessToken.getScopes().stream() + .map(scope -> "SCOPE_" + scope) + .map(SimpleGrantedAuthority::new) + .collect(Collectors.toSet()); + } + + static Collection tokenClaimsToAuthorities(Map attributes, String claimKey) { + + if (!CollectionUtils.isEmpty(attributes) && StringUtils.hasText(claimKey)) { + Object rawRoleClaim = attributes.get(claimKey); + if (rawRoleClaim instanceof Collection) { + return ((Collection) rawRoleClaim).stream() + .map(SimpleGrantedAuthority::new) + .collect(Collectors.toSet()); + } else if (rawRoleClaim != null) { // don't log when null, that is the default condition + log.debug("Could not extract authorities from claim '{}', value was not a collection", claimKey); + } + } + return Collections.emptySet(); + } +} diff --git a/config/src/main/java/com/okta/spring/config/OktaOAuth2Properties.java b/oauth2/src/main/java/com/okta/spring/boot/oauth/config/OktaOAuth2Properties.java similarity index 60% rename from config/src/main/java/com/okta/spring/config/OktaOAuth2Properties.java rename to oauth2/src/main/java/com/okta/spring/boot/oauth/config/OktaOAuth2Properties.java index cc94da41e..092262e36 100644 --- a/config/src/main/java/com/okta/spring/config/OktaOAuth2Properties.java +++ b/oauth2/src/main/java/com/okta/spring/boot/oauth/config/OktaOAuth2Properties.java @@ -13,28 +13,27 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.okta.spring.config; +package com.okta.spring.boot.oauth.config; import com.okta.commons.configcheck.ConfigurationValidator; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientProperties; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.validation.Errors; import org.springframework.validation.Validator; -import java.util.Arrays; -import java.util.List; +import java.util.Optional; +import java.util.Set; @ConfigurationProperties("okta.oauth2") -public class OktaOAuth2Properties implements Validator { +public final class OktaOAuth2Properties implements Validator { - /** - * Login route path. - */ - private String redirectUri = "/login"; + private final OAuth2ClientProperties clientProperties; /** - * Custom login page hosted by this application. + * Login route path. This property should NOT be used with applications that have multiple OAuth2 providers. */ - private String customLoginRoute; + private String redirectUri; /** * OAuth2 clientId value. @@ -46,40 +45,33 @@ public class OktaOAuth2Properties implements Validator { */ private String clientSecret; - /** - * OIDC discovery URL, when set all properties that are discoverable will be populated automatically. - */ - private String discoveryUri; - /** * Custom authorization server issuer URL: i.e. 'https://dev-123456.oktapreview.com/oauth2/ausar5cbq5TRooicu812'. */ private String issuer; /** - * Expected access token audience claim value. + * Authorization scopes. */ - private String audience = "api://default"; + private Set scopes; /** - * Access token scope claim key. + * Expected access token audience claim value. */ - private String scopeClaim = "scp"; + private String audience = "api://default"; /** * Access token roles/groups claim key. */ - private String rolesClaim = "groups"; - - private List scopes = Arrays.asList("openid", "profile", "email"); + private String groupsClaim = "groups"; - /** - * Claim to pull the principal name from. - */ - private String principalClaim = "email"; + public OktaOAuth2Properties(@Autowired(required = false) OAuth2ClientProperties clientProperties) { + this.clientProperties = clientProperties; + } public String getClientId() { - return clientId; + return getRegistration().map(OAuth2ClientProperties.Registration::getClientId) + .orElse(clientId); } public void setClientId(String clientId) { @@ -87,7 +79,8 @@ public void setClientId(String clientId) { } public String getClientSecret() { - return clientSecret; + return getRegistration().map(OAuth2ClientProperties.Registration::getClientSecret) + .orElse(clientSecret); } public void setClientSecret(String clientSecret) { @@ -110,36 +103,21 @@ public void setAudience(String audience) { this.audience = audience; } - public String getScopeClaim() { - return scopeClaim; - } - - public void setScopeClaim(String scopeClaim) { - this.scopeClaim = scopeClaim; + public String getGroupsClaim() { + return groupsClaim; } - public String getRolesClaim() { - return rolesClaim; + public void setGroupsClaim(String groupsClaim) { + this.groupsClaim = groupsClaim; } - public void setRolesClaim(String rolesClaim) { - this.rolesClaim = rolesClaim; + public Set getScopes() { + return getRegistration().map(OAuth2ClientProperties.Registration::getScope) + .orElse(scopes); } - public String getDiscoveryUri() { - return discoveryUri; - } - - public void setDiscoveryUri(String discoveryUri) { - this.discoveryUri = discoveryUri; - } - - public String getPrincipalClaim() { - return principalClaim; - } - - public void setPrincipalClaim(String principalClaim) { - this.principalClaim = principalClaim; + public void setScopes(Set scopes) { + this.scopes = scopes; } public String getRedirectUri() { @@ -150,20 +128,10 @@ public void setRedirectUri(String redirectUri) { this.redirectUri = redirectUri; } - public String getCustomLoginRoute() { - return customLoginRoute; - } - - public void setCustomLoginRoute(String customLoginRoute) { - this.customLoginRoute = customLoginRoute; - } - - public List getScopes() { - return scopes; - } - - public void setScopes(List scopes) { - this.scopes = scopes; + private Optional getRegistration() { + return Optional.ofNullable(clientProperties != null + ? clientProperties.getRegistration().get("okta") + : null); } @Override @@ -176,19 +144,19 @@ public void validate(Object target, Errors errors) { OktaOAuth2Properties properties = (OktaOAuth2Properties) target; - if (properties.getIssuer() != null) { - ConfigurationValidator.validateIssuer(properties.getIssuer()).ifInvalid(res -> - errors.rejectValue("issuer", res.getMessage())); - } - if (properties.getClientId() != null) { - ConfigurationValidator.validateClientId(properties.getClientId()).ifInvalid(res -> - errors.rejectValue("clientId", res.getMessage())); + ConfigurationValidator.validateClientId(properties.getClientId()).ifInvalid(res -> + errors.rejectValue("clientId", res.getMessage())); } if (properties.getClientSecret() != null) { ConfigurationValidator.validateClientSecret(properties.getClientSecret()).ifInvalid(res -> errors.rejectValue("clientSecret", res.getMessage())); } + + if (properties.getIssuer() != null) { + ConfigurationValidator.validateIssuer(properties.getIssuer()).ifInvalid(res -> + errors.rejectValue("issuer", res.getMessage())); + } } } \ No newline at end of file diff --git a/oauth2/src/main/java/com/okta/spring/boot/oauth/env/OktaOAuth2PropertiesMappingEnvironmentPostProcessor.java b/oauth2/src/main/java/com/okta/spring/boot/oauth/env/OktaOAuth2PropertiesMappingEnvironmentPostProcessor.java new file mode 100644 index 000000000..cf8a11c2b --- /dev/null +++ b/oauth2/src/main/java/com/okta/spring/boot/oauth/env/OktaOAuth2PropertiesMappingEnvironmentPostProcessor.java @@ -0,0 +1,168 @@ +/* + * Copyright 2017 Okta, Inc. + * + * 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 + * + * http://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 com.okta.spring.boot.oauth.env; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.context.properties.bind.Bindable; +import org.springframework.boot.context.properties.bind.Binder; +import org.springframework.boot.env.EnvironmentPostProcessor; +import org.springframework.core.Ordered; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.Environment; +import org.springframework.core.env.MapPropertySource; +import org.springframework.core.env.PropertySource; +import java.util.HashMap; +import java.util.Map; + +/** + * This {@link EnvironmentPostProcessor} configures additional {@link PropertySource}s that map OIDC discovery metadata + * and standard Okta properties to standard Spring Boot OAuth2 properties. + * + *

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Okta PropertySpring Boot Property
okta.oauth2.client-idspring.security.oauth2.client.registration.okta.client-id
okta.oauth2.client-secretspring.security.oauth2.client.registration.okta.client-secret + *
okta.oauth2.scopesspring.security.oauth2.client.registration.okta.scope
${okta.oauth2.issuer}/v1/authorizespring.security.oauth2.client.provider.okta.authorization-uri
${okta.oauth2.issuer}/v1/tokenspring.security.oauth2.client.provider.okta.token-uri
${okta.oauth2.issuer}/v1/userinfospring.security.oauth2.client.provider.okta.user-info-uri
${okta.oauth2.issuer}/v1/keysspring.security.oauth2.client.provider.okta.jwk-set-uri
${okta.oauth2.issuer}spring.security.oauth2.resourceserver.jwt.issuer-uri
${okta.oauth2.issuer}/v1/keysspring.security.oauth2.resourceserver.jwt.jwk-set-uri
+ * + * @since 0.2.0 + */ +final class OktaOAuth2PropertiesMappingEnvironmentPostProcessor implements EnvironmentPostProcessor, Ordered { + + private static final String OKTA_OAUTH_PREFIX = "okta.oauth2."; + private static final String OKTA_OAUTH_ISSUER = OKTA_OAUTH_PREFIX + "issuer"; + private static final String OKTA_OAUTH_CLIENT_ID = OKTA_OAUTH_PREFIX + "client-id"; + private static final String OKTA_OAUTH_CLIENT_SECRET = OKTA_OAUTH_PREFIX + "client-secret"; + private static final String OKTA_OAUTH_SCOPES = OKTA_OAUTH_PREFIX + "scopes"; // array vs string + + @Override + public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) { + + // convert okta.oauth2.* properties to long form spring oauth properties + environment.getPropertySources().addLast(remappedOktaToStandardOAuthPropertySource(environment)); + environment.getPropertySources().addLast(remappedOktaOAuth2ScopesPropertySource(environment)); + // okta's endpoints can be resolved from an issuer + environment.getPropertySources().addLast(oktaStaticDiscoveryPropertySource(environment)); + environment.getPropertySources().addLast(oktaRedirectUriPropertySource(environment)); + + } + + private PropertySource remappedOktaToStandardOAuthPropertySource(Environment environment) { + Map aliasMap = new HashMap<>(); + + aliasMap.put("spring.security.oauth2.client.registration.okta.client-id", OKTA_OAUTH_CLIENT_ID); + aliasMap.put("spring.security.oauth2.client.registration.okta.client-secret", OKTA_OAUTH_CLIENT_SECRET); + + return new RemappedPropertySource("okta-to-oauth2", aliasMap, environment); + } + + private PropertySource remappedOktaOAuth2ScopesPropertySource(Environment environment) { + + Map properties = new HashMap<>(); + properties.put("spring.security.oauth2.client.registration.okta.scope", "${" + OKTA_OAUTH_SCOPES + "}"); + return new MapPropertySource("okta-scope-remaper", properties) { + @Override + public Object getProperty(String name) { + + if (containsProperty(name)) { + return Binder.get(environment).bind(OKTA_OAUTH_SCOPES, Bindable.setOf(String.class)).orElse(null); + } + + return null; + } + }; + } + + private PropertySource oktaRedirectUriPropertySource(Environment environment) { + Map properties = new HashMap<>(); + properties.put("spring.security.oauth2.client.registration.okta.redirect-uri", "{baseUrl}${okta.oauth2.redirect-uri}"); + return new ConditionalMapPropertySource("okta-redirect-uri-helper", properties, environment, "okta.oauth2.redirect-uri"); + } + + private PropertySource oktaStaticDiscoveryPropertySource(Environment environment) { + + Map properties = new HashMap<>(); + properties.put("spring.security.oauth2.resourceserver.jwt.issuer-uri", "${okta.oauth2.issuer}"); + properties.put("spring.security.oauth2.resourceserver.jwt.jwk-set-uri", "${okta.oauth2.issuer}/v1/keys"); + properties.put("spring.security.oauth2.client.provider.okta.authorization-uri", "${okta.oauth2.issuer}/v1/authorize"); + properties.put("spring.security.oauth2.client.provider.okta.token-uri", "${okta.oauth2.issuer}/v1/token"); + properties.put("spring.security.oauth2.client.provider.okta.user-info-uri", "${okta.oauth2.issuer}/v1/userinfo"); + properties.put("spring.security.oauth2.client.provider.okta.jwk-set-uri", "${okta.oauth2.issuer}/v1/keys"); + + return new ConditionalMapPropertySource("okta-static-discovery", properties, environment, OKTA_OAUTH_ISSUER); + } + + private static class ConditionalMapPropertySource extends MapPropertySource { + + private final Environment environment; + private final String conditionalProperty; + + private ConditionalMapPropertySource(String name, Map source, Environment environment, String conditionalProperty) { + super(name, source); + this.environment = environment; + this.conditionalProperty = conditionalProperty; + } + + @Override + public Object getProperty(String name) { + + return (containsProperty(name) && environment.containsProperty(conditionalProperty)) + ? super.getProperty(name) + : null; + } + } + @Override + public int getOrder() { + return LOWEST_PRECEDENCE - 1; + } +} \ No newline at end of file diff --git a/oauth2/src/main/java/com/okta/spring/oauth/env/RemappedPropertySource.java b/oauth2/src/main/java/com/okta/spring/boot/oauth/env/RemappedPropertySource.java similarity index 95% rename from oauth2/src/main/java/com/okta/spring/oauth/env/RemappedPropertySource.java rename to oauth2/src/main/java/com/okta/spring/boot/oauth/env/RemappedPropertySource.java index f7ab43b0a..e954936e7 100644 --- a/oauth2/src/main/java/com/okta/spring/oauth/env/RemappedPropertySource.java +++ b/oauth2/src/main/java/com/okta/spring/boot/oauth/env/RemappedPropertySource.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.okta.spring.oauth.env; +package com.okta.spring.boot.oauth.env; import org.springframework.core.env.EnumerablePropertySource; import org.springframework.core.env.Environment; @@ -31,7 +31,7 @@ * * @since 0.3.0 */ -public class RemappedPropertySource extends EnumerablePropertySource { +final class RemappedPropertySource extends EnumerablePropertySource { private final Map aliasMap = new HashMap<>(); private final Environment environment; diff --git a/oauth2/src/main/java/com/okta/spring/oauth/ClaimsAuthoritiesExtractor.java b/oauth2/src/main/java/com/okta/spring/oauth/ClaimsAuthoritiesExtractor.java deleted file mode 100644 index d5879f283..000000000 --- a/oauth2/src/main/java/com/okta/spring/oauth/ClaimsAuthoritiesExtractor.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright 2017 Okta, Inc. - * - * 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 - * - * http://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 com.okta.spring.oauth; - -import org.springframework.boot.autoconfigure.security.oauth2.resource.AuthoritiesExtractor; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.authority.SimpleGrantedAuthority; - -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - -/** - * A {@link AuthoritiesExtractor} converts a list of Groups/Roles in an JWT token into Spring Security - * {@link GrantedAuthority}. - * @since 0.2.0 - */ -public class ClaimsAuthoritiesExtractor implements AuthoritiesExtractor { - - private final String rolesClaimKey; - - public ClaimsAuthoritiesExtractor(String rolesClaimKey) { - this.rolesClaimKey = rolesClaimKey; - } - - @Override - public List extractAuthorities(Map map) { - // extract the string groups from map - List groups= Collections.emptyList(); - if (map.containsKey(rolesClaimKey)) { - Object rawGroups = map.get(rolesClaimKey); - if (rawGroups instanceof List) { - groups = (List) rawGroups; - } - } - - // convert them to authorities - return (List) groups.stream() - .filter(String.class::isInstance) - .map(group -> new SimpleGrantedAuthority(group.toString())) - .collect(Collectors.toList()); - } -} diff --git a/oauth2/src/main/java/com/okta/spring/oauth/ClaimsPrincipalExtractor.java b/oauth2/src/main/java/com/okta/spring/oauth/ClaimsPrincipalExtractor.java deleted file mode 100644 index b46037d51..000000000 --- a/oauth2/src/main/java/com/okta/spring/oauth/ClaimsPrincipalExtractor.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2017 Okta, Inc. - * - * 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 - * - * http://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 com.okta.spring.oauth; - -import org.springframework.boot.autoconfigure.security.oauth2.resource.PrincipalExtractor; - -import java.util.Map; - -/** - * A {@link PrincipalExtractor} pulls a {@link java.security.Principal Principal}, out of a configurable claim - * based on the {code}principalClaimKey{code}. - * @since 0.2.0 - */ -public class ClaimsPrincipalExtractor implements PrincipalExtractor { - - private final String principalClaimKey; - - public ClaimsPrincipalExtractor(String principalClaimKey) { - this.principalClaimKey = principalClaimKey; - } - - @Override - public Object extractPrincipal(Map map) { - return map.get(principalClaimKey); - } -} diff --git a/oauth2/src/main/java/com/okta/spring/oauth/ConfigurableAccessTokenConverter.java b/oauth2/src/main/java/com/okta/spring/oauth/ConfigurableAccessTokenConverter.java deleted file mode 100644 index 7ef75e142..000000000 --- a/oauth2/src/main/java/com/okta/spring/oauth/ConfigurableAccessTokenConverter.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright 2017 Okta, Inc. - * - * 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 - * - * http://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 com.okta.spring.oauth; - -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.oauth2.common.OAuth2AccessToken; -import org.springframework.security.oauth2.provider.OAuth2Authentication; -import org.springframework.security.oauth2.provider.token.DefaultAccessTokenConverter; -import org.springframework.security.oauth2.provider.token.UserAuthenticationConverter; -import org.springframework.util.Assert; -import org.springframework.util.ObjectUtils; - -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.Map; - -/** - * Adjusts the default map of JWT access token claims based on a configurable scope and role claim field. - * @since 0.1.0 - */ -public class ConfigurableAccessTokenConverter extends DefaultAccessTokenConverter { - - private static final String SUBJECT_CLAIM = "sub"; - - private final String scopeClaim; - private final String rolesClaim; - - public ConfigurableAccessTokenConverter(String scopeClaim, String rolesClaim) { - Assert.hasText(scopeClaim, "scopeClaim cannot be null or empty."); - Assert.hasText(rolesClaim, "rolesClaim cannot be null or empty."); - this.scopeClaim = scopeClaim; - this.rolesClaim = rolesClaim; - } - - private Map tweakScopeMap(Map map) { - Map tokenMap = new LinkedHashMap<>(map); - if (tokenMap.containsKey(scopeClaim)) { - Object scope = tokenMap.get(scopeClaim); - if (!ObjectUtils.isEmpty(scope)) { - tokenMap.put(OAuth2AccessToken.SCOPE, scope); - } - } - - if (tokenMap.containsKey(rolesClaim)) { - Object roles = tokenMap.get(rolesClaim); - if (!ObjectUtils.isEmpty(roles)) { - tokenMap.put(UserAuthenticationConverter.AUTHORITIES, roles); - } - } - - if (tokenMap.containsKey(SUBJECT_CLAIM)) { - Object sub = tokenMap.get(SUBJECT_CLAIM); - tokenMap.put(UserAuthenticationConverter.USERNAME, sub); - } - - return tokenMap; - } - - @Override - public OAuth2AccessToken extractAccessToken(String value, Map map) { - return super.extractAccessToken(value, tweakScopeMap(map)); - } - - @Override - public OAuth2Authentication extractAuthentication(Map map) { - // call super with the updated map - OAuth2Authentication originalOAuth2Authentication = super.extractAuthentication(tweakScopeMap(map)); - - // If this token has UserAuthentication we need to rebuild it (we do not call setDetails() directly, - // as the implementation might change, currently it is a UsernamePasswordAuthenticationToken) - Authentication originalUserAuthentication = originalOAuth2Authentication.getUserAuthentication(); - if (originalUserAuthentication != null) { - UsernamePasswordAuthenticationToken newToken = new UsernamePasswordAuthenticationToken(originalUserAuthentication.getPrincipal(), - "N/A", - originalUserAuthentication.getAuthorities()); - // now set the details on the new token directly, and return a new OAuth2Authentication - newToken.setDetails(Collections.unmodifiableMap(map)); - return new OAuth2Authentication(originalOAuth2Authentication.getOAuth2Request(), newToken); - } - return originalOAuth2Authentication; - } -} \ No newline at end of file diff --git a/oauth2/src/main/java/com/okta/spring/oauth/OAuth2AccessTokenValidationException.java b/oauth2/src/main/java/com/okta/spring/oauth/OAuth2AccessTokenValidationException.java deleted file mode 100644 index 4992b3309..000000000 --- a/oauth2/src/main/java/com/okta/spring/oauth/OAuth2AccessTokenValidationException.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright 2017 Okta, Inc. - * - * 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 - * - * http://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 com.okta.spring.oauth; - -import org.springframework.security.core.AuthenticationException; - -public class OAuth2AccessTokenValidationException extends AuthenticationException { - public OAuth2AccessTokenValidationException(String msg, Throwable t) { - super(msg, t); - } - - public OAuth2AccessTokenValidationException(String msg) { - super(msg); - } -} diff --git a/oauth2/src/main/java/com/okta/spring/oauth/OktaTokenServicesConfig.java b/oauth2/src/main/java/com/okta/spring/oauth/OktaTokenServicesConfig.java deleted file mode 100644 index d7f0a98fb..000000000 --- a/oauth2/src/main/java/com/okta/spring/oauth/OktaTokenServicesConfig.java +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Copyright 2017 Okta, Inc. - * - * 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 - * - * http://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 com.okta.spring.oauth; - - -import com.okta.spring.config.OktaOAuth2Properties; -import org.springframework.beans.InvalidPropertyException; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.autoconfigure.security.oauth2.resource.AuthoritiesExtractor; -import org.springframework.boot.autoconfigure.security.oauth2.resource.PrincipalExtractor; -import org.springframework.boot.autoconfigure.security.oauth2.resource.ResourceServerProperties; -import org.springframework.boot.autoconfigure.security.oauth2.resource.UserInfoRestTemplateFactory; -import org.springframework.boot.autoconfigure.security.oauth2.resource.UserInfoTokenServices; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; -import org.springframework.context.annotation.Primary; -import org.springframework.security.oauth2.client.OAuth2ClientContext; -import org.springframework.security.oauth2.provider.token.AccessTokenConverter; -import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices; -import org.springframework.security.oauth2.provider.token.TokenStore; -import org.springframework.security.oauth2.provider.token.store.IssuerClaimVerifier; -import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter; -import org.springframework.security.oauth2.provider.token.store.JwtClaimsSetVerifier; -import org.springframework.security.oauth2.provider.token.store.jwk.JwkTokenStore; -import org.springframework.util.StringUtils; - -import java.net.MalformedURLException; -import java.net.URL; - -@Configuration -@Import({OktaTokenServicesConfig.RemoteTokenValidationConfig.class, - OktaTokenServicesConfig.LocalTokenValidationConfig.class}) -public class OktaTokenServicesConfig { - - @Configuration - @ConditionalOnProperty(name = "okta.oauth2.localTokenValidation", havingValue = "false") - public static class RemoteTokenValidationConfig { - - private final OktaOAuth2Properties oktaOAuth2Properties; - - public RemoteTokenValidationConfig(OktaOAuth2Properties oktaOAuth2Properties) { - this.oktaOAuth2Properties = oktaOAuth2Properties; - } - - @Bean - @ConditionalOnMissingBean - protected AuthoritiesExtractor authoritiesExtractor() { - return new ClaimsAuthoritiesExtractor(oktaOAuth2Properties.getRolesClaim()); - } - - @Bean - @ConditionalOnMissingBean - protected PrincipalExtractor principalExtractor() { - return new ClaimsPrincipalExtractor(oktaOAuth2Properties.getPrincipalClaim()); - } - - @Bean - @Primary - protected ResourceServerTokenServices resourceServerTokenServices(ResourceServerProperties sso, - OAuth2ClientContext oauth2ClientContext, - UserInfoRestTemplateFactory restTemplateFactory) { - - UserInfoTokenServices services = new OktaUserInfoTokenServices(sso.getUserInfoUri(), sso.getClientId(), oauth2ClientContext); - services.setRestTemplate(restTemplateFactory.getUserInfoRestTemplate()); - services.setTokenType(sso.getTokenType()); - services.setAuthoritiesExtractor(authoritiesExtractor()); - services.setPrincipalExtractor(principalExtractor()); - - return services; - } - } - - @Configuration - @ConditionalOnProperty(name = "okta.oauth2.localTokenValidation", matchIfMissing = true) - public static class LocalTokenValidationConfig { - - private final OktaOAuth2Properties oktaOAuth2Properties; - - public LocalTokenValidationConfig(OktaOAuth2Properties oktaOAuth2Properties) { - this.oktaOAuth2Properties = oktaOAuth2Properties; - } - - @Bean - @ConditionalOnMissingBean - protected AuthoritiesExtractor authoritiesExtractor() { - return new ClaimsAuthoritiesExtractor(oktaOAuth2Properties.getRolesClaim()); - } - - @Bean - @ConditionalOnMissingBean - protected PrincipalExtractor principalExtractor() { - return new ClaimsPrincipalExtractor(oktaOAuth2Properties.getPrincipalClaim()); - } - - @Bean - public TokenStore tokenStore() { - return new JwkTokenStore(oktaOAuth2Properties.getIssuer() + "/v1/keys", accessTokenConverter(), jwtClaimsSetVerifier()); - } - - @Bean - @ConditionalOnMissingBean - public JwtClaimsSetVerifier jwtClaimsSetVerifier() { - - if (!StringUtils.hasText(oktaOAuth2Properties.getIssuer())) { - throw new InvalidPropertyException(JwtClaimsSetVerifier.class, "okta.oauth2.issuer", "Property 'okta.oauth2.issuer' is required."); - } - - try { - return new IssuerClaimVerifier(new URL(oktaOAuth2Properties.getIssuer())); - } catch (MalformedURLException e) { - throw new InvalidPropertyException(JwtClaimsSetVerifier.class, "okta.oauth2.issuer", "Failed to parse issuer URL", e); - } - } - - @Bean - @ConditionalOnMissingBean - public AccessTokenConverter accessTokenConverter() { - JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter(); - jwtAccessTokenConverter.setAccessTokenConverter(new ConfigurableAccessTokenConverter(oktaOAuth2Properties.getScopeClaim(), oktaOAuth2Properties.getRolesClaim())); - return jwtAccessTokenConverter; - } - } -} \ No newline at end of file diff --git a/oauth2/src/main/java/com/okta/spring/oauth/OktaUserInfoTokenServices.java b/oauth2/src/main/java/com/okta/spring/oauth/OktaUserInfoTokenServices.java deleted file mode 100644 index 080e5ed9f..000000000 --- a/oauth2/src/main/java/com/okta/spring/oauth/OktaUserInfoTokenServices.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2017 Okta, Inc. - * - * 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 - * - * http://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 com.okta.spring.oauth; - -import org.springframework.boot.autoconfigure.security.oauth2.resource.UserInfoTokenServices; -import org.springframework.security.oauth2.client.OAuth2ClientContext; -import org.springframework.security.oauth2.common.OAuth2AccessToken; -import org.springframework.security.oauth2.provider.OAuth2Authentication; - -/** - * Okta UserInfoTokenServices that supports OAuth scopes. The default {@link UserInfoTokenServices} does not. - * @since 0.2.0 - */ -public class OktaUserInfoTokenServices extends UserInfoTokenServices { - - private final OAuth2ClientContext oauth2ClientContext; - - public OktaUserInfoTokenServices(String userInfoEndpointUrl, String clientId, OAuth2ClientContext oauth2ClientContext) { - super(userInfoEndpointUrl, clientId); - this.oauth2ClientContext = oauth2ClientContext; - } - - @Override - public OAuth2Authentication loadAuthentication(String accessToken) { - - OAuth2Authentication originalOAuth = super.loadAuthentication(accessToken); - OAuth2AccessToken existingToken = oauth2ClientContext.getAccessToken(); - - ScopeSupportedOAuth2Request customOAuth2Request = new ScopeSupportedOAuth2Request(originalOAuth.getOAuth2Request()); - customOAuth2Request.setScope(existingToken.getScope()); - return new OAuth2Authentication(customOAuth2Request, originalOAuth.getUserAuthentication()); - } -} \ No newline at end of file diff --git a/oauth2/src/main/java/com/okta/spring/oauth/ScopeSupportedOAuth2Request.java b/oauth2/src/main/java/com/okta/spring/oauth/ScopeSupportedOAuth2Request.java deleted file mode 100644 index 50d28c0bd..000000000 --- a/oauth2/src/main/java/com/okta/spring/oauth/ScopeSupportedOAuth2Request.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2017 Okta, Inc. - * - * 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 - * - * http://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 com.okta.spring.oauth; - -import org.springframework.security.oauth2.provider.OAuth2Request; - -import java.util.Collection; - -public class ScopeSupportedOAuth2Request extends OAuth2Request { - - private static final long serialVersionUID = 42L; - - public ScopeSupportedOAuth2Request(OAuth2Request other) { - super(other); - } - - @Override - @SuppressWarnings("PMD.UselessOverridingMethod") - public void setScope(Collection scope) { - super.setScope(scope); - } -} diff --git a/oauth2/src/main/java/com/okta/spring/oauth/code/CodeFlowAudienceValidatingTokenServices.java b/oauth2/src/main/java/com/okta/spring/oauth/code/CodeFlowAudienceValidatingTokenServices.java deleted file mode 100644 index 85f7f1747..000000000 --- a/oauth2/src/main/java/com/okta/spring/oauth/code/CodeFlowAudienceValidatingTokenServices.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright 2017 Okta, Inc. - * - * 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 - * - * http://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 com.okta.spring.oauth.code; - -import com.okta.spring.oauth.OAuth2AccessTokenValidationException; -import org.springframework.security.jwt.crypto.sign.InvalidSignatureException; -import org.springframework.security.oauth2.provider.OAuth2Authentication; -import org.springframework.security.oauth2.provider.token.DefaultTokenServices; - -public class CodeFlowAudienceValidatingTokenServices extends DefaultTokenServices { - - private final String audience; - - public CodeFlowAudienceValidatingTokenServices(String audience) { - this.audience = audience; - } - - @Override - public OAuth2Authentication loadAuthentication(String accessTokenValue) { - try { - OAuth2Authentication originalOAuth = super.loadAuthentication(accessTokenValue); - - // validate audience - if (!originalOAuth.getOAuth2Request().getResourceIds().contains(audience)) { - throw new OAuth2AccessTokenValidationException("Invalid token, 'aud' claim does not contain the expected audience of: " + audience); - } - - return originalOAuth; - - } catch(InvalidSignatureException e) { - throw new OAuth2AccessTokenValidationException("Invalid token, invalid signature", e); - } - } -} \ No newline at end of file diff --git a/oauth2/src/main/java/com/okta/spring/oauth/code/OktaOAuthCodeFlowConfiguration.java b/oauth2/src/main/java/com/okta/spring/oauth/code/OktaOAuthCodeFlowConfiguration.java deleted file mode 100644 index 98cc1d98e..000000000 --- a/oauth2/src/main/java/com/okta/spring/oauth/code/OktaOAuthCodeFlowConfiguration.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright 2017-Present Okta, Inc. - * - * 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 - * - * http://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 com.okta.spring.oauth.code; - -import com.okta.spring.config.OktaOAuth2Properties; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Primary; -import org.springframework.security.oauth2.provider.token.DefaultTokenServices; -import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices; -import org.springframework.security.oauth2.provider.token.TokenStore; - -/** - * Spring Configuration which adds a little Okta sugar to the standard Spring Boot OAuth2 support. - *

- * Features: - *

- *
    - *
  • Customizable PrincipalExtractor based on the property {code}okta.oauth2.rolesClaim{code}
  • - *
  • Customizable AuthoritiesExtractor based on the property {code}okta.oauth2.principalClaim{code}
  • - *
  • UserInfoTokenServices that supports OAuth2 scopes from the current request
  • - *
- * @since 0.2.0 - */ -@Configuration -class OktaOAuthCodeFlowConfiguration { - - @Configuration - @ConditionalOnProperty(name = "okta.oauth2.localTokenValidation", matchIfMissing = true) - public static class LocalTokenValidationConfig { - @Bean - @Primary - protected ResourceServerTokenServices resourceServerTokenServices(TokenStore tokenStore, OktaOAuth2Properties properties) { - DefaultTokenServices services = new CodeFlowAudienceValidatingTokenServices(properties.getAudience()); - services.setTokenStore(tokenStore); - return services; - } - } -} \ No newline at end of file diff --git a/oauth2/src/main/java/com/okta/spring/oauth/code/OktaOAuthCodeFlowCustomConfiguration.java b/oauth2/src/main/java/com/okta/spring/oauth/code/OktaOAuthCodeFlowCustomConfiguration.java deleted file mode 100644 index 6d82591b5..000000000 --- a/oauth2/src/main/java/com/okta/spring/oauth/code/OktaOAuthCodeFlowCustomConfiguration.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2018 Okta, Inc. - * - * 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 - * - * http://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 com.okta.spring.oauth.code; - - -import com.okta.spring.oauth.OktaTokenServicesConfig; -import org.springframework.boot.autoconfigure.AutoConfigureBefore; -import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; -import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2SsoCustomConfiguration; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; - -/** - * @since 0.6.0 - */ -@Configuration -@AutoConfigureBefore(OAuth2SsoCustomConfiguration.class) -@ConditionalOnBean(OAuth2SsoCustomConfiguration.class) -@Import({OktaOAuthCodeFlowConfiguration.class, OktaTokenServicesConfig.class}) -public class OktaOAuthCodeFlowCustomConfiguration {} diff --git a/oauth2/src/main/java/com/okta/spring/oauth/code/OktaOAuthCodeFlowDefaultConfiguration.java b/oauth2/src/main/java/com/okta/spring/oauth/code/OktaOAuthCodeFlowDefaultConfiguration.java deleted file mode 100644 index 9c23f56c9..000000000 --- a/oauth2/src/main/java/com/okta/spring/oauth/code/OktaOAuthCodeFlowDefaultConfiguration.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2018 Okta, Inc. - * - * 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 - * - * http://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 com.okta.spring.oauth.code; - - -import com.okta.spring.oauth.OktaTokenServicesConfig; -import org.springframework.boot.autoconfigure.AutoConfigureBefore; -import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; -import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2SsoDefaultConfiguration; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; - -/** - * @since 0.6.0 - */ -@Configuration -@AutoConfigureBefore(OAuth2SsoDefaultConfiguration.class) -@ConditionalOnBean(OAuth2SsoDefaultConfiguration.class) -@Import({OktaOAuthCodeFlowConfiguration.class, OktaTokenServicesConfig.class}) -public class OktaOAuthCodeFlowDefaultConfiguration {} diff --git a/oauth2/src/main/java/com/okta/spring/oauth/discovery/DiscoveryPropertySource.java b/oauth2/src/main/java/com/okta/spring/oauth/discovery/DiscoveryPropertySource.java deleted file mode 100644 index 933d3f286..000000000 --- a/oauth2/src/main/java/com/okta/spring/oauth/discovery/DiscoveryPropertySource.java +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Copyright 2017 Okta, Inc. - * - * 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 - * - * http://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 com.okta.spring.oauth.discovery; - -import com.okta.commons.configcheck.ConfigurationValidator; -import org.springframework.core.env.EnumerablePropertySource; -import org.springframework.core.env.Environment; - -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * A {@link org.springframework.core.env.PropertySource PropertySource} that maps the values from an OIDC discovery - * metadata endpoint to Spring Security's expected {code}security.oauth2.*{code} properties. - *

- * NOTE: Discovery can be disabled by setting the property {code}okta.oauth2.discoveryDisabled=true{code}. - * - * @since 0.3.0 - */ -public class DiscoveryPropertySource extends EnumerablePropertySource { - - private static final String OAUTH_CLIENT_PREFIX = "security.oauth2.client."; - private static final String OAUTH_RESOURCE_PREFIX = "security.oauth2.resource."; - private static final String OKTA_OAUTH_ISSUER = "okta.oauth2.issuer"; - private static final String OKTA_OAUTH_DISCOVERY_DISABLED = "okta.oauth2.discoveryDisabled"; - - private static final String OAUTH_RESOURCE_JWK_SUB_KEY = OAUTH_RESOURCE_PREFIX + "jwk"; - - private static final String OAUTH_ACCESS_TOKEN_URI_KEY = OAUTH_CLIENT_PREFIX + "accessTokenUri"; - private static final String OAUTH_ACCESS_USER_AUTH_URI_KEY = OAUTH_CLIENT_PREFIX + "userAuthorizationUri"; - private static final String OAUTH_ACCESS_USER_INFO_URI_KEY = OAUTH_RESOURCE_PREFIX + "userInfoUri"; - private static final String OAUTH_RESOURCE_JWT_KEY_SET_URI_KEY = OAUTH_RESOURCE_JWK_SUB_KEY + ".keySetUri"; - private static final String OAUTH_RESOURCE_JWT_KEY_SET_URI_DASH_KEY = OAUTH_RESOURCE_JWK_SUB_KEY + ".key-set-uri"; - private static final String OAUTH_RESOURCE_TOKEN_INFO_URI = OAUTH_RESOURCE_PREFIX + "tokenInfoUri"; - - private static final String[] supportedKeys = {OAUTH_ACCESS_TOKEN_URI_KEY, - OAUTH_ACCESS_USER_AUTH_URI_KEY, - OAUTH_ACCESS_USER_INFO_URI_KEY, - OAUTH_RESOURCE_JWK_SUB_KEY, - OAUTH_RESOURCE_JWT_KEY_SET_URI_KEY, - OAUTH_RESOURCE_JWT_KEY_SET_URI_DASH_KEY, - OAUTH_RESOURCE_TOKEN_INFO_URI}; - private static final List supportedKeysList = Collections.unmodifiableList(Arrays.asList(supportedKeys)); - - - private final boolean isEnabled; - private final Environment environment; - private Map metadataProperties = null; - - public DiscoveryPropertySource(Environment environment) { - super("Okta-OIDC-Discovery-Client"); - this.environment = environment; - this.isEnabled = !Boolean.parseBoolean(environment.getProperty(OKTA_OAUTH_DISCOVERY_DISABLED)); - } - - @Override - public Object getProperty(String name) { - // there are some cases where 'containsProperty' is not called before calling this method, so we need to guard - // against it because we are using the 'environment' direction, otherwise we would end up recursively - // calling this method. - return containsProperty(name) && isReady() - ? getDiscoveryMetadata().get(name) - : null; - } - - @Override - public boolean containsProperty(String name) { - return isEnabled && supportedKeysList.contains(name); - } - - @Override - public String[] getPropertyNames() { - return Arrays.copyOf(supportedKeys, supportedKeys.length); - } - - private boolean isReady() { - return environment.containsProperty(OKTA_OAUTH_ISSUER); - } - - private Map getDiscoveryMetadata() { - - if (!isEnabled) { - return Collections.emptyMap(); - } - - // lazy load the properties the first time they are actually used - if (metadataProperties == null) { - synchronized (this) { - String issuerUrl = environment.getRequiredProperty(OKTA_OAUTH_ISSUER); - ConfigurationValidator.validateOrgUrl(issuerUrl); - OidcDiscoveryMetadata discoveryMetadata = createDiscoveryClient(issuerUrl).discover(); - Map tmpValues = new HashMap<>(); - - if (discoveryMetadata != null) { - putIfNotNull(tmpValues, OAUTH_ACCESS_TOKEN_URI_KEY, discoveryMetadata.getTokenEndpoint()); - putIfNotNull(tmpValues, OAUTH_ACCESS_USER_AUTH_URI_KEY, discoveryMetadata.getAuthorizationEndpoint()); - putIfNotNull(tmpValues, OAUTH_ACCESS_USER_INFO_URI_KEY, discoveryMetadata.getUserinfoEndpoint()); - putIfNotNull(tmpValues, OAUTH_RESOURCE_JWT_KEY_SET_URI_KEY, discoveryMetadata.getJwksUri()); - putIfNotNull(tmpValues, OAUTH_RESOURCE_JWT_KEY_SET_URI_DASH_KEY, discoveryMetadata.getJwksUri()); - putIfNotNull(tmpValues, OAUTH_RESOURCE_TOKEN_INFO_URI, discoveryMetadata.getIntrospectionEndpoint()); - } - metadataProperties = tmpValues; - } - } - return metadataProperties; - } - - private static void putIfNotNull(Map map, String key, Object value) { - if (value != null) { - map.put(key, value); - } - } - - OidcDiscoveryClient createDiscoveryClient(String issuerUrl) { - return new OidcDiscoveryClient(issuerUrl); - } -} \ No newline at end of file diff --git a/oauth2/src/main/java/com/okta/spring/oauth/discovery/OidcDiscoveryClient.java b/oauth2/src/main/java/com/okta/spring/oauth/discovery/OidcDiscoveryClient.java deleted file mode 100644 index f4fc90e22..000000000 --- a/oauth2/src/main/java/com/okta/spring/oauth/discovery/OidcDiscoveryClient.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright 2012-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://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 com.okta.spring.oauth.discovery; - -import com.okta.spring.oauth.http.UserAgent; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpRequest; -import org.springframework.http.client.ClientHttpRequestExecution; -import org.springframework.http.client.ClientHttpRequestInterceptor; -import org.springframework.http.client.ClientHttpResponse; -import org.springframework.util.Assert; -import org.springframework.web.client.RestTemplate; -import org.springframework.web.util.UriComponentsBuilder; - -import java.io.IOException; -import java.net.URI; - -/** - * OIDC discovery client. - * NOTE: parts of this class were heavily borrowed from {code}org.springframework.security.oauth2.client.discovery.ProviderDiscoveryClient{code} - * @since 0.2.0 - */ -public class OidcDiscoveryClient { - - private final URI issuerUri; - private final RestTemplate restTemplate = restTemplate(); - - public OidcDiscoveryClient(String issuer) { - - Assert.hasText(issuer, "issuer cannot be empty"); - try { - this.issuerUri = UriComponentsBuilder.fromHttpUrl(issuer) - .path("/.well-known/openid-configuration") - .build() - .encode() - .toUri(); - } catch (Exception ex) { - throw new IllegalArgumentException("Invalid URI for issuer: " + ex.getMessage(), ex); - } - } - - public OidcDiscoveryMetadata discover() { - return this.restTemplate.getForObject(issuerUri, OidcDiscoveryMetadata.class); - } - - private static RestTemplate restTemplate() { - RestTemplate template = new RestTemplate(); - template.getInterceptors().add(new UserAgentInterceptor()); - return template; - } - - private static class UserAgentInterceptor implements ClientHttpRequestInterceptor { - @Override - public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException { - request.getHeaders().add(HttpHeaders.USER_AGENT, UserAgent.getUserAgentString()); - return execution.execute(request, body); - } - } -} \ No newline at end of file diff --git a/oauth2/src/main/java/com/okta/spring/oauth/discovery/OidcDiscoveryMetadata.java b/oauth2/src/main/java/com/okta/spring/oauth/discovery/OidcDiscoveryMetadata.java deleted file mode 100644 index 271ffa556..000000000 --- a/oauth2/src/main/java/com/okta/spring/oauth/discovery/OidcDiscoveryMetadata.java +++ /dev/null @@ -1,268 +0,0 @@ -/* - * Copyright 2017 Okta, Inc. - * - * 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 - * - * http://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 com.okta.spring.oauth.discovery; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; - -import java.util.List; - -/** - * OIDC discovery metadata represented as a simple bean. - * @since 0.2.0 - */ -@JsonIgnoreProperties(ignoreUnknown = true) -public class OidcDiscoveryMetadata { - - private String issuer; - - @JsonProperty("authorization_endpoint") - private String authorizationEndpoint; - - @JsonProperty("token_endpoint") - private String tokenEndpoint; - - @JsonProperty("userinfo_endpoint") - private String userinfoEndpoint; - - @JsonProperty("registration_endpoint") - private String registrationEndpoint; - - @JsonProperty("jwks_uri") - private String jwksUri; - - @JsonProperty("introspection_endpoint") - private String introspectionEndpoint; - - @JsonProperty("revocation_endpoint") - private String revocationEndpoint; - - @JsonProperty("end_session_endpoint") - private String endSessionEndpoint; - - @JsonProperty("response_types_supported") - private List responseTypesSupported; - - @JsonProperty("response_modes_supported") - private List responseModesSupported; - - @JsonProperty("grant_types_supported") - private List grantTypesSupported; - - @JsonProperty("subject_types_supported") - private List subjectTypesSupported; - - @JsonProperty("id_token_signing_alg_values_supported") - private List idTokenSigningAlgValuesSupported; - - @JsonProperty("scopes_supported") - private List scopesSupported; - - @JsonProperty("token_endpoint_auth_methods_supported") - private List tokenEndpointAuthMethodsSupported; - - @JsonProperty("claims_supported") - private List claimsSupported; - - @JsonProperty("code_challenge_methods_supported") - private List codeChallengeMethodsSupported; - - @JsonProperty("introspection_endpoint_auth_methods_supported") - private List introspectionEndpointAuthMethodsSupported; - - @JsonProperty("revocation_endpoint_auth_methods_supported") - private List revocationEndpointAuthMethodsSupported; - - public String getIssuer() { - return issuer; - } - - public OidcDiscoveryMetadata setIssuer(String issuer) { - this.issuer = issuer; - return this; - } - - public String getAuthorizationEndpoint() { - return authorizationEndpoint; - } - - public OidcDiscoveryMetadata setAuthorizationEndpoint(String authorizationEndpoint) { - this.authorizationEndpoint = authorizationEndpoint; - return this; - } - - public String getTokenEndpoint() { - return tokenEndpoint; - } - - public OidcDiscoveryMetadata setTokenEndpoint(String tokenEndpoint) { - this.tokenEndpoint = tokenEndpoint; - return this; - } - - public String getUserinfoEndpoint() { - return userinfoEndpoint; - } - - public OidcDiscoveryMetadata setUserinfoEndpoint(String userinfoEndpoint) { - this.userinfoEndpoint = userinfoEndpoint; - return this; - } - - public String getRegistrationEndpoint() { - return registrationEndpoint; - } - - public OidcDiscoveryMetadata setRegistrationEndpoint(String registrationEndpoint) { - this.registrationEndpoint = registrationEndpoint; - return this; - } - - public String getJwksUri() { - return jwksUri; - } - - public OidcDiscoveryMetadata setJwksUri(String jwksUri) { - this.jwksUri = jwksUri; - return this; - } - - public String getIntrospectionEndpoint() { - return introspectionEndpoint; - } - - public OidcDiscoveryMetadata setIntrospectionEndpoint(String introspectionEndpoint) { - this.introspectionEndpoint = introspectionEndpoint; - return this; - } - - public String getRevocationEndpoint() { - return revocationEndpoint; - } - - public OidcDiscoveryMetadata setRevocationEndpoint(String revocationEndpoint) { - this.revocationEndpoint = revocationEndpoint; - return this; - } - - public String getEndSessionEndpoint() { - return endSessionEndpoint; - } - - public OidcDiscoveryMetadata setEndSessionEndpoint(String endSessionEndpoint) { - this.endSessionEndpoint = endSessionEndpoint; - return this; - } - - public List getResponseTypesSupported() { - return responseTypesSupported; - } - - public OidcDiscoveryMetadata setResponseTypesSupported(List responseTypesSupported) { - this.responseTypesSupported = responseTypesSupported; - return this; - } - - public List getResponseModesSupported() { - return responseModesSupported; - } - - public OidcDiscoveryMetadata setResponseModesSupported(List responseModesSupported) { - this.responseModesSupported = responseModesSupported; - return this; - } - - public List getGrantTypesSupported() { - return grantTypesSupported; - } - - public OidcDiscoveryMetadata setGrantTypesSupported(List grantTypesSupported) { - this.grantTypesSupported = grantTypesSupported; - return this; - } - - public List getSubjectTypesSupported() { - return subjectTypesSupported; - } - - public OidcDiscoveryMetadata setSubjectTypesSupported(List subjectTypesSupported) { - this.subjectTypesSupported = subjectTypesSupported; - return this; - } - - public List getIdTokenSigningAlgValuesSupported() { - return idTokenSigningAlgValuesSupported; - } - - public OidcDiscoveryMetadata setIdTokenSigningAlgValuesSupported(List idTokenSigningAlgValuesSupported) { - this.idTokenSigningAlgValuesSupported = idTokenSigningAlgValuesSupported; - return this; - } - - public List getScopesSupported() { - return scopesSupported; - } - - public OidcDiscoveryMetadata setScopesSupported(List scopesSupported) { - this.scopesSupported = scopesSupported; - return this; - } - - public List getTokenEndpointAuthMethodsSupported() { - return tokenEndpointAuthMethodsSupported; - } - - public OidcDiscoveryMetadata setTokenEndpointAuthMethodsSupported(List tokenEndpointAuthMethodsSupported) { - this.tokenEndpointAuthMethodsSupported = tokenEndpointAuthMethodsSupported; - return this; - } - - public List getClaimsSupported() { - return claimsSupported; - } - - public OidcDiscoveryMetadata setClaimsSupported(List claimsSupported) { - this.claimsSupported = claimsSupported; - return this; - } - - public List getCodeChallengeMethodsSupported() { - return codeChallengeMethodsSupported; - } - - public OidcDiscoveryMetadata setCodeChallengeMethodsSupported(List codeChallengeMethodsSupported) { - this.codeChallengeMethodsSupported = codeChallengeMethodsSupported; - return this; - } - - public List getIntrospectionEndpointAuthMethodsSupported() { - return introspectionEndpointAuthMethodsSupported; - } - - public OidcDiscoveryMetadata setIntrospectionEndpointAuthMethodsSupported(List introspectionEndpointAuthMethodsSupported) { - this.introspectionEndpointAuthMethodsSupported = introspectionEndpointAuthMethodsSupported; - return this; - } - - public List getRevocationEndpointAuthMethodsSupported() { - return revocationEndpointAuthMethodsSupported; - } - - public OidcDiscoveryMetadata setRevocationEndpointAuthMethodsSupported(List revocationEndpointAuthMethodsSupported) { - this.revocationEndpointAuthMethodsSupported = revocationEndpointAuthMethodsSupported; - return this; - } -} diff --git a/oauth2/src/main/java/com/okta/spring/oauth/env/OktaPropertiesMappingEnvironmentPostProcessor.java b/oauth2/src/main/java/com/okta/spring/oauth/env/OktaPropertiesMappingEnvironmentPostProcessor.java deleted file mode 100644 index a5e39f76d..000000000 --- a/oauth2/src/main/java/com/okta/spring/oauth/env/OktaPropertiesMappingEnvironmentPostProcessor.java +++ /dev/null @@ -1,203 +0,0 @@ -/* - * Copyright 2017 Okta, Inc. - * - * 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 - * - * http://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 com.okta.spring.oauth.env; - -import com.okta.commons.configcheck.ConfigurationValidator; -import com.okta.spring.oauth.discovery.DiscoveryPropertySource; -import org.springframework.boot.SpringApplication; -import org.springframework.boot.env.EnvironmentPostProcessor; -import org.springframework.boot.env.YamlPropertySourceLoader; -import org.springframework.core.Ordered; -import org.springframework.core.env.ConfigurableEnvironment; -import org.springframework.core.env.Environment; -import org.springframework.core.env.MapPropertySource; -import org.springframework.core.env.PropertySource; -import org.springframework.core.io.ClassPathResource; -import org.springframework.core.io.FileSystemResource; -import org.springframework.core.io.Resource; -import org.springframework.util.ClassUtils; -import org.springframework.util.StringUtils; - -import java.io.File; -import java.io.IOException; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; - -/** - * This {@link EnvironmentPostProcessor} configures additional {@link PropertySource}s that map OIDC discovery metadata - * and standard Okta properties to standard Spring Boot OAuth2 properties. - * - *

- * - * - * - * - * - * - * - * - * - * - * - * - * - *
Okta PropertySpring Boot Property
okta.oauth2.client-idsecurity.oauth2.client.client-id
okta.oauth2.client-secretsecurity.oauth2.client.client-secret
- * Discovery properties: - *

- * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
Discovery PropertySpring Boot Property
OidcDiscoveryMetadata.getTokenEndpoint()security.oauth2.client.access-token-uri
OidcDiscoveryMetadata.getAuthorizationEndpoint()security.oauth2.client.user-authorization-uri
OidcDiscoveryMetadata.getUserinfoEndpoint()security.oauth2.resource.user-info-uri
- * As well as updating default properties values from 'com.okta.spring.okta.yml'. And setting 'okta.client.org-url' based - * on 'okta.oauth2.issuer' - * - * NOTE: for discovery can be disabled by setting the property {@code okta.oauth2.discoveryDisabled=true}. - * - * @since 0.2.0 - */ -public class OktaPropertiesMappingEnvironmentPostProcessor implements EnvironmentPostProcessor, Ordered { - - private static final String OAUTH_CLIENT_PREFIX = "security.oauth2.client."; - private static final String OAUTH_RESOURCE_PREFIX = "security.oauth2.resource."; - private static final String OKTA_OAUTH_PREFIX = "okta.oauth2."; - private static final String OKTA_OAUTH_ISSUER = OKTA_OAUTH_PREFIX + "issuer"; - private static final String OKTA_OAUTH_CLIENT_ID = OKTA_OAUTH_PREFIX + "clientId"; - private static final String OKTA_OAUTH_CLIENT_SECRET = OKTA_OAUTH_PREFIX + "clientSecret"; - private static final String OKTA_OAUTH_AUDIENCE = OKTA_OAUTH_PREFIX + "audience"; - - @Override - public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) { - - // Validate only Okta properties - String issuer = environment.getProperty(OKTA_OAUTH_ISSUER); - Optional.ofNullable(issuer).ifPresent(ConfigurationValidator::validateOrgUrl); - - String clientId = environment.getProperty(OKTA_OAUTH_CLIENT_ID); - Optional.ofNullable(clientId).ifPresent(ConfigurationValidator::validateClientId); - - String clientSecret = environment.getProperty(OKTA_OAUTH_CLIENT_SECRET); - Optional.ofNullable(clientSecret).ifPresent(ConfigurationValidator::validateClientSecret); - - environment.getPropertySources().addLast(remappedOktaToStandardOAuthPropertySource(environment)); - environment.getPropertySources().addLast(loadYaml(new FileSystemResource(new File(System.getProperty("user.home"), ".okta/okta.yml")), false)); - environment.getPropertySources().addLast(loadYaml(new FileSystemResource(new File(System.getProperty("user.home"), ".okta/okta.yaml")), false)); - environment.getPropertySources().addLast(new DiscoveryPropertySource(environment)); - environment.getPropertySources().addLast(oauthToClientPropertiesSource(environment)); - environment.getPropertySources().addLast(loadYaml(new ClassPathResource("com/okta/spring/okta.yml"), true)); - } - - private PropertySource loadYaml(Resource resource, boolean required) { - YamlPropertySourceLoader loader = new YamlPropertySourceLoader(); - if (!resource.exists() && required) { - throw new IllegalArgumentException("Resource " + resource + " does not exist"); - } - - if (resource.exists()) { - try { - - // Spring Boot 2 - Method method = ClassUtils.getMethodIfAvailable(YamlPropertySourceLoader.class,"load", String.class, Resource.class); - if (method != null) { - List> list = (List>) method.invoke(loader, resource.getFilename(), resource); - return list.get(0); // TODO: hack - } else { - // Spring Boot 1.x - return loader.load(resource.getFilename(), resource, null); - } - } catch (IllegalAccessException | InvocationTargetException | IOException ex) { - throw new IllegalStateException("Failed to load yaml configuration from " + resource, ex); - } - } else { - return new MapPropertySource("Missing "+ resource.getFilename(), Collections.emptyMap()); - } - } - - /** - * Map {@code okta.oauth2.*} properties to {@code security.oauth2.*}. - * @param environment Environment used to read the known 'okta' properties from. - * @return A PropertySource containing the newly mapped values. - */ - private PropertySource remappedOktaToStandardOAuthPropertySource(Environment environment) { - Map aliasMap = new HashMap<>(); - - // when we drop Spring Boot 1.x support these properties should be changed to kabab format - aliasMap.put(OAUTH_CLIENT_PREFIX + "clientId", OKTA_OAUTH_CLIENT_ID); - aliasMap.put(OAUTH_CLIENT_PREFIX + "clientSecret", OKTA_OAUTH_CLIENT_SECRET); - aliasMap.put(OAUTH_RESOURCE_PREFIX + "serviceId", OKTA_OAUTH_AUDIENCE); - return new RemappedPropertySource("okta-to-oauth2", aliasMap, environment); - } - - /** - * Maps the baseUrl of {@code okta.oauth2.issuer} to {@code okta.client.org-url}. - * @param environment Environment used to read the {@code okta.oauth2.*} properties from. - * @return A PropertySource containing the newly mapped values. - */ - private PropertySource oauthToClientPropertiesSource(final Environment environment) { - return new IssuerToOrgUrlPropertySource(environment); - } - - @Override - public int getOrder() { - return LOWEST_PRECEDENCE; - } - - private static class IssuerToOrgUrlPropertySource extends PropertySource { - - private final Environment environment; - - private IssuerToOrgUrlPropertySource(Environment environment) { - super("okta-oauth-to-client"); - this.environment = environment; - } - - @Override - public Object getProperty(String name) { - if (containsProperty(name)) { - // first lookup the issuer - String issuerUrl = environment.getProperty(OKTA_OAUTH_PREFIX + "issuer"); - // if we don't have one just return null - if (StringUtils.hasText(issuerUrl)) { - return issuerUrl.substring(0, issuerUrl.lastIndexOf("/oauth2/")); - } - } - return null; - } - - @Override - public boolean containsProperty(String name) { - return "okta.client.org-url".equals(name) || "okta.client.orgUrl".equals(name); - } - } -} \ No newline at end of file diff --git a/oauth2/src/main/java/com/okta/spring/oauth/http/UserAgent.java b/oauth2/src/main/java/com/okta/spring/oauth/http/UserAgent.java deleted file mode 100644 index a9830547e..000000000 --- a/oauth2/src/main/java/com/okta/spring/oauth/http/UserAgent.java +++ /dev/null @@ -1,485 +0,0 @@ -/* - * Copyright 2014 Stormpath, Inc. - * Modifications Copyright 2018 Okta, Inc. - * - * 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 - * - * http://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 com.okta.spring.oauth.http; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.util.ClassUtils; -import org.springframework.util.StringUtils; - -import java.io.BufferedReader; -import java.io.InputStreamReader; -import java.io.PrintWriter; -import java.io.StringWriter; -import java.lang.reflect.Method; -import java.nio.charset.StandardCharsets; -import java.util.Enumeration; -import java.util.jar.JarEntry; -import java.util.jar.JarFile; - -/** - * This class is in charge of constructing the - * User-Agent http header string that will be sent to Okta in order to describe the current running environment of this Java SDK. - *

- * The form of this string is the concatenation of the following sub-items: - *

    - *
  1. The okta integration and version separated by a '/'. If there is no integration being used, this can be omitted - *
  2. The okta sdk and version separated by a '/' - *
  3. The runtime information (runtime/version) - *
      - *
    1. Integration Runtime (if there is no integration being used, this can be omitted) - *
    2. SDK Runtime - *
    - *
  4. The OS common name and version separated by a '/'. - *
  5. All other system information included in parentheses - *
- *

- * The User-Agent value is created when this class is loaded. The string can be obtained just by invoking - * {@link UserAgent#getUserAgentString() UserAgent.getUserAgentString()}. - *

- * This is a sample User-Agent string: - * okta-spring-security/0.7.0 okta-sdk-java/0.5.0 spring/4.0.4.RELEASE java/1.7.0_45 Mac OS X/10.9.2 (spring-security/3.2.0.RELEASE jetty/8.1.5.v20120716) - * - * @since 0.4.0 - */ -public class UserAgent { - - private static final Logger LOG = LoggerFactory.getLogger(UserAgent.class); - - //Integrations (aka Plugins) - private static final String INTEGRATION_SHIRO_ID = "okta-shiro"; - private static final String INTEGRATION_SHIRO_CLASS = "com.okta.shiro.realm.ApplicationRealm"; - private static final String INTEGRATION_SPRING_SECURITY_ID = "okta-spring-security"; - - //SDK - private static final String SDK_VERSION_FILE = "/com/okta/sdk/version.properties"; - private static final String OKTA_SDK_STRING = "okta-sdk-java"; - private static final String OKTA_SDK_CLASS = "com.okta.sdk.resource.user.User"; - - //Okta Zuul support - private static final String OKTA_ZUUL_ID = "okta-zuul"; - private static final String OKTA_ZUUL_CLASS = "com.okta.zuul.filter.AppliedRequestHeaderFilter"; - - //Okta Java Servlet Plugin - private static final String OKTA_SDK_SERVLET_ID = "okta-servlet-java"; - private static final String OKTA_SDK_SERVLET_CLASS = "com.okta.sdk.servlet.config.Config"; - - private static final String OKTA_SDK_RUNTIME_SPRING_WEBMVC_ID = "okta-spring-webmvc"; - private static final String OKTA_SDK_RUNTIME_SPRING_WEBMVC_CLASS = "com.okta.spring.mvc.AbstractSpringControllerConfig"; - - //Okta Spring Boot - private static final String OKTA_SDK_SPRING_BOOT_STARTER_ID = "okta-spring-boot-starter"; - private static final String OKTA_SDK_SPRING_BOOT_STARTER_CLASS = "com.okta.spring.boot.autoconfigure.OktaAutoConfiguration"; - - //Integration Runtimes - private static final String INTEGRATION_RUNTIME_SPRING_ID = "spring"; - private static final String INTEGRATION_RUNTIME_SPRING_CLASS = "org.springframework.context.ApplicationContext"; - - //Rapid Prototyping - private static final String RAPID_PROTOTYPING_SPRING_BOOT_ID = "spring-boot"; - private static final String RAPID_PROTOTYPING_SPRING_BOOT_CLASS = "org.springframework.boot.SpringApplication"; - - //Runtime - private static final String JAVA_SDK_RUNTIME_STRING = "java"; - - ////Additional Information//// - - //Security Frameworks - private static final String SECURITY_FRAMEWORK_SHIRO_ID = "shiro"; - private static final String SECURITY_FRAMEWORK_SHIRO_CLASS = "org.apache.shiro.SecurityUtils"; - private static final String SECURITY_FRAMEWORK_SPRING_SECURITY_ID = "spring-security"; - private static final String SECURITY_FRAMEWORK_SPRING_SECURITY_CLASS = "org.springframework.security.core.SpringSecurityCoreVersion"; - - //Web Servers - private static final String WEB_SERVER_TOMCAT_ID = "tomcat"; - private static final String WEB_SERVER_TOMCAT_BOOTSTRAP_CLASS = "org.apache.catalina.startup.Bootstrap"; - private static final String WEB_SERVER_TOMCAT_EMBEDDED_CLASS = "org.apache.catalina.startup.Tomcat"; - private static final String WEB_SERVER_JETTY_ID = "jetty"; - private static final String WEB_SERVER_JETTY_CLASS = "org.eclipse.jetty.servlet.listener.ELContextCleaner"; - private static final String WEB_SERVER_JBOSS_ID = "jboss"; - private static final String WEB_SERVER_JBOSS_CLASS = "org.jboss.as.security.plugins.AuthenticationCacheEvictionListener"; - private static final String WEB_SERVER_WEBSPHERE_ID = "websphere"; - private static final String WEB_SERVER_WEBSPHERE_CLASS = "com.ibm.websphere.product.VersionInfo"; - private static final String WEB_SERVER_GLASSFISH_ID = "glassfish"; - private static final String WEB_SERVER_GLASSFISH_CLASS = "com.sun.enterprise.glassfish.bootstrap.GlassFishMain"; - private static final String WEB_SERVER_WEBLOGIC_ID = "weblogic"; - private static final String WEB_SERVER_WEBLOGIC_CLASS = "weblogic.version"; - private static final String WEB_SERVER_WILDFLY_ID = "wildfly"; - private static final String WEB_SERVER_WILDFLY_CLASS = "org.jboss.as.security.ModuleName"; - - private static final String VERSION_SEPARATOR = "/"; - private static final String ENTRY_SEPARATOR = " "; - - //Placeholder for the actual User-Agent String - private static final String USER_AGENT = createUserAgentString(); - - private UserAgent() { - } - - public static String getUserAgentString() { - return USER_AGENT; - } - - private static String createUserAgentString() { - String userAgent = - getOktaSpringString() + // okta-spring-security - getOktaShiroString() + // okta-shiro - getOktaSDKComponentsString() + // okta-servlet-java | okta-spring-boot-starter - getOktaSdkString() + // okta-sdk-java - getSecurityFrameworkString() + // shiro | spring-security - getIntegrationRuntimeString() + // spring - getSpringBootString() + // spring-boot - getWebServerString() + // tomcat | jetty | jboss | websphere | glassfish | weblogic | wildfly - getJavaSDKRuntimeString() + // java - getOSString(); // Mac OS X - return userAgent.trim(); - } - - private static String getOktaShiroString() { - String integrationString; - integrationString = getFullEntryStringUsingPomProperties(INTEGRATION_SHIRO_CLASS, INTEGRATION_SHIRO_ID); - if (StringUtils.hasText(integrationString)) { - return integrationString; - } - return ""; - } - - private static String getOktaSpringString() { - return INTEGRATION_SPRING_SECURITY_ID + VERSION_SEPARATOR + Version.getClientVersion() + ENTRY_SEPARATOR; - } - - private static String getOktaSdkString() { - - if (ClassUtils.isPresent(OKTA_SDK_CLASS, null)) { - return OKTA_SDK_STRING + VERSION_SEPARATOR + Version.getClientVersion(SDK_VERSION_FILE) + ENTRY_SEPARATOR; - } - return ""; - } - - private static String getIntegrationRuntimeString() { - String integrationRuntimeString; - integrationRuntimeString = getFullEntryStringUsingManifest(INTEGRATION_RUNTIME_SPRING_CLASS, INTEGRATION_RUNTIME_SPRING_ID); - if (StringUtils.hasText(integrationRuntimeString)) { - return integrationRuntimeString; - } - return ""; - } - - private static String getJavaSDKRuntimeString() { - return JAVA_SDK_RUNTIME_STRING + VERSION_SEPARATOR + System.getProperty("java.version") + ENTRY_SEPARATOR; - } - - private static String getOSString() { - return System.getProperty("os.name") + VERSION_SEPARATOR + System.getProperty("os.version") + ENTRY_SEPARATOR; - } - - //Spring Boot - private static String getSpringBootString() { - String springBootStarter = getFullEntryStringUsingManifest(RAPID_PROTOTYPING_SPRING_BOOT_CLASS, RAPID_PROTOTYPING_SPRING_BOOT_ID); - if (StringUtils.hasText(springBootStarter)) { - return springBootStarter; - } - return ""; - } - - private static String getSecurityFrameworkString() { - - String securityFrameworkString; - securityFrameworkString = getFullEntryStringUsingManifest(SECURITY_FRAMEWORK_SHIRO_CLASS, SECURITY_FRAMEWORK_SHIRO_ID); - if (StringUtils.hasText(securityFrameworkString)) { - return securityFrameworkString; - } - securityFrameworkString = getFullEntryStringUsingManifest(SECURITY_FRAMEWORK_SPRING_SECURITY_CLASS, SECURITY_FRAMEWORK_SPRING_SECURITY_ID); - if (StringUtils.hasText(securityFrameworkString)) { - return securityFrameworkString; - } - return ""; - } - - //Okta SDK Components - private static String getOktaSDKComponentsString() { - - StringBuilder sb = new StringBuilder(); - - append(sb, getFullEntryStringUsingSDKVersion(OKTA_ZUUL_CLASS, OKTA_ZUUL_ID)); - append(sb, getFullEntryStringUsingSDKVersion(OKTA_SDK_SERVLET_CLASS, OKTA_SDK_SERVLET_ID)); - append(sb, getFullEntryStringUsingSDKVersion(OKTA_SDK_RUNTIME_SPRING_WEBMVC_CLASS, OKTA_SDK_RUNTIME_SPRING_WEBMVC_ID)); - append(sb, getFullEntryStringUsingSDKVersion(OKTA_SDK_SPRING_BOOT_STARTER_CLASS, OKTA_SDK_SPRING_BOOT_STARTER_ID)); - - return sb.toString(); - } - - private static void append(StringBuilder sb, String value) { - if (StringUtils.hasText(value)) { - sb.append(value); - } - } - - private static String getWebServerString() { - String webServerString; - //Glassfish uses Tomcat internally, therefore the Glassfish check must be carried out before Tomcat's - webServerString = getFullEntryStringUsingManifest(WEB_SERVER_GLASSFISH_CLASS, WEB_SERVER_GLASSFISH_ID); - if (StringUtils.hasText(webServerString)) { - return webServerString; - } - webServerString = getFullEntryStringUsingManifest(WEB_SERVER_TOMCAT_BOOTSTRAP_CLASS, WEB_SERVER_TOMCAT_ID); - if (StringUtils.hasText(webServerString)) { - return webServerString; - } - webServerString = getFullEntryStringUsingManifest(WEB_SERVER_TOMCAT_EMBEDDED_CLASS, WEB_SERVER_TOMCAT_ID); - if (StringUtils.hasText(webServerString)) { - return webServerString; - } - webServerString = getFullEntryStringUsingManifest(WEB_SERVER_JETTY_CLASS, WEB_SERVER_JETTY_ID); - if (StringUtils.hasText(webServerString)) { - return webServerString; - } - //WildFly must be before JBoss - webServerString = getWildFlyEntryString(); - if (StringUtils.hasText(webServerString)) { - return webServerString; - } - webServerString = getFullEntryStringUsingManifest(WEB_SERVER_JBOSS_CLASS, WEB_SERVER_JBOSS_ID); - if (StringUtils.hasText(webServerString)) { - return webServerString; - } - webServerString = getWebSphereEntryString(); - if (StringUtils.hasText(webServerString)) { - return webServerString; - } - webServerString = getWebLogicEntryString(); - if (StringUtils.hasText(webServerString)) { - return webServerString; - } - - return ""; - } - - private static String getFullEntryStringUsingPomProperties(String fqcn, String entryId) { - if (ClassUtils.isPresent(fqcn, null)) { - return entryId + VERSION_SEPARATOR + getVersionInfoFromPomProperties(fqcn) + ENTRY_SEPARATOR; - } - return null; - } - - private static String getFullEntryStringUsingManifest(String fqcn, String entryId) { - if (ClassUtils.isPresent(fqcn, null)) { - return entryId + VERSION_SEPARATOR + getVersionInfoInManifest(fqcn) + ENTRY_SEPARATOR; - } - return null; - } - - private static String getFullEntryStringUsingSDKVersion(String fqcn, String entryId) { - if (ClassUtils.isPresent(fqcn, null)) { - return entryId + VERSION_SEPARATOR + getVersionInfoInManifest(fqcn) + ENTRY_SEPARATOR; - } - return null; - } - - private static String getWebSphereEntryString() { - if (ClassUtils.isPresent(WEB_SERVER_WEBSPHERE_CLASS, null)) { - return WEB_SERVER_WEBSPHERE_ID + VERSION_SEPARATOR + getWebSphereVersion() + ENTRY_SEPARATOR; - } - return null; - } - - private static String getWebLogicEntryString() { - if (ClassUtils.isPresent(WEB_SERVER_WEBLOGIC_CLASS, null)) { - return WEB_SERVER_WEBLOGIC_ID + VERSION_SEPARATOR + getWebLogicVersion() + ENTRY_SEPARATOR; - } - return null; - } - - private static String getWildFlyEntryString() { - try { - if (ClassUtils.isPresent(WEB_SERVER_WILDFLY_CLASS, null)) { - Package wildFlyPkg = ClassUtils.forName(WEB_SERVER_WILDFLY_CLASS, null).getPackage(); - if (wildFlyPkg != null - && StringUtils.hasText(wildFlyPkg.getImplementationTitle()) && wildFlyPkg.getImplementationTitle().contains("WildFly")) { - return WEB_SERVER_WILDFLY_ID + VERSION_SEPARATOR + wildFlyPkg.getImplementationVersion() + ENTRY_SEPARATOR; - } - - } - - } catch (RuntimeException e) { - throw e; - } catch (Exception e){ //NOPMD - //there was a problem obtaining the WildFly version - } - return null; - } - - /** - * WARNING: This method must never be invoked unless we already know that the class identified by the parameter {@code fqcn} - * really exists in the classpath. For example, we first need to assure that {@code Classes.isAvailable(fqcn))} is TRUE - */ - private static String getVersionInfoFromPomProperties(String fqcn) { - String version = "unknown"; - try{ - Class clazz = ClassUtils.forName(fqcn, null); - String className = clazz.getSimpleName() + ".class"; - String classPath = clazz.getResource(className).toString(); - - String jarPath = null; - if (classPath.startsWith("jar:file:")) { - //Let's remove "jar:file:" from the beginning and also the className - jarPath = classPath.subSequence(9, classPath.lastIndexOf("!")).toString(); - } else if (classPath.startsWith("vfs:")) { - //Let's remove "vfs:" from the beginning and also the className - jarPath = classPath.subSequence(4, classPath.lastIndexOf(".jar") + 4).toString(); - } - - if (jarPath == null) { - //we were not able to obtain the jar path. Let's abort - return version; - } - - Enumeration enumeration; - String pomPropertiesPath; - try (JarFile jarFile = new JarFile(jarPath)) { - enumeration = jarFile.entries(); - } - pomPropertiesPath = null; - while (enumeration.hasMoreElements()) { - JarEntry entry = enumeration.nextElement(); - if (entry.getName().endsWith("pom.properties")) { - pomPropertiesPath = entry.getName(); - break; - } - } - if (pomPropertiesPath != null) { - try (BufferedReader reader = new BufferedReader(new InputStreamReader(clazz.getResourceAsStream("/" + pomPropertiesPath), StandardCharsets.UTF_8))) { - String line; - while ((line = reader.readLine()) != null) { - if (line.startsWith("version=")) { - version = line.split("=")[1]; - break; - } - } - } - } - } catch (RuntimeException e) { - throw e; - } catch (Exception e) { //NOPMD - //Either the jar file or the internal "pom.properties" file could not be read, there is nothing we can do... - } - return version; - } - - /** - * WARNING: This method must never be invoked unless we already know that the class identified by the parameter {@code fqcn} - * really exists in the classpath. For example, we first need to assure that {@code Classes.isAvailable(fqcn))} is TRUE - */ - private static String getVersionInfoInManifest(String fqcn){ - String version = null; - try { - //get class package - Package thePackage = ClassUtils.forName(fqcn, null).getPackage(); - //examine the package object - version = thePackage.getSpecificationVersion(); - if (!StringUtils.hasText(version)) { - version = thePackage.getImplementationVersion(); - } - } catch (ClassNotFoundException e) { - LOG.debug("Failed resolve version for class '{}'", fqcn, e); - } - - if (!StringUtils.hasText(version)) { - version = "null"; - } - return version; - } - - /** - * This method should only be invoked after already knowing that the class identified by {@code WEB_SERVER_WEBSPHERE_CLASS} - * really exists in the classpath. For example, it can be checked that {@code Classes.isAvailable(WEB_SERVER_WEBSPHERE_CLASS))} - * is {@code TRUE} - */ - private static String getWebSphereVersion() { - try { - Class versionClass = Class.forName(WEB_SERVER_WEBSPHERE_CLASS); - Object versionInfo = versionClass.newInstance(); - Method method = versionClass.getDeclaredMethod("runReport", String.class, PrintWriter.class); - StringWriter stringWriter = new StringWriter(); - PrintWriter printWriter = new PrintWriter(stringWriter); - method.invoke(versionInfo, "", printWriter); - String version = stringWriter.toString(); - // version looks like this, so we need to "substring" it: - // - // - //IBM WebSphere Product Installation Status Report - //-------------------------------------------------------------------------------- - // - //Report at date and time August 13, 2014 1:12:06 PM ART - // - //Installation - //-------------------------------------------------------------------------------- - //Product Directory C:\Program Files\IBM\WebSphere\AppServer - //Version Directory C:\Program Files\IBM\WebSphere\AppServer\properties\version - //DTD Directory C:\Program Files\IBM\WebSphere\AppServer\properties\version\dtd - //Log Directory C:\Documents and Settings\All Users\Application Data\IBM\Installation Manager\logs - // - //Product List - //-------------------------------------------------------------------------------- - //BASETRIAL installed - // - //Installed Product - //-------------------------------------------------------------------------------- - //Name IBM WebSphere Application Server - //Version 8.5.5.2 - //ID BASETRIAL - //Build Level cf021414.01 - //Build Date 4/8/14 - //Package com.ibm.websphere.BASETRIAL.v85_8.5.5002.20140408_1947 - //Architecture x86 (32 bit) - //Installed Features IBM 32-bit WebSphere SDK for Java - //WebSphere Application Server Full Profile - - version = version.substring(version.indexOf("Installed Product")); - version = version.substring(version.indexOf("Version")); - version = version.substring(version.indexOf(" "), version.indexOf("\n")).trim(); - return version; - - } catch (RuntimeException e) { - throw e; - } catch (Exception e) { //NOPMD - //there was a problem obtaining the WebSphere version - } - //returning null so we can identify in the User-Agent String that we are not properly handling some WebSphere version - return "null"; - } - - /** - * This method should only be invoked after already knowing that the class identified by {@code WEB_SERVER_WEBLOGIC_CLASS} - * really exists in the classpath. For example, it can be checked that {@code Classes.isAvailable(WEB_SERVER_WEBLOGIC_CLASS))} - * is {@code TRUE} - */ - private static String getWebLogicVersion() { - try { - Class versionClass = Class.forName(WEB_SERVER_WEBLOGIC_CLASS); - Object version = versionClass.newInstance(); - Method method = versionClass.getDeclaredMethod("getReleaseBuildVersion"); - return (String) method.invoke(version); - } catch (RuntimeException e) { - throw e; - } catch (Exception e) { //NOPMD - //there was a problem obtaining the WebLogic version - } - //returning null so we can identify in the User-Agent String that we are not properly handling some WebLogic version - return "null"; - } -} diff --git a/oauth2/src/main/java/com/okta/spring/oauth/http/Version.java b/oauth2/src/main/java/com/okta/spring/oauth/http/Version.java deleted file mode 100644 index d2eb78349..000000000 --- a/oauth2/src/main/java/com/okta/spring/oauth/http/Version.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright 2014 Stormpath, Inc. - * Modifications Copyright 2018 Okta, Inc. - * - * 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 - * - * http://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 com.okta.spring.oauth.http; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.nio.charset.StandardCharsets; - -/** - * @since 0.4.0 - */ -class Version { - - private static final String VERSION_FILE = "/com/okta/spring/version.properties"; - private static final String CLIENT_VERSION = lookupClientVersion(VERSION_FILE); - - private Version() {} - - public static String getClientVersion() { - return CLIENT_VERSION; - } - - public static String getClientVersion(String versionFile) { - return lookupClientVersion(versionFile); - } - - private static String lookupClientVersion(String versionFile) { - Class clazz = Version.class; - InputStream inputStream = null; - BufferedReader reader = null; - try { - inputStream = clazz.getResourceAsStream(versionFile); - reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)); - String line; - do { - line = reader.readLine(); - } while (line != null && (line.startsWith("#") || line.isEmpty())); - return line; - } catch (IOException e) { - throw new RuntimeException("Unable to obtain version from [" + versionFile + "].", e); - } finally { - if (reader != null) { - try { - reader.close(); - } catch (IOException e) { - throw new RuntimeException("Exception while trying to close file [" + versionFile + "].", e); - } - } - if (inputStream != null) { - try { - inputStream.close(); - } catch (IOException e) { - throw new RuntimeException("Exception while trying to close file [" + versionFile + "].", e); - } - } - } - } -} \ No newline at end of file diff --git a/oauth2/src/main/java/com/okta/spring/oauth/implicit/ImplicitAudienceValidatingTokenServices.java b/oauth2/src/main/java/com/okta/spring/oauth/implicit/ImplicitAudienceValidatingTokenServices.java deleted file mode 100644 index 85b81bd76..000000000 --- a/oauth2/src/main/java/com/okta/spring/oauth/implicit/ImplicitAudienceValidatingTokenServices.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright 2017 Okta, Inc. - * - * 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 - * - * http://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 com.okta.spring.oauth.implicit; - -import com.okta.spring.oauth.OAuth2AccessTokenValidationException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.security.jwt.crypto.sign.InvalidSignatureException; -import org.springframework.security.oauth2.provider.OAuth2Authentication; -import org.springframework.security.oauth2.provider.token.DefaultTokenServices; - -public class ImplicitAudienceValidatingTokenServices extends DefaultTokenServices { - - private final Logger logger = LoggerFactory.getLogger(ImplicitAudienceValidatingTokenServices.class); - - private final String audience; - - public ImplicitAudienceValidatingTokenServices(String audience) { - this.audience = audience; - } - - @Override - public OAuth2Authentication loadAuthentication(String accessTokenValue) { - try { - OAuth2Authentication originalOAuth = super.loadAuthentication(accessTokenValue); - - // validate audience - if (!originalOAuth.getOAuth2Request().getResourceIds().contains(audience)) { - throw new OAuth2AccessTokenValidationException("Invalid token, 'aud' claim does not contain the expected audience of: " + audience); - } - - return originalOAuth; - - } catch(InvalidSignatureException e) { - logger.debug("Invalid Token Signature: {}", e.getMessage(), e); - return null; - - } - } -} \ No newline at end of file diff --git a/oauth2/src/main/java/com/okta/spring/oauth/implicit/ResourceServerConfig.java b/oauth2/src/main/java/com/okta/spring/oauth/implicit/ResourceServerConfig.java deleted file mode 100644 index 289a965ce..000000000 --- a/oauth2/src/main/java/com/okta/spring/oauth/implicit/ResourceServerConfig.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright 2017 Okta, Inc. - * - * 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 - * - * http://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 com.okta.spring.oauth.implicit; - -import com.okta.spring.config.OktaOAuth2Properties; -import com.okta.spring.oauth.OktaTokenServicesConfig; -import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; -import org.springframework.context.annotation.Primary; -import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfiguration; -import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter; -import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer; -import org.springframework.security.oauth2.provider.token.DefaultTokenServices; -import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices; -import org.springframework.security.oauth2.provider.token.TokenStore; - -/** - * Configuration for OAuth2 Implicit flow. - * @since 0.1.0 - */ -@ConditionalOnBean(ResourceServerConfiguration.class) -@Configuration -@Import(OktaTokenServicesConfig.class) -public class ResourceServerConfig { - - private final OktaOAuth2Properties oktaOAuth2Properties; - - public ResourceServerConfig(OktaOAuth2Properties oktaOAuth2Properties) { - this.oktaOAuth2Properties = oktaOAuth2Properties; - } - - @Bean - @Primary - @ConditionalOnBean(ResourceServerTokenServices.class) - public ResourceServerConfigurerAdapter oktaResourceServerConfigurerAdapter(ResourceServerTokenServices tokenServices) { - return new ResourceServerConfigurerAdapter() { - @Override - public void configure(final ResourceServerSecurityConfigurer config) { - config.resourceId(oktaOAuth2Properties.getAudience()); // set resourceId to the audience - config.tokenServices(tokenServices); - } - }; - } - - @Configuration - @ConditionalOnProperty(name = "okta.oauth2.localTokenValidation", matchIfMissing = true) - public static class LocalTokenValidationConfig { - @Bean - @Primary - protected ResourceServerTokenServices resourceServerTokenServices(TokenStore tokenStore, OktaOAuth2Properties properties) { - DefaultTokenServices services = new ImplicitAudienceValidatingTokenServices(properties.getAudience()); - services.setTokenStore(tokenStore); - return services; - } - } -} \ No newline at end of file diff --git a/oauth2/src/main/resources/META-INF/spring.factories b/oauth2/src/main/resources/META-INF/spring.factories index c9a62acc7..c00bbdc15 100644 --- a/oauth2/src/main/resources/META-INF/spring.factories +++ b/oauth2/src/main/resources/META-INF/spring.factories @@ -1,5 +1,7 @@ org.springframework.boot.autoconfigure.EnableAutoConfiguration = \ - com.okta.spring.oauth.implicit.ResourceServerConfig,\ - com.okta.spring.oauth.code.OktaOAuthCodeFlowDefaultConfiguration,\ - com.okta.spring.oauth.code.OktaOAuthCodeFlowCustomConfiguration -org.springframework.boot.env.EnvironmentPostProcessor=com.okta.spring.oauth.env.OktaPropertiesMappingEnvironmentPostProcessor \ No newline at end of file + com.okta.spring.boot.oauth.OktaOAuth2AutoConfig,\ + com.okta.spring.boot.oauth.OktaOAuth2ResourceServerAutoConfig +org.springframework.boot.env.EnvironmentPostProcessor = com.okta.spring.boot.oauth.env.OktaOAuth2PropertiesMappingEnvironmentPostProcessor + +org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer = \ + com.okta.spring.boot.oauth.OktaOAuth2Configurer \ No newline at end of file diff --git a/oauth2/src/main/resources/com/okta/spring/okta.yml b/oauth2/src/main/resources/com/okta/spring/okta.yml deleted file mode 100644 index c46197312..000000000 --- a/oauth2/src/main/resources/com/okta/spring/okta.yml +++ /dev/null @@ -1,24 +0,0 @@ -# -# Copyright 2017 Okta, Inc. -# -# 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 -# -# http://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. -# - - -security: - oauth2: - client: - clientAuthenticationScheme: form - scope: profile email openid - resource: - preferTokenInfo: false \ No newline at end of file diff --git a/oauth2/src/test/groovy/com/okta/spring/boot/oauth/OktaJwtAuthenticationConverterTest.groovy b/oauth2/src/test/groovy/com/okta/spring/boot/oauth/OktaJwtAuthenticationConverterTest.groovy new file mode 100644 index 000000000..1b671aefc --- /dev/null +++ b/oauth2/src/test/groovy/com/okta/spring/boot/oauth/OktaJwtAuthenticationConverterTest.groovy @@ -0,0 +1,40 @@ +package com.okta.spring.boot.oauth + +import org.springframework.security.core.authority.SimpleGrantedAuthority +import org.springframework.security.oauth2.jwt.Jwt +import org.testng.annotations.Test + +import java.time.Instant + +import static org.hamcrest.Matchers.hasItems +import static org.hamcrest.MatcherAssert.assertThat +import static org.hamcrest.Matchers.hasSize + +class OktaJwtAuthenticationConverterTest { + + @Test + void extractAuthorities_simpleTest() { + + // these maps must not be empty + def jwt = new Jwt("foo", Instant.now(), Instant.now().plusMillis(1000L), [simple: "value"], [ + scp: ["one", "two", "three"], + myGroups: ["g1", "g2"] + ]) + + def authorities = new OktaJwtAuthenticationConverter("myGroups").extractAuthorities(jwt) + assertThat authorities, hasItems( + new SimpleGrantedAuthority("SCOPE_one"), + new SimpleGrantedAuthority("SCOPE_two"), + new SimpleGrantedAuthority("SCOPE_three"), + new SimpleGrantedAuthority("g1"), + new SimpleGrantedAuthority("g2")) + } + + @Test + void extractAuthorities_emptyTest() { + def jwt = new Jwt("foo", Instant.now(), Instant.now().plusMillis(1000L), [simple: "value"], [simple: "value"]) // these maps must not be empty + + def authorities = new OktaJwtAuthenticationConverter("myGroups").extractAuthorities(jwt) + assertThat authorities, hasSize(0) + } +} \ No newline at end of file diff --git a/oauth2/src/test/groovy/com/okta/spring/boot/oauth/TokenUtilTest.groovy b/oauth2/src/test/groovy/com/okta/spring/boot/oauth/TokenUtilTest.groovy new file mode 100644 index 000000000..29bf446c7 --- /dev/null +++ b/oauth2/src/test/groovy/com/okta/spring/boot/oauth/TokenUtilTest.groovy @@ -0,0 +1,94 @@ +package com.okta.spring.boot.oauth + +import org.springframework.security.core.GrantedAuthority +import org.springframework.security.core.authority.SimpleGrantedAuthority +import org.springframework.security.oauth2.core.OAuth2AccessToken +import org.testng.annotations.Test + +import static org.mockito.Mockito.* +import static org.hamcrest.Matchers.* +import static org.hamcrest.MatcherAssert.assertThat + +class TokenUtilTest { + + @Test + void tokenScopesToAuthorities_emptyScopesTest() { + + def token = mock(OAuth2AccessToken) + when(token.getScopes()).thenReturn(Collections.emptySet()) + + assertThat TokenUtil.tokenScopesToAuthorities(token), emptyCollectionOf(GrantedAuthority) + } + + @Test + void tokenScopesToAuthorities_nullScopesTest() { + + def token = mock(OAuth2AccessToken) + when(token.getScopes()).thenReturn(null) + + assertThat TokenUtil.tokenScopesToAuthorities(token), emptyCollectionOf(GrantedAuthority) + } + + @Test + void tokenScopesToAuthorities_nullTokenTest() { + assertThat TokenUtil.tokenScopesToAuthorities(null), emptyCollectionOf(GrantedAuthority) + } + + @Test + void tokenScopesToAuthorities_withScopesTest() { + def token = mock(OAuth2AccessToken) + when(token.getScopes()).thenReturn(new HashSet<>(["A", "b", "see"])) + + assertThat TokenUtil.tokenScopesToAuthorities(token), both( + hasItems( + new SimpleGrantedAuthority("SCOPE_A"), + new SimpleGrantedAuthority("SCOPE_b"), + new SimpleGrantedAuthority("SCOPE_see"))).and( + hasSize(3)) + } + + @Test + void tokenClaimsToAuthorities_emptyMapTest() { + assertThat TokenUtil.tokenClaimsToAuthorities(Collections.emptyMap(), "a-key"), emptyCollectionOf(GrantedAuthority) + } + + @Test + void tokenClaimsToAuthorities_nullMapTest() { + assertThat TokenUtil.tokenClaimsToAuthorities(null, "a-key"), emptyCollectionOf(GrantedAuthority) + } + + @Test + void tokenClaimsToAuthorities_nullKeyTest() { + def attributes = [one: "two"] + assertThat TokenUtil.tokenClaimsToAuthorities(attributes, null), emptyCollectionOf(GrantedAuthority) + } + + @Test + void tokenClaimsToAuthorities_keyMissTest() { + def attributes = [one: "two"] + assertThat TokenUtil.tokenClaimsToAuthorities(attributes, "something-else"), emptyCollectionOf(GrantedAuthority) + } + + @Test + void tokenClaimsToAuthorities_notStringArrayValueTest() { + def attributes = [groups: [complex: "object", goes: "here"]] + assertThat TokenUtil.tokenClaimsToAuthorities(attributes, "groups"), emptyCollectionOf(GrantedAuthority) + } + + @Test + void tokenClaimsToAuthorities_stringValueTest() { + def attributes = [simple: "foo"] + assertThat TokenUtil.tokenClaimsToAuthorities(attributes, "simple"), emptyCollectionOf(GrantedAuthority) + } + + @Test + void tokenClaimsToAuthorities_multipleValuesTest() { + def attributes = [rolesHere: ["a", "B", "sEa"]] + assertThat TokenUtil.tokenClaimsToAuthorities(attributes, "rolesHere"), both( + hasItems( + new SimpleGrantedAuthority("a"), + new SimpleGrantedAuthority("B"), + new SimpleGrantedAuthority("sEa"))).and( + hasSize(3)) + } +} \ No newline at end of file diff --git a/oauth2/src/test/groovy/com/okta/spring/boot/oauth/config/OktaOAuth2PropertiesTest.groovy b/oauth2/src/test/groovy/com/okta/spring/boot/oauth/config/OktaOAuth2PropertiesTest.groovy new file mode 100644 index 000000000..f42d12d15 --- /dev/null +++ b/oauth2/src/test/groovy/com/okta/spring/boot/oauth/config/OktaOAuth2PropertiesTest.groovy @@ -0,0 +1,76 @@ +package com.okta.spring.boot.oauth.config + +import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientProperties +import org.springframework.validation.Errors +import org.testng.annotations.Test + +import static org.hamcrest.MatcherAssert.assertThat +import static org.hamcrest.Matchers.is +import static org.hamcrest.Matchers.nullValue +import static org.mockito.Mockito.* + +class OktaOAuth2PropertiesTest { + + @Test + void validationErrors_allInvalidTest() { + + def oauthProps = mock(OAuth2ClientProperties) + def oktaRegistration = mock(OAuth2ClientProperties.Registration) + def regMap = [okta: oktaRegistration] + def errors = mock(Errors) + + when(oauthProps.getRegistration()).thenReturn(regMap) + when(oktaRegistration.getClientId()).thenReturn("{clientId}") + when(oktaRegistration.getClientSecret()).thenReturn("{clientSecret}") + + def underTest = new OktaOAuth2Properties(oauthProps) + underTest.setIssuer("foobar") + underTest.validate(underTest, errors) + + verify(errors).rejectValue(eq("issuer"), startsWith("It looks like there's a typo in your Okta Issuer URL")) + verify(errors).rejectValue(eq("issuer"), contains("foobar")) + verify(errors).rejectValue(eq("clientId"), contains("Replace {clientId}")) + verify(errors).rejectValue(eq("clientSecret"), contains("Replace {clientSecret}")) + } + + @Test + void validationErrors_issuerNonHttpsTest() { + + def oauthProps = mock(OAuth2ClientProperties) + def oktaRegistration = mock(OAuth2ClientProperties.Registration) + def regMap = [okta: oktaRegistration] + def errors = mock(Errors) + + when(oauthProps.getRegistration()).thenReturn(regMap) + + def underTest = new OktaOAuth2Properties(oauthProps) + underTest.setIssuer("http://okta.example.com") + underTest.validate(underTest, errors) + + verify(errors).rejectValue(eq("issuer"), contains("Your Okta Issuer URL must start with https")) + verify(errors).rejectValue(eq("issuer"), contains("http://okta.example.com")) + } + + @Test + void accessNullClientIdWithoutSpringOAuthProps() { + + def oauthProps = mock(OAuth2ClientProperties) + def regMap = [okta: null] + + when(oauthProps.getRegistration()).thenReturn(regMap) + + def underTest = new OktaOAuth2Properties(oauthProps) + assertThat underTest.clientId, nullValue() + } + + @Test + void accessValidClientIdWithoutSpringOAuthProps() { + def oauthProps = mock(OAuth2ClientProperties) + def regMap = [okta: null] + when(oauthProps.getRegistration()).thenReturn(regMap) + + def underTest = new OktaOAuth2Properties(oauthProps) + underTest.setClientId("a-client-id") + assertThat underTest.clientId, is("a-client-id") + } +} \ No newline at end of file diff --git a/oauth2/src/test/groovy/com/okta/spring/boot/oauth/env/OktaOAuth2PropertiesMappingEnvironmentPostProcessorTest.groovy b/oauth2/src/test/groovy/com/okta/spring/boot/oauth/env/OktaOAuth2PropertiesMappingEnvironmentPostProcessorTest.groovy new file mode 100644 index 000000000..412981582 --- /dev/null +++ b/oauth2/src/test/groovy/com/okta/spring/boot/oauth/env/OktaOAuth2PropertiesMappingEnvironmentPostProcessorTest.groovy @@ -0,0 +1,84 @@ +/* + * Copyright 2018-Present Okta, Inc. + * + * 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 + * + * http://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 com.okta.spring.boot.oauth.env + +import org.springframework.core.env.Environment +import org.springframework.core.env.MapPropertySource +import org.springframework.mock.env.MockEnvironment +import org.testng.annotations.Test + +import static org.hamcrest.MatcherAssert.assertThat +import static org.hamcrest.Matchers.is +import static org.hamcrest.Matchers.nullValue + +class OktaOAuth2PropertiesMappingEnvironmentPostProcessorTest { + + final static String CLIENT_ID = "spring.security.oauth2.client.registration.okta.client-id" + final static String CLIENT_SECRET = "spring.security.oauth2.client.registration.okta.client-secret" + final static String SCOPE = "spring.security.oauth2.client.registration.okta.scope" + final static String ISSUER = "spring.security.oauth2.resourceserver.jwt.issuer-uri" + final static String RS_KEYS_URI = "spring.security.oauth2.resourceserver.jwt.jwk-set-uri" + final static String AUTHZ_URI = "spring.security.oauth2.client.provider.okta.authorization-uri" + final static String TOKEN_URI = "spring.security.oauth2.client.provider.okta.token-uri" + final static String USER_INFO_URI = "spring.security.oauth2.client.provider.okta.user-info-uri" + final static String PROVIDER_KEYS_URI = "spring.security.oauth2.client.provider.okta.jwk-set-uri" + + @Test + void happyPath() { + def environment = buildAndProcessEnvironment([ + "okta.oauth2.client-id": "test-client-id", + "okta.oauth2.client-secret": "test-client-secret", + "okta.oauth2.issuer": "https://issuer.example.com/foobar", + "okta.oauth2.scopes": ["one", "two", "three"], + ]) + + assertThat environment.getProperty(CLIENT_ID), is("test-client-id") + assertThat environment.getProperty(CLIENT_SECRET), is("test-client-secret") + assertThat environment.getProperty(SCOPE, Set), is(["one", "two", "three"] as Set) + assertThat environment.getProperty(ISSUER), is("https://issuer.example.com/foobar") + assertThat environment.getProperty(RS_KEYS_URI),is("https://issuer.example.com/foobar/v1/keys") + assertThat environment.getProperty(AUTHZ_URI), is("https://issuer.example.com/foobar/v1/authorize") + assertThat environment.getProperty(TOKEN_URI), is("https://issuer.example.com/foobar/v1/token") + assertThat environment.getProperty(USER_INFO_URI), is("https://issuer.example.com/foobar/v1/userinfo") + assertThat environment.getProperty(PROVIDER_KEYS_URI), is("https://issuer.example.com/foobar/v1/keys") + } + + @Test + void noPropertiesTest() { + def environment = buildAndProcessEnvironment(Collections.emptyMap()) + + assertThat environment.getProperty(CLIENT_ID), nullValue() + assertThat environment.getProperty(CLIENT_SECRET), nullValue() + assertThat environment.getProperty(SCOPE, Set), nullValue() + assertThat environment.getProperty(ISSUER), nullValue() + assertThat environment.getProperty(RS_KEYS_URI), nullValue() + assertThat environment.getProperty(AUTHZ_URI), nullValue() + assertThat environment.getProperty(TOKEN_URI), nullValue() + assertThat environment.getProperty(USER_INFO_URI), nullValue() + assertThat environment.getProperty(PROVIDER_KEYS_URI), nullValue() + } + + private Environment buildAndProcessEnvironment(Map properties) { + def environment = new MockEnvironment() + environment.getPropertySources().addFirst(new MapPropertySource("test", properties)) + + def underTest = new OktaOAuth2PropertiesMappingEnvironmentPostProcessor() + underTest.postProcessEnvironment(environment, null) + + return environment + } + +} \ No newline at end of file diff --git a/oauth2/src/test/groovy/com/okta/spring/oauth/ClaimsAuthoritiesExtractorTest.groovy b/oauth2/src/test/groovy/com/okta/spring/oauth/ClaimsAuthoritiesExtractorTest.groovy deleted file mode 100644 index 169785fd7..000000000 --- a/oauth2/src/test/groovy/com/okta/spring/oauth/ClaimsAuthoritiesExtractorTest.groovy +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright 2017 Okta, Inc. - * - * 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 - * - * http://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 com.okta.spring.oauth - -import com.okta.spring.oauth.ClaimsAuthoritiesExtractor -import org.springframework.security.core.GrantedAuthority -import org.springframework.security.core.authority.SimpleGrantedAuthority -import org.testng.annotations.Test - -import static org.hamcrest.MatcherAssert.assertThat -import static org.hamcrest.Matchers.allOf -import static org.hamcrest.Matchers.emptyCollectionOf -import static org.hamcrest.Matchers.containsInAnyOrder -import static org.hamcrest.Matchers.hasSize - -/** - * @since 0.2.0 - */ -class ClaimsAuthoritiesExtractorTest { - - private Map dataMap = [ - number: 42, - roles: [ - "role1", - "role2" - ], - groups: [ - "group1", - "group2" - ], - complex: [ - one: 1, - two: "bar" - ], - numbers: [ - 1, - 2, - 3 - ], - mixed: [ - 1, - null, - "a group", - "🤘" - ] - ] - - @Test - void basicStringGroups() { - def extractor = new ClaimsAuthoritiesExtractor("groups") - assertThat extractor.extractAuthorities(dataMap), allOf( - containsInAnyOrder( - new SimpleGrantedAuthority("group1"), - new SimpleGrantedAuthority("group2")), - hasSize(2)) - } - - @Test - void missingKey() { - def extractor = new ClaimsAuthoritiesExtractor("missingKey") - assertThat extractor.extractAuthorities(dataMap), emptyCollectionOf(GrantedAuthority) - } - - @Test - void complexType() { - def extractor = new ClaimsAuthoritiesExtractor("complex") - assertThat extractor.extractAuthorities(dataMap), emptyCollectionOf(GrantedAuthority) - } - - @Test - void numberType() { - def extractor = new ClaimsAuthoritiesExtractor("numbers") - assertThat extractor.extractAuthorities(dataMap), emptyCollectionOf(GrantedAuthority) - } - - @Test - void mixedTypes() { - def extractor = new ClaimsAuthoritiesExtractor("mixed") - assertThat extractor.extractAuthorities(dataMap), allOf( - containsInAnyOrder( - new SimpleGrantedAuthority("a group"), - new SimpleGrantedAuthority("🤘")), - hasSize(2)) - } - -} diff --git a/oauth2/src/test/groovy/com/okta/spring/oauth/ClaimsPrincipalExtractorTest.groovy b/oauth2/src/test/groovy/com/okta/spring/oauth/ClaimsPrincipalExtractorTest.groovy deleted file mode 100644 index c0d048995..000000000 --- a/oauth2/src/test/groovy/com/okta/spring/oauth/ClaimsPrincipalExtractorTest.groovy +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright 2017 Okta, Inc. - * - * 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 - * - * http://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 com.okta.spring.oauth - -import com.okta.spring.oauth.ClaimsPrincipalExtractor -import org.testng.annotations.Test - -import static org.hamcrest.MatcherAssert.assertThat -import static org.hamcrest.Matchers.equalTo -import static org.hamcrest.Matchers.nullValue - -/** - * @since 0.2.0 - */ -class ClaimsPrincipalExtractorTest { - - private Map dataMap = [ - expectedKey: "joe.coder@example.com", - expectedKey1: "jill.code@example.com", - number: 42, - subMap: [ - key1: "value1", - key2: "value2" - ] - ] - - @Test - void stringPrincipal() { - def extractor = new ClaimsPrincipalExtractor("expectedKey") - assertThat extractor.extractPrincipal(dataMap), equalTo("joe.coder@example.com") - } - - @Test - void numberPrincipal() { - def extractor = new ClaimsPrincipalExtractor("number") - assertThat extractor.extractPrincipal(dataMap), equalTo(42) - } - - @Test - void mapPrincipal() { - def extractor = new ClaimsPrincipalExtractor("subMap") - assertThat extractor.extractPrincipal(dataMap), equalTo([ - key1: "value1", - key2: "value2" - ]) - } - - @Test - void missingValue() { - def extractor = new ClaimsPrincipalExtractor("missingKey") - assertThat extractor.extractPrincipal(dataMap), nullValue() - - } -} diff --git a/oauth2/src/test/groovy/com/okta/spring/oauth/ConfigurableAccessTokenConverterTest.groovy b/oauth2/src/test/groovy/com/okta/spring/oauth/ConfigurableAccessTokenConverterTest.groovy deleted file mode 100644 index d43f532d1..000000000 --- a/oauth2/src/test/groovy/com/okta/spring/oauth/ConfigurableAccessTokenConverterTest.groovy +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright 2017 Okta, Inc. - * - * 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 - * - * http://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 com.okta.spring.oauth - -import com.okta.spring.oauth.ConfigurableAccessTokenConverter -import org.springframework.security.core.authority.SimpleGrantedAuthority -import org.springframework.security.oauth2.provider.token.UserAuthenticationConverter -import org.testng.annotations.Test - - -import static org.hamcrest.MatcherAssert.assertThat -import static org.hamcrest.Matchers.allOf -import static org.hamcrest.Matchers.equalTo -import static org.hamcrest.Matchers.containsInAnyOrder -import static org.hamcrest.Matchers.hasSize -import static org.hamcrest.Matchers.nullValue - -/** - * @since 0.1.0 - */ -class ConfigurableAccessTokenConverterTest { - - @Test - void mapConversionTestWithScopeArray() { - def scopeClaim = "custom_scope" - def roleClaim = "custom_role" - def accessTokenString = "an_access_token" - def converter = new ConfigurableAccessTokenConverter(scopeClaim, roleClaim) - def initialClaimMap = [ - custom_scope: ["my_custom_scope", "as_an_array"], - custom_role: ["one_role", "two_role", "red_role", "blue_role"] - ] - def accessToken = converter.extractAccessToken(accessTokenString, initialClaimMap) - assertThat accessToken.value, equalTo(accessTokenString) - assertThat accessToken.scope, allOf(containsInAnyOrder("my_custom_scope", "as_an_array"), hasSize(2)) - assertThat accessToken.additionalInformation.get(UserAuthenticationConverter.AUTHORITIES), allOf(containsInAnyOrder("one_role", "two_role", "red_role", "blue_role"), hasSize(4)) - } - - @Test - void mapConversionTestWithScopeString() { - def scopeClaim = "custom_scope" - def roleClaim = "custom_role" - def accessTokenString = "an_access_token" - def converter = new ConfigurableAccessTokenConverter(scopeClaim, roleClaim) - def initialClaimMap = [ - custom_scope: "my_custom_scope as_an_array", - custom_role: ["one_role", "two_role", "red_role", "blue_role"] - ] - def accessToken = converter.extractAccessToken(accessTokenString, initialClaimMap) - assertThat accessToken.value, equalTo(accessTokenString) - assertThat accessToken.scope, allOf(containsInAnyOrder("my_custom_scope", "as_an_array"), hasSize(2)) - assertThat accessToken.additionalInformation.get(UserAuthenticationConverter.AUTHORITIES), - allOf( - containsInAnyOrder("one_role", "two_role", "red_role", "blue_role"), - hasSize(4)) - } - - @Test - void mapConversionTestWithEmptyScopeEmptyRole() { - def scopeClaim = "custom_scope" - def roleClaim = "custom_role" - def accessTokenString = "an_access_token" - def converter = new ConfigurableAccessTokenConverter(scopeClaim, roleClaim) - def initialClaimMap = [ - custom_scope: "", - custom_role: "" - ] - def accessToken = converter.extractAccessToken(accessTokenString, initialClaimMap) - assertThat accessToken.value, equalTo(accessTokenString) - assertThat accessToken.scope, hasSize(0) - assertThat accessToken.additionalInformation.get(UserAuthenticationConverter.AUTHORITIES), nullValue() - } - - @Test - void extractAuthenticationTestWithNullScopeNullRole() { - def scopeClaim = "custom_scope" - def roleClaim = "custom_role" - def converter = new ConfigurableAccessTokenConverter(scopeClaim, roleClaim) - def initialClaimMap = [ - custom_scope: null, - custom_role: null - ] - def auth = converter.extractAuthentication(initialClaimMap) - assertThat auth.authorities, hasSize(0) - assertThat auth.getOAuth2Request().scope, hasSize(0) - } - - @Test - void extractAuthenticationTestWithScopeArray() { - def scopeClaim = "custom_scope" - def roleClaim = "custom_role" - def converter = new ConfigurableAccessTokenConverter(scopeClaim, roleClaim) - def initialClaimMap = [ - custom_scope: ["my_custom_scope", "as_an_array"], - custom_role: ["one_role", "two_role", "red_role", "blue_role"] - ] - def auth = converter.extractAuthentication(initialClaimMap) - assertThat auth.getOAuth2Request().scope, allOf(containsInAnyOrder("my_custom_scope", "as_an_array"), hasSize(2)) - assertThat auth.authorities, allOf( - containsInAnyOrder( - new SimpleGrantedAuthority("one_role"), - new SimpleGrantedAuthority("two_role"), - new SimpleGrantedAuthority("red_role"), - new SimpleGrantedAuthority("blue_role")), - hasSize(4)) - assertThat auth.getUserAuthentication(), nullValue() - } - - @Test - void extractSubject() { - def scopeClaim = "custom_scope" - def roleClaim = "custom_role" - def email = "joe.coder@example.com" - def converter = new ConfigurableAccessTokenConverter(scopeClaim, roleClaim) - def initialClaimMap = [ - sub: email, - custom_scope: ["my_custom_scope", "as_an_array"], - custom_role: ["one_role", "two_role", "red_role", "blue_role"] - ] - def auth = converter.extractAuthentication(initialClaimMap) - assertThat auth.getUserAuthentication().name, equalTo(email) - assertThat auth.getUserAuthentication().details, equalTo(initialClaimMap) - } -} diff --git a/oauth2/src/test/groovy/com/okta/spring/oauth/OktaPropertiesMappingEnvironmentPostProcessorTest.groovy b/oauth2/src/test/groovy/com/okta/spring/oauth/OktaPropertiesMappingEnvironmentPostProcessorTest.groovy deleted file mode 100644 index 4593a7e73..000000000 --- a/oauth2/src/test/groovy/com/okta/spring/oauth/OktaPropertiesMappingEnvironmentPostProcessorTest.groovy +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright 2018 Okta, Inc. - * - * 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 - * - * http://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 com.okta.spring.oauth - -import com.okta.spring.oauth.env.OktaPropertiesMappingEnvironmentPostProcessor -import org.springframework.boot.SpringApplication -import org.springframework.core.env.ConfigurableEnvironment -import org.springframework.core.env.MutablePropertySources -import org.testng.annotations.Test - -import static org.hamcrest.MatcherAssert.assertThat -import static org.hamcrest.Matchers.* -import static org.mockito.Mockito.* - -class OktaPropertiesMappingEnvironmentPostProcessorTest { - - @Test - void issuerToOrgTest() { - - def app = mock(SpringApplication) - def env = mock(ConfigurableEnvironment) - def propertySources = new MutablePropertySources() - when(env.getPropertySources()).thenReturn(propertySources) - when(env.getProperty("okta.oauth2.issuer")).thenReturn("http://example.com/foo/oauth2/bar") // called for validation - .thenReturn("http://example.com/foo/oauth2/bar") - .thenReturn("http://example.com/bar/oauth2/foo") - def underTest = new OktaPropertiesMappingEnvironmentPostProcessor() - underTest.postProcessEnvironment(env, app) - def propSource = propertySources.get("okta-oauth-to-client") - - assertThat propSource.getProperty("okta.client.org-url"), is("http://example.com/foo") - assertThat propSource.getProperty("okta.client.org-url"), is("http://example.com/bar") - } -} \ No newline at end of file diff --git a/oauth2/src/test/groovy/com/okta/spring/oauth/code/MockCodeFlowApp.groovy b/oauth2/src/test/groovy/com/okta/spring/oauth/code/MockCodeFlowApp.groovy deleted file mode 100644 index d9c4c98ac..000000000 --- a/oauth2/src/test/groovy/com/okta/spring/oauth/code/MockCodeFlowApp.groovy +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2017 Okta, Inc. - * - * 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 - * - * http://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 com.okta.spring.oauth.code - -import com.okta.spring.oauth.discovery.OidcDiscoveryMetadata -import org.springframework.boot.autoconfigure.EnableAutoConfiguration -import org.springframework.boot.autoconfigure.security.oauth2.client.EnableOAuth2Sso -import org.springframework.context.annotation.Bean -import org.springframework.context.annotation.Configuration - -@Configuration -@EnableOAuth2Sso -@EnableAutoConfiguration -class MockCodeFlowApp { - - @Bean - OidcDiscoveryMetadata oktaOidcDiscoveryMetadata() { - return new OidcDiscoveryMetadata() - .setUserinfoEndpoint("https://okta.example.com/userinfoEndpoint") - .setIntrospectionEndpoint("https://okta.example.com/introspectionEndpoint") - .setIssuer("https://okta.example.com/oauth2/my_issuer") - } -} diff --git a/oauth2/src/test/groovy/com/okta/spring/oauth/code/MockCustomSsoCodeFlowApp.groovy b/oauth2/src/test/groovy/com/okta/spring/oauth/code/MockCustomSsoCodeFlowApp.groovy deleted file mode 100644 index 356b69cf2..000000000 --- a/oauth2/src/test/groovy/com/okta/spring/oauth/code/MockCustomSsoCodeFlowApp.groovy +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2017-present Okta, Inc. - * - * 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 - * - * http://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 com.okta.spring.oauth.code - -import com.okta.spring.oauth.discovery.OidcDiscoveryMetadata -import org.springframework.boot.autoconfigure.EnableAutoConfiguration -import org.springframework.boot.autoconfigure.security.oauth2.client.EnableOAuth2Sso -import org.springframework.context.annotation.Bean -import org.springframework.context.annotation.Configuration -import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter - -@Configuration -@EnableOAuth2Sso -@EnableAutoConfiguration -class MockCustomSsoCodeFlowApp extends WebSecurityConfigurerAdapter { - - @Bean - OidcDiscoveryMetadata oktaOidcDiscoveryMetadata() { - return new OidcDiscoveryMetadata() - .setUserinfoEndpoint("https://okta.example.com/userinfoEndpoint") - .setIntrospectionEndpoint("https://okta.example.com/introspectionEndpoint") - .setIssuer("https://okta.example.com/oauth2/my_issuer") - } -} diff --git a/oauth2/src/test/groovy/com/okta/spring/oauth/code/OktaOAuthCodeFlowCustomConfigurationTest.groovy b/oauth2/src/test/groovy/com/okta/spring/oauth/code/OktaOAuthCodeFlowCustomConfigurationTest.groovy deleted file mode 100644 index 18ab02851..000000000 --- a/oauth2/src/test/groovy/com/okta/spring/oauth/code/OktaOAuthCodeFlowCustomConfigurationTest.groovy +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright 2018-present Okta, Inc. - * - * 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 - * - * http://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 com.okta.spring.oauth.code - -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2SsoCustomConfiguration -import org.springframework.boot.autoconfigure.security.oauth2.resource.AuthoritiesExtractor -import org.springframework.boot.test.context.SpringBootTest -import org.springframework.security.oauth2.provider.token.TokenStore -import org.springframework.security.oauth2.provider.token.store.jwk.JwkTokenStore -import org.springframework.test.context.testng.AbstractTestNGSpringContextTests -import org.testng.annotations.Test - -import static org.hamcrest.MatcherAssert.assertThat -import static org.hamcrest.Matchers.isA -import static org.hamcrest.Matchers.notNullValue - -/** - * Ensures a Spring Security {@link OAuth2SsoCustomConfiguration} when OktaOAuthCodeFlowCustomConfiguration is used and - * a {@link org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter WebSecurityConfigurerAdapter} is found with a @{@link org.springframework.boot.autoconfigure.security.oauth2.client.EnableOAuth2Sso EnableOAuth2Sso}. - * - * @since 0.6.0 - */ -class OktaOAuthCodeFlowCustomConfigurationTest extends AbstractTestNGSpringContextTests { - - @SpringBootTest(classes = [MockCustomSsoCodeFlowApp, OktaOAuthCodeFlowCustomConfiguration], - properties = ["okta.oauth2.issuer=https://okta.example.com/oauth2/my_issuer", - "okta.oauth2.discoveryDisabled=true", - "okta.oauth2.localTokenValidation=false"]) - static class RemoteValidationConfigTest extends AbstractTestNGSpringContextTests { - - @Autowired - OAuth2SsoCustomConfiguration oAuth2SsoCustomConfiguration - - @Autowired - AuthoritiesExtractor authoritiesExtractor - - @Test - void theBasics() { - assertThat authoritiesExtractor, notNullValue() - assertThat oAuth2SsoCustomConfiguration, notNullValue() - } - } - - @SpringBootTest(classes = [MockCustomSsoCodeFlowApp, OktaOAuthCodeFlowCustomConfiguration], - properties = ["okta.oauth2.issuer=https://okta.example.com/oauth2/my_issuer", - "okta.oauth2.discoveryDisabled=true"]) - static class LocalValidationConfigTest extends AbstractTestNGSpringContextTests { - - @Autowired - OAuth2SsoCustomConfiguration oAuth2SsoCustomConfiguration - - @Autowired - TokenStore tokenStore - - @Test - void theBasics() { - assertThat tokenStore, isA(JwkTokenStore) - assertThat oAuth2SsoCustomConfiguration, notNullValue() - } - } -} \ No newline at end of file diff --git a/oauth2/src/test/groovy/com/okta/spring/oauth/code/OktaOAuthCodeFlowDefaultConfigurationTest.groovy b/oauth2/src/test/groovy/com/okta/spring/oauth/code/OktaOAuthCodeFlowDefaultConfigurationTest.groovy deleted file mode 100644 index f760af70c..000000000 --- a/oauth2/src/test/groovy/com/okta/spring/oauth/code/OktaOAuthCodeFlowDefaultConfigurationTest.groovy +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright 2017 Okta, Inc. - * - * 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 - * - * http://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 com.okta.spring.oauth.code - -import com.okta.spring.oauth.OktaTokenServicesConfig -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.autoconfigure.security.oauth2.resource.AuthoritiesExtractor -import org.springframework.boot.test.context.SpringBootTest -import org.springframework.security.oauth2.provider.token.TokenStore -import org.springframework.security.oauth2.provider.token.store.jwk.JwkTokenStore -import org.springframework.test.context.testng.AbstractTestNGSpringContextTests -import org.testng.annotations.Test - -import static org.hamcrest.MatcherAssert.assertThat -import static org.hamcrest.Matchers.isA -import static org.hamcrest.Matchers.notNullValue - -/** - * @since 0.2.0 - */ -class OktaOAuthCodeFlowDefaultConfigurationTest extends AbstractTestNGSpringContextTests { - - @SpringBootTest(classes = [MockCodeFlowApp, OktaOAuthCodeFlowDefaultConfiguration], - properties = ["okta.oauth2.issuer=https://okta.example.com/oauth2/my_issuer", - "okta.oauth2.discoveryDisabled=true", - "okta.oauth2.localTokenValidation=false"]) - static class RemoteValidationConfigTest extends AbstractTestNGSpringContextTests { - - @Autowired - OktaTokenServicesConfig oktaTokenServicesConfig - - @Autowired - AuthoritiesExtractor authoritiesExtractor - - @Test - void theBasics() { - assertThat authoritiesExtractor, notNullValue() - assertThat oktaTokenServicesConfig, notNullValue() - } - } - - @SpringBootTest(classes = [MockCodeFlowApp, OktaOAuthCodeFlowDefaultConfiguration], - properties = ["okta.oauth2.issuer=https://okta.example.com/oauth2/my_issuer", - "okta.oauth2.discoveryDisabled=true"]) - static class LocalValidationConfigTest extends AbstractTestNGSpringContextTests { - - @Autowired - OktaTokenServicesConfig oktaTokenServicesConfig - - @Autowired - TokenStore tokenStore - - @Test - void theBasics() { - assertThat tokenStore, isA(JwkTokenStore) - assertThat oktaTokenServicesConfig, notNullValue() - } - } -} \ No newline at end of file diff --git a/oauth2/src/test/groovy/com/okta/spring/oauth/code/OktaUserInfoTokenServicesTest.groovy b/oauth2/src/test/groovy/com/okta/spring/oauth/code/OktaUserInfoTokenServicesTest.groovy deleted file mode 100644 index 74a533f36..000000000 --- a/oauth2/src/test/groovy/com/okta/spring/oauth/code/OktaUserInfoTokenServicesTest.groovy +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright 2017 Okta, Inc. - * - * 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 - * - * http://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 com.okta.spring.oauth.code - -import com.okta.spring.oauth.OktaUserInfoTokenServices -import org.springframework.http.HttpStatus -import org.springframework.http.ResponseEntity -import org.springframework.security.oauth2.client.OAuth2ClientContext -import org.springframework.security.oauth2.client.OAuth2RestOperations -import org.springframework.security.oauth2.common.OAuth2AccessToken -import org.springframework.security.oauth2.provider.OAuth2Authentication -import org.testng.annotations.Test - -import static org.hamcrest.MatcherAssert.assertThat -import static org.hamcrest.Matchers.equalTo -import static org.mockito.Mockito.mock -import static org.mockito.Mockito.when - -/** - * @since 0.2.0 - */ -class OktaUserInfoTokenServicesTest { - - @Test - void retrieveScopeTest() { - - // mocks - def responseMapBody = [foo: "bar"] // must be non empty - def tokenString = "mock-access-token" - def userInfoEndpoint = "https://example.com/token" - def oauthToken = mock(OAuth2AccessToken) - def oauthContext = mock(OAuth2ClientContext) - def oauthRestTemplate = mock(OAuth2RestOperations) - when(oauthRestTemplate.getOAuth2ClientContext()).thenReturn(oauthContext) - when(oauthContext.getAccessToken()).thenReturn(oauthToken) - when(oauthToken.getValue()).thenReturn(tokenString) - when(oauthToken.getScope()).thenReturn(["one", "two", "red", "blue"].toSet()) - when(oauthRestTemplate.getForEntity(userInfoEndpoint, Map)).thenReturn(new ResponseEntity(responseMapBody, HttpStatus.OK)) - - // configure the service - def tokenServices = new OktaUserInfoTokenServices(userInfoEndpoint, "client-id", oauthContext) - tokenServices.restTemplate = oauthRestTemplate - - // try to get the auth - OAuth2Authentication auth = tokenServices.loadAuthentication(tokenString) - assertThat auth.getOAuth2Request().scope, equalTo(["one", "two", "red", "blue"].toSet()) - - } -} diff --git a/oauth2/src/test/groovy/com/okta/spring/oauth/discovery/DiscoveryPropertySourceTest.groovy b/oauth2/src/test/groovy/com/okta/spring/oauth/discovery/DiscoveryPropertySourceTest.groovy deleted file mode 100644 index a615966bd..000000000 --- a/oauth2/src/test/groovy/com/okta/spring/oauth/discovery/DiscoveryPropertySourceTest.groovy +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright 2017 Okta, Inc. - * - * 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 - * - * http://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 com.okta.spring.oauth.discovery - -import org.springframework.core.env.Environment -import org.testng.annotations.Test - -import static org.hamcrest.Matchers.* -import static org.hamcrest.MatcherAssert.assertThat -import static org.mockito.Mockito.* - -/** - * Tests for {@link DiscoveryPropertySource}. - * - * @since 0.3.0 - */ -class DiscoveryPropertySourceTest { - - /** - * Validates that when disabled, discovery will NOT throw an exception or attempt to make a remote connection. - */ - @Test - void disabledDiscoveryTest() { - Environment env = mock(Environment) - when(env.getProperty("okta.oauth2.discoveryDisabled")).thenReturn("true") - DiscoveryPropertySource propertySource = new DiscoveryPropertySource(env) - - assertThat propertySource.containsProperty("security.oauth2.client.accessTokenUri"), equalTo(false) - assertThat propertySource.getProperty("security.oauth2.client.userAuthorizationUri"), nullValue() - } - - @Test - void nullMetadataTest() { - - Environment env = mock(Environment) - when(env.containsProperty("okta.oauth2.issuer")).thenReturn(true) - when(env.getRequiredProperty("okta.oauth2.issuer")).thenReturn("https://okta.example.com/issuer") - - OidcDiscoveryClient discoveryClient = mock(OidcDiscoveryClient) - when(discoveryClient.discover()).thenReturn(null) - - DiscoveryPropertySource propertySource = new DiscoveryPropertySource(env) { - @Override - OidcDiscoveryClient createDiscoveryClient(String issuerUrl) { - return discoveryClient - } - } - assertThat propertySource.getProperty("security.oauth2.client.userAuthorizationUri"), nullValue() - } - - @Test - void nullMetadataValueTest() { - - Environment env = mock(Environment) - when(env.containsProperty("okta.oauth2.issuer")).thenReturn(true) - when(env.getRequiredProperty("okta.oauth2.issuer")).thenReturn("https://okta.example.com/issuer") - - OidcDiscoveryMetadata metadata = mock(OidcDiscoveryMetadata) - when(metadata.getTokenEndpoint()).thenReturn(null) - - OidcDiscoveryClient discoveryClient = mock(OidcDiscoveryClient) - when(discoveryClient.discover()).thenReturn(metadata) - - DiscoveryPropertySource propertySource = new DiscoveryPropertySource(env) { - @Override - OidcDiscoveryClient createDiscoveryClient(String issuerUrl) { - return discoveryClient - } - } - assertThat propertySource.getProperty("security.oauth2.client.accessTokenUri"), nullValue() - } - - @Test - void metadataValuesTest() { - - Environment env = mock(Environment) - when(env.containsProperty("okta.oauth2.issuer")).thenReturn(true) - when(env.getRequiredProperty("okta.oauth2.issuer")).thenReturn("https://okta.example.com/issuer") - - OidcDiscoveryMetadata metadata = mock(OidcDiscoveryMetadata) - when(metadata.getTokenEndpoint()).thenReturn("tokenEnpoint") - when(metadata.getAuthorizationEndpoint()).thenReturn("authorizationEndpoint") - when(metadata.getUserinfoEndpoint()).thenReturn("userinfoEndpoint") - when(metadata.getJwksUri()).thenReturn("jwksUri") - when(metadata.getIntrospectionEndpoint()).thenReturn("introspectionEndpoint") - - OidcDiscoveryClient discoveryClient = mock(OidcDiscoveryClient) - when(discoveryClient.discover()).thenReturn(metadata) - - DiscoveryPropertySource propertySource = new DiscoveryPropertySource(env) { - @Override - OidcDiscoveryClient createDiscoveryClient(String issuerUrl) { - return discoveryClient - } - } - assertThat propertySource.getProperty("security.oauth2.client.accessTokenUri"), equalTo("tokenEnpoint") - assertThat propertySource.getProperty("security.oauth2.client.userAuthorizationUri"), equalTo("authorizationEndpoint") - assertThat propertySource.getProperty("security.oauth2.resource.userInfoUri"), equalTo("userinfoEndpoint") - assertThat propertySource.getProperty("security.oauth2.resource.jwk.keySetUri"), equalTo("jwksUri") - assertThat propertySource.getProperty("security.oauth2.resource.jwk.key-set-uri"), equalTo("jwksUri") - assertThat propertySource.getProperty("security.oauth2.resource.tokenInfoUri"), equalTo("introspectionEndpoint") - } -} diff --git a/oauth2/src/test/groovy/com/okta/spring/oauth/discovery/OidcDiscoveryClientTest.groovy b/oauth2/src/test/groovy/com/okta/spring/oauth/discovery/OidcDiscoveryClientTest.groovy deleted file mode 100644 index 8d3117b9f..000000000 --- a/oauth2/src/test/groovy/com/okta/spring/oauth/discovery/OidcDiscoveryClientTest.groovy +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2017 Okta, Inc. - * - * 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 - * - * http://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 com.okta.spring.oauth.discovery - -import org.testng.annotations.Test - -/** - * Tests for {@link OidcDiscoveryClient}. - * - * @since 0.3.0 - */ -class OidcDiscoveryClientTest { - - @Test(expectedExceptions = IllegalArgumentException) - void nullIssuerTest() { - new OidcDiscoveryClient(null) - } - - @Test(expectedExceptions = IllegalArgumentException) - void emptyIssuerTest() { - new OidcDiscoveryClient("") - } - - @Test(expectedExceptions = IllegalArgumentException) - void justSpaceIssuerTest() { - new OidcDiscoveryClient(" ") - } - - @Test(expectedExceptions = IllegalArgumentException) - void invalidUriIssuerTest() { - new OidcDiscoveryClient(" invalid uri") - } -} diff --git a/oauth2/src/test/groovy/com/okta/spring/oauth/discovery/OidcDiscoveryTest.groovy b/oauth2/src/test/groovy/com/okta/spring/oauth/discovery/OidcDiscoveryTest.groovy deleted file mode 100644 index 8229bd5a8..000000000 --- a/oauth2/src/test/groovy/com/okta/spring/oauth/discovery/OidcDiscoveryTest.groovy +++ /dev/null @@ -1,147 +0,0 @@ -/* - * Copyright 2017 Okta, Inc. - * - * 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 - * - * http://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 com.okta.spring.oauth.discovery - -import org.springframework.http.HttpHeaders -import org.springframework.http.HttpInputMessage -import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter -import org.testng.annotations.Test - -import static org.hamcrest.MatcherAssert.assertThat -import static org.hamcrest.Matchers.equalTo - -/** - * @since 0.2.0 - */ -class OidcDiscoveryTest { - - @Test - void basicParseTest() { - - def converter = new MappingJackson2HttpMessageConverter() - OidcDiscoveryMetadata metadata = converter.read(OidcDiscoveryMetadata, new HttpInputMessage() { - - @Override - InputStream getBody() throws IOException { - return OidcDiscoveryTest.getResource("/discovery-test.json").openStream() - } - - @Override - HttpHeaders getHeaders() { - return new HttpHeaders() - } - }) - - assertThat metadata.issuer, equalTo("https://dev-259824.oktapreview.com/oauth2/ausar5cbq5TRRsbcJ0h7") - assertThat metadata.authorizationEndpoint, equalTo("https://dev-259824.oktapreview.com/oauth2/ausar5cbq5TRRsbcJ0h7/v1/authorize") - assertThat metadata.tokenEndpoint, equalTo("https://dev-259824.oktapreview.com/oauth2/ausar5cbq5TRRsbcJ0h7/v1/token") - assertThat metadata.userinfoEndpoint, equalTo("https://dev-259824.oktapreview.com/oauth2/ausar5cbq5TRRsbcJ0h7/v1/userinfo") - assertThat metadata.registrationEndpoint, equalTo("https://dev-259824.oktapreview.com/oauth2/v1/clients") - assertThat metadata.jwksUri, equalTo("https://dev-259824.oktapreview.com/oauth2/ausar5cbq5TRRsbcJ0h7/v1/keys") - assertThat metadata.responseTypesSupported, equalTo([ - "code", - "id_token", - "code id_token", - "code token", - "id_token token", - "code id_token token" - ]) - assertThat metadata.responseModesSupported, equalTo([ - "query", - "fragment", - "form_post", - "okta_post_message" - ]) - - assertThat metadata.grantTypesSupported, equalTo([ - "authorization_code", - "implicit", - "refresh_token", - "password" - ]) - assertThat metadata.subjectTypesSupported, equalTo([ - "public" - ]) - assertThat metadata.idTokenSigningAlgValuesSupported, equalTo([ - "RS256" - ]) - assertThat metadata.scopesSupported, equalTo([ - "openid", - "email", - "profile", - "address", - "phone", - "offline_access" - ]) - assertThat metadata.tokenEndpointAuthMethodsSupported, equalTo([ - "client_secret_basic", - "client_secret_post", - "client_secret_jwt", - "none" - ]) - assertThat metadata.claimsSupported, equalTo([ - "iss", - "ver", - "sub", - "aud", - "iat", - "exp", - "jti", - "auth_time", - "amr", - "idp", - "nonce", - "name", - "nickname", - "preferred_username", - "given_name", - "middle_name", - "family_name", - "email", - "email_verified", - "profile", - "zoneinfo", - "locale", - "address", - "phone_number", - "picture", - "website", - "gender", - "birthdate", - "updated_at", - "at_hash", - "c_hash" - ]) - assertThat metadata.codeChallengeMethodsSupported, equalTo([ - "S256" - ]) - assertThat metadata.introspectionEndpoint, equalTo("https://dev-259824.oktapreview.com/oauth2/ausar5cbq5TRRsbcJ0h7/v1/introspect") - assertThat metadata.introspectionEndpointAuthMethodsSupported, equalTo([ - "client_secret_basic", - "client_secret_post", - "client_secret_jwt", - "none" - ]) - assertThat metadata.revocationEndpoint, equalTo("https://dev-259824.oktapreview.com/oauth2/ausar5cbq5TRRsbcJ0h7/v1/revoke") - assertThat metadata.revocationEndpointAuthMethodsSupported, equalTo([ - "client_secret_basic", - "client_secret_post", - "client_secret_jwt", - "none" - ]) - assertThat metadata.endSessionEndpoint, equalTo("https://dev-259824.oktapreview.com/oauth2/ausar5cbq5TRRsbcJ0h7/v1/logout") - } -} diff --git a/oauth2/src/test/groovy/com/okta/spring/oauth/implicit/ResourceServerConfigTest.groovy b/oauth2/src/test/groovy/com/okta/spring/oauth/implicit/ResourceServerConfigTest.groovy deleted file mode 100644 index 223403248..000000000 --- a/oauth2/src/test/groovy/com/okta/spring/oauth/implicit/ResourceServerConfigTest.groovy +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2017 Okta, Inc. - * - * 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 - * - * http://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 com.okta.spring.oauth.implicit - -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.context.SpringBootTest -import org.springframework.security.oauth2.provider.token.AccessTokenConverter -import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices -import org.springframework.security.oauth2.provider.token.TokenStore -import org.springframework.security.oauth2.provider.token.store.JwtClaimsSetVerifier -import org.springframework.test.context.testng.AbstractTestNGSpringContextTests -import org.testng.annotations.Test - -import static org.hamcrest.MatcherAssert.assertThat -import static org.hamcrest.Matchers.notNullValue - -/** - * @since 0.1.0 - */ -@SpringBootTest(classes = [StubApp, ResourceServerConfig], - properties = ["okta.oauth2.issuer=https://okta.example.com/oauth2/my_issuer", - "okta.oauth2.audience=custom_audience", - "okta.oauth2.scopeClaim=custom_scope_claim", - "okta.oauth2.rolesClaim=custom_roles_claim", - "okta.oauth2.discoveryDisabled=true"]) -class ResourceServerConfigTest extends AbstractTestNGSpringContextTests { - - @Autowired - ResourceServerConfig resourceServerConfig - - @Autowired - ResourceServerTokenServices tokenServices - - @Autowired - TokenStore tokenStore - - @Autowired - AccessTokenConverter accessTokenConverter - - @Autowired - JwtClaimsSetVerifier jwtClaimsSetVerifier - - @Test - void basicAutowireTest() { - assertThat resourceServerConfig, notNullValue() - assertThat tokenServices, notNullValue() - assertThat tokenStore, notNullValue() - assertThat accessTokenConverter, notNullValue() - assertThat jwtClaimsSetVerifier, notNullValue() - } - -} diff --git a/oauth2/src/test/groovy/com/okta/spring/oauth/implicit/StubApp.groovy b/oauth2/src/test/groovy/com/okta/spring/oauth/implicit/StubApp.groovy deleted file mode 100644 index fffed9104..000000000 --- a/oauth2/src/test/groovy/com/okta/spring/oauth/implicit/StubApp.groovy +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright 2017 Okta, Inc. - * - * 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 - * - * http://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 com.okta.spring.oauth.implicit - -import org.springframework.boot.autoconfigure.SpringBootApplication -import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer - -@SpringBootApplication -@EnableResourceServer -class StubApp { -} diff --git a/okta-spring-boot-starter/src/test/groovy/com/okta/spring/oauth/http/UserAgentTest.groovy b/okta-spring-boot-starter/src/test/groovy/com/okta/spring/oauth/http/UserAgentTest.groovy deleted file mode 100644 index 8dc37f119..000000000 --- a/okta-spring-boot-starter/src/test/groovy/com/okta/spring/oauth/http/UserAgentTest.groovy +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright 2014 Stormpath, Inc. - * Modifications Copyright 2018 Okta, Inc. - * - * 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 - * - * http://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 com.okta.spring.oauth.http - -import org.springframework.util.StringUtils -import org.testng.annotations.Test - -import static org.hamcrest.MatcherAssert.assertThat -import static org.hamcrest.Matchers.* - -/** - * @since 0.4.0 - */ -class UserAgentTest { - - private static final String VERSION_SEPARATOR = "/" - private static final String ENTRY_SEPARATOR = " " - private static final String OKTA_SPRING_KEY = "okta-spring-security" - - private static ResourceBundle versionBundle = ResourceBundle.getBundle("versions") - - @Test - void testGetUserAgentString() { - String userAgent = UserAgent.getUserAgentString() - assertThat userAgent, allOf( - not(emptyString()), - containsString(OKTA_SPRING_KEY + VERSION_SEPARATOR + getSpringIntegrationVersion() + ENTRY_SEPARATOR), - containsString("java" + VERSION_SEPARATOR + System.getProperty("java.version") + ENTRY_SEPARATOR) - ) - assertThat "Expected '${OKTA_SPRING_KEY}' to appear in userAgent once once.", StringUtils.countOccurrencesOf(userAgent, OKTA_SPRING_KEY), equalTo(1) - } - - // cheat a little we are filtering a file to load the known versions - private String getSpringIntegrationVersion() { - return versionBundle.getString("project.version") - } -} \ No newline at end of file diff --git a/pom.xml b/pom.xml index 8110bd1dd..8ec50bddf 100644 --- a/pom.xml +++ b/pom.xml @@ -31,19 +31,17 @@ pom - 1.5.16.RELEASE - 2.0.5.RELEASE + 2.1.0.RC1 + 2.0.2.RELEASE okta/okta-spring-boot 1.3.0 - config oauth2 sdk okta-spring-boot-starter integration-tests/oauth2 - integration-tests/oauth2-boot2 examples coverage @@ -54,7 +52,7 @@ com.fasterxml.jackson jackson-bom - 2.9.5 + 2.9.7 import pom @@ -67,31 +65,6 @@ import - - - org.springframework.security.oauth - spring-security-oauth2 - 2.3.3.RELEASE - - - - * - * - - - - - - org.springframework - spring-core - - - commons-logging - commons-logging - - - - com.okta.spring @@ -103,11 +76,7 @@ okta-spring-security-oauth2 0.7.0-SNAPSHOT - - com.okta.spring - okta-spring-config - 0.7.0-SNAPSHOT - + com.okta.spring okta-spring-sdk @@ -118,6 +87,7 @@ okta-spring-boot-integration-tests-oauth2 0.7.0-SNAPSHOT + com.okta.sdk okta-sdk-api @@ -133,11 +103,6 @@ okta-sdk-httpclient ${okta.sdk.version} - - org.mockito - mockito-core - 2.10.0 - com.okta.commons okta-config-check @@ -146,9 +111,10 @@ com.okta.oidc.tck okta-oidc-tck - 0.4.0 + 0.4.1-SNAPSHOT + com.github.tomakehurst wiremock-standalone @@ -160,6 +126,51 @@ rest-assured 3.0.7 + + org.mockito + mockito-core + 2.10.0 + + + + + + spring-snapshots + Spring Snapshots + https://repo.spring.io/snapshot + + true + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + false + + + + + + + spring-snapshots + Spring Snapshots + https://repo.spring.io/snapshot + + true + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + false + + + + \ No newline at end of file diff --git a/sdk/pom.xml b/sdk/pom.xml index ec467fdec..0337377d2 100644 --- a/sdk/pom.xml +++ b/sdk/pom.xml @@ -35,10 +35,6 @@ org.springframework.boot spring-boot-autoconfigure - - com.okta.spring - okta-spring-config - com.okta.sdk okta-sdk-api diff --git a/sdk/src/main/java/com/okta/spring/sdk/OktaSdkConfig.java b/sdk/src/main/java/com/okta/spring/boot/sdk/OktaSdkConfig.java similarity index 94% rename from sdk/src/main/java/com/okta/spring/sdk/OktaSdkConfig.java rename to sdk/src/main/java/com/okta/spring/boot/sdk/OktaSdkConfig.java index 6fe810c4b..9bc384995 100644 --- a/sdk/src/main/java/com/okta/spring/sdk/OktaSdkConfig.java +++ b/sdk/src/main/java/com/okta/spring/boot/sdk/OktaSdkConfig.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.okta.spring.sdk; +package com.okta.spring.boot.sdk; import com.okta.sdk.authc.credentials.ClientCredentials; import com.okta.sdk.authc.credentials.TokenClientCredentials; @@ -24,14 +24,15 @@ import com.okta.sdk.client.Clients; import com.okta.sdk.client.Proxy; import com.okta.sdk.lang.Strings; -import com.okta.spring.config.OktaClientProperties; -import com.okta.spring.sdk.cache.SpringCacheManager; +import com.okta.spring.boot.sdk.config.OktaClientProperties; +import com.okta.spring.boot.sdk.cache.SpringCacheManager; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionMessage; import org.springframework.boot.autoconfigure.condition.ConditionOutcome; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.SpringBootCondition; +import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ConditionContext; import org.springframework.context.annotation.Conditional; @@ -47,6 +48,7 @@ @Configuration @Conditional(OktaSdkConfig.OktaApiTokenCondition.class) @ConditionalOnClass(Client.class) +@EnableConfigurationProperties(OktaClientProperties.class) public class OktaSdkConfig { private final OktaClientProperties oktaClientProperties; diff --git a/sdk/src/main/java/com/okta/spring/sdk/cache/SpringCache.java b/sdk/src/main/java/com/okta/spring/boot/sdk/cache/SpringCache.java similarity index 98% rename from sdk/src/main/java/com/okta/spring/sdk/cache/SpringCache.java rename to sdk/src/main/java/com/okta/spring/boot/sdk/cache/SpringCache.java index b701fe258..37332bc18 100644 --- a/sdk/src/main/java/com/okta/spring/sdk/cache/SpringCache.java +++ b/sdk/src/main/java/com/okta/spring/boot/sdk/cache/SpringCache.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.okta.spring.sdk.cache; +package com.okta.spring.boot.sdk.cache; import com.okta.sdk.cache.Cache; import com.okta.sdk.lang.Assert; diff --git a/sdk/src/main/java/com/okta/spring/sdk/cache/SpringCacheManager.java b/sdk/src/main/java/com/okta/spring/boot/sdk/cache/SpringCacheManager.java similarity index 98% rename from sdk/src/main/java/com/okta/spring/sdk/cache/SpringCacheManager.java rename to sdk/src/main/java/com/okta/spring/boot/sdk/cache/SpringCacheManager.java index 70eae56e2..8cd7e800a 100644 --- a/sdk/src/main/java/com/okta/spring/sdk/cache/SpringCacheManager.java +++ b/sdk/src/main/java/com/okta/spring/boot/sdk/cache/SpringCacheManager.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.okta.spring.sdk.cache; +package com.okta.spring.boot.sdk.cache; import com.okta.sdk.cache.Cache; import com.okta.sdk.cache.CacheManager; diff --git a/config/src/main/java/com/okta/spring/config/OktaClientProperties.java b/sdk/src/main/java/com/okta/spring/boot/sdk/config/OktaClientProperties.java similarity index 98% rename from config/src/main/java/com/okta/spring/config/OktaClientProperties.java rename to sdk/src/main/java/com/okta/spring/boot/sdk/config/OktaClientProperties.java index dd686acbb..33e1b9dec 100644 --- a/config/src/main/java/com/okta/spring/config/OktaClientProperties.java +++ b/sdk/src/main/java/com/okta/spring/boot/sdk/config/OktaClientProperties.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.okta.spring.config; +package com.okta.spring.boot.sdk.config; import org.springframework.boot.context.properties.ConfigurationProperties; diff --git a/sdk/src/main/java/com/okta/spring/boot/sdk/env/OktaSdkPropertiesEnvironmentPostProcessor.java b/sdk/src/main/java/com/okta/spring/boot/sdk/env/OktaSdkPropertiesEnvironmentPostProcessor.java new file mode 100644 index 000000000..0e857bba2 --- /dev/null +++ b/sdk/src/main/java/com/okta/spring/boot/sdk/env/OktaSdkPropertiesEnvironmentPostProcessor.java @@ -0,0 +1,67 @@ +/* + * Copyright 2017-Present Okta, Inc. + * + * 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 + * + * http://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 com.okta.spring.boot.sdk.env; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.env.EnvironmentPostProcessor; +import org.springframework.boot.env.YamlPropertySourceLoader; +import org.springframework.core.Ordered; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.MapPropertySource; +import org.springframework.core.env.PropertySource; +import org.springframework.core.io.FileSystemResource; +import org.springframework.core.io.Resource; + +import java.io.File; +import java.io.IOException; +import java.util.Collections; +import java.util.List; + +/** + * This {@link EnvironmentPostProcessor} configures additional {@link PropertySource}s for {code ~/.okta/okta.yaml} and {code ~/.okta/okta.yml}. + */ +final class OktaSdkPropertiesEnvironmentPostProcessor implements EnvironmentPostProcessor, Ordered { + + @Override + public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) { + + environment.getPropertySources().addLast(loadYaml(new FileSystemResource(new File(System.getProperty("user.home"), ".okta/okta.yml")), false)); + environment.getPropertySources().addLast(loadYaml(new FileSystemResource(new File(System.getProperty("user.home"), ".okta/okta.yaml")), false)); + } + + private PropertySource loadYaml(Resource resource, boolean required) { + YamlPropertySourceLoader loader = new YamlPropertySourceLoader(); + if (!resource.exists() && required) { + throw new IllegalArgumentException("Resource " + resource + " does not exist"); + } + + if (resource.exists()) { + try { + List> list = loader.load(resource.getFilename(), resource); + return list.get(0); + } catch (IOException ex) { + throw new IllegalStateException("Failed to load yaml configuration from " + resource, ex); + } + } else { + return new MapPropertySource("Missing "+ resource.getFilename(), Collections.emptyMap()); + } + } + + @Override + public int getOrder() { + return LOWEST_PRECEDENCE; + } +} \ No newline at end of file diff --git a/sdk/src/main/resources/META-INF/spring.factories b/sdk/src/main/resources/META-INF/spring.factories index 78d1644e1..5cf9c3914 100644 --- a/sdk/src/main/resources/META-INF/spring.factories +++ b/sdk/src/main/resources/META-INF/spring.factories @@ -1 +1,3 @@ -org.springframework.boot.autoconfigure.EnableAutoConfiguration = com.okta.spring.sdk.OktaSdkConfig \ No newline at end of file +org.springframework.boot.autoconfigure.EnableAutoConfiguration = com.okta.spring.boot.sdk.OktaSdkConfig + +org.springframework.boot.env.EnvironmentPostProcessor = com.okta.spring.boot.sdk.env.OktaSdkPropertiesEnvironmentPostProcessor \ No newline at end of file diff --git a/sdk/src/test/groovy/com/okta/spring/sdk/MockSdkApp.groovy b/sdk/src/test/groovy/com/okta/spring/boot/sdk/MockSdkApp.groovy similarity index 96% rename from sdk/src/test/groovy/com/okta/spring/sdk/MockSdkApp.groovy rename to sdk/src/test/groovy/com/okta/spring/boot/sdk/MockSdkApp.groovy index 0fa744186..81f61763f 100644 --- a/sdk/src/test/groovy/com/okta/spring/sdk/MockSdkApp.groovy +++ b/sdk/src/test/groovy/com/okta/spring/boot/sdk/MockSdkApp.groovy @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.okta.spring.sdk +package com.okta.spring.boot.sdk import org.springframework.boot.autoconfigure.EnableAutoConfiguration import org.springframework.cache.CacheManager diff --git a/sdk/src/test/groovy/com/okta/spring/sdk/MockSdkAppWithCache.groovy b/sdk/src/test/groovy/com/okta/spring/boot/sdk/MockSdkAppWithCache.groovy similarity index 97% rename from sdk/src/test/groovy/com/okta/spring/sdk/MockSdkAppWithCache.groovy rename to sdk/src/test/groovy/com/okta/spring/boot/sdk/MockSdkAppWithCache.groovy index 11caf0fea..3f48cab56 100644 --- a/sdk/src/test/groovy/com/okta/spring/sdk/MockSdkAppWithCache.groovy +++ b/sdk/src/test/groovy/com/okta/spring/boot/sdk/MockSdkAppWithCache.groovy @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.okta.spring.sdk +package com.okta.spring.boot.sdk import org.springframework.boot.autoconfigure.EnableAutoConfiguration import org.springframework.cache.CacheManager diff --git a/sdk/src/test/groovy/com/okta/spring/sdk/OktaSdkConfigTest.groovy b/sdk/src/test/groovy/com/okta/spring/boot/sdk/OktaSdkConfigTest.groovy similarity index 98% rename from sdk/src/test/groovy/com/okta/spring/sdk/OktaSdkConfigTest.groovy rename to sdk/src/test/groovy/com/okta/spring/boot/sdk/OktaSdkConfigTest.groovy index bbf053ba6..85cc09a7e 100644 --- a/sdk/src/test/groovy/com/okta/spring/sdk/OktaSdkConfigTest.groovy +++ b/sdk/src/test/groovy/com/okta/spring/boot/sdk/OktaSdkConfigTest.groovy @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.okta.spring.sdk +package com.okta.spring.boot.sdk import com.okta.sdk.client.Client import com.okta.sdk.impl.cache.DefaultCacheManager diff --git a/sdk/src/test/groovy/com/okta/spring/sdk/OktaSdkConfigWithCacheTest.groovy b/sdk/src/test/groovy/com/okta/spring/boot/sdk/OktaSdkConfigWithCacheTest.groovy similarity index 94% rename from sdk/src/test/groovy/com/okta/spring/sdk/OktaSdkConfigWithCacheTest.groovy rename to sdk/src/test/groovy/com/okta/spring/boot/sdk/OktaSdkConfigWithCacheTest.groovy index a7e076409..942c386b6 100644 --- a/sdk/src/test/groovy/com/okta/spring/sdk/OktaSdkConfigWithCacheTest.groovy +++ b/sdk/src/test/groovy/com/okta/spring/boot/sdk/OktaSdkConfigWithCacheTest.groovy @@ -13,10 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.okta.spring.sdk +package com.okta.spring.boot.sdk import com.okta.sdk.client.Client -import com.okta.spring.sdk.cache.SpringCacheManager +import com.okta.spring.boot.sdk.cache.SpringCacheManager import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.context.SpringBootTest import org.springframework.test.context.testng.AbstractTestNGSpringContextTests diff --git a/sdk/src/test/groovy/com/okta/spring/sdk/OktaSdkConfigWithProxyTest.groovy b/sdk/src/test/groovy/com/okta/spring/boot/sdk/OktaSdkConfigWithProxyTest.groovy similarity index 97% rename from sdk/src/test/groovy/com/okta/spring/sdk/OktaSdkConfigWithProxyTest.groovy rename to sdk/src/test/groovy/com/okta/spring/boot/sdk/OktaSdkConfigWithProxyTest.groovy index b3709070e..2ec5850e5 100644 --- a/sdk/src/test/groovy/com/okta/spring/sdk/OktaSdkConfigWithProxyTest.groovy +++ b/sdk/src/test/groovy/com/okta/spring/boot/sdk/OktaSdkConfigWithProxyTest.groovy @@ -13,10 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.okta.spring.sdk +package com.okta.spring.boot.sdk import com.okta.sdk.client.Proxy -import com.okta.spring.config.OktaClientProperties +import com.okta.spring.boot.sdk.config.OktaClientProperties import org.testng.annotations.Test import static org.hamcrest.MatcherAssert.assertThat diff --git a/sdk/src/test/groovy/com/okta/spring/sdk/cache/SpringCacheManagerTest.groovy b/sdk/src/test/groovy/com/okta/spring/boot/sdk/cache/SpringCacheManagerTest.groovy similarity index 97% rename from sdk/src/test/groovy/com/okta/spring/sdk/cache/SpringCacheManagerTest.groovy rename to sdk/src/test/groovy/com/okta/spring/boot/sdk/cache/SpringCacheManagerTest.groovy index bcf236cca..6a8c72e50 100644 --- a/sdk/src/test/groovy/com/okta/spring/sdk/cache/SpringCacheManagerTest.groovy +++ b/sdk/src/test/groovy/com/okta/spring/boot/sdk/cache/SpringCacheManagerTest.groovy @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.okta.spring.sdk.cache +package com.okta.spring.boot.sdk.cache import org.springframework.cache.concurrent.ConcurrentMapCache import org.springframework.cache.support.SimpleCacheManager diff --git a/sdk/src/test/groovy/com/okta/spring/sdk/cache/SpringCacheTest.groovy b/sdk/src/test/groovy/com/okta/spring/boot/sdk/cache/SpringCacheTest.groovy similarity index 98% rename from sdk/src/test/groovy/com/okta/spring/sdk/cache/SpringCacheTest.groovy rename to sdk/src/test/groovy/com/okta/spring/boot/sdk/cache/SpringCacheTest.groovy index 5546e9572..3ed8bb2a2 100644 --- a/sdk/src/test/groovy/com/okta/spring/sdk/cache/SpringCacheTest.groovy +++ b/sdk/src/test/groovy/com/okta/spring/boot/sdk/cache/SpringCacheTest.groovy @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.okta.spring.sdk.cache +package com.okta.spring.boot.sdk.cache import org.springframework.cache.concurrent.ConcurrentMapCache import org.testng.annotations.Test diff --git a/config/pom.xml b/starter/pom.xml similarity index 66% rename from config/pom.xml rename to starter/pom.xml index 2006c3cd1..3d7a2f0dd 100644 --- a/config/pom.xml +++ b/starter/pom.xml @@ -23,55 +23,74 @@ 0.7.0-SNAPSHOT - okta-spring-config - Okta Spring Boot :: Config + okta-spring-boot-simple + Okta Spring Boot :: Simple + - com.fasterxml.jackson.core - jackson-databind + org.springframework.boot + spring-boot-starter-security - org.springframework.boot - spring-boot + com.okta.commons + okta-config-check + + + + + org.springframework.security + spring-security-config org.springframework.boot - spring-boot-configuration-processor - true + spring-boot-starter-security - com.okta.commons - okta-config-check + org.springframework.security + spring-security-oauth2-client + + + org.springframework.security + spring-security-oauth2-jose + + + org.springframework.security + spring-security-oauth2-resource-server - + org.springframework.boot - spring-boot-test + spring-boot-starter-web + + - org.springframework - spring-test + org.springframework.boot + spring-boot-test test org.springframework - spring-web + spring-test test - org.slf4j - slf4j-simple - - - org.slf4j - jcl-over-slf4j + org.mockito + mockito-core test + + + + src/test/resources + true + + org.jacoco From bf78c78398802816486c8704b6bd25bfa3c0e6a9 Mon Sep 17 00:00:00 2001 From: Brian Demers Date: Tue, 6 Nov 2018 13:10:14 -0500 Subject: [PATCH 03/12] Update to release Spring Boot 2.1 --- pom.xml | 42 +----------------------------------------- 1 file changed, 1 insertion(+), 41 deletions(-) diff --git a/pom.xml b/pom.xml index 8ec50bddf..db46bad82 100644 --- a/pom.xml +++ b/pom.xml @@ -31,7 +31,7 @@ pom - 2.1.0.RC1 + 2.1.0.RELEASE 2.0.2.RELEASE okta/okta-spring-boot 1.3.0 @@ -133,44 +133,4 @@ - - - - - spring-snapshots - Spring Snapshots - https://repo.spring.io/snapshot - - true - - - - spring-milestones - Spring Milestones - https://repo.spring.io/milestone - - false - - - - - - - spring-snapshots - Spring Snapshots - https://repo.spring.io/snapshot - - true - - - - spring-milestones - Spring Milestones - https://repo.spring.io/milestone - - false - - - - \ No newline at end of file From 1ef5eac2832889d17e6927f8f8fbd61d8aa19516 Mon Sep 17 00:00:00 2001 From: Brian Demers Date: Wed, 7 Nov 2018 10:49:03 -0500 Subject: [PATCH 04/12] license headers, findbugs, PMD & nits --- .../configserver/ConfigServerApplication.java | 15 +++ .../src/main/resources/application.yml | 16 +++ .../resources/config-repo/application.yml | 16 +++ .../oauth/OktaJwtAuthenticationConverter.java | 17 ++- .../boot/oauth/OktaOAuth2AutoConfig.java | 15 +++ .../boot/oauth/OktaOAuth2Configurer.java | 17 ++- .../OktaOAuth2ResourceServerAutoConfig.java | 18 ++- .../boot/oauth/OktaOAuth2UserService.java | 15 +++ .../boot/oauth/OktaOidcUserService.java | 17 ++- .../com/okta/spring/boot/oauth/TokenUtil.java | 17 ++- ...ertiesMappingEnvironmentPostProcessor.java | 4 +- .../OktaJwtAuthenticationConverterTest.groovy | 15 +++ .../spring/boot/oauth/TokenUtilTest.groovy | 15 +++ .../config/OktaOAuth2PropertiesTest.groovy | 15 +++ .../main/resources/META-INF/spring.factories | 0 sdk/pom.xml | 5 + src/findbugs/findbugs-exclude.xml | 5 +- starter/pom.xml | 110 ------------------ 18 files changed, 212 insertions(+), 120 deletions(-) delete mode 100644 okta-spring-boot-starter/src/main/resources/META-INF/spring.factories delete mode 100644 starter/pom.xml diff --git a/examples/config-server/src/main/java/com/okta/example/cloud/configserver/ConfigServerApplication.java b/examples/config-server/src/main/java/com/okta/example/cloud/configserver/ConfigServerApplication.java index 226865719..b28606f59 100644 --- a/examples/config-server/src/main/java/com/okta/example/cloud/configserver/ConfigServerApplication.java +++ b/examples/config-server/src/main/java/com/okta/example/cloud/configserver/ConfigServerApplication.java @@ -1,3 +1,18 @@ +/* + * Copyright 2018-Present Okta, Inc. + * + * 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 + * + * http://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 com.okta.example.cloud.configserver; import org.springframework.boot.SpringApplication; diff --git a/examples/config-server/src/main/resources/application.yml b/examples/config-server/src/main/resources/application.yml index 7a05a5f10..6117cfd3c 100644 --- a/examples/config-server/src/main/resources/application.yml +++ b/examples/config-server/src/main/resources/application.yml @@ -1,3 +1,19 @@ +# +# Copyright 2018-Present Okta, Inc. +# +# 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 +# +# http://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. +# + server: port: 8888 diff --git a/examples/config-server/src/main/resources/config-repo/application.yml b/examples/config-server/src/main/resources/config-repo/application.yml index 6c7096223..3ec365a1e 100644 --- a/examples/config-server/src/main/resources/config-repo/application.yml +++ b/examples/config-server/src/main/resources/config-repo/application.yml @@ -1,3 +1,19 @@ +# +# Copyright 2018-Present Okta, Inc. +# +# 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 +# +# http://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. +# + okta: oauth2: issuer: "https://{yourOktaDomain}/oauth2/default" diff --git a/oauth2/src/main/java/com/okta/spring/boot/oauth/OktaJwtAuthenticationConverter.java b/oauth2/src/main/java/com/okta/spring/boot/oauth/OktaJwtAuthenticationConverter.java index a917f3b70..759fd152e 100644 --- a/oauth2/src/main/java/com/okta/spring/boot/oauth/OktaJwtAuthenticationConverter.java +++ b/oauth2/src/main/java/com/okta/spring/boot/oauth/OktaJwtAuthenticationConverter.java @@ -1,3 +1,18 @@ +/* + * Copyright 2018-Present Okta, Inc. + * + * 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 + * + * http://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 com.okta.spring.boot.oauth; import org.springframework.security.core.GrantedAuthority; @@ -23,4 +38,4 @@ protected Collection extractAuthorities(Jwt jwt) { return result; } -} +} \ No newline at end of file diff --git a/oauth2/src/main/java/com/okta/spring/boot/oauth/OktaOAuth2AutoConfig.java b/oauth2/src/main/java/com/okta/spring/boot/oauth/OktaOAuth2AutoConfig.java index f58283b88..6620f5f95 100644 --- a/oauth2/src/main/java/com/okta/spring/boot/oauth/OktaOAuth2AutoConfig.java +++ b/oauth2/src/main/java/com/okta/spring/boot/oauth/OktaOAuth2AutoConfig.java @@ -1,3 +1,18 @@ +/* + * Copyright 2018-Present Okta, Inc. + * + * 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 + * + * http://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 com.okta.spring.boot.oauth; import com.okta.spring.boot.oauth.config.OktaOAuth2Properties; diff --git a/oauth2/src/main/java/com/okta/spring/boot/oauth/OktaOAuth2Configurer.java b/oauth2/src/main/java/com/okta/spring/boot/oauth/OktaOAuth2Configurer.java index 976830f9e..7f84f5308 100644 --- a/oauth2/src/main/java/com/okta/spring/boot/oauth/OktaOAuth2Configurer.java +++ b/oauth2/src/main/java/com/okta/spring/boot/oauth/OktaOAuth2Configurer.java @@ -1,3 +1,18 @@ +/* + * Copyright 2018-Present Okta, Inc. + * + * 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 + * + * http://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 com.okta.spring.boot.oauth; import com.okta.spring.boot.oauth.config.OktaOAuth2Properties; @@ -45,4 +60,4 @@ private void configureLogin(HttpSecurity http, OktaOAuth2Properties oktaOAuth2Pr private void configureResourceServer(HttpSecurity http, OktaOAuth2Properties oktaOAuth2Properties) throws Exception { http.oauth2ResourceServer().jwt().jwtAuthenticationConverter(new OktaJwtAuthenticationConverter(oktaOAuth2Properties.getGroupsClaim())); } -} +} \ No newline at end of file diff --git a/oauth2/src/main/java/com/okta/spring/boot/oauth/OktaOAuth2ResourceServerAutoConfig.java b/oauth2/src/main/java/com/okta/spring/boot/oauth/OktaOAuth2ResourceServerAutoConfig.java index 85a749db4..acfe1ec48 100644 --- a/oauth2/src/main/java/com/okta/spring/boot/oauth/OktaOAuth2ResourceServerAutoConfig.java +++ b/oauth2/src/main/java/com/okta/spring/boot/oauth/OktaOAuth2ResourceServerAutoConfig.java @@ -1,7 +1,21 @@ +/* + * Copyright 2018-Present Okta, Inc. + * + * 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 + * + * http://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 com.okta.spring.boot.oauth; import com.okta.spring.boot.oauth.config.OktaOAuth2Properties; -import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; @@ -53,7 +67,7 @@ JwtDecoder jwtDecoder(OAuth2ResourceServerProperties oAuth2ResourceServerPropert validators.add(token -> { Set expectedAudience = new HashSet<>(); expectedAudience.add(oktaOAuth2Properties.getAudience()); - return (!Collections.disjoint(token.getAudience(), expectedAudience)) + return !Collections.disjoint(token.getAudience(), expectedAudience) ? OAuth2TokenValidatorResult.success() : OAuth2TokenValidatorResult.failure(INVALID_AUDIENCE); }); diff --git a/oauth2/src/main/java/com/okta/spring/boot/oauth/OktaOAuth2UserService.java b/oauth2/src/main/java/com/okta/spring/boot/oauth/OktaOAuth2UserService.java index 6b938abd6..582ecd60c 100644 --- a/oauth2/src/main/java/com/okta/spring/boot/oauth/OktaOAuth2UserService.java +++ b/oauth2/src/main/java/com/okta/spring/boot/oauth/OktaOAuth2UserService.java @@ -1,3 +1,18 @@ +/* + * Copyright 2018-Present Okta, Inc. + * + * 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 + * + * http://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 com.okta.spring.boot.oauth; import org.springframework.security.core.GrantedAuthority; diff --git a/oauth2/src/main/java/com/okta/spring/boot/oauth/OktaOidcUserService.java b/oauth2/src/main/java/com/okta/spring/boot/oauth/OktaOidcUserService.java index dbec5150d..c9780a0e7 100644 --- a/oauth2/src/main/java/com/okta/spring/boot/oauth/OktaOidcUserService.java +++ b/oauth2/src/main/java/com/okta/spring/boot/oauth/OktaOidcUserService.java @@ -1,3 +1,18 @@ +/* + * Copyright 2018-Present Okta, Inc. + * + * 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 + * + * http://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 com.okta.spring.boot.oauth; import org.springframework.security.core.GrantedAuthority; @@ -42,4 +57,4 @@ public OidcUser loadUser(OidcUserRequest userRequest) throws OAuth2Authenticatio ? new DefaultOidcUser(authorities, user.getIdToken(), user.getUserInfo(), userNameAttributeName) : new DefaultOidcUser(authorities, user.getIdToken(), user.getUserInfo()); } -} +} \ No newline at end of file diff --git a/oauth2/src/main/java/com/okta/spring/boot/oauth/TokenUtil.java b/oauth2/src/main/java/com/okta/spring/boot/oauth/TokenUtil.java index 0acc5f623..c3e423666 100644 --- a/oauth2/src/main/java/com/okta/spring/boot/oauth/TokenUtil.java +++ b/oauth2/src/main/java/com/okta/spring/boot/oauth/TokenUtil.java @@ -1,3 +1,18 @@ +/* + * Copyright 2018-Present Okta, Inc. + * + * 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 + * + * http://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 com.okta.spring.boot.oauth; import org.slf4j.Logger; @@ -45,4 +60,4 @@ static Collection tokenClaimsToAuthorities(Map source, En @Override public Object getProperty(String name) { - return (containsProperty(name) && environment.containsProperty(conditionalProperty)) + return containsProperty(name) && environment.containsProperty(conditionalProperty) ? super.getProperty(name) : null; } diff --git a/oauth2/src/test/groovy/com/okta/spring/boot/oauth/OktaJwtAuthenticationConverterTest.groovy b/oauth2/src/test/groovy/com/okta/spring/boot/oauth/OktaJwtAuthenticationConverterTest.groovy index 1b671aefc..1c89f5b30 100644 --- a/oauth2/src/test/groovy/com/okta/spring/boot/oauth/OktaJwtAuthenticationConverterTest.groovy +++ b/oauth2/src/test/groovy/com/okta/spring/boot/oauth/OktaJwtAuthenticationConverterTest.groovy @@ -1,3 +1,18 @@ +/* + * Copyright 2018-Present Okta, Inc. + * + * 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 + * + * http://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 com.okta.spring.boot.oauth import org.springframework.security.core.authority.SimpleGrantedAuthority diff --git a/oauth2/src/test/groovy/com/okta/spring/boot/oauth/TokenUtilTest.groovy b/oauth2/src/test/groovy/com/okta/spring/boot/oauth/TokenUtilTest.groovy index 29bf446c7..d3fd52abe 100644 --- a/oauth2/src/test/groovy/com/okta/spring/boot/oauth/TokenUtilTest.groovy +++ b/oauth2/src/test/groovy/com/okta/spring/boot/oauth/TokenUtilTest.groovy @@ -1,3 +1,18 @@ +/* + * Copyright 2018-Present Okta, Inc. + * + * 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 + * + * http://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 com.okta.spring.boot.oauth import org.springframework.security.core.GrantedAuthority diff --git a/oauth2/src/test/groovy/com/okta/spring/boot/oauth/config/OktaOAuth2PropertiesTest.groovy b/oauth2/src/test/groovy/com/okta/spring/boot/oauth/config/OktaOAuth2PropertiesTest.groovy index f42d12d15..ed98af6bf 100644 --- a/oauth2/src/test/groovy/com/okta/spring/boot/oauth/config/OktaOAuth2PropertiesTest.groovy +++ b/oauth2/src/test/groovy/com/okta/spring/boot/oauth/config/OktaOAuth2PropertiesTest.groovy @@ -1,3 +1,18 @@ +/* + * Copyright 2018-Present Okta, Inc. + * + * 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 + * + * http://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 com.okta.spring.boot.oauth.config import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientProperties diff --git a/okta-spring-boot-starter/src/main/resources/META-INF/spring.factories b/okta-spring-boot-starter/src/main/resources/META-INF/spring.factories deleted file mode 100644 index e69de29bb..000000000 diff --git a/sdk/pom.xml b/sdk/pom.xml index 0337377d2..59f62b0be 100644 --- a/sdk/pom.xml +++ b/sdk/pom.xml @@ -35,6 +35,11 @@ org.springframework.boot spring-boot-autoconfigure + + org.springframework.boot + spring-boot-configuration-processor + true + com.okta.sdk okta-sdk-api diff --git a/src/findbugs/findbugs-exclude.xml b/src/findbugs/findbugs-exclude.xml index 053429da0..43e4d7d3f 100644 --- a/src/findbugs/findbugs-exclude.xml +++ b/src/findbugs/findbugs-exclude.xml @@ -42,6 +42,9 @@ - + + + + \ No newline at end of file diff --git a/starter/pom.xml b/starter/pom.xml deleted file mode 100644 index 3d7a2f0dd..000000000 --- a/starter/pom.xml +++ /dev/null @@ -1,110 +0,0 @@ - - - - 4.0.0 - - - com.okta.spring - okta-spring-boot-parent - 0.7.0-SNAPSHOT - - - okta-spring-boot-simple - Okta Spring Boot :: Simple - - - - - org.springframework.boot - spring-boot-starter-security - - - com.okta.commons - okta-config-check - - - - - org.springframework.security - spring-security-config - - - org.springframework.boot - spring-boot-starter-security - - - org.springframework.security - spring-security-oauth2-client - - - org.springframework.security - spring-security-oauth2-jose - - - org.springframework.security - spring-security-oauth2-resource-server - - - - - org.springframework.boot - spring-boot-starter-web - - - - - org.springframework.boot - spring-boot-test - test - - - org.springframework - spring-test - test - - - org.mockito - mockito-core - test - - - - - - - - src/test/resources - true - - - - - org.jacoco - jacoco-maven-plugin - - - jacoco-report - - report - - verify - - - - - - \ No newline at end of file From b481927c3b300cf1f9fbf054f9b2a0f82694e1f6 Mon Sep 17 00:00:00 2001 From: Brian Demers Date: Wed, 7 Nov 2018 12:25:58 -0500 Subject: [PATCH 05/12] quick/dirty CI debug --- .travis.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index 54bf52bce..8da5039c5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,6 +16,9 @@ script: after_success: - bash <(curl -s https://codecov.io/bash) -f okta-spring-security-starter/target/site/jacoco/jacoco.xml +after_failure: + - find integration-tests/oauth2/target/failsafe-reports/ -type f | xargs -I{} sh -c 'echo {}; cat {}' + deploy: - provider: pages skip_cleanup: true From b9827e6292d551eb4bf9afc1bf5c5e7916ad44c4 Mon Sep 17 00:00:00 2001 From: Brian Demers Date: Wed, 7 Nov 2018 14:54:26 -0500 Subject: [PATCH 06/12] bump tck to 0.5.0 release --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index db46bad82..5378e6c9d 100644 --- a/pom.xml +++ b/pom.xml @@ -111,7 +111,7 @@ com.okta.oidc.tck okta-oidc-tck - 0.4.1-SNAPSHOT + 0.5.0 From c546a1e55d2decf383fac7ea993872905929fb85 Mon Sep 17 00:00:00 2001 From: Brian Demers Date: Wed, 7 Nov 2018 17:53:20 -0500 Subject: [PATCH 07/12] Bumped version to 1.0.0-SNAPSHOT --- coverage/pom.xml | 2 +- examples/config-server/pom.xml | 2 +- examples/hosted-login-code-flow/pom.xml | 4 ++-- examples/pom.xml | 2 +- examples/redirect-code-flow/pom.xml | 2 +- examples/siw-jquery/pom.xml | 4 ++-- integration-tests/oauth2/pom.xml | 2 +- oauth2/pom.xml | 2 +- okta-spring-boot-starter/pom.xml | 2 +- pom.xml | 10 +++++----- sdk/pom.xml | 2 +- 11 files changed, 17 insertions(+), 17 deletions(-) diff --git a/coverage/pom.xml b/coverage/pom.xml index 8eb472f8e..a006f2a27 100644 --- a/coverage/pom.xml +++ b/coverage/pom.xml @@ -20,7 +20,7 @@ com.okta.spring okta-spring-boot-parent - 0.7.0-SNAPSHOT + 1.0.0-SNAPSHOT okta-spring-boot-coverage diff --git a/examples/config-server/pom.xml b/examples/config-server/pom.xml index 5dc601c94..1c4ef1961 100644 --- a/examples/config-server/pom.xml +++ b/examples/config-server/pom.xml @@ -20,7 +20,7 @@ com.okta.spring.examples okta-spring-boot-examples - 0.7.0-SNAPSHOT + 1.0.0-SNAPSHOT okta-spring-boot-cloud-config-example diff --git a/examples/hosted-login-code-flow/pom.xml b/examples/hosted-login-code-flow/pom.xml index e4cac81c9..1380ab2b8 100644 --- a/examples/hosted-login-code-flow/pom.xml +++ b/examples/hosted-login-code-flow/pom.xml @@ -20,7 +20,7 @@ com.okta.spring.examples okta-spring-boot-examples - 0.7.0-SNAPSHOT + 1.0.0-SNAPSHOT okta-spring-boot-hosted-code-flow-example @@ -43,7 +43,7 @@ com.okta.spring okta-spring-sdk - 0.7.0-SNAPSHOT + 1.0.0-SNAPSHOT org.springframework.boot diff --git a/examples/pom.xml b/examples/pom.xml index 520ac4d0e..8426f8fb3 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -21,7 +21,7 @@ com.okta.spring okta-spring-boot-parent - 0.7.0-SNAPSHOT + 1.0.0-SNAPSHOT com.okta.spring.examples diff --git a/examples/redirect-code-flow/pom.xml b/examples/redirect-code-flow/pom.xml index 3ac68c795..1d38b0b1c 100644 --- a/examples/redirect-code-flow/pom.xml +++ b/examples/redirect-code-flow/pom.xml @@ -20,7 +20,7 @@ com.okta.spring.examples okta-spring-boot-examples - 0.7.0-SNAPSHOT + 1.0.0-SNAPSHOT okta-spring-boot-redirect-code-flow-example diff --git a/examples/siw-jquery/pom.xml b/examples/siw-jquery/pom.xml index 69708d317..c521b3cd9 100644 --- a/examples/siw-jquery/pom.xml +++ b/examples/siw-jquery/pom.xml @@ -20,7 +20,7 @@ com.okta.spring.examples okta-spring-boot-examples - 0.7.0-SNAPSHOT + 1.0.0-SNAPSHOT okta-spring-boot-siw-jquery-example @@ -52,7 +52,7 @@ com.okta.spring okta-spring-boot-starter - 0.7.0-SNAPSHOT + 1.0.0-SNAPSHOT diff --git a/integration-tests/oauth2/pom.xml b/integration-tests/oauth2/pom.xml index 7d633adb8..5844480c5 100644 --- a/integration-tests/oauth2/pom.xml +++ b/integration-tests/oauth2/pom.xml @@ -20,7 +20,7 @@ com.okta.spring okta-spring-boot-parent - 0.7.0-SNAPSHOT + 1.0.0-SNAPSHOT okta-spring-boot-integration-tests-oauth2 diff --git a/oauth2/pom.xml b/oauth2/pom.xml index d5e4cc326..1fdfb43e5 100644 --- a/oauth2/pom.xml +++ b/oauth2/pom.xml @@ -20,7 +20,7 @@ com.okta.spring okta-spring-boot-parent - 0.7.0-SNAPSHOT + 1.0.0-SNAPSHOT okta-spring-security-oauth2 diff --git a/okta-spring-boot-starter/pom.xml b/okta-spring-boot-starter/pom.xml index 1e6285ffb..9c9bfb35a 100644 --- a/okta-spring-boot-starter/pom.xml +++ b/okta-spring-boot-starter/pom.xml @@ -20,7 +20,7 @@ com.okta.spring okta-spring-boot-parent - 0.7.0-SNAPSHOT + 1.0.0-SNAPSHOT okta-spring-boot-starter diff --git a/pom.xml b/pom.xml index 5378e6c9d..b72f569d4 100644 --- a/pom.xml +++ b/pom.xml @@ -26,7 +26,7 @@ com.okta.spring okta-spring-boot-parent - 0.7.0-SNAPSHOT + 1.0.0-SNAPSHOT Okta Spring Boot pom @@ -69,23 +69,23 @@ com.okta.spring okta-spring-boot-starter - 0.7.0-SNAPSHOT + 1.0.0-SNAPSHOT com.okta.spring okta-spring-security-oauth2 - 0.7.0-SNAPSHOT + 1.0.0-SNAPSHOT com.okta.spring okta-spring-sdk - 0.7.0-SNAPSHOT + 1.0.0-SNAPSHOT com.okta.spring okta-spring-boot-integration-tests-oauth2 - 0.7.0-SNAPSHOT + 1.0.0-SNAPSHOT diff --git a/sdk/pom.xml b/sdk/pom.xml index 59f62b0be..0e6d2e7b8 100644 --- a/sdk/pom.xml +++ b/sdk/pom.xml @@ -20,7 +20,7 @@ com.okta.spring okta-spring-boot-parent - 0.7.0-SNAPSHOT + 1.0.0-SNAPSHOT okta-spring-sdk From 544094d88f0e903190eed92bf8fa43a02e1ea09f Mon Sep 17 00:00:00 2001 From: Brian Demers Date: Thu, 8 Nov 2018 16:37:58 -0500 Subject: [PATCH 08/12] Added User-Agent configuration back in --- examples/redirect-code-flow/pom.xml | 6 - .../SignInWidgetConfigController.java | 4 +- .../boot/oauth/OktaOAuth2Configurer.java | 37 +- .../OktaOAuth2ResourceServerAutoConfig.java | 10 + .../boot/oauth/OktaOAuth2UserService.java | 12 + .../boot/oauth/OktaOidcUserService.java | 1 + .../spring/boot/oauth/http/UserAgent.java | 485 ++++++++++++++++++ .../http/UserAgentRequestInterceptor.java | 18 + .../okta/spring/boot/oauth/http/Version.java | 55 ++ .../META-INF/okta/version.properties | 1 + .../spring/{ => oauth}/version.properties | 0 .../UserAgentRequestInterceptorTest.groovy | 34 ++ .../spring/boot/oauth/http/VersionTest.groovy | 25 + .../spring/boot/oauth/http/VersionUtil.java | 35 ++ 14 files changed, 711 insertions(+), 12 deletions(-) create mode 100644 oauth2/src/main/java/com/okta/spring/boot/oauth/http/UserAgent.java create mode 100644 oauth2/src/main/java/com/okta/spring/boot/oauth/http/UserAgentRequestInterceptor.java create mode 100644 oauth2/src/main/java/com/okta/spring/boot/oauth/http/Version.java create mode 100644 oauth2/src/main/resources/META-INF/okta/version.properties rename oauth2/src/main/resources/com/okta/spring/{ => oauth}/version.properties (100%) create mode 100644 oauth2/src/test/groovy/com/okta/spring/boot/oauth/http/UserAgentRequestInterceptorTest.groovy create mode 100644 oauth2/src/test/groovy/com/okta/spring/boot/oauth/http/VersionTest.groovy create mode 100644 oauth2/src/test/groovy/com/okta/spring/boot/oauth/http/VersionUtil.java diff --git a/examples/redirect-code-flow/pom.xml b/examples/redirect-code-flow/pom.xml index 1d38b0b1c..c0a3840e2 100644 --- a/examples/redirect-code-flow/pom.xml +++ b/examples/redirect-code-flow/pom.xml @@ -67,12 +67,6 @@ runtime - - org.springframework.cloud - spring-cloud-starter-config - ${spring-cloud.version} - - ch.qos.logback diff --git a/examples/siw-jquery/src/main/java/com/okta/spring/example/controllers/SignInWidgetConfigController.java b/examples/siw-jquery/src/main/java/com/okta/spring/example/controllers/SignInWidgetConfigController.java index 6122e4d59..744773f57 100644 --- a/examples/siw-jquery/src/main/java/com/okta/spring/example/controllers/SignInWidgetConfigController.java +++ b/examples/siw-jquery/src/main/java/com/okta/spring/example/controllers/SignInWidgetConfigController.java @@ -29,8 +29,8 @@ public class SignInWidgetConfigController { private final String clientId; - public SignInWidgetConfigController(@Value("okta.oauth2.issuer") String issuer, - @Value("okta.oauth2.client-id") String clientId) { + public SignInWidgetConfigController(@Value("${okta.oauth2.issuer}") String issuer, + @Value("${okta.oauth2.client-id}") String clientId) { this.issuer = issuer; this.clientId = clientId; } diff --git a/oauth2/src/main/java/com/okta/spring/boot/oauth/OktaOAuth2Configurer.java b/oauth2/src/main/java/com/okta/spring/boot/oauth/OktaOAuth2Configurer.java index 7f84f5308..c0a3b6199 100644 --- a/oauth2/src/main/java/com/okta/spring/boot/oauth/OktaOAuth2Configurer.java +++ b/oauth2/src/main/java/com/okta/spring/boot/oauth/OktaOAuth2Configurer.java @@ -16,11 +16,21 @@ package com.okta.spring.boot.oauth; import com.okta.spring.boot.oauth.config.OktaOAuth2Properties; +import com.okta.spring.boot.oauth.http.UserAgentRequestInterceptor; import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientProperties; import org.springframework.boot.autoconfigure.security.oauth2.resource.OAuth2ResourceServerProperties; import org.springframework.context.ApplicationContext; +import org.springframework.http.converter.FormHttpMessageConverter; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; +import org.springframework.security.oauth2.client.endpoint.DefaultAuthorizationCodeTokenResponseClient; +import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient; +import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest; +import org.springframework.security.oauth2.client.http.OAuth2ErrorResponseErrorHandler; +import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter; +import org.springframework.web.client.RestTemplate; + +import java.util.Arrays; final class OktaOAuth2Configurer extends AbstractHttpConfigurer { @@ -47,10 +57,14 @@ public void init(HttpSecurity http) throws Exception { } private void configureLogin(HttpSecurity http, OktaOAuth2Properties oktaOAuth2Properties) throws Exception { + http.oauth2Login() - .userInfoEndpoint() - .userService(new OktaOAuth2UserService(oktaOAuth2Properties.getGroupsClaim())) - .oidcUserService(new OktaOidcUserService(oktaOAuth2Properties.getGroupsClaim())); + .userInfoEndpoint() + .userService(new OktaOAuth2UserService(oktaOAuth2Properties.getGroupsClaim())) + .oidcUserService(new OktaOidcUserService(oktaOAuth2Properties.getGroupsClaim())) + .and() + .tokenEndpoint() + .accessTokenResponseClient(accessTokenResponseClient()); if (oktaOAuth2Properties.getRedirectUri() != null) { http.oauth2Login().redirectionEndpoint().baseUri(oktaOAuth2Properties.getRedirectUri()); @@ -58,6 +72,21 @@ private void configureLogin(HttpSecurity http, OktaOAuth2Properties oktaOAuth2Pr } private void configureResourceServer(HttpSecurity http, OktaOAuth2Properties oktaOAuth2Properties) throws Exception { - http.oauth2ResourceServer().jwt().jwtAuthenticationConverter(new OktaJwtAuthenticationConverter(oktaOAuth2Properties.getGroupsClaim())); + + http.oauth2ResourceServer() + .jwt().jwtAuthenticationConverter(new OktaJwtAuthenticationConverter(oktaOAuth2Properties.getGroupsClaim())); + } + + private OAuth2AccessTokenResponseClient accessTokenResponseClient() { + + RestTemplate restTemplate = new RestTemplate(Arrays.asList(new FormHttpMessageConverter(), + new OAuth2AccessTokenResponseHttpMessageConverter())); + restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler()); + restTemplate.getInterceptors().add(new UserAgentRequestInterceptor()); + + DefaultAuthorizationCodeTokenResponseClient accessTokenResponseClient = new DefaultAuthorizationCodeTokenResponseClient(); + accessTokenResponseClient.setRestOperations(restTemplate); + + return accessTokenResponseClient; } } \ No newline at end of file diff --git a/oauth2/src/main/java/com/okta/spring/boot/oauth/OktaOAuth2ResourceServerAutoConfig.java b/oauth2/src/main/java/com/okta/spring/boot/oauth/OktaOAuth2ResourceServerAutoConfig.java index acfe1ec48..5b6f4459d 100644 --- a/oauth2/src/main/java/com/okta/spring/boot/oauth/OktaOAuth2ResourceServerAutoConfig.java +++ b/oauth2/src/main/java/com/okta/spring/boot/oauth/OktaOAuth2ResourceServerAutoConfig.java @@ -16,6 +16,7 @@ package com.okta.spring.boot.oauth; import com.okta.spring.boot.oauth.config.OktaOAuth2Properties; +import com.okta.spring.boot.oauth.http.UserAgentRequestInterceptor; import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; @@ -37,6 +38,8 @@ import org.springframework.security.oauth2.jwt.JwtTimestampValidator; import org.springframework.security.oauth2.jwt.NimbusJwtDecoderJwkSupport; import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; +import org.springframework.web.client.RestOperations; +import org.springframework.web.client.RestTemplate; import java.util.ArrayList; import java.util.Collections; @@ -75,7 +78,14 @@ JwtDecoder jwtDecoder(OAuth2ResourceServerProperties oAuth2ResourceServerPropert NimbusJwtDecoderJwkSupport decoder = new NimbusJwtDecoderJwkSupport(oAuth2ResourceServerProperties.getJwt().getJwkSetUri()); decoder.setJwtValidator(validator); + decoder.setRestOperations(restOperations()); return decoder; } + + private RestOperations restOperations() { + RestTemplate restTemplate = new RestTemplate(); + restTemplate.getInterceptors().add(new UserAgentRequestInterceptor()); + return restTemplate; + } } \ No newline at end of file diff --git a/oauth2/src/main/java/com/okta/spring/boot/oauth/OktaOAuth2UserService.java b/oauth2/src/main/java/com/okta/spring/boot/oauth/OktaOAuth2UserService.java index 582ecd60c..3e6fd0e9c 100644 --- a/oauth2/src/main/java/com/okta/spring/boot/oauth/OktaOAuth2UserService.java +++ b/oauth2/src/main/java/com/okta/spring/boot/oauth/OktaOAuth2UserService.java @@ -15,11 +15,15 @@ */ package com.okta.spring.boot.oauth; +import com.okta.spring.boot.oauth.http.UserAgentRequestInterceptor; import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.oauth2.client.http.OAuth2ErrorResponseErrorHandler; import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService; import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; import org.springframework.security.oauth2.core.user.DefaultOAuth2User; import org.springframework.security.oauth2.core.user.OAuth2User; +import org.springframework.web.client.RestOperations; +import org.springframework.web.client.RestTemplate; import java.util.HashSet; import java.util.Set; @@ -30,6 +34,14 @@ final class OktaOAuth2UserService extends DefaultOAuth2UserService { OktaOAuth2UserService(String groupClaim) { this.groupClaim = groupClaim; + setRestOperations(restOperations()); + } + + private RestOperations restOperations() { + RestTemplate restTemplate = new RestTemplate(); + restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler()); + restTemplate.getInterceptors().add(new UserAgentRequestInterceptor()); + return restTemplate; } @Override diff --git a/oauth2/src/main/java/com/okta/spring/boot/oauth/OktaOidcUserService.java b/oauth2/src/main/java/com/okta/spring/boot/oauth/OktaOidcUserService.java index c9780a0e7..9f220b8ac 100644 --- a/oauth2/src/main/java/com/okta/spring/boot/oauth/OktaOidcUserService.java +++ b/oauth2/src/main/java/com/okta/spring/boot/oauth/OktaOidcUserService.java @@ -32,6 +32,7 @@ final class OktaOidcUserService extends OidcUserService { OktaOidcUserService(String groupClaim) { this.groupClaim = groupClaim; + this.setOauth2UserService(new OktaOAuth2UserService(groupClaim)); } @Override diff --git a/oauth2/src/main/java/com/okta/spring/boot/oauth/http/UserAgent.java b/oauth2/src/main/java/com/okta/spring/boot/oauth/http/UserAgent.java new file mode 100644 index 000000000..60fbce632 --- /dev/null +++ b/oauth2/src/main/java/com/okta/spring/boot/oauth/http/UserAgent.java @@ -0,0 +1,485 @@ +/* + * Copyright 2014 Stormpath, Inc. + * Modifications Copyright 2018 Okta, Inc. + * + * 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 + * + * http://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 com.okta.spring.boot.oauth.http; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.util.ClassUtils; +import org.springframework.util.StringUtils; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.lang.reflect.Method; +import java.nio.charset.StandardCharsets; +import java.util.Enumeration; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +/** + * This class is in charge of constructing the + * User-Agent http header string that will be sent to Okta in order to describe the current running environment of this Java SDK. + *

+ * The form of this string is the concatenation of the following sub-items: + *

    + *
  1. The okta integration and version separated by a '/'. If there is no integration being used, this can be omitted + *
  2. The okta sdk and version separated by a '/' + *
  3. The runtime information (runtime/version) + *
      + *
    1. Integration Runtime (if there is no integration being used, this can be omitted) + *
    2. SDK Runtime + *
    + *
  4. The OS common name and version separated by a '/'. + *
  5. All other system information included in parentheses + *
+ *

+ * The User-Agent value is created when this class is loaded. The string can be obtained just by invoking + * {@link UserAgent#getUserAgentString() UserAgent.getUserAgentString()}. + *

+ * This is a sample User-Agent string: + * okta-spring-security/0.7.0 okta-sdk-java/0.5.0 spring/4.0.4.RELEASE java/1.7.0_45 Mac OS X/10.9.2 (spring-security/3.2.0.RELEASE jetty/8.1.5.v20120716) + * + * @since 0.4.0 + */ +final class UserAgent { + + private static final Logger LOG = LoggerFactory.getLogger(UserAgent.class); + + //Integrations (aka Plugins) + private static final String INTEGRATION_SHIRO_ID = "okta-shiro"; + private static final String INTEGRATION_SHIRO_CLASS = "com.okta.shiro.realm.ApplicationRealm"; + private static final String INTEGRATION_SPRING_SECURITY_ID = "okta-spring-security"; + + //SDK + private static final String SDK_VERSION_FILE = "/com/okta/sdk/version.properties"; + private static final String OKTA_SDK_STRING = "okta-sdk-java"; + private static final String OKTA_SDK_CLASS = "com.okta.sdk.resource.user.User"; + + //Okta Zuul support + private static final String OKTA_ZUUL_ID = "okta-zuul"; + private static final String OKTA_ZUUL_CLASS = "com.okta.zuul.filter.AppliedRequestHeaderFilter"; + + //Okta Java Servlet Plugin + private static final String OKTA_SDK_SERVLET_ID = "okta-servlet-java"; + private static final String OKTA_SDK_SERVLET_CLASS = "com.okta.sdk.servlet.config.Config"; + + private static final String OKTA_SDK_RUNTIME_SPRING_WEBMVC_ID = "okta-spring-webmvc"; + private static final String OKTA_SDK_RUNTIME_SPRING_WEBMVC_CLASS = "com.okta.spring.mvc.AbstractSpringControllerConfig"; + + //Okta Spring Boot + private static final String OKTA_SDK_SPRING_BOOT_STARTER_ID = "okta-spring-boot-starter"; + private static final String OKTA_SDK_SPRING_BOOT_STARTER_CLASS = "com.okta.spring.boot.autoconfigure.OktaAutoConfiguration"; + + //Integration Runtimes + private static final String INTEGRATION_RUNTIME_SPRING_ID = "spring"; + private static final String INTEGRATION_RUNTIME_SPRING_CLASS = "org.springframework.context.ApplicationContext"; + + //Rapid Prototyping + private static final String RAPID_PROTOTYPING_SPRING_BOOT_ID = "spring-boot"; + private static final String RAPID_PROTOTYPING_SPRING_BOOT_CLASS = "org.springframework.boot.SpringApplication"; + + //Runtime + private static final String JAVA_SDK_RUNTIME_STRING = "java"; + + ////Additional Information//// + + //Security Frameworks + private static final String SECURITY_FRAMEWORK_SHIRO_ID = "shiro"; + private static final String SECURITY_FRAMEWORK_SHIRO_CLASS = "org.apache.shiro.SecurityUtils"; + private static final String SECURITY_FRAMEWORK_SPRING_SECURITY_ID = "spring-security"; + private static final String SECURITY_FRAMEWORK_SPRING_SECURITY_CLASS = "org.springframework.security.core.SpringSecurityCoreVersion"; + + //Web Servers + private static final String WEB_SERVER_TOMCAT_ID = "tomcat"; + private static final String WEB_SERVER_TOMCAT_BOOTSTRAP_CLASS = "org.apache.catalina.startup.Bootstrap"; + private static final String WEB_SERVER_TOMCAT_EMBEDDED_CLASS = "org.apache.catalina.startup.Tomcat"; + private static final String WEB_SERVER_JETTY_ID = "jetty"; + private static final String WEB_SERVER_JETTY_CLASS = "org.eclipse.jetty.servlet.listener.ELContextCleaner"; + private static final String WEB_SERVER_JBOSS_ID = "jboss"; + private static final String WEB_SERVER_JBOSS_CLASS = "org.jboss.as.security.plugins.AuthenticationCacheEvictionListener"; + private static final String WEB_SERVER_WEBSPHERE_ID = "websphere"; + private static final String WEB_SERVER_WEBSPHERE_CLASS = "com.ibm.websphere.product.VersionInfo"; + private static final String WEB_SERVER_GLASSFISH_ID = "glassfish"; + private static final String WEB_SERVER_GLASSFISH_CLASS = "com.sun.enterprise.glassfish.bootstrap.GlassFishMain"; + private static final String WEB_SERVER_WEBLOGIC_ID = "weblogic"; + private static final String WEB_SERVER_WEBLOGIC_CLASS = "weblogic.version"; + private static final String WEB_SERVER_WILDFLY_ID = "wildfly"; + private static final String WEB_SERVER_WILDFLY_CLASS = "org.jboss.as.security.ModuleName"; + + private static final String VERSION_SEPARATOR = "/"; + private static final String ENTRY_SEPARATOR = " "; + + //Placeholder for the actual User-Agent String + private static final String USER_AGENT = createUserAgentString(); + + private UserAgent() { + } + + public static String getUserAgentString() { + return USER_AGENT; + } + + private static String createUserAgentString() { + String userAgent = + getOktaSpringString() + // okta-spring-security + getOktaShiroString() + // okta-shiro + getOktaSDKComponentsString() + // okta-servlet-java | okta-spring-boot-starter + getOktaSdkString() + // okta-sdk-java + getSecurityFrameworkString() + // shiro | spring-security + getIntegrationRuntimeString() + // spring + getSpringBootString() + // spring-boot + getWebServerString() + // tomcat | jetty | jboss | websphere | glassfish | weblogic | wildfly + getJavaSDKRuntimeString() + // java + getOSString(); // Mac OS X + return userAgent.trim(); + } + + private static String getOktaShiroString() { + String integrationString; + integrationString = getFullEntryStringUsingPomProperties(INTEGRATION_SHIRO_CLASS, INTEGRATION_SHIRO_ID); + if (StringUtils.hasText(integrationString)) { + return integrationString; + } + return ""; + } + + private static String getOktaSpringString() { + return INTEGRATION_SPRING_SECURITY_ID + VERSION_SEPARATOR + Version.getClientVersion() + ENTRY_SEPARATOR; + } + + private static String getOktaSdkString() { + + if (ClassUtils.isPresent(OKTA_SDK_CLASS, null)) { + return OKTA_SDK_STRING + VERSION_SEPARATOR + Version.getClientVersion(SDK_VERSION_FILE) + ENTRY_SEPARATOR; + } + return ""; + } + + private static String getIntegrationRuntimeString() { + String integrationRuntimeString; + integrationRuntimeString = getFullEntryStringUsingManifest(INTEGRATION_RUNTIME_SPRING_CLASS, INTEGRATION_RUNTIME_SPRING_ID); + if (StringUtils.hasText(integrationRuntimeString)) { + return integrationRuntimeString; + } + return ""; + } + + private static String getJavaSDKRuntimeString() { + return JAVA_SDK_RUNTIME_STRING + VERSION_SEPARATOR + System.getProperty("java.version") + ENTRY_SEPARATOR; + } + + private static String getOSString() { + return System.getProperty("os.name") + VERSION_SEPARATOR + System.getProperty("os.version") + ENTRY_SEPARATOR; + } + + //Spring Boot + private static String getSpringBootString() { + String springBootStarter = getFullEntryStringUsingManifest(RAPID_PROTOTYPING_SPRING_BOOT_CLASS, RAPID_PROTOTYPING_SPRING_BOOT_ID); + if (StringUtils.hasText(springBootStarter)) { + return springBootStarter; + } + return ""; + } + + private static String getSecurityFrameworkString() { + + String securityFrameworkString; + securityFrameworkString = getFullEntryStringUsingManifest(SECURITY_FRAMEWORK_SHIRO_CLASS, SECURITY_FRAMEWORK_SHIRO_ID); + if (StringUtils.hasText(securityFrameworkString)) { + return securityFrameworkString; + } + securityFrameworkString = getFullEntryStringUsingManifest(SECURITY_FRAMEWORK_SPRING_SECURITY_CLASS, SECURITY_FRAMEWORK_SPRING_SECURITY_ID); + if (StringUtils.hasText(securityFrameworkString)) { + return securityFrameworkString; + } + return ""; + } + + //Okta SDK Components + private static String getOktaSDKComponentsString() { + + StringBuilder sb = new StringBuilder(); + + append(sb, getFullEntryStringUsingSDKVersion(OKTA_ZUUL_CLASS, OKTA_ZUUL_ID)); + append(sb, getFullEntryStringUsingSDKVersion(OKTA_SDK_SERVLET_CLASS, OKTA_SDK_SERVLET_ID)); + append(sb, getFullEntryStringUsingSDKVersion(OKTA_SDK_RUNTIME_SPRING_WEBMVC_CLASS, OKTA_SDK_RUNTIME_SPRING_WEBMVC_ID)); + append(sb, getFullEntryStringUsingSDKVersion(OKTA_SDK_SPRING_BOOT_STARTER_CLASS, OKTA_SDK_SPRING_BOOT_STARTER_ID)); + + return sb.toString(); + } + + private static void append(StringBuilder sb, String value) { + if (StringUtils.hasText(value)) { + sb.append(value); + } + } + + private static String getWebServerString() { + String webServerString; + //Glassfish uses Tomcat internally, therefore the Glassfish check must be carried out before Tomcat's + webServerString = getFullEntryStringUsingManifest(WEB_SERVER_GLASSFISH_CLASS, WEB_SERVER_GLASSFISH_ID); + if (StringUtils.hasText(webServerString)) { + return webServerString; + } + webServerString = getFullEntryStringUsingManifest(WEB_SERVER_TOMCAT_BOOTSTRAP_CLASS, WEB_SERVER_TOMCAT_ID); + if (StringUtils.hasText(webServerString)) { + return webServerString; + } + webServerString = getFullEntryStringUsingManifest(WEB_SERVER_TOMCAT_EMBEDDED_CLASS, WEB_SERVER_TOMCAT_ID); + if (StringUtils.hasText(webServerString)) { + return webServerString; + } + webServerString = getFullEntryStringUsingManifest(WEB_SERVER_JETTY_CLASS, WEB_SERVER_JETTY_ID); + if (StringUtils.hasText(webServerString)) { + return webServerString; + } + //WildFly must be before JBoss + webServerString = getWildFlyEntryString(); + if (StringUtils.hasText(webServerString)) { + return webServerString; + } + webServerString = getFullEntryStringUsingManifest(WEB_SERVER_JBOSS_CLASS, WEB_SERVER_JBOSS_ID); + if (StringUtils.hasText(webServerString)) { + return webServerString; + } + webServerString = getWebSphereEntryString(); + if (StringUtils.hasText(webServerString)) { + return webServerString; + } + webServerString = getWebLogicEntryString(); + if (StringUtils.hasText(webServerString)) { + return webServerString; + } + + return ""; + } + + private static String getFullEntryStringUsingPomProperties(String fqcn, String entryId) { + if (ClassUtils.isPresent(fqcn, null)) { + return entryId + VERSION_SEPARATOR + getVersionInfoFromPomProperties(fqcn) + ENTRY_SEPARATOR; + } + return null; + } + + private static String getFullEntryStringUsingManifest(String fqcn, String entryId) { + if (ClassUtils.isPresent(fqcn, null)) { + return entryId + VERSION_SEPARATOR + getVersionInfoInManifest(fqcn) + ENTRY_SEPARATOR; + } + return null; + } + + private static String getFullEntryStringUsingSDKVersion(String fqcn, String entryId) { + if (ClassUtils.isPresent(fqcn, null)) { + return entryId + VERSION_SEPARATOR + getVersionInfoInManifest(fqcn) + ENTRY_SEPARATOR; + } + return null; + } + + private static String getWebSphereEntryString() { + if (ClassUtils.isPresent(WEB_SERVER_WEBSPHERE_CLASS, null)) { + return WEB_SERVER_WEBSPHERE_ID + VERSION_SEPARATOR + getWebSphereVersion() + ENTRY_SEPARATOR; + } + return null; + } + + private static String getWebLogicEntryString() { + if (ClassUtils.isPresent(WEB_SERVER_WEBLOGIC_CLASS, null)) { + return WEB_SERVER_WEBLOGIC_ID + VERSION_SEPARATOR + getWebLogicVersion() + ENTRY_SEPARATOR; + } + return null; + } + + private static String getWildFlyEntryString() { + try { + if (ClassUtils.isPresent(WEB_SERVER_WILDFLY_CLASS, null)) { + Package wildFlyPkg = ClassUtils.forName(WEB_SERVER_WILDFLY_CLASS, null).getPackage(); + if (wildFlyPkg != null + && StringUtils.hasText(wildFlyPkg.getImplementationTitle()) && wildFlyPkg.getImplementationTitle().contains("WildFly")) { + return WEB_SERVER_WILDFLY_ID + VERSION_SEPARATOR + wildFlyPkg.getImplementationVersion() + ENTRY_SEPARATOR; + } + + } + + } catch (RuntimeException e) { + throw e; + } catch (Exception e){ //NOPMD + //there was a problem obtaining the WildFly version + } + return null; + } + + /** + * WARNING: This method must never be invoked unless we already know that the class identified by the parameter {@code fqcn} + * really exists in the classpath. For example, we first need to assure that {@code Classes.isAvailable(fqcn))} is TRUE + */ + private static String getVersionInfoFromPomProperties(String fqcn) { + String version = "unknown"; + try{ + Class clazz = ClassUtils.forName(fqcn, null); + String className = clazz.getSimpleName() + ".class"; + String classPath = clazz.getResource(className).toString(); + + String jarPath = null; + if (classPath.startsWith("jar:file:")) { + //Let's remove "jar:file:" from the beginning and also the className + jarPath = classPath.subSequence(9, classPath.lastIndexOf("!")).toString(); + } else if (classPath.startsWith("vfs:")) { + //Let's remove "vfs:" from the beginning and also the className + jarPath = classPath.subSequence(4, classPath.lastIndexOf(".jar") + 4).toString(); + } + + if (jarPath == null) { + //we were not able to obtain the jar path. Let's abort + return version; + } + + Enumeration enumeration; + String pomPropertiesPath; + try (JarFile jarFile = new JarFile(jarPath)) { + enumeration = jarFile.entries(); + } + pomPropertiesPath = null; + while (enumeration.hasMoreElements()) { + JarEntry entry = enumeration.nextElement(); + if (entry.getName().endsWith("pom.properties")) { + pomPropertiesPath = entry.getName(); + break; + } + } + if (pomPropertiesPath != null) { + try (BufferedReader reader = new BufferedReader(new InputStreamReader(clazz.getResourceAsStream("/" + pomPropertiesPath), StandardCharsets.UTF_8))) { + String line; + while ((line = reader.readLine()) != null) { + if (line.startsWith("version=")) { + version = line.split("=")[1]; + break; + } + } + } + } + } catch (RuntimeException e) { + throw e; + } catch (Exception e) { //NOPMD + //Either the jar file or the internal "pom.properties" file could not be read, there is nothing we can do... + } + return version; + } + + /** + * WARNING: This method must never be invoked unless we already know that the class identified by the parameter {@code fqcn} + * really exists in the classpath. For example, we first need to assure that {@code Classes.isAvailable(fqcn))} is TRUE + */ + private static String getVersionInfoInManifest(String fqcn){ + String version = null; + try { + //get class package + Package thePackage = ClassUtils.forName(fqcn, null).getPackage(); + //examine the package object + version = thePackage.getSpecificationVersion(); + if (!StringUtils.hasText(version)) { + version = thePackage.getImplementationVersion(); + } + } catch (ClassNotFoundException e) { + LOG.debug("Failed resolve version for class '{}'", fqcn, e); + } + + if (!StringUtils.hasText(version)) { + version = "null"; + } + return version; + } + + /** + * This method should only be invoked after already knowing that the class identified by {@code WEB_SERVER_WEBSPHERE_CLASS} + * really exists in the classpath. For example, it can be checked that {@code Classes.isAvailable(WEB_SERVER_WEBSPHERE_CLASS))} + * is {@code TRUE} + */ + private static String getWebSphereVersion() { + try { + Class versionClass = Class.forName(WEB_SERVER_WEBSPHERE_CLASS); + Object versionInfo = versionClass.newInstance(); + Method method = versionClass.getDeclaredMethod("runReport", String.class, PrintWriter.class); + StringWriter stringWriter = new StringWriter(); + PrintWriter printWriter = new PrintWriter(stringWriter); + method.invoke(versionInfo, "", printWriter); + String version = stringWriter.toString(); + // version looks like this, so we need to "substring" it: + // + // + //IBM WebSphere Product Installation Status Report + //-------------------------------------------------------------------------------- + // + //Report at date and time August 13, 2014 1:12:06 PM ART + // + //Installation + //-------------------------------------------------------------------------------- + //Product Directory C:\Program Files\IBM\WebSphere\AppServer + //Version Directory C:\Program Files\IBM\WebSphere\AppServer\properties\version + //DTD Directory C:\Program Files\IBM\WebSphere\AppServer\properties\version\dtd + //Log Directory C:\Documents and Settings\All Users\Application Data\IBM\Installation Manager\logs + // + //Product List + //-------------------------------------------------------------------------------- + //BASETRIAL installed + // + //Installed Product + //-------------------------------------------------------------------------------- + //Name IBM WebSphere Application Server + //Version 8.5.5.2 + //ID BASETRIAL + //Build Level cf021414.01 + //Build Date 4/8/14 + //Package com.ibm.websphere.BASETRIAL.v85_8.5.5002.20140408_1947 + //Architecture x86 (32 bit) + //Installed Features IBM 32-bit WebSphere SDK for Java + //WebSphere Application Server Full Profile + + version = version.substring(version.indexOf("Installed Product")); + version = version.substring(version.indexOf("Version")); + version = version.substring(version.indexOf(" "), version.indexOf("\n")).trim(); + return version; + + } catch (RuntimeException e) { + throw e; + } catch (Exception e) { //NOPMD + //there was a problem obtaining the WebSphere version + } + //returning null so we can identify in the User-Agent String that we are not properly handling some WebSphere version + return "null"; + } + + /** + * This method should only be invoked after already knowing that the class identified by {@code WEB_SERVER_WEBLOGIC_CLASS} + * really exists in the classpath. For example, it can be checked that {@code Classes.isAvailable(WEB_SERVER_WEBLOGIC_CLASS))} + * is {@code TRUE} + */ + private static String getWebLogicVersion() { + try { + Class versionClass = Class.forName(WEB_SERVER_WEBLOGIC_CLASS); + Object version = versionClass.newInstance(); + Method method = versionClass.getDeclaredMethod("getReleaseBuildVersion"); + return (String) method.invoke(version); + } catch (RuntimeException e) { + throw e; + } catch (Exception e) { //NOPMD + //there was a problem obtaining the WebLogic version + } + //returning null so we can identify in the User-Agent String that we are not properly handling some WebLogic version + return "null"; + } +} \ No newline at end of file diff --git a/oauth2/src/main/java/com/okta/spring/boot/oauth/http/UserAgentRequestInterceptor.java b/oauth2/src/main/java/com/okta/spring/boot/oauth/http/UserAgentRequestInterceptor.java new file mode 100644 index 000000000..de3e3d379 --- /dev/null +++ b/oauth2/src/main/java/com/okta/spring/boot/oauth/http/UserAgentRequestInterceptor.java @@ -0,0 +1,18 @@ +package com.okta.spring.boot.oauth.http; + +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpRequest; +import org.springframework.http.client.ClientHttpRequestExecution; +import org.springframework.http.client.ClientHttpRequestInterceptor; +import org.springframework.http.client.ClientHttpResponse; + +import java.io.IOException; + +public final class UserAgentRequestInterceptor implements ClientHttpRequestInterceptor { + + @Override + public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException { + request.getHeaders().add(HttpHeaders.USER_AGENT, UserAgent.getUserAgentString()); + return execution.execute(request, body); + } +} \ No newline at end of file diff --git a/oauth2/src/main/java/com/okta/spring/boot/oauth/http/Version.java b/oauth2/src/main/java/com/okta/spring/boot/oauth/http/Version.java new file mode 100644 index 000000000..773d3e0ba --- /dev/null +++ b/oauth2/src/main/java/com/okta/spring/boot/oauth/http/Version.java @@ -0,0 +1,55 @@ +/* + * Copyright 2014 Stormpath, Inc. + * Modifications Copyright 2018 Okta, Inc. + * + * 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 + * + * http://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 com.okta.spring.boot.oauth.http; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; + +/** + * @since 0.4.0 + */ +final class Version { + + private static final String VERSION_FILE = "/com/okta/spring/oauth/version.properties"; + private static final String CLIENT_VERSION = lookupClientVersion(VERSION_FILE); + + private Version() {} + + static String getClientVersion() { + return CLIENT_VERSION; + } + + static String getClientVersion(String versionFile) { + return lookupClientVersion(versionFile); + } + + private static String lookupClientVersion(String versionFile) { + Class clazz = Version.class; + + try (BufferedReader reader = new BufferedReader(new InputStreamReader(clazz.getResourceAsStream(versionFile), StandardCharsets.UTF_8))) { + String line; + do { + line = reader.readLine(); + } while (line != null && (line.startsWith("#") || line.isEmpty())); + return line; + } catch (IOException e) { + throw new IllegalStateException("Exception while trying to close file [" + versionFile + "].", e); + } + } +} \ No newline at end of file diff --git a/oauth2/src/main/resources/META-INF/okta/version.properties b/oauth2/src/main/resources/META-INF/okta/version.properties new file mode 100644 index 000000000..43d2fdcbf --- /dev/null +++ b/oauth2/src/main/resources/META-INF/okta/version.properties @@ -0,0 +1 @@ +okta-spring-security=${project.version} \ No newline at end of file diff --git a/oauth2/src/main/resources/com/okta/spring/version.properties b/oauth2/src/main/resources/com/okta/spring/oauth/version.properties similarity index 100% rename from oauth2/src/main/resources/com/okta/spring/version.properties rename to oauth2/src/main/resources/com/okta/spring/oauth/version.properties diff --git a/oauth2/src/test/groovy/com/okta/spring/boot/oauth/http/UserAgentRequestInterceptorTest.groovy b/oauth2/src/test/groovy/com/okta/spring/boot/oauth/http/UserAgentRequestInterceptorTest.groovy new file mode 100644 index 000000000..96a482f32 --- /dev/null +++ b/oauth2/src/test/groovy/com/okta/spring/boot/oauth/http/UserAgentRequestInterceptorTest.groovy @@ -0,0 +1,34 @@ +package com.okta.spring.boot.oauth.http + +import org.hamcrest.MatcherAssert +import org.springframework.http.HttpHeaders +import org.springframework.http.HttpRequest +import org.springframework.http.client.ClientHttpRequestExecution +import org.springframework.http.client.ClientHttpResponse +import org.testng.annotations.Test + +import static org.hamcrest.Matchers.is +import static org.mockito.Mockito.mock +import static org.mockito.Mockito.verify +import static org.mockito.Mockito.when +import static org.hamcrest.MatcherAssert.assertThat + +class UserAgentRequestInterceptorTest { + + @Test + void headerAddedTest() { + + def request = mock(HttpRequest) + def execution = mock(ClientHttpRequestExecution) + def response = mock(ClientHttpResponse) + def headers = mock(HttpHeaders) + + when(request.getHeaders()).thenReturn(headers) + when(execution.execute(request, null)).thenReturn(response) + + def underTest = new UserAgentRequestInterceptor() + assertThat underTest.intercept(request, null, execution), is(response) + + verify(headers).add("User-Agent", UserAgent.userAgentString) + } +} diff --git a/oauth2/src/test/groovy/com/okta/spring/boot/oauth/http/VersionTest.groovy b/oauth2/src/test/groovy/com/okta/spring/boot/oauth/http/VersionTest.groovy new file mode 100644 index 000000000..d79c2588c --- /dev/null +++ b/oauth2/src/test/groovy/com/okta/spring/boot/oauth/http/VersionTest.groovy @@ -0,0 +1,25 @@ +package com.okta.spring.boot.oauth.http + +import org.hamcrest.MatcherAssert +import org.hamcrest.Matchers +import org.springframework.util.Assert +import org.testng.annotations.Test + +import static org.hamcrest.MatcherAssert.assertThat +import static org.hamcrest.Matchers.* + +class VersionTest { + + @Test + void testGetClientVersion() { + assertThat Version.clientVersion, allOf(not(emptyString()), + containsString("."), + not(containsString("\$"))) + } + + @Test + void testVersionUtil() { + assertThat VersionUtil.userAgentsFromVersionMetadata(), containsString("okta-spring-security/${Version.clientVersion}") + } + +} diff --git a/oauth2/src/test/groovy/com/okta/spring/boot/oauth/http/VersionUtil.java b/oauth2/src/test/groovy/com/okta/spring/boot/oauth/http/VersionUtil.java new file mode 100644 index 000000000..7e7264dd3 --- /dev/null +++ b/oauth2/src/test/groovy/com/okta/spring/boot/oauth/http/VersionUtil.java @@ -0,0 +1,35 @@ +package com.okta.spring.boot.oauth.http; + +import java.io.IOException; +import java.net.URL; +import java.util.Properties; +import java.util.stream.Collectors; + +import static java.util.Collections.list; + +/** + * Future method to load version metadata, aggregate data from META-INF/okta/version.properties files + */ +public class VersionUtil { + + private static final String VERSION_FILE_LOCATION = "META-INF/okta/version.properties"; + + public static String userAgentsFromVersionMetadata() throws IOException { + return list(VersionUtil.class.getClassLoader().getResources(VERSION_FILE_LOCATION)).stream() + .map(VersionUtil::loadProps) + .map(properties -> properties.entrySet().stream() + .map(entry -> entry.getKey() + "/" + entry.getValue()) + .collect(Collectors.joining(" "))) + .collect(Collectors.joining(" ")); + } + + private static Properties loadProps(URL resourceUrl) { + try { + Properties props = new Properties(); + props.load(resourceUrl.openStream()); + return props; + } catch (IOException e) { + throw new IllegalStateException("Failed to open resource "+ resourceUrl, e); + } + } +} From cda7dc1284d6182b5a2ccb259ee8945757be9042 Mon Sep 17 00:00:00 2001 From: Brian Demers Date: Fri, 9 Nov 2018 11:11:07 -0500 Subject: [PATCH 09/12] Add missing license headers --- .../oauth/http/UserAgentRequestInterceptor.java | 15 +++++++++++++++ .../resources/META-INF/okta/version.properties | 16 ++++++++++++++++ .../http/UserAgentRequestInterceptorTest.groovy | 15 +++++++++++++++ .../spring/boot/oauth/http/VersionTest.groovy | 15 +++++++++++++++ .../okta/spring/boot/oauth/http/VersionUtil.java | 15 +++++++++++++++ 5 files changed, 76 insertions(+) diff --git a/oauth2/src/main/java/com/okta/spring/boot/oauth/http/UserAgentRequestInterceptor.java b/oauth2/src/main/java/com/okta/spring/boot/oauth/http/UserAgentRequestInterceptor.java index de3e3d379..b0a645bce 100644 --- a/oauth2/src/main/java/com/okta/spring/boot/oauth/http/UserAgentRequestInterceptor.java +++ b/oauth2/src/main/java/com/okta/spring/boot/oauth/http/UserAgentRequestInterceptor.java @@ -1,3 +1,18 @@ +/* + * Copyright 2018-Present Okta, Inc. + * + * 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 + * + * http://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 com.okta.spring.boot.oauth.http; import org.springframework.http.HttpHeaders; diff --git a/oauth2/src/main/resources/META-INF/okta/version.properties b/oauth2/src/main/resources/META-INF/okta/version.properties index 43d2fdcbf..4e365f612 100644 --- a/oauth2/src/main/resources/META-INF/okta/version.properties +++ b/oauth2/src/main/resources/META-INF/okta/version.properties @@ -1 +1,17 @@ +# +# Copyright 2018-Present Okta, Inc. +# +# 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 +# +# http://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. +# + okta-spring-security=${project.version} \ No newline at end of file diff --git a/oauth2/src/test/groovy/com/okta/spring/boot/oauth/http/UserAgentRequestInterceptorTest.groovy b/oauth2/src/test/groovy/com/okta/spring/boot/oauth/http/UserAgentRequestInterceptorTest.groovy index 96a482f32..30e32d0cc 100644 --- a/oauth2/src/test/groovy/com/okta/spring/boot/oauth/http/UserAgentRequestInterceptorTest.groovy +++ b/oauth2/src/test/groovy/com/okta/spring/boot/oauth/http/UserAgentRequestInterceptorTest.groovy @@ -1,3 +1,18 @@ +/* + * Copyright 2018-Present Okta, Inc. + * + * 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 + * + * http://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 com.okta.spring.boot.oauth.http import org.hamcrest.MatcherAssert diff --git a/oauth2/src/test/groovy/com/okta/spring/boot/oauth/http/VersionTest.groovy b/oauth2/src/test/groovy/com/okta/spring/boot/oauth/http/VersionTest.groovy index d79c2588c..e151d28bc 100644 --- a/oauth2/src/test/groovy/com/okta/spring/boot/oauth/http/VersionTest.groovy +++ b/oauth2/src/test/groovy/com/okta/spring/boot/oauth/http/VersionTest.groovy @@ -1,3 +1,18 @@ +/* + * Copyright 2018-Present Okta, Inc. + * + * 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 + * + * http://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 com.okta.spring.boot.oauth.http import org.hamcrest.MatcherAssert diff --git a/oauth2/src/test/groovy/com/okta/spring/boot/oauth/http/VersionUtil.java b/oauth2/src/test/groovy/com/okta/spring/boot/oauth/http/VersionUtil.java index 7e7264dd3..95e38d40d 100644 --- a/oauth2/src/test/groovy/com/okta/spring/boot/oauth/http/VersionUtil.java +++ b/oauth2/src/test/groovy/com/okta/spring/boot/oauth/http/VersionUtil.java @@ -1,3 +1,18 @@ +/* + * Copyright 2018-Present Okta, Inc. + * + * 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 + * + * http://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 com.okta.spring.boot.oauth.http; import java.io.IOException; From 453f0a355f423c84f5f671f319df90af5d800f8f Mon Sep 17 00:00:00 2001 From: Brian Demers Date: Wed, 14 Nov 2018 15:33:14 -0500 Subject: [PATCH 10/12] Fix the parent pom relative link for the ITs --- integration-tests/oauth2/pom.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/integration-tests/oauth2/pom.xml b/integration-tests/oauth2/pom.xml index 5844480c5..2cb0fdf87 100644 --- a/integration-tests/oauth2/pom.xml +++ b/integration-tests/oauth2/pom.xml @@ -21,6 +21,7 @@ com.okta.spring okta-spring-boot-parent 1.0.0-SNAPSHOT + ../.. okta-spring-boot-integration-tests-oauth2 From 48bb6079c870146fbffe731985619e963096254c Mon Sep 17 00:00:00 2001 From: Brian Demers Date: Fri, 30 Nov 2018 11:25:51 -0500 Subject: [PATCH 11/12] Updated to latest Spring-Boot 2.1.1 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index b72f569d4..5789d535a 100644 --- a/pom.xml +++ b/pom.xml @@ -31,7 +31,7 @@ pom - 2.1.0.RELEASE + 2.1.1.RELEASE 2.0.2.RELEASE okta/okta-spring-boot 1.3.0 From 804a274f24a22f8e12796370b1e0efd593230aaf Mon Sep 17 00:00:00 2001 From: Brian Demers Date: Fri, 14 Dec 2018 17:03:04 -0500 Subject: [PATCH 12/12] Removed duplicate UserAgent logic used okta-commons-lang ApplicationInfo instead --- oauth2/pom.xml | 4 + .../spring/boot/oauth/http/UserAgent.java | 485 ------------------ .../http/UserAgentRequestInterceptor.java | 8 +- .../okta/spring/boot/oauth/http/Version.java | 55 -- .../UserAgentRequestInterceptorTest.groovy | 20 +- .../spring/boot/oauth/http/VersionTest.groovy | 40 -- .../spring/boot/oauth/http/VersionUtil.java | 50 -- pom.xml | 8 +- 8 files changed, 36 insertions(+), 634 deletions(-) delete mode 100644 oauth2/src/main/java/com/okta/spring/boot/oauth/http/UserAgent.java delete mode 100644 oauth2/src/main/java/com/okta/spring/boot/oauth/http/Version.java delete mode 100644 oauth2/src/test/groovy/com/okta/spring/boot/oauth/http/VersionTest.groovy delete mode 100644 oauth2/src/test/groovy/com/okta/spring/boot/oauth/http/VersionUtil.java diff --git a/oauth2/pom.xml b/oauth2/pom.xml index 1fdfb43e5..131449b99 100644 --- a/oauth2/pom.xml +++ b/oauth2/pom.xml @@ -38,6 +38,10 @@ com.okta.commons okta-config-check + + com.okta.commons + okta-commons-lang + diff --git a/oauth2/src/main/java/com/okta/spring/boot/oauth/http/UserAgent.java b/oauth2/src/main/java/com/okta/spring/boot/oauth/http/UserAgent.java deleted file mode 100644 index 60fbce632..000000000 --- a/oauth2/src/main/java/com/okta/spring/boot/oauth/http/UserAgent.java +++ /dev/null @@ -1,485 +0,0 @@ -/* - * Copyright 2014 Stormpath, Inc. - * Modifications Copyright 2018 Okta, Inc. - * - * 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 - * - * http://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 com.okta.spring.boot.oauth.http; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.util.ClassUtils; -import org.springframework.util.StringUtils; - -import java.io.BufferedReader; -import java.io.InputStreamReader; -import java.io.PrintWriter; -import java.io.StringWriter; -import java.lang.reflect.Method; -import java.nio.charset.StandardCharsets; -import java.util.Enumeration; -import java.util.jar.JarEntry; -import java.util.jar.JarFile; - -/** - * This class is in charge of constructing the - * User-Agent http header string that will be sent to Okta in order to describe the current running environment of this Java SDK. - *

- * The form of this string is the concatenation of the following sub-items: - *

    - *
  1. The okta integration and version separated by a '/'. If there is no integration being used, this can be omitted - *
  2. The okta sdk and version separated by a '/' - *
  3. The runtime information (runtime/version) - *
      - *
    1. Integration Runtime (if there is no integration being used, this can be omitted) - *
    2. SDK Runtime - *
    - *
  4. The OS common name and version separated by a '/'. - *
  5. All other system information included in parentheses - *
- *

- * The User-Agent value is created when this class is loaded. The string can be obtained just by invoking - * {@link UserAgent#getUserAgentString() UserAgent.getUserAgentString()}. - *

- * This is a sample User-Agent string: - * okta-spring-security/0.7.0 okta-sdk-java/0.5.0 spring/4.0.4.RELEASE java/1.7.0_45 Mac OS X/10.9.2 (spring-security/3.2.0.RELEASE jetty/8.1.5.v20120716) - * - * @since 0.4.0 - */ -final class UserAgent { - - private static final Logger LOG = LoggerFactory.getLogger(UserAgent.class); - - //Integrations (aka Plugins) - private static final String INTEGRATION_SHIRO_ID = "okta-shiro"; - private static final String INTEGRATION_SHIRO_CLASS = "com.okta.shiro.realm.ApplicationRealm"; - private static final String INTEGRATION_SPRING_SECURITY_ID = "okta-spring-security"; - - //SDK - private static final String SDK_VERSION_FILE = "/com/okta/sdk/version.properties"; - private static final String OKTA_SDK_STRING = "okta-sdk-java"; - private static final String OKTA_SDK_CLASS = "com.okta.sdk.resource.user.User"; - - //Okta Zuul support - private static final String OKTA_ZUUL_ID = "okta-zuul"; - private static final String OKTA_ZUUL_CLASS = "com.okta.zuul.filter.AppliedRequestHeaderFilter"; - - //Okta Java Servlet Plugin - private static final String OKTA_SDK_SERVLET_ID = "okta-servlet-java"; - private static final String OKTA_SDK_SERVLET_CLASS = "com.okta.sdk.servlet.config.Config"; - - private static final String OKTA_SDK_RUNTIME_SPRING_WEBMVC_ID = "okta-spring-webmvc"; - private static final String OKTA_SDK_RUNTIME_SPRING_WEBMVC_CLASS = "com.okta.spring.mvc.AbstractSpringControllerConfig"; - - //Okta Spring Boot - private static final String OKTA_SDK_SPRING_BOOT_STARTER_ID = "okta-spring-boot-starter"; - private static final String OKTA_SDK_SPRING_BOOT_STARTER_CLASS = "com.okta.spring.boot.autoconfigure.OktaAutoConfiguration"; - - //Integration Runtimes - private static final String INTEGRATION_RUNTIME_SPRING_ID = "spring"; - private static final String INTEGRATION_RUNTIME_SPRING_CLASS = "org.springframework.context.ApplicationContext"; - - //Rapid Prototyping - private static final String RAPID_PROTOTYPING_SPRING_BOOT_ID = "spring-boot"; - private static final String RAPID_PROTOTYPING_SPRING_BOOT_CLASS = "org.springframework.boot.SpringApplication"; - - //Runtime - private static final String JAVA_SDK_RUNTIME_STRING = "java"; - - ////Additional Information//// - - //Security Frameworks - private static final String SECURITY_FRAMEWORK_SHIRO_ID = "shiro"; - private static final String SECURITY_FRAMEWORK_SHIRO_CLASS = "org.apache.shiro.SecurityUtils"; - private static final String SECURITY_FRAMEWORK_SPRING_SECURITY_ID = "spring-security"; - private static final String SECURITY_FRAMEWORK_SPRING_SECURITY_CLASS = "org.springframework.security.core.SpringSecurityCoreVersion"; - - //Web Servers - private static final String WEB_SERVER_TOMCAT_ID = "tomcat"; - private static final String WEB_SERVER_TOMCAT_BOOTSTRAP_CLASS = "org.apache.catalina.startup.Bootstrap"; - private static final String WEB_SERVER_TOMCAT_EMBEDDED_CLASS = "org.apache.catalina.startup.Tomcat"; - private static final String WEB_SERVER_JETTY_ID = "jetty"; - private static final String WEB_SERVER_JETTY_CLASS = "org.eclipse.jetty.servlet.listener.ELContextCleaner"; - private static final String WEB_SERVER_JBOSS_ID = "jboss"; - private static final String WEB_SERVER_JBOSS_CLASS = "org.jboss.as.security.plugins.AuthenticationCacheEvictionListener"; - private static final String WEB_SERVER_WEBSPHERE_ID = "websphere"; - private static final String WEB_SERVER_WEBSPHERE_CLASS = "com.ibm.websphere.product.VersionInfo"; - private static final String WEB_SERVER_GLASSFISH_ID = "glassfish"; - private static final String WEB_SERVER_GLASSFISH_CLASS = "com.sun.enterprise.glassfish.bootstrap.GlassFishMain"; - private static final String WEB_SERVER_WEBLOGIC_ID = "weblogic"; - private static final String WEB_SERVER_WEBLOGIC_CLASS = "weblogic.version"; - private static final String WEB_SERVER_WILDFLY_ID = "wildfly"; - private static final String WEB_SERVER_WILDFLY_CLASS = "org.jboss.as.security.ModuleName"; - - private static final String VERSION_SEPARATOR = "/"; - private static final String ENTRY_SEPARATOR = " "; - - //Placeholder for the actual User-Agent String - private static final String USER_AGENT = createUserAgentString(); - - private UserAgent() { - } - - public static String getUserAgentString() { - return USER_AGENT; - } - - private static String createUserAgentString() { - String userAgent = - getOktaSpringString() + // okta-spring-security - getOktaShiroString() + // okta-shiro - getOktaSDKComponentsString() + // okta-servlet-java | okta-spring-boot-starter - getOktaSdkString() + // okta-sdk-java - getSecurityFrameworkString() + // shiro | spring-security - getIntegrationRuntimeString() + // spring - getSpringBootString() + // spring-boot - getWebServerString() + // tomcat | jetty | jboss | websphere | glassfish | weblogic | wildfly - getJavaSDKRuntimeString() + // java - getOSString(); // Mac OS X - return userAgent.trim(); - } - - private static String getOktaShiroString() { - String integrationString; - integrationString = getFullEntryStringUsingPomProperties(INTEGRATION_SHIRO_CLASS, INTEGRATION_SHIRO_ID); - if (StringUtils.hasText(integrationString)) { - return integrationString; - } - return ""; - } - - private static String getOktaSpringString() { - return INTEGRATION_SPRING_SECURITY_ID + VERSION_SEPARATOR + Version.getClientVersion() + ENTRY_SEPARATOR; - } - - private static String getOktaSdkString() { - - if (ClassUtils.isPresent(OKTA_SDK_CLASS, null)) { - return OKTA_SDK_STRING + VERSION_SEPARATOR + Version.getClientVersion(SDK_VERSION_FILE) + ENTRY_SEPARATOR; - } - return ""; - } - - private static String getIntegrationRuntimeString() { - String integrationRuntimeString; - integrationRuntimeString = getFullEntryStringUsingManifest(INTEGRATION_RUNTIME_SPRING_CLASS, INTEGRATION_RUNTIME_SPRING_ID); - if (StringUtils.hasText(integrationRuntimeString)) { - return integrationRuntimeString; - } - return ""; - } - - private static String getJavaSDKRuntimeString() { - return JAVA_SDK_RUNTIME_STRING + VERSION_SEPARATOR + System.getProperty("java.version") + ENTRY_SEPARATOR; - } - - private static String getOSString() { - return System.getProperty("os.name") + VERSION_SEPARATOR + System.getProperty("os.version") + ENTRY_SEPARATOR; - } - - //Spring Boot - private static String getSpringBootString() { - String springBootStarter = getFullEntryStringUsingManifest(RAPID_PROTOTYPING_SPRING_BOOT_CLASS, RAPID_PROTOTYPING_SPRING_BOOT_ID); - if (StringUtils.hasText(springBootStarter)) { - return springBootStarter; - } - return ""; - } - - private static String getSecurityFrameworkString() { - - String securityFrameworkString; - securityFrameworkString = getFullEntryStringUsingManifest(SECURITY_FRAMEWORK_SHIRO_CLASS, SECURITY_FRAMEWORK_SHIRO_ID); - if (StringUtils.hasText(securityFrameworkString)) { - return securityFrameworkString; - } - securityFrameworkString = getFullEntryStringUsingManifest(SECURITY_FRAMEWORK_SPRING_SECURITY_CLASS, SECURITY_FRAMEWORK_SPRING_SECURITY_ID); - if (StringUtils.hasText(securityFrameworkString)) { - return securityFrameworkString; - } - return ""; - } - - //Okta SDK Components - private static String getOktaSDKComponentsString() { - - StringBuilder sb = new StringBuilder(); - - append(sb, getFullEntryStringUsingSDKVersion(OKTA_ZUUL_CLASS, OKTA_ZUUL_ID)); - append(sb, getFullEntryStringUsingSDKVersion(OKTA_SDK_SERVLET_CLASS, OKTA_SDK_SERVLET_ID)); - append(sb, getFullEntryStringUsingSDKVersion(OKTA_SDK_RUNTIME_SPRING_WEBMVC_CLASS, OKTA_SDK_RUNTIME_SPRING_WEBMVC_ID)); - append(sb, getFullEntryStringUsingSDKVersion(OKTA_SDK_SPRING_BOOT_STARTER_CLASS, OKTA_SDK_SPRING_BOOT_STARTER_ID)); - - return sb.toString(); - } - - private static void append(StringBuilder sb, String value) { - if (StringUtils.hasText(value)) { - sb.append(value); - } - } - - private static String getWebServerString() { - String webServerString; - //Glassfish uses Tomcat internally, therefore the Glassfish check must be carried out before Tomcat's - webServerString = getFullEntryStringUsingManifest(WEB_SERVER_GLASSFISH_CLASS, WEB_SERVER_GLASSFISH_ID); - if (StringUtils.hasText(webServerString)) { - return webServerString; - } - webServerString = getFullEntryStringUsingManifest(WEB_SERVER_TOMCAT_BOOTSTRAP_CLASS, WEB_SERVER_TOMCAT_ID); - if (StringUtils.hasText(webServerString)) { - return webServerString; - } - webServerString = getFullEntryStringUsingManifest(WEB_SERVER_TOMCAT_EMBEDDED_CLASS, WEB_SERVER_TOMCAT_ID); - if (StringUtils.hasText(webServerString)) { - return webServerString; - } - webServerString = getFullEntryStringUsingManifest(WEB_SERVER_JETTY_CLASS, WEB_SERVER_JETTY_ID); - if (StringUtils.hasText(webServerString)) { - return webServerString; - } - //WildFly must be before JBoss - webServerString = getWildFlyEntryString(); - if (StringUtils.hasText(webServerString)) { - return webServerString; - } - webServerString = getFullEntryStringUsingManifest(WEB_SERVER_JBOSS_CLASS, WEB_SERVER_JBOSS_ID); - if (StringUtils.hasText(webServerString)) { - return webServerString; - } - webServerString = getWebSphereEntryString(); - if (StringUtils.hasText(webServerString)) { - return webServerString; - } - webServerString = getWebLogicEntryString(); - if (StringUtils.hasText(webServerString)) { - return webServerString; - } - - return ""; - } - - private static String getFullEntryStringUsingPomProperties(String fqcn, String entryId) { - if (ClassUtils.isPresent(fqcn, null)) { - return entryId + VERSION_SEPARATOR + getVersionInfoFromPomProperties(fqcn) + ENTRY_SEPARATOR; - } - return null; - } - - private static String getFullEntryStringUsingManifest(String fqcn, String entryId) { - if (ClassUtils.isPresent(fqcn, null)) { - return entryId + VERSION_SEPARATOR + getVersionInfoInManifest(fqcn) + ENTRY_SEPARATOR; - } - return null; - } - - private static String getFullEntryStringUsingSDKVersion(String fqcn, String entryId) { - if (ClassUtils.isPresent(fqcn, null)) { - return entryId + VERSION_SEPARATOR + getVersionInfoInManifest(fqcn) + ENTRY_SEPARATOR; - } - return null; - } - - private static String getWebSphereEntryString() { - if (ClassUtils.isPresent(WEB_SERVER_WEBSPHERE_CLASS, null)) { - return WEB_SERVER_WEBSPHERE_ID + VERSION_SEPARATOR + getWebSphereVersion() + ENTRY_SEPARATOR; - } - return null; - } - - private static String getWebLogicEntryString() { - if (ClassUtils.isPresent(WEB_SERVER_WEBLOGIC_CLASS, null)) { - return WEB_SERVER_WEBLOGIC_ID + VERSION_SEPARATOR + getWebLogicVersion() + ENTRY_SEPARATOR; - } - return null; - } - - private static String getWildFlyEntryString() { - try { - if (ClassUtils.isPresent(WEB_SERVER_WILDFLY_CLASS, null)) { - Package wildFlyPkg = ClassUtils.forName(WEB_SERVER_WILDFLY_CLASS, null).getPackage(); - if (wildFlyPkg != null - && StringUtils.hasText(wildFlyPkg.getImplementationTitle()) && wildFlyPkg.getImplementationTitle().contains("WildFly")) { - return WEB_SERVER_WILDFLY_ID + VERSION_SEPARATOR + wildFlyPkg.getImplementationVersion() + ENTRY_SEPARATOR; - } - - } - - } catch (RuntimeException e) { - throw e; - } catch (Exception e){ //NOPMD - //there was a problem obtaining the WildFly version - } - return null; - } - - /** - * WARNING: This method must never be invoked unless we already know that the class identified by the parameter {@code fqcn} - * really exists in the classpath. For example, we first need to assure that {@code Classes.isAvailable(fqcn))} is TRUE - */ - private static String getVersionInfoFromPomProperties(String fqcn) { - String version = "unknown"; - try{ - Class clazz = ClassUtils.forName(fqcn, null); - String className = clazz.getSimpleName() + ".class"; - String classPath = clazz.getResource(className).toString(); - - String jarPath = null; - if (classPath.startsWith("jar:file:")) { - //Let's remove "jar:file:" from the beginning and also the className - jarPath = classPath.subSequence(9, classPath.lastIndexOf("!")).toString(); - } else if (classPath.startsWith("vfs:")) { - //Let's remove "vfs:" from the beginning and also the className - jarPath = classPath.subSequence(4, classPath.lastIndexOf(".jar") + 4).toString(); - } - - if (jarPath == null) { - //we were not able to obtain the jar path. Let's abort - return version; - } - - Enumeration enumeration; - String pomPropertiesPath; - try (JarFile jarFile = new JarFile(jarPath)) { - enumeration = jarFile.entries(); - } - pomPropertiesPath = null; - while (enumeration.hasMoreElements()) { - JarEntry entry = enumeration.nextElement(); - if (entry.getName().endsWith("pom.properties")) { - pomPropertiesPath = entry.getName(); - break; - } - } - if (pomPropertiesPath != null) { - try (BufferedReader reader = new BufferedReader(new InputStreamReader(clazz.getResourceAsStream("/" + pomPropertiesPath), StandardCharsets.UTF_8))) { - String line; - while ((line = reader.readLine()) != null) { - if (line.startsWith("version=")) { - version = line.split("=")[1]; - break; - } - } - } - } - } catch (RuntimeException e) { - throw e; - } catch (Exception e) { //NOPMD - //Either the jar file or the internal "pom.properties" file could not be read, there is nothing we can do... - } - return version; - } - - /** - * WARNING: This method must never be invoked unless we already know that the class identified by the parameter {@code fqcn} - * really exists in the classpath. For example, we first need to assure that {@code Classes.isAvailable(fqcn))} is TRUE - */ - private static String getVersionInfoInManifest(String fqcn){ - String version = null; - try { - //get class package - Package thePackage = ClassUtils.forName(fqcn, null).getPackage(); - //examine the package object - version = thePackage.getSpecificationVersion(); - if (!StringUtils.hasText(version)) { - version = thePackage.getImplementationVersion(); - } - } catch (ClassNotFoundException e) { - LOG.debug("Failed resolve version for class '{}'", fqcn, e); - } - - if (!StringUtils.hasText(version)) { - version = "null"; - } - return version; - } - - /** - * This method should only be invoked after already knowing that the class identified by {@code WEB_SERVER_WEBSPHERE_CLASS} - * really exists in the classpath. For example, it can be checked that {@code Classes.isAvailable(WEB_SERVER_WEBSPHERE_CLASS))} - * is {@code TRUE} - */ - private static String getWebSphereVersion() { - try { - Class versionClass = Class.forName(WEB_SERVER_WEBSPHERE_CLASS); - Object versionInfo = versionClass.newInstance(); - Method method = versionClass.getDeclaredMethod("runReport", String.class, PrintWriter.class); - StringWriter stringWriter = new StringWriter(); - PrintWriter printWriter = new PrintWriter(stringWriter); - method.invoke(versionInfo, "", printWriter); - String version = stringWriter.toString(); - // version looks like this, so we need to "substring" it: - // - // - //IBM WebSphere Product Installation Status Report - //-------------------------------------------------------------------------------- - // - //Report at date and time August 13, 2014 1:12:06 PM ART - // - //Installation - //-------------------------------------------------------------------------------- - //Product Directory C:\Program Files\IBM\WebSphere\AppServer - //Version Directory C:\Program Files\IBM\WebSphere\AppServer\properties\version - //DTD Directory C:\Program Files\IBM\WebSphere\AppServer\properties\version\dtd - //Log Directory C:\Documents and Settings\All Users\Application Data\IBM\Installation Manager\logs - // - //Product List - //-------------------------------------------------------------------------------- - //BASETRIAL installed - // - //Installed Product - //-------------------------------------------------------------------------------- - //Name IBM WebSphere Application Server - //Version 8.5.5.2 - //ID BASETRIAL - //Build Level cf021414.01 - //Build Date 4/8/14 - //Package com.ibm.websphere.BASETRIAL.v85_8.5.5002.20140408_1947 - //Architecture x86 (32 bit) - //Installed Features IBM 32-bit WebSphere SDK for Java - //WebSphere Application Server Full Profile - - version = version.substring(version.indexOf("Installed Product")); - version = version.substring(version.indexOf("Version")); - version = version.substring(version.indexOf(" "), version.indexOf("\n")).trim(); - return version; - - } catch (RuntimeException e) { - throw e; - } catch (Exception e) { //NOPMD - //there was a problem obtaining the WebSphere version - } - //returning null so we can identify in the User-Agent String that we are not properly handling some WebSphere version - return "null"; - } - - /** - * This method should only be invoked after already knowing that the class identified by {@code WEB_SERVER_WEBLOGIC_CLASS} - * really exists in the classpath. For example, it can be checked that {@code Classes.isAvailable(WEB_SERVER_WEBLOGIC_CLASS))} - * is {@code TRUE} - */ - private static String getWebLogicVersion() { - try { - Class versionClass = Class.forName(WEB_SERVER_WEBLOGIC_CLASS); - Object version = versionClass.newInstance(); - Method method = versionClass.getDeclaredMethod("getReleaseBuildVersion"); - return (String) method.invoke(version); - } catch (RuntimeException e) { - throw e; - } catch (Exception e) { //NOPMD - //there was a problem obtaining the WebLogic version - } - //returning null so we can identify in the User-Agent String that we are not properly handling some WebLogic version - return "null"; - } -} \ No newline at end of file diff --git a/oauth2/src/main/java/com/okta/spring/boot/oauth/http/UserAgentRequestInterceptor.java b/oauth2/src/main/java/com/okta/spring/boot/oauth/http/UserAgentRequestInterceptor.java index b0a645bce..99a11fbde 100644 --- a/oauth2/src/main/java/com/okta/spring/boot/oauth/http/UserAgentRequestInterceptor.java +++ b/oauth2/src/main/java/com/okta/spring/boot/oauth/http/UserAgentRequestInterceptor.java @@ -15,6 +15,7 @@ */ package com.okta.spring.boot.oauth.http; +import com.okta.commons.lang.ApplicationInfo; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpRequest; import org.springframework.http.client.ClientHttpRequestExecution; @@ -22,12 +23,17 @@ import org.springframework.http.client.ClientHttpResponse; import java.io.IOException; +import java.util.stream.Collectors; public final class UserAgentRequestInterceptor implements ClientHttpRequestInterceptor { + private final static String USER_AGENT_VALUE = ApplicationInfo.get().entrySet().stream() + .map(e -> e.getKey() + "/" + e.getValue()) + .collect(Collectors.joining(" ")); + @Override public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException { - request.getHeaders().add(HttpHeaders.USER_AGENT, UserAgent.getUserAgentString()); + request.getHeaders().add(HttpHeaders.USER_AGENT, USER_AGENT_VALUE); return execution.execute(request, body); } } \ No newline at end of file diff --git a/oauth2/src/main/java/com/okta/spring/boot/oauth/http/Version.java b/oauth2/src/main/java/com/okta/spring/boot/oauth/http/Version.java deleted file mode 100644 index 773d3e0ba..000000000 --- a/oauth2/src/main/java/com/okta/spring/boot/oauth/http/Version.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2014 Stormpath, Inc. - * Modifications Copyright 2018 Okta, Inc. - * - * 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 - * - * http://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 com.okta.spring.boot.oauth.http; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.nio.charset.StandardCharsets; - -/** - * @since 0.4.0 - */ -final class Version { - - private static final String VERSION_FILE = "/com/okta/spring/oauth/version.properties"; - private static final String CLIENT_VERSION = lookupClientVersion(VERSION_FILE); - - private Version() {} - - static String getClientVersion() { - return CLIENT_VERSION; - } - - static String getClientVersion(String versionFile) { - return lookupClientVersion(versionFile); - } - - private static String lookupClientVersion(String versionFile) { - Class clazz = Version.class; - - try (BufferedReader reader = new BufferedReader(new InputStreamReader(clazz.getResourceAsStream(versionFile), StandardCharsets.UTF_8))) { - String line; - do { - line = reader.readLine(); - } while (line != null && (line.startsWith("#") || line.isEmpty())); - return line; - } catch (IOException e) { - throw new IllegalStateException("Exception while trying to close file [" + versionFile + "].", e); - } - } -} \ No newline at end of file diff --git a/oauth2/src/test/groovy/com/okta/spring/boot/oauth/http/UserAgentRequestInterceptorTest.groovy b/oauth2/src/test/groovy/com/okta/spring/boot/oauth/http/UserAgentRequestInterceptorTest.groovy index 30e32d0cc..4bf69958c 100644 --- a/oauth2/src/test/groovy/com/okta/spring/boot/oauth/http/UserAgentRequestInterceptorTest.groovy +++ b/oauth2/src/test/groovy/com/okta/spring/boot/oauth/http/UserAgentRequestInterceptorTest.groovy @@ -15,14 +15,19 @@ */ package com.okta.spring.boot.oauth.http -import org.hamcrest.MatcherAssert +import org.mockito.ArgumentCaptor import org.springframework.http.HttpHeaders import org.springframework.http.HttpRequest import org.springframework.http.client.ClientHttpRequestExecution import org.springframework.http.client.ClientHttpResponse import org.testng.annotations.Test +import static org.hamcrest.Matchers.allOf +import static org.hamcrest.Matchers.containsString import static org.hamcrest.Matchers.is +import static org.hamcrest.Matchers.matchesPattern +import static org.hamcrest.Matchers.not +import static org.mockito.ArgumentMatchers.eq import static org.mockito.Mockito.mock import static org.mockito.Mockito.verify import static org.mockito.Mockito.when @@ -44,6 +49,17 @@ class UserAgentRequestInterceptorTest { def underTest = new UserAgentRequestInterceptor() assertThat underTest.intercept(request, null, execution), is(response) - verify(headers).add("User-Agent", UserAgent.userAgentString) + def userAgentCapture = ArgumentCaptor.forClass(String) + + verify(headers).add(eq("User-Agent"), userAgentCapture.capture()) + def userAgentString = userAgentCapture.getValue() + assertThat userAgentString, allOf( + matchesPattern(".*okta-spring-security/\\d.*"), + matchesPattern(".* spring/\\d.*"), + matchesPattern(".* spring-boot/\\d.*"), + containsString("java/${System.getProperty("java.version")}"), + containsString("${System.getProperty("os.name")}/${System.getProperty("os.version")}"), + not(containsString('${')) + ) } } diff --git a/oauth2/src/test/groovy/com/okta/spring/boot/oauth/http/VersionTest.groovy b/oauth2/src/test/groovy/com/okta/spring/boot/oauth/http/VersionTest.groovy deleted file mode 100644 index e151d28bc..000000000 --- a/oauth2/src/test/groovy/com/okta/spring/boot/oauth/http/VersionTest.groovy +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2018-Present Okta, Inc. - * - * 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 - * - * http://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 com.okta.spring.boot.oauth.http - -import org.hamcrest.MatcherAssert -import org.hamcrest.Matchers -import org.springframework.util.Assert -import org.testng.annotations.Test - -import static org.hamcrest.MatcherAssert.assertThat -import static org.hamcrest.Matchers.* - -class VersionTest { - - @Test - void testGetClientVersion() { - assertThat Version.clientVersion, allOf(not(emptyString()), - containsString("."), - not(containsString("\$"))) - } - - @Test - void testVersionUtil() { - assertThat VersionUtil.userAgentsFromVersionMetadata(), containsString("okta-spring-security/${Version.clientVersion}") - } - -} diff --git a/oauth2/src/test/groovy/com/okta/spring/boot/oauth/http/VersionUtil.java b/oauth2/src/test/groovy/com/okta/spring/boot/oauth/http/VersionUtil.java deleted file mode 100644 index 95e38d40d..000000000 --- a/oauth2/src/test/groovy/com/okta/spring/boot/oauth/http/VersionUtil.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright 2018-Present Okta, Inc. - * - * 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 - * - * http://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 com.okta.spring.boot.oauth.http; - -import java.io.IOException; -import java.net.URL; -import java.util.Properties; -import java.util.stream.Collectors; - -import static java.util.Collections.list; - -/** - * Future method to load version metadata, aggregate data from META-INF/okta/version.properties files - */ -public class VersionUtil { - - private static final String VERSION_FILE_LOCATION = "META-INF/okta/version.properties"; - - public static String userAgentsFromVersionMetadata() throws IOException { - return list(VersionUtil.class.getClassLoader().getResources(VERSION_FILE_LOCATION)).stream() - .map(VersionUtil::loadProps) - .map(properties -> properties.entrySet().stream() - .map(entry -> entry.getKey() + "/" + entry.getValue()) - .collect(Collectors.joining(" "))) - .collect(Collectors.joining(" ")); - } - - private static Properties loadProps(URL resourceUrl) { - try { - Properties props = new Properties(); - props.load(resourceUrl.openStream()); - return props; - } catch (IOException e) { - throw new IllegalStateException("Failed to open resource "+ resourceUrl, e); - } - } -} diff --git a/pom.xml b/pom.xml index 5789d535a..901d950b8 100644 --- a/pom.xml +++ b/pom.xml @@ -35,6 +35,7 @@ 2.0.2.RELEASE okta/okta-spring-boot 1.3.0 + 1.1.0 @@ -106,7 +107,12 @@ com.okta.commons okta-config-check - 1.0.0 + ${okta.commons.version} + + + com.okta.commons + okta-commons-lang + ${okta.commons.version} com.okta.oidc.tck