diff --git a/docs/modules/ROOT/pages/reactive/oauth2/resource-server/jwt.adoc b/docs/modules/ROOT/pages/reactive/oauth2/resource-server/jwt.adoc index 8b704c2627..519da49f77 100644 --- a/docs/modules/ROOT/pages/reactive/oauth2/resource-server/jwt.adoc +++ b/docs/modules/ROOT/pages/reactive/oauth2/resource-server/jwt.adoc @@ -165,11 +165,13 @@ Java:: + [source,java,role="primary"] ---- +import static org.springframework.security.oauth2.core.authorization.OAuth2ReactiveAuthorizationManagers.hasScope; + @Bean SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) { http .authorizeExchange(exchanges -> exchanges - .pathMatchers("/message/**").hasAuthority("SCOPE_message:read") + .pathMatchers("/message/**").access(hasScope("message:read")) .anyExchange().authenticated() ) .oauth2ResourceServer(oauth2 -> oauth2 @@ -183,11 +185,13 @@ Kotlin:: + [source,kotlin,role="secondary"] ---- +import org.springframework.security.oauth2.core.authorization.OAuth2ReactiveAuthorizationManagers.hasScope + @Bean fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain { return http { authorizeExchange { - authorize("/message/**", hasAuthority("SCOPE_message:read")) + authorize("/message/**", hasScope("message:read")) authorize(anyExchange, authenticated) } oauth2ResourceServer { @@ -682,12 +686,14 @@ Java:: + [source,java,role="primary"] ---- +import static org.springframework.security.oauth2.core.authorization.OAuth2ReactiveAuthorizationManagers.hasScope; + @Bean SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) { http .authorizeExchange(exchanges -> exchanges - .mvcMatchers("/contacts/**").hasAuthority("SCOPE_contacts") - .mvcMatchers("/messages/**").hasAuthority("SCOPE_messages") + .mvcMatchers("/contacts/**").access(hasScope("contacts")) + .mvcMatchers("/messages/**").access(hasScope("messages")) .anyExchange().authenticated() ) .oauth2ResourceServer(OAuth2ResourceServerSpec::jwt); @@ -699,12 +705,14 @@ Kotlin:: + [source,kotlin,role="secondary"] ---- +import org.springframework.security.oauth2.core.authorization.OAuth2ReactiveAuthorizationManagers.hasScope + @Bean fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain { return http { authorizeExchange { - authorize("/contacts/**", hasAuthority("SCOPE_contacts")) - authorize("/messages/**", hasAuthority("SCOPE_messages")) + authorize("/contacts/**", hasScope("contacts")) + authorize("/messages/**", hasScope("messages")) authorize(anyExchange, authenticated) } oauth2ResourceServer { diff --git a/docs/modules/ROOT/pages/reactive/oauth2/resource-server/opaque-token.adoc b/docs/modules/ROOT/pages/reactive/oauth2/resource-server/opaque-token.adoc index bd622258ed..8cab012346 100644 --- a/docs/modules/ROOT/pages/reactive/oauth2/resource-server/opaque-token.adoc +++ b/docs/modules/ROOT/pages/reactive/oauth2/resource-server/opaque-token.adoc @@ -214,6 +214,8 @@ Java:: + [source,java,role="primary"] ---- +import static org.springframework.security.oauth2.core.authorization.OAuth2ReactiveAuthorizationManagers.hasScope; + @Configuration @EnableWebFluxSecurity public class MyCustomSecurityConfiguration { @@ -221,7 +223,7 @@ public class MyCustomSecurityConfiguration { SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) { http .authorizeExchange(exchanges -> exchanges - .pathMatchers("/messages/**").hasAuthority("SCOPE_message:read") + .pathMatchers("/messages/**").access(hasScope("message:read")) .anyExchange().authenticated() ) .oauth2ResourceServer(oauth2 -> oauth2 @@ -238,11 +240,13 @@ Kotlin:: + [source,kotlin,role="secondary"] ---- +import org.springframework.security.oauth2.core.authorization.OAuth2ReactiveAuthorizationManagers.hasScope + @Bean fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain { return http { authorizeExchange { - authorize("/messages/**", hasAuthority("SCOPE_message:read")) + authorize("/messages/**", hasScope("message:read")) authorize(anyExchange, authenticated) } oauth2ResourceServer { @@ -442,6 +446,8 @@ Java:: + [source,java,role="primary"] ---- +import static org.springframework.security.oauth2.core.authorization.OAuth2ReactiveAuthorizationManagers.hasScope; + @Configuration @EnableWebFluxSecurity public class MappedAuthorities { @@ -449,8 +455,8 @@ public class MappedAuthorities { SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) { http .authorizeExchange(exchange -> exchange - .pathMatchers("/contacts/**").hasAuthority("SCOPE_contacts") - .pathMatchers("/messages/**").hasAuthority("SCOPE_messages") + .pathMatchers("/contacts/**").access(hasScope("contacts")) + .pathMatchers("/messages/**").access(hasScope("messages")) .anyExchange().authenticated() ) .oauth2ResourceServer(ServerHttpSecurity.OAuth2ResourceServerSpec::opaqueToken); @@ -463,12 +469,14 @@ Kotlin:: + [source,kotlin,role="secondary"] ---- +import org.springframework.security.oauth2.core.authorization.OAuth2ReactiveAuthorizationManagers.hasScope + @Bean fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain { return http { authorizeExchange { - authorize("/contacts/**", hasAuthority("SCOPE_contacts")) - authorize("/messages/**", hasAuthority("SCOPE_messages")) + authorize("/contacts/**", hasScope("contacts")) + authorize("/messages/**", hasScope("messages")) authorize(anyExchange, authenticated) } oauth2ResourceServer { diff --git a/docs/modules/ROOT/pages/servlet/oauth2/resource-server/jwt.adoc b/docs/modules/ROOT/pages/servlet/oauth2/resource-server/jwt.adoc index 0c03d4ecd6..5353163642 100644 --- a/docs/modules/ROOT/pages/servlet/oauth2/resource-server/jwt.adoc +++ b/docs/modules/ROOT/pages/servlet/oauth2/resource-server/jwt.adoc @@ -211,6 +211,8 @@ Java:: + [source,java,role="primary"] ---- +import static org.springframework.security.oauth2.core.authorization.OAuth2AuthorizationManagers.hasScope; + @Configuration @EnableWebSecurity public class MyCustomSecurityConfiguration { @@ -218,7 +220,7 @@ public class MyCustomSecurityConfiguration { public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(authorize -> authorize - .requestMatchers("/messages/**").hasAuthority("SCOPE_message:read") + .requestMatchers("/messages/**").access(hasScope("message:read")) .anyRequest().authenticated() ) .oauth2ResourceServer(oauth2 -> oauth2 @@ -235,6 +237,8 @@ Kotlin:: + [source,kotlin,role="secondary"] ---- +import org.springframework.security.oauth2.core.authorization.OAuth2AuthorizationManagers.hasScope + @Configuration @EnableWebSecurity class MyCustomSecurityConfiguration { @@ -242,7 +246,7 @@ class MyCustomSecurityConfiguration { open fun filterChain(http: HttpSecurity): SecurityFilterChain { http { authorizeRequests { - authorize("/messages/**", hasAuthority("SCOPE_message:read")) + authorize("/messages/**", hasScope("message:read")) authorize(anyRequest, authenticated) } oauth2ResourceServer { @@ -862,6 +866,8 @@ Java:: + [source,java,role="primary"] ---- +import static org.springframework.security.oauth2.core.authorization.OAuth2AuthorizationManagers.hasScope; + @Configuration @EnableWebSecurity public class DirectlyConfiguredJwkSetUri { @@ -869,8 +875,8 @@ public class DirectlyConfiguredJwkSetUri { public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(authorize -> authorize - .requestMatchers("/contacts/**").hasAuthority("SCOPE_contacts") - .requestMatchers("/messages/**").hasAuthority("SCOPE_messages") + .requestMatchers("/contacts/**").access(hasScope("contacts")) + .requestMatchers("/messages/**").access(hasScope("messages")) .anyRequest().authenticated() ) .oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt); @@ -883,6 +889,8 @@ Kotlin:: + [source,kotlin,role="secondary"] ---- +import org.springframework.security.oauth2.core.authorization.OAuth2AuthorizationManagers.hasScope; + @Configuration @EnableWebSecurity class DirectlyConfiguredJwkSetUri { @@ -890,8 +898,8 @@ class DirectlyConfiguredJwkSetUri { open fun filterChain(http: HttpSecurity): SecurityFilterChain { http { authorizeRequests { - authorize("/contacts/**", hasAuthority("SCOPE_contacts")) - authorize("/messages/**", hasAuthority("SCOPE_messages")) + authorize("/contacts/**", hasScope("contacts")) + authorize("/messages/**", hasScope("messages")) authorize(anyRequest, authenticated) } oauth2ResourceServer { diff --git a/docs/modules/ROOT/pages/servlet/oauth2/resource-server/opaque-token.adoc b/docs/modules/ROOT/pages/servlet/oauth2/resource-server/opaque-token.adoc index 45a6f20be9..fcd3b4f2c4 100644 --- a/docs/modules/ROOT/pages/servlet/oauth2/resource-server/opaque-token.adoc +++ b/docs/modules/ROOT/pages/servlet/oauth2/resource-server/opaque-token.adoc @@ -239,6 +239,8 @@ Java:: + [source,java,role="primary"] ---- +import static org.springframework.security.oauth2.core.authorization.OAuth2AuthorizationManagers.hasScope; + @Configuration @EnableWebSecurity public class MyCustomSecurityConfiguration { @@ -246,7 +248,7 @@ public class MyCustomSecurityConfiguration { public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(authorize -> authorize - .requestMatchers("/messages/**").hasAuthority("SCOPE_message:read") + .requestMatchers("/messages/**").access(hasScope("message:read")) .anyRequest().authenticated() ) .oauth2ResourceServer(oauth2 -> oauth2 @@ -263,6 +265,8 @@ Kotlin:: + [source,kotlin,role="secondary"] ---- +import org.springframework.security.oauth2.core.authorization.OAuth2AuthorizationManagers.hasScope; + @Configuration @EnableWebSecurity class MyCustomSecurityConfiguration { @@ -270,7 +274,7 @@ class MyCustomSecurityConfiguration { open fun filterChain(http: HttpSecurity): SecurityFilterChain { http { authorizeRequests { - authorize("/messages/**", hasAuthority("SCOPE_message:read")) + authorize("/messages/**", hasScope("SCOPE_message:read")) authorize(anyRequest, authenticated) } oauth2ResourceServer { @@ -547,6 +551,8 @@ Java:: + [source,java,role="primary"] ---- +import static org.springframework.security.oauth2.core.authorization.OAuth2AuthorizationManagers.hasScope; + @Configuration @EnableWebSecurity public class MappedAuthorities { @@ -554,8 +560,8 @@ public class MappedAuthorities { public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(authorizeRequests -> authorizeRequests - .requestMatchers("/contacts/**").hasAuthority("SCOPE_contacts") - .requestMatchers("/messages/**").hasAuthority("SCOPE_messages") + .requestMatchers("/contacts/**").access(hasScope("contacts")) + .requestMatchers("/messages/**").access(hasScope("messages")) .anyRequest().authenticated() ) .oauth2ResourceServer(OAuth2ResourceServerConfigurer::opaqueToken); @@ -568,6 +574,8 @@ Kotlin:: + [source,kotlin,role="secondary"] ---- +import org.springframework.security.oauth2.core.authorization.OAuth2AuthorizationManagers.hasScope + @Configuration @EnableWebSecurity class MappedAuthorities { @@ -575,8 +583,8 @@ class MappedAuthorities { open fun filterChain(http: HttpSecurity): SecurityFilterChain { http { authorizeRequests { - authorize("/contacts/**", hasAuthority("SCOPE_contacts")) - authorize("/messages/**", hasAuthority("SCOPE_messages")) + authorize("/contacts/**", hasScope("contacts")) + authorize("/messages/**", hasScope("messages")) authorize(anyRequest, authenticated) } oauth2ResourceServer { diff --git a/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/OAuth2AuthorizationManagers.java b/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/OAuth2AuthorizationManagers.java deleted file mode 100644 index 7f0368f902..0000000000 --- a/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/OAuth2AuthorizationManagers.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2002-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.oauth2.core; - -import org.springframework.security.authorization.AuthorityAuthorizationManager; - -/** - * @author Mario Petrovski - * @since 6.2 - */ -public final class OAuth2AuthorizationManagers { - - private OAuth2AuthorizationManagers() { - } - - public static AuthorityAuthorizationManager hasScope(String scope) { - verifyScope(scope); - return AuthorityAuthorizationManager.hasAuthority("SCOPE_" + scope); - } - - public static AuthorityAuthorizationManager hasAnyScope(String... scopes) { - verifyScopes(scopes); - String[] mappedScopes = new String[scopes.length]; - for (int i = 0; i < scopes.length; i++) { - mappedScopes[i] = "SCOPE_" + scopes[i]; - } - return AuthorityAuthorizationManager.hasAnyAuthority(mappedScopes); - } - - private static void verifyScopes(String... scopes) throws IllegalArgumentException { - for (String scope : scopes) { - verifyScope(scope); - } - } - - private static void verifyScope(String scope) { - if (scope.startsWith("SCOPE_")) { - throw new IllegalArgumentException("Scope '" + scope + "' start with 'SCOPE_' prefix."); - } - } - -} diff --git a/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/authorization/OAuth2AuthorizationManagers.java b/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/authorization/OAuth2AuthorizationManagers.java new file mode 100644 index 0000000000..7697b4a9d6 --- /dev/null +++ b/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/authorization/OAuth2AuthorizationManagers.java @@ -0,0 +1,95 @@ +/* + * Copyright 2002-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.oauth2.core.authorization; + +import org.springframework.security.authorization.AuthorityAuthorizationManager; +import org.springframework.security.authorization.AuthorizationManager; +import org.springframework.security.core.Authentication; +import org.springframework.util.Assert; + +/** + * A convenience class for creating OAuth 2.0-specific {@link AuthorizationManager}s. + * + * @author Mario Petrovski + * @author Josh Cummings + * @since 6.2 + * @see AuthorityAuthorizationManager + */ +public final class OAuth2AuthorizationManagers { + + private OAuth2AuthorizationManagers() { + } + + /** + * Create an {@link AuthorizationManager} that requires an {@link Authentication} to + * have a {@code SCOPE_scope} authority. + * + *

+ * For example, if you call {@code hasScope("read")}, then this will require that each + * authentication have a {@link org.springframework.security.core.GrantedAuthority} + * whose value is {@code SCOPE_read}. + * + *

+ * This would equivalent to calling + * {@code AuthorityAuthorizationManager#hasAuthority("SCOPE_read")}. + * @param scope the scope value to require + * @param the secure object + * @return an {@link AuthorizationManager} that requires a {@code "SCOPE_scope"} + * authority + */ + public static AuthorizationManager hasScope(String scope) { + assertScope(scope); + return AuthorityAuthorizationManager.hasAuthority("SCOPE_" + scope); + } + + /** + * Create an {@link AuthorizationManager} that requires an {@link Authentication} to + * have at least one authority among {@code SCOPE_scope1}, {@code SCOPE_scope2}, ... + * {@code SCOPE_scopeN}. + * + *

+ * For example, if you call {@code hasAnyScope("read", "write")}, then this will + * require that each authentication have at least a + * {@link org.springframework.security.core.GrantedAuthority} whose value is either + * {@code SCOPE_read} or {@code SCOPE_write}. + * + *

+ * This would equivalent to calling + * {@code AuthorityAuthorizationManager#hasAnyAuthority("SCOPE_read", "SCOPE_write")}. + * @param scopes the scope values to allow + * @param the secure object + * @return an {@link AuthorizationManager} that requires at least one authority among + * {@code "SCOPE_scope1"}, {@code SCOPE_scope2}, ... {@code SCOPE_scopeN}. + * + */ + public static AuthorizationManager hasAnyScope(String... scopes) { + String[] mappedScopes = new String[scopes.length]; + for (int i = 0; i < scopes.length; i++) { + assertScope(scopes[i]); + mappedScopes[i] = "SCOPE_" + scopes[i]; + } + return AuthorityAuthorizationManager.hasAnyAuthority(mappedScopes); + } + + private static void assertScope(String scope) { + Assert.isTrue(!scope.startsWith("SCOPE_"), + () -> scope + " should not start with SCOPE_ since SCOPE_" + + " is automatically prepended when using hasScope and hasAnyScope. Consider using " + + " AuthorityAuthorizationManager#hasAuthority or #hasAnyAuthority instead."); + } + +} diff --git a/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/authorization/OAuth2ReactiveAuthorizationManagers.java b/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/authorization/OAuth2ReactiveAuthorizationManagers.java new file mode 100644 index 0000000000..753cf1aabd --- /dev/null +++ b/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/authorization/OAuth2ReactiveAuthorizationManagers.java @@ -0,0 +1,95 @@ +/* + * Copyright 2002-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.oauth2.core.authorization; + +import org.springframework.security.authorization.AuthorityReactiveAuthorizationManager; +import org.springframework.security.authorization.AuthorizationManager; +import org.springframework.security.authorization.ReactiveAuthorizationManager; +import org.springframework.security.core.Authentication; +import org.springframework.util.Assert; + +/** + * A convenience class for creating OAuth 2.0-specific {@link AuthorizationManager}s. + * + * @author Josh Cummings + * @since 6.2 + * @see AuthorityReactiveAuthorizationManager + */ +public final class OAuth2ReactiveAuthorizationManagers { + + private OAuth2ReactiveAuthorizationManagers() { + } + + /** + * Create a {@link ReactiveAuthorizationManager} that requires an + * {@link Authentication} to have a {@code SCOPE_scope} authority. + * + *

+ * For example, if you call {@code hasScope("read")}, then this will require that each + * authentication have a {@link org.springframework.security.core.GrantedAuthority} + * whose value is {@code SCOPE_read}. + * + *

+ * This would equivalent to calling + * {@code AuthorityReactiveAuthorizationManager#hasAuthority("SCOPE_read")}. + * @param scope the scope value to require + * @param the secure object + * @return an {@link ReactiveAuthorizationManager} that requires a + * {@code "SCOPE_scope"} authority + */ + public static ReactiveAuthorizationManager hasScope(String scope) { + assertScope(scope); + return AuthorityReactiveAuthorizationManager.hasAuthority("SCOPE_" + scope); + } + + /** + * Create a {@link ReactiveAuthorizationManager} that requires an + * {@link Authentication} to have at least one authority among {@code SCOPE_scope1}, + * {@code SCOPE_scope2}, ... {@code SCOPE_scopeN}. + * + *

+ * For example, if you call {@code hasAnyScope("read", "write")}, then this will + * require that each authentication have at least a + * {@link org.springframework.security.core.GrantedAuthority} whose value is either + * {@code SCOPE_read} or {@code SCOPE_write}. + * + *

+ * This would equivalent to calling + * {@code AuthorityReactiveAuthorizationManager#hasAnyAuthority("SCOPE_read", "SCOPE_write")}. + * @param scopes the scope values to allow + * @param the secure object + * @return an {@link ReactiveAuthorizationManager} that requires at least one + * authority among {@code "SCOPE_scope1"}, {@code SCOPE_scope2}, ... + * {@code SCOPE_scopeN}. + */ + public static ReactiveAuthorizationManager hasAnyScope(String... scopes) { + String[] mappedScopes = new String[scopes.length]; + for (int i = 0; i < scopes.length; i++) { + assertScope(scopes[i]); + mappedScopes[i] = "SCOPE_" + scopes[i]; + } + return AuthorityReactiveAuthorizationManager.hasAnyAuthority(mappedScopes); + } + + private static void assertScope(String scope) { + Assert.isTrue(!scope.startsWith("SCOPE_"), + () -> scope + " should not start with SCOPE_ since SCOPE_" + + " is automatically prepended when using hasScope and hasAnyScope. Consider using " + + " AuthorityReactiveAuthorizationManager#hasAuthority or #hasAnyAuthority instead."); + } + +} diff --git a/oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/OAuth2AuthorizationManagersTests.java b/oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/OAuth2AuthorizationManagersTests.java deleted file mode 100644 index f331da52ff..0000000000 --- a/oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/OAuth2AuthorizationManagersTests.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright 2002-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.oauth2.core; - -import org.junit.jupiter.api.Test; - -import org.springframework.security.authorization.AuthorityAuthorizationManager; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; - -/** - * Tests for {@link OAuth2AuthorizationManagers} - * - * @author Mario Petrovski - */ -public class OAuth2AuthorizationManagersTests { - - @Test - void hasScope_withInvalidScope_shouldThrowIllegalArgumentException() { - String scope = "SCOPE_invalid"; - assertThatExceptionOfType(IllegalArgumentException.class) - .isThrownBy(() -> OAuth2AuthorizationManagers.hasScope(scope)) - .withMessage("Scope 'SCOPE_invalid' start with 'SCOPE_' prefix."); - } - - @Test - void hasScopes_withInvalidScope_shouldThrowIllegalArgumentException() { - String[] scopes = { "read", "write", "SCOPE_invalid" }; - assertThatExceptionOfType(IllegalArgumentException.class) - .isThrownBy(() -> OAuth2AuthorizationManagers.hasAnyScope(scopes)) - .withMessage("Scope 'SCOPE_invalid' start with 'SCOPE_' prefix."); - } - - @Test - void hasScope_withValidScope_shouldPass() { - String scope = "read"; - AuthorityAuthorizationManager authorizationManager = OAuth2AuthorizationManagers.hasScope(scope); - assertThat(authorizationManager).isNotNull(); - } - - @Test - void hasScope_withValidScopes_shouldPass() { - String[] scopes = { "read", "write" }; - AuthorityAuthorizationManager authorizationManager = OAuth2AuthorizationManagers.hasAnyScope(scopes); - assertThat(authorizationManager).isNotNull(); - } - -} diff --git a/oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/authorization/OAuth2AuthorizationManagersTests.java b/oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/authorization/OAuth2AuthorizationManagersTests.java new file mode 100644 index 0000000000..487411ad18 --- /dev/null +++ b/oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/authorization/OAuth2AuthorizationManagersTests.java @@ -0,0 +1,76 @@ +/* + * Copyright 2002-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.oauth2.core.authorization; + +import org.junit.jupiter.api.Test; + +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.authentication.TestingAuthenticationToken; +import org.springframework.security.authorization.AuthorizationManager; +import org.springframework.security.core.Authentication; + +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; + +/** + * Tests for {@link OAuth2AuthorizationManagers} + * + * @author Mario Petrovski + * @author Josh Cummings + */ +public class OAuth2AuthorizationManagersTests { + + @Test + void hasScopeWhenInvalidScopeThenThrowIllegalArgument() { + String scope = "SCOPE_invalid"; + assertThatExceptionOfType(IllegalArgumentException.class) + .isThrownBy(() -> OAuth2AuthorizationManagers.hasScope(scope)) + .withMessageContaining("SCOPE_invalid should not start with SCOPE_"); + } + + @Test + void hasAnyScopeWhenInvalidScopeThenThrowIllegalArgument() { + String[] scopes = { "read", "write", "SCOPE_invalid" }; + assertThatExceptionOfType(IllegalArgumentException.class) + .isThrownBy(() -> OAuth2AuthorizationManagers.hasAnyScope(scopes)) + .withMessageContaining("SCOPE_invalid should not start with SCOPE_"); + } + + @Test + void hasScopeWhenValidScopeThenAuthorizationManager() { + String scope = "read"; + AuthorizationManager authorizationManager = OAuth2AuthorizationManagers.hasScope(scope); + authorizationManager.verify(() -> hasScope(scope), new Object()); + assertThatExceptionOfType(AccessDeniedException.class) + .isThrownBy(() -> authorizationManager.verify(() -> hasScope("wrong"), new Object())); + } + + @Test + void hasAnyScopeWhenValidScopesThenAuthorizationManager() { + String[] scopes = { "read", "write" }; + AuthorizationManager authorizationManager = OAuth2AuthorizationManagers.hasAnyScope(scopes); + for (String scope : scopes) { + authorizationManager.verify(() -> hasScope(scope), new Object()); + } + assertThatExceptionOfType(AccessDeniedException.class) + .isThrownBy(() -> authorizationManager.verify(() -> hasScope("wrong"), new Object())); + } + + Authentication hasScope(String scope) { + return new TestingAuthenticationToken("user", "pass", "SCOPE_" + scope); + } + +} diff --git a/oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/authorization/OAuth2ReactiveAuthorizationManagersTests.java b/oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/authorization/OAuth2ReactiveAuthorizationManagersTests.java new file mode 100644 index 0000000000..8747b9c1ba --- /dev/null +++ b/oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/authorization/OAuth2ReactiveAuthorizationManagersTests.java @@ -0,0 +1,77 @@ +/* + * Copyright 2002-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.oauth2.core.authorization; + +import org.junit.jupiter.api.Test; +import reactor.core.publisher.Mono; + +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.authentication.TestingAuthenticationToken; +import org.springframework.security.authorization.ReactiveAuthorizationManager; +import org.springframework.security.core.Authentication; + +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; + +/** + * Tests for {@link OAuth2ReactiveAuthorizationManagers} + * + * @author Josh Cummings + */ +public class OAuth2ReactiveAuthorizationManagersTests { + + @Test + void hasScopeWhenInvalidScopeThenThrowIllegalArgument() { + String scope = "SCOPE_invalid"; + assertThatExceptionOfType(IllegalArgumentException.class) + .isThrownBy(() -> OAuth2ReactiveAuthorizationManagers.hasScope(scope)) + .withMessageContaining("SCOPE_invalid should not start with SCOPE_"); + } + + @Test + void hasAnyScopeWhenInvalidScopeThenThrowIllegalArgument() { + String[] scopes = { "read", "write", "SCOPE_invalid" }; + assertThatExceptionOfType(IllegalArgumentException.class) + .isThrownBy(() -> OAuth2ReactiveAuthorizationManagers.hasAnyScope(scopes)) + .withMessageContaining("SCOPE_invalid should not start with SCOPE_"); + } + + @Test + void hasScopeWhenValidScopeThenAuthorizationManager() { + String scope = "read"; + ReactiveAuthorizationManager authorizationManager = OAuth2ReactiveAuthorizationManagers.hasScope(scope); + authorizationManager.verify(hasScope(scope), new Object()).block(); + assertThatExceptionOfType(AccessDeniedException.class) + .isThrownBy(() -> authorizationManager.verify(hasScope("wrong"), new Object()).block()); + } + + @Test + void hasAnyScopeWhenValidScopesThenAuthorizationManager() { + String[] scopes = { "read", "write" }; + ReactiveAuthorizationManager authorizationManager = OAuth2ReactiveAuthorizationManagers + .hasAnyScope(scopes); + for (String scope : scopes) { + authorizationManager.verify(hasScope(scope), new Object()).block(); + } + assertThatExceptionOfType(AccessDeniedException.class) + .isThrownBy(() -> authorizationManager.verify(hasScope("wrong"), new Object()).block()); + } + + Mono hasScope(String scope) { + return Mono.just(new TestingAuthenticationToken("user", "pass", "SCOPE_" + scope)); + } + +}