From 2dffa2bada158feb6e9df2712589ece68ae8e025 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Wacongne?= Date: Wed, 13 Mar 2019 12:30:34 +0100 Subject: [PATCH 1/9] gh-6557 @WithMockJwt --- ...ecurity-oauth2-resource-server-test.gradle | 7 + .../support/oauth2/StringObjectMapHelper.java | 53 ++++ .../support/oauth2/StringParsingHelper.java | 61 ++++ .../context/support/oauth2/WithMockJwt.java | 167 +++++++++++ .../WithMockJwtSecurityContextFactory.java | 74 +++++ .../properties/BooleanPropertyParser.java | 35 +++ .../properties/DoublePropertyParser.java | 35 +++ .../properties/InstantPropertyParser.java | 37 +++ .../properties/IntegerPropertyParser.java | 35 +++ .../oauth2/properties/LongPropertyParser.java | 35 +++ .../oauth2/properties/NoOpPropertyParser.java | 35 +++ .../support/oauth2/properties/Property.java | 83 ++++++ .../oauth2/properties/PropertyParser.java | 37 +++ .../properties/PropertyParsersHelper.java | 261 ++++++++++++++++++ .../properties/StringListPropertyParser.java | 38 +++ .../properties/StringSetPropertyParser.java | 37 +++ .../oauth2/properties/UrlPropertyParser.java | 43 +++ .../main/resources/META-INF/spring.factories | 3 + ...ithMockJwtSecurityContextFactoryTests.java | 131 +++++++++ .../support/oauth2/WithMockJwtTests.java | 96 +++++++ .../properties/PropertyParsersHelperTest.java | 149 ++++++++++ oauth2-test/template.mf | 19 ++ .../OAuth2IntrospectionClaimNames.java | 2 +- ...y-samples-boot-oauth2resourceserver.gradle | 1 + .../OAuth2ResourceServerControllerTest.java | 60 ++++ 25 files changed, 1533 insertions(+), 1 deletion(-) create mode 100644 oauth2-test/spring-security-oauth2-resource-server-test.gradle create mode 100644 oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/StringObjectMapHelper.java create mode 100644 oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/StringParsingHelper.java create mode 100644 oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/WithMockJwt.java create mode 100644 oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/WithMockJwtSecurityContextFactory.java create mode 100644 oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/properties/BooleanPropertyParser.java create mode 100644 oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/properties/DoublePropertyParser.java create mode 100644 oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/properties/InstantPropertyParser.java create mode 100644 oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/properties/IntegerPropertyParser.java create mode 100644 oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/properties/LongPropertyParser.java create mode 100644 oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/properties/NoOpPropertyParser.java create mode 100644 oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/properties/Property.java create mode 100644 oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/properties/PropertyParser.java create mode 100644 oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/properties/PropertyParsersHelper.java create mode 100644 oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/properties/StringListPropertyParser.java create mode 100644 oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/properties/StringSetPropertyParser.java create mode 100644 oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/properties/UrlPropertyParser.java create mode 100644 oauth2-test/src/main/resources/META-INF/spring.factories create mode 100644 oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/WithMockJwtSecurityContextFactoryTests.java create mode 100644 oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/WithMockJwtTests.java create mode 100644 oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/properties/PropertyParsersHelperTest.java create mode 100644 oauth2-test/template.mf create mode 100644 samples/boot/oauth2resourceserver/src/test/java/sample/OAuth2ResourceServerControllerTest.java diff --git a/oauth2-test/spring-security-oauth2-resource-server-test.gradle b/oauth2-test/spring-security-oauth2-resource-server-test.gradle new file mode 100644 index 00000000000..1395227adff --- /dev/null +++ b/oauth2-test/spring-security-oauth2-resource-server-test.gradle @@ -0,0 +1,7 @@ +apply plugin: 'io.spring.convention.spring-module' + +dependencies { + compile project(':spring-security-test') + compile project(':spring-security-oauth2-resource-server') + compile project(':spring-security-oauth2-jose') +} diff --git a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/StringObjectMapHelper.java b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/StringObjectMapHelper.java new file mode 100644 index 00000000000..5111a20cc65 --- /dev/null +++ b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/StringObjectMapHelper.java @@ -0,0 +1,53 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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 org.springframework.security.test.context.support.oauth2; + +import java.util.List; +import java.util.Map; + +/** + * + * @author Jérôme Wacongne <ch4mp@c4-soft.com> + * @since 5.2.0 + * + */ +class StringObjectMapHelper { + + public static final Map putIfNotEmpty(String key, String value, + Map map) { + if (value != null && !value.isEmpty()) { + map.put(key, value); + } + return map; + } + + public static final Map putIfNotEmpty(String key, List value, + Map map) { + if (value != null && !value.isEmpty()) { + map.put(key, value); + } + return map; + } + + public static final Map putIfNotEmpty(String key, T value, + Map map) { + if (value != null) { + map.put(key, value); + } + return map; + } + +} diff --git a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/StringParsingHelper.java b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/StringParsingHelper.java new file mode 100644 index 00000000000..e21e3f37ae7 --- /dev/null +++ b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/StringParsingHelper.java @@ -0,0 +1,61 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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 org.springframework.security.test.context.support.oauth2; + +import java.net.MalformedURLException; +import java.net.URL; +import java.time.Instant; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.springframework.security.core.authority.SimpleGrantedAuthority; + +/** + * + * @author Jérôme Wacongne <ch4mp@c4-soft.com> + * @since 5.2.0 + * + */ +class StringParsingHelper { + + public static String nullIfEmpty(String str) { + return str == null || str.isEmpty() ? null : str; + } + + public static Instant intant(String str) { + return str == null || str.isEmpty() ? null : Instant.parse(str); + } + + public static URL url(String str) throws MalformedURLException { + return str == null || str.isEmpty() ? null : new URL(str); + } + + public static List stringList(String[] stringArr) { + return Stream.of(stringArr).collect(Collectors.toList()); + } + + public static Set stringSet(String[] stringArr) { + return Stream.of(stringArr).collect(Collectors.toSet()); + } + + public static Set grantedAuthorities(String[] stringArr) { + return Stream.of(stringArr).map(SimpleGrantedAuthority::new) + .collect(Collectors.toSet()); + } + +} \ No newline at end of file diff --git a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/WithMockJwt.java b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/WithMockJwt.java new file mode 100644 index 00000000000..e589a274efd --- /dev/null +++ b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/WithMockJwt.java @@ -0,0 +1,167 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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 org.springframework.security.test.context.support.oauth2; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.core.annotation.AliasFor; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.oauth2.jwt.Jwt; +import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; +import org.springframework.security.test.context.support.TestExecutionEvent; +import org.springframework.security.test.context.support.WithSecurityContext; +import org.springframework.security.test.context.support.WithSecurityContextTestExecutionListener; +import org.springframework.security.test.context.support.oauth2.properties.Property; +import org.springframework.security.test.context.support.oauth2.properties.PropertyParser; +import org.springframework.security.test.context.support.oauth2.properties.PropertyParsersHelper; +import org.springframework.test.context.TestContext; +import org.springframework.test.web.servlet.MockMvc; + +/** + *

+ * A lot like + * {@link org.springframework.security.test.context.support.WithMockUser @WithMockUser}: + * when used with {@link WithSecurityContextTestExecutionListener} this annotation can be + * added to a test method to emulate running with a mocked authentication created out of a + * {@link Jwt JWT}. + *

+ *

+ * Main steps are: + *

+ *
    + *
  • A {@link Jwt JWT} is created as per this annotation {@code name} (forces + * {@code subject} claim), {@code headers} and {@code claims}
  • + *
  • A {@link JwtAuthenticationToken JwtAuthenticationToken} is then created and fed + * with this new JWT token
  • + *
  • An empty {@link SecurityContext} is instantiated and populated with this + * {@code JwtAuthenticationToken}
  • + *
+ *

+ * As a result, the {@link Authentication} {@link MockMvc} gets from security context will + * have the following properties: + *

+ *
    + *
  • {@link Authentication#getPrincipal() getPrincipal()} returns a {@link Jwt}
  • + *
  • {@link Authentication#getName() getName()} returns the JWT {@code subject} claim + * (set from this annotation {@code name} value)
  • + *
  • {@link Authentication#getAuthorities() authorities} will be a collection of + * {@link SimpleGrantedAuthority} as defined by this annotation {@code authorities}
  • + *
+ * + * Sample Usage: + * + *
+ * @WithMockJwt
+ * @Test
+ * public void testSomethingWithDefaultJwtAuthentication() {
+ *   //no authority
+ *   //single {@link DEFAULT_HEADER_NAME} header (can't be empty)
+ *   //"sub" claim (subject) with {@link DEFAULT_AUTH_NAME} as value
+ *   ...
+ * }
+ *
+ * @WithMockJwt(
+ *   authorities = {"ROLE_USER", "ROLE_ADMIN"},
+ *   name = "user",
+ *   headers = { @Property(name = "foo", value = "bar") },
+ *   claims = { @Property(name = "machin", value = "chose") })
+ * @Test
+ * public void testSomethingWithCustomJwtAuthentication() {
+ *   //two authorities
+ *   //single "foo" header with "bar" as value
+ *   //"machin" claim with "chose" as value
+ *   //"sub" claim (subject) with "user" as value
+ *   ...
+ * }
+ * 
+ * + * @see Property + * @see PropertyParser + * @see PropertyParsersHelper#DEFAULT_PARSERS + * + * @author Jérôme Wacongne <ch4mp@c4-soft.com> + * @since 5.2.0 + * + */ +@Target({ ElementType.METHOD, ElementType.TYPE }) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +@Documented +@WithSecurityContext(factory = WithMockJwtSecurityContextFactory.class) +public @interface WithMockJwt { + public static final String DEFAULT_AUTH_NAME = "user"; + public static final String DEFAULT_HEADER_NAME = "alg"; + public static final String DEFAULT_HEADER_VALUE = "test-algorythm"; + + /** + * Alias for authorities + * @return Authorities the client is to be granted + */ + @AliasFor("authorities") + String[] value() default {}; + + /** + * Alias for value + * @return Authorities the client is to be granted + */ + @AliasFor("value") + String[] authorities() default {}; + + /** + * To be used both as authentication {@code Principal} name and token {@code username} + * attribute. + * @return Resource owner name + */ + String name() default DEFAULT_AUTH_NAME; + + /** + * @return JWT claims + */ + Property[] claims() default {}; + + /** + * Of little use at unit test time... + * @return JWT headers + */ + Property[] headers() default { + @Property(name = DEFAULT_HEADER_NAME, value = DEFAULT_HEADER_VALUE) }; + + /** + * {@link PropertyParsersHelper#DEFAULT_PARSERS Defaulted parsers} are provided for + * most common value types. + * + * @return parsers to add to default ones (or override) + * + * @see PropertyParsersHelper#DEFAULT_PARSERS + */ + String[] additionalParsers() default {}; + + /** + * Determines when the {@link SecurityContext} is setup. The default is before + * {@link TestExecutionEvent#TEST_METHOD} which occurs during + * {@link org.springframework.test.context.TestExecutionListener#beforeTestMethod(TestContext)} + * @return the {@link TestExecutionEvent} to initialize before + */ + @AliasFor(annotation = WithSecurityContext.class) + TestExecutionEvent setupBefore() default TestExecutionEvent.TEST_METHOD; +} diff --git a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/WithMockJwtSecurityContextFactory.java b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/WithMockJwtSecurityContextFactory.java new file mode 100644 index 00000000000..f6d5dd07ebb --- /dev/null +++ b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/WithMockJwtSecurityContextFactory.java @@ -0,0 +1,74 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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 org.springframework.security.test.context.support.oauth2; + +import static org.springframework.security.test.context.support.oauth2.StringObjectMapHelper.putIfNotEmpty; +import static org.springframework.security.test.context.support.oauth2.StringParsingHelper.grantedAuthorities; + +import java.time.Instant; +import java.util.HashMap; +import java.util.Map; + +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.oauth2.jwt.Jwt; +import org.springframework.security.oauth2.jwt.JwtClaimNames; +import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; +import org.springframework.security.test.context.support.WithSecurityContextFactory; +import org.springframework.security.test.context.support.oauth2.properties.PropertyParsersHelper; + +/** + * Create a {@link org.springframework.security.core.context.SecurityContext + * SecurityContext} populated with a + * {@link org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken + * JwtAuthenticationToken} containing a {@link org.springframework.security.oauth2.jwt.Jwt + * JWT} as described by + * {@link org.springframework.security.test.context.support.oauth2.WithMockJwt @WithMockJwt} + * + * @author Jérôme Wacongne <ch4mp@c4-soft.com> + * @since 5.2.0 + * + */ +public final class WithMockJwtSecurityContextFactory + implements WithSecurityContextFactory { + public static final String DEFAULT_TOKEN_VALUE = "test.jwt.value"; + + @Override + public SecurityContext createSecurityContext(final WithMockJwt annotation) { + final PropertyParsersHelper propertyParsers = PropertyParsersHelper + .withDefaultParsers(annotation.additionalParsers()); + + final Map headers = propertyParsers.parse(annotation.headers()); + + Map claims = propertyParsers.parse(annotation.claims()); + if (claims.containsKey(JwtClaimNames.SUB)) { + throw new RuntimeException(JwtClaimNames.SUB + + " claim is not configurable (forced to @WithMockJwt.name)"); + } + else { + claims = new HashMap<>(claims); + putIfNotEmpty(JwtClaimNames.SUB, annotation.name(), claims); + } + + final SecurityContext context = SecurityContextHolder.createEmptyContext(); + context.setAuthentication(new JwtAuthenticationToken( + new Jwt(DEFAULT_TOKEN_VALUE, (Instant) claims.get(JwtClaimNames.IAT), + (Instant) claims.get(JwtClaimNames.EXP), headers, claims), + grantedAuthorities(annotation.authorities()))); + + return context; + } +} \ No newline at end of file diff --git a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/properties/BooleanPropertyParser.java b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/properties/BooleanPropertyParser.java new file mode 100644 index 00000000000..023f88b4406 --- /dev/null +++ b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/properties/BooleanPropertyParser.java @@ -0,0 +1,35 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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 org.springframework.security.test.context.support.oauth2.properties; + +/** + * Turns an annotation String value into a Boolean + * + * @author Jérôme Wacongne <ch4mp@c4-soft.com> + * @since 5.2.0 + * + */ +public class BooleanPropertyParser implements PropertyParser { + + /** + * {@inheritDoc} + */ + @Override + public Boolean parse(String value) { + return Boolean.valueOf(value); + } + +} diff --git a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/properties/DoublePropertyParser.java b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/properties/DoublePropertyParser.java new file mode 100644 index 00000000000..0d2a4d2cfdf --- /dev/null +++ b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/properties/DoublePropertyParser.java @@ -0,0 +1,35 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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 org.springframework.security.test.context.support.oauth2.properties; + +/** + * Turns an annotation String value into a Double + * + * @author Jérôme Wacongne <ch4mp@c4-soft.com> + * @since 5.2.0 + * + */ +public class DoublePropertyParser implements PropertyParser { + + /** + * {@inheritDoc} + */ + @Override + public Double parse(String value) { + return Double.valueOf(value); + } + +} diff --git a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/properties/InstantPropertyParser.java b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/properties/InstantPropertyParser.java new file mode 100644 index 00000000000..2317227ffe2 --- /dev/null +++ b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/properties/InstantPropertyParser.java @@ -0,0 +1,37 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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 org.springframework.security.test.context.support.oauth2.properties; + +import java.time.Instant; + +/** + * Turns an annotation String value into an {@link java.time.Instant Instant} + * + * @author Jérôme Wacongne <ch4mp@c4-soft.com> + * @since 5.2.0 + * + */ +public class InstantPropertyParser implements PropertyParser { + + /** + * {@inheritDoc} + */ + @Override + public Instant parse(String value) { + return Instant.parse(value); + } + +} diff --git a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/properties/IntegerPropertyParser.java b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/properties/IntegerPropertyParser.java new file mode 100644 index 00000000000..767fa2184df --- /dev/null +++ b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/properties/IntegerPropertyParser.java @@ -0,0 +1,35 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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 org.springframework.security.test.context.support.oauth2.properties; + +/** + * Turns an annotation String value into an Integer + * + * @author Jérôme Wacongne <ch4mp@c4-soft.com> + * @since 5.2.0 + * + */ +public class IntegerPropertyParser implements PropertyParser { + + /** + * {@inheritDoc} + */ + @Override + public Integer parse(String value) { + return Integer.valueOf(value); + } + +} diff --git a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/properties/LongPropertyParser.java b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/properties/LongPropertyParser.java new file mode 100644 index 00000000000..2771cf72e86 --- /dev/null +++ b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/properties/LongPropertyParser.java @@ -0,0 +1,35 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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 org.springframework.security.test.context.support.oauth2.properties; + +/** + * Turns an annotation String value into a Long + * + * @author Jérôme Wacongne <ch4mp@c4-soft.com> + * @since 5.2.0 + * + */ +public class LongPropertyParser implements PropertyParser { + + /** + * {@inheritDoc} + */ + @Override + public Long parse(String value) { + return Long.valueOf(value); + } + +} diff --git a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/properties/NoOpPropertyParser.java b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/properties/NoOpPropertyParser.java new file mode 100644 index 00000000000..261ada9b3d6 --- /dev/null +++ b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/properties/NoOpPropertyParser.java @@ -0,0 +1,35 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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 org.springframework.security.test.context.support.oauth2.properties; + +/** + * Leaves an annotation String value untouched + * + * @author Jérôme Wacongne <ch4mp@c4-soft.com> + * @since 5.2.0 + * + */ +public class NoOpPropertyParser implements PropertyParser { + + /** + * {@inheritDoc} + */ + @Override + public String parse(String value) { + return value; + } + +} diff --git a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/properties/Property.java b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/properties/Property.java new file mode 100644 index 00000000000..4b22cdd5c1a --- /dev/null +++ b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/properties/Property.java @@ -0,0 +1,83 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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 org.springframework.security.test.context.support.oauth2.properties; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + *

+ * Annotation to create an entry in a {@link java.util.Map Map<String, Object>} such + * as {@link org.springframework.security.oauth2.jwt.Jwt JWT} headers or claims. + *

+ *

+ * See + * {@link org.springframework.security.test.context.support.oauth2.properties.PropertyParser + * PropertyParser} and its already provided implementations + *

+ * Sample usage:
+ * + *
+ * @WithMockJwt(
+ *   claims = {
+ *     @Property(name = "audience", value = "first audience", parser = "org.springframework.security.test.context.support.oauth2.properties.StringListPropertyParser"),
+ *     @Property(name = "audience", value = "second audience", parser = "org.springframework.security.test.context.support.oauth2.properties.StringListPropertyParser"),
+ *     @Property(name = "issuer", value = "https://test-issuer.org", parser = "org.springframework.security.test.context.support.oauth2.properties.UrlPropertyParser"),
+ *     @Property(name = "machin", value = "chose"),
+ *     @Property(name = "truc", value = "bidule", parser = "your.fancy.ParserImpl")})
+ * 
+ * + * This will create + *
    + *
  • an {@code audience} claim with a value being a {@code List} with two + * entries
  • + *
  • an {@code issuer} claim with a value being a {@code java.net.URL} instance
  • + *
  • a {@code machin} claim with {@code chose} String as value (default parser is + * {@link org.springframework.security.test.context.support.oauth2.properties.NoOpPropertyParser + * NoOpPropertyParser})
  • + *
  • a {@code truc} claim whith an instance of what {@code your.fancy.ParserImpl} is + * designed to build from {@code bidule} string as value
  • + *
+ * + * @author Jérôme Wacongne <ch4mp@c4-soft.com> + * @since 5.2.0 + * + */ +@Target({ ElementType.METHOD, ElementType.TYPE }) +@Retention(RetentionPolicy.RUNTIME) +public @interface Property { + + /** + * @return the key in the {@link java.util.Map Map<String, Object>} + */ + String name(); + + /** + * @return a value to be transformed using "parser" before being put as value in + * {@link java.util.Map Map<String, Object>} + */ + String value(); + + /** + * @return a + * {@link org.springframework.security.test.context.support.oauth2.properties.PropertyParser + * PropertyParser} implementation class name + */ + String parser() default "org.springframework.security.test.context.support.oauth2.properties.NoOpPropertyParser"; + +} diff --git a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/properties/PropertyParser.java b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/properties/PropertyParser.java new file mode 100644 index 00000000000..272705200da --- /dev/null +++ b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/properties/PropertyParser.java @@ -0,0 +1,37 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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 org.springframework.security.test.context.support.oauth2.properties; + +/** + * De-serializes an annotation String value into an Object of type T + * + * @author Jérôme Wacongne <ch4mp@c4-soft.com> + * @since 5.2.0 + * + * @param what + * {@link org.springframework.security.test.context.support.oauth2.properties.Property#value() @Property.value} + * should be turned into + * + */ +public interface PropertyParser { + + /** + * @param value String to de-serialize + * @return an Object + */ + T parse(String value); + +} diff --git a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/properties/PropertyParsersHelper.java b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/properties/PropertyParsersHelper.java new file mode 100644 index 00000000000..297bbb2ce06 --- /dev/null +++ b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/properties/PropertyParsersHelper.java @@ -0,0 +1,261 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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 org.springframework.security.test.context.support.oauth2.properties; + +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.BinaryOperator; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * Helps turn a + * {@link org.springframework.security.test.context.support.oauth2.properties.Property @Property} + * array into a {@link java.util.Map Map<String, Object>} + * + * @author Jérôme Wacongne <ch4mp@c4-soft.com> + * @since 5.2.0 + * + */ +public class PropertyParsersHelper { + /** + *
    + *
  • {@link org.springframework.security.test.context.support.oauth2.properties.NoOpPropertyParser + * NoOpPropertyParser} => keeps value as is.
  • + *
  • {@link org.springframework.security.test.context.support.oauth2.properties.BooleanPropertyParser + * BooleanPropertyParser} => Boolean
  • + *
  • {@link org.springframework.security.test.context.support.oauth2.properties.DoublePropertyParser + * DoublePropertyParser} => Double
  • + *
  • {@link org.springframework.security.test.context.support.oauth2.properties.InstantPropertyParser + * InstantPropertyParser} => {@link java.time.Instant Instant}
  • + *
  • {@link org.springframework.security.test.context.support.oauth2.properties.IntegerPropertyParser + * IntegerPropertyParser} => Integer
  • + *
  • {@link org.springframework.security.test.context.support.oauth2.properties.LongPropertyParser + * LongPropertyParser} => Long
  • + *
  • {@link org.springframework.security.test.context.support.oauth2.properties.StringListPropertyParser + * StringListPropertyParser} => List<String>
  • + *
  • {@link org.springframework.security.test.context.support.oauth2.properties.StringSetPropertyParser + * StringSetPropertyParser} => Set<String>
  • + *
  • {@link org.springframework.security.test.context.support.oauth2.properties.UrlPropertyParser + * UrlPropertyParser} => URL
  • + *
+ */ + public static final Set DEFAULT_PARSERS = new HashSet<>(); + + static { + DEFAULT_PARSERS.add( + "org.springframework.security.test.context.support.oauth2.properties.NoOpPropertyParser"); + DEFAULT_PARSERS.add( + "org.springframework.security.test.context.support.oauth2.properties.BooleanPropertyParser"); + DEFAULT_PARSERS.add( + "org.springframework.security.test.context.support.oauth2.properties.DoublePropertyParser"); + DEFAULT_PARSERS.add( + "org.springframework.security.test.context.support.oauth2.properties.InstantPropertyParser"); + DEFAULT_PARSERS.add( + "org.springframework.security.test.context.support.oauth2.properties.IntegerPropertyParser"); + DEFAULT_PARSERS.add( + "org.springframework.security.test.context.support.oauth2.properties.LongPropertyParser"); + DEFAULT_PARSERS.add( + "org.springframework.security.test.context.support.oauth2.properties.StringListPropertyParser"); + DEFAULT_PARSERS.add( + "org.springframework.security.test.context.support.oauth2.properties.StringSetPropertyParser"); + DEFAULT_PARSERS.add( + "org.springframework.security.test.context.support.oauth2.properties.UrlPropertyParser"); + } + + private final Map> parsers; + + private PropertyParsersHelper(final Set parsers) { + this.parsers = parsers.stream().map(t -> { + try { + return Class.forName(t); + } + catch (final ClassNotFoundException e) { + throw new RuntimeException(e); + } + }).map(c -> { + try { + return (PropertyParser) c.getDeclaredConstructor().newInstance(); + } + catch (InstantiationException | IllegalAccessException + | IllegalArgumentException | InvocationTargetException + | NoSuchMethodException | SecurityException e) { + throw new RuntimeException( + "Missing public no-arg constructor on " + c.getName()); + } + }).collect(Collectors.toMap(p -> p.getClass().getName(), p -> p)); + } + + /** + * @param parserClassName {@code Parser.class.getName()} + * @return Parser instance + */ + public PropertyParser getParser(final String parserClassName) { + return this.parsers.get(parserClassName); + } + + private ParsedProperty parse(final Property p) { + final PropertyParser parser = getParser(p.parser()); + if (parser == null) { + throw new RuntimeException( + "No registered PropertyParser implementation for " + p.parser()); + } + + return new ParsedProperty<>(p.name(), parser.parse(p.value())); + } + + /** + *

+ * Turns a + * {@link org.springframework.security.test.context.support.oauth2.properties.Property @Property} + * array into a {@link java.util.Map Map<String, Object>} as required for + * {@link org.springframework.security.oauth2.jwt.Jwt JWT} headers and claims. + *

+ *

+ * Process highlights: + *

+ *
    + *
  • each + * {@link org.springframework.security.test.context.support.oauth2.properties.Property#value() + * value()} is parsed according to + * {@link org.springframework.security.test.context.support.oauth2.properties.Property#parser() + * parser()}
  • + *
  • obtained values are associated with + * {@link org.springframework.security.test.context.support.oauth2.properties.Property#name() + * name()}
  • + *
  • values with same name are accumulated in the same collection
  • + *
+ * + * @param properties to be transformed + * @return processed properties + */ + @SuppressWarnings("unchecked") + public Map parse(final Property... properties) { + return Stream.of(properties).map(this::parse).collect(Collectors + .toMap(ParsedProperty::getName, ParsedProperty::getValue, (v1, v2) -> { + if (!(v1 instanceof Collection) || !(v2 instanceof Collection)) { + throw new UnsupportedOperationException( + "@Property values can be accumuleted only if instance of Collection"); + } + if (v1 instanceof Map) { + if (v2 instanceof Map) { + return MAP_ACCUMULATOR.apply((Map) v1, + (Map) v2); + } + throw new UnsupportedOperationException( + "@Property \"Map\" values can only be accumulated with Maps"); + } + if (v2 instanceof Map) { + throw new UnsupportedOperationException( + "@Property \"Map\" values can only be accumulated with Maps"); + } + if (v1 instanceof List) { + return LIST_ACCUMULATOR.apply((List) v1, + (Collection) v2); + } + return SET_ACCUMULATOR.apply((Collection) v1, + (Collection) v2); + })); + } + + private static Set defaultParserNamesPlus( + final String... additionalParserNames) { + final Set allParserNames = new HashSet<>(DEFAULT_PARSERS); + allParserNames + .addAll(Stream.of(additionalParserNames).collect(Collectors.toSet())); + return allParserNames; + } + + /** + * Instantiates default + * {@link org.springframework.security.test.context.support.oauth2.properties.PropertyParser + * PropertyParser}s plus all provided ones (using default constructor) + * + * @param additionalParserNames + * {@link org.springframework.security.test.context.support.oauth2.properties.PropertyParser + * PropertyParser} implementations class names to add to + * {@link org.springframework.security.test.context.support.oauth2.properties.PropertyParsersHelper#DEFAULT_PARSERS + * default ones} + * @return helper instance with provided parsers plus default ones + */ + public static PropertyParsersHelper withDefaultParsers( + final String... additionalParserNames) { + return new PropertyParsersHelper(defaultParserNamesPlus(additionalParserNames)); + } + + /** + * Instantiates all provided + * {@link org.springframework.security.test.context.support.oauth2.properties.PropertyParser + * PropertyParser}s using default constructor + * + * @param allParserNames + * {@link org.springframework.security.test.context.support.oauth2.properties.PropertyParser + * PropertyParser} implementations class names + * @return helper instance with provided parsers only + */ + public static PropertyParsersHelper withoutDefaultParsers( + final String... allParserNames) { + return new PropertyParsersHelper( + Stream.of(allParserNames).collect(Collectors.toSet())); + } + + private static final class ParsedProperty { + private final String name; + private final T value; + + public ParsedProperty(final String name, final T value) { + super(); + this.name = name; + this.value = value; + } + + public String getName() { + return name; + } + + public T getValue() { + return value; + } + + } + + private static BinaryOperator> SET_ACCUMULATOR = (v1, v2) -> { + final HashSet all = new HashSet<>(v1.size() + v2.size()); + all.addAll(v1); + all.addAll(v2); + return all; + }; + + private static BinaryOperator> LIST_ACCUMULATOR = (v1, v2) -> { + final ArrayList all = new ArrayList<>(v1.size() + v2.size()); + all.addAll(v1); + all.addAll(v2); + return all; + }; + + private static BinaryOperator> MAP_ACCUMULATOR = (v1, v2) -> { + final HashMap all = new HashMap<>(v1.size() + v2.size()); + all.putAll(v1); + all.putAll(v2); + return all; + }; +} diff --git a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/properties/StringListPropertyParser.java b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/properties/StringListPropertyParser.java new file mode 100644 index 00000000000..884ef7688e8 --- /dev/null +++ b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/properties/StringListPropertyParser.java @@ -0,0 +1,38 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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 org.springframework.security.test.context.support.oauth2.properties; + +import java.util.Collections; +import java.util.List; + +/** + * Turns an annotation String value into a {@link java.util.List List<String>} + * + * @author Jérôme Wacongne <ch4mp@c4-soft.com> + * @since 5.2.0 + * + */ +public class StringListPropertyParser implements PropertyParser> { + + /** + * {@inheritDoc} + */ + @Override + public List parse(final String value) { + return Collections.singletonList(value); + } + +} diff --git a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/properties/StringSetPropertyParser.java b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/properties/StringSetPropertyParser.java new file mode 100644 index 00000000000..4f781fd4c8e --- /dev/null +++ b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/properties/StringSetPropertyParser.java @@ -0,0 +1,37 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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 org.springframework.security.test.context.support.oauth2.properties; + +import java.util.Collections; +import java.util.Set; + +/** + * Turns an annotation String value into a {@link java.util.Set Set<String>} + * + * @author Jérôme Wacongne <ch4mp@c4-soft.com> + * @since 5.2.0 + * + */ +public class StringSetPropertyParser implements PropertyParser> { + + /** + * {@inheritDoc} + */ + @Override + public Set parse(final String value) { + return Collections.singleton(value); + } +} diff --git a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/properties/UrlPropertyParser.java b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/properties/UrlPropertyParser.java new file mode 100644 index 00000000000..47f2085410d --- /dev/null +++ b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/properties/UrlPropertyParser.java @@ -0,0 +1,43 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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 org.springframework.security.test.context.support.oauth2.properties; + +import java.net.MalformedURLException; +import java.net.URL; + +/** + * Turns an annotation String value into an {@link java.net.URL URL} + * + * @author Jérôme Wacongne <ch4mp@c4-soft.com> + * @since 5.2.0 + * + */ +public class UrlPropertyParser implements PropertyParser { + + /** + * {@inheritDoc} + */ + @Override + public URL parse(String value) { + try { + return new URL(value); + } + catch (MalformedURLException e) { + throw new RuntimeException(e); + } + } + +} diff --git a/oauth2-test/src/main/resources/META-INF/spring.factories b/oauth2-test/src/main/resources/META-INF/spring.factories new file mode 100644 index 00000000000..48be9c54768 --- /dev/null +++ b/oauth2-test/src/main/resources/META-INF/spring.factories @@ -0,0 +1,3 @@ +org.springframework.test.context.TestExecutionListener = \ + org.springframework.security.test.context.support.WithSecurityContextTestExecutionListener,\ + org.springframework.security.test.context.support.ReactorContextTestExecutionListener diff --git a/oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/WithMockJwtSecurityContextFactoryTests.java b/oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/WithMockJwtSecurityContextFactoryTests.java new file mode 100644 index 00000000000..4a812da982d --- /dev/null +++ b/oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/WithMockJwtSecurityContextFactoryTests.java @@ -0,0 +1,131 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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 org.springframework.security.test.context.support.oauth2; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.when; + +import java.util.Map; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.oauth2.jwt.Jwt; +import org.springframework.security.test.context.support.oauth2.properties.NoOpPropertyParser; +import org.springframework.security.test.context.support.oauth2.properties.Property; + +@RunWith(MockitoJUnitRunner.class) +public class WithMockJwtSecurityContextFactoryTests { + + @Mock + private WithMockJwt authAnnotation; + + @Mock + private Property defaultHeaderAnnotation; + + private WithMockJwtSecurityContextFactory factory; + + @Before + public void setup() { + factory = new WithMockJwtSecurityContextFactory(); + when(defaultHeaderAnnotation.name()).thenReturn(WithMockJwt.DEFAULT_HEADER_NAME); + when(defaultHeaderAnnotation.value()) + .thenReturn(WithMockJwt.DEFAULT_HEADER_VALUE); + when(defaultHeaderAnnotation.parser()) + .thenReturn(NoOpPropertyParser.class.getName()); + } + + @Test + public void defaults() { + when(authAnnotation.name()).thenReturn(WithMockJwt.DEFAULT_AUTH_NAME); + when(authAnnotation.authorities()).thenReturn(new String[] {}); + when(authAnnotation.headers()) + .thenReturn(new Property[] { defaultHeaderAnnotation }); + when(authAnnotation.claims()).thenReturn(new Property[] {}); + when(authAnnotation.additionalParsers()).thenReturn(new String[] {}); + + final Authentication auth = factory.createSecurityContext(authAnnotation) + .getAuthentication(); + + assertThat(auth.getName()).isEqualTo(WithMockJwt.DEFAULT_AUTH_NAME); + assertThat(auth.getAuthorities()).isEmpty(); + assertThat(auth.getPrincipal()).isInstanceOf(Jwt.class); + + final Jwt jwt = (Jwt) auth.getPrincipal(); + + assertThat(auth.getCredentials()).isEqualTo(jwt); + assertThat(auth.getDetails()).isNull(); + + assertThat(jwt.getTokenValue()) + .isEqualTo(WithMockJwtSecurityContextFactory.DEFAULT_TOKEN_VALUE); + assertThat(jwt.getSubject()).isEqualTo(WithMockJwt.DEFAULT_AUTH_NAME); + assertThat(jwt.getAudience()).isNull(); + assertThat(jwt.getIssuer()).isNull(); + assertThat(jwt.getIssuedAt()).isNull(); + assertThat(jwt.getExpiresAt()).isNull(); + assertThat(jwt.getNotBefore()).isNull(); + assertThat(jwt.getId()).isNull(); + + final Map headers = jwt.getHeaders(); + assertThat(headers).hasSize(1); + assertThat(headers.get(WithMockJwt.DEFAULT_HEADER_NAME)) + .isEqualTo(WithMockJwt.DEFAULT_HEADER_VALUE); + } + + @Test + public void custom() throws Exception { + final SimpleGrantedAuthority machinAuthority = new SimpleGrantedAuthority( + "machin"); + final SimpleGrantedAuthority choseAuthority = new SimpleGrantedAuthority("chose"); + + when(authAnnotation.name()).thenReturn("bidule"); + when(authAnnotation.authorities()).thenReturn(new String[] { + machinAuthority.getAuthority(), choseAuthority.getAuthority() }); + when(authAnnotation.headers()) + .thenReturn(new Property[] { defaultHeaderAnnotation }); + when(authAnnotation.claims()).thenReturn(new Property[] {}); + when(authAnnotation.additionalParsers()).thenReturn(new String[] {}); + + final Authentication auth = factory.createSecurityContext(authAnnotation) + .getAuthentication(); + + assertThat(auth.getName()).isEqualTo("bidule"); + assertThat(auth.getAuthorities()).hasSize(2); + assertThat(auth.getAuthorities().stream() + .allMatch(a -> a.equals(machinAuthority) || a.equals(choseAuthority))) + .isTrue(); + assertThat(auth.getPrincipal()).isInstanceOf(Jwt.class); + + final Jwt jwt = (Jwt) auth.getPrincipal(); + + assertThat(auth.getCredentials()).isEqualTo(jwt); + assertThat(auth.getDetails()).isNull(); + + assertThat(jwt.getTokenValue()) + .isEqualTo(WithMockJwtSecurityContextFactory.DEFAULT_TOKEN_VALUE); + assertThat(jwt.getSubject()).isEqualTo("bidule"); + + final Map headers = jwt.getHeaders(); + assertThat(headers).hasSize(1); + assertThat(headers.get(WithMockJwt.DEFAULT_HEADER_NAME)) + .isEqualTo(WithMockJwt.DEFAULT_HEADER_VALUE); + } + +} diff --git a/oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/WithMockJwtTests.java b/oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/WithMockJwtTests.java new file mode 100644 index 00000000000..bbebfc7a358 --- /dev/null +++ b/oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/WithMockJwtTests.java @@ -0,0 +1,96 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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 org.springframework.security.test.context.support.oauth2; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.Test; +import org.springframework.core.annotation.AnnotatedElementUtils; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.security.test.context.support.TestExecutionEvent; +import org.springframework.security.test.context.support.WithSecurityContext; +import org.springframework.security.test.context.support.WithUserDetails; +import org.springframework.security.test.context.support.oauth2.properties.Property; + +public class WithMockJwtTests { + + @Test + public void defaults() { + final WithMockJwt auth = AnnotationUtils.findAnnotation(Annotated.class, + WithMockJwt.class); + assertThat(auth.name()).isEqualTo(WithMockJwt.DEFAULT_AUTH_NAME); + assertThat(auth.authorities()).isEmpty(); + assertThat(auth.headers()).hasAtLeastOneElementOfType(Property.class); + assertThat(auth.claims()).isNotNull(); + + final WithSecurityContext context = AnnotatedElementUtils + .findMergedAnnotation(Annotated.class, WithSecurityContext.class); + + assertThat(context.setupBefore()).isEqualTo(TestExecutionEvent.TEST_METHOD); + } + + @WithMockJwt + private static class Annotated { + } + + @Test + public void findMergedAnnotationWhenSetupExplicitThenOverridden() { + final WithSecurityContext context = AnnotatedElementUtils + .findMergedAnnotation(SetupExplicit.class, WithSecurityContext.class); + + assertThat(context.setupBefore()).isEqualTo(TestExecutionEvent.TEST_METHOD); + } + + @WithUserDetails(setupBefore = TestExecutionEvent.TEST_METHOD) + private class SetupExplicit { + } + + @Test + public void findMergedAnnotationWhenSetupOverriddenThenOverridden() { + final WithSecurityContext context = AnnotatedElementUtils + .findMergedAnnotation(SetupOverridden.class, WithSecurityContext.class); + + assertThat(context.setupBefore()).isEqualTo(TestExecutionEvent.TEST_EXECUTION); + } + + @WithMockJwt(setupBefore = TestExecutionEvent.TEST_EXECUTION) + private class SetupOverridden { + } + + @Test + public void custom() { + final WithMockJwt auth = AnnotationUtils.findAnnotation(Custom.class, + WithMockJwt.class); + assertThat(auth.name()).isEqualTo("truc"); + assertThat(auth.authorities()).hasSize(2); + assertThat(auth.authorities()).contains("machin", "chose"); + assertThat(auth.headers()).hasSize(1); + assertThat(auth.headers()[0].name()).isEqualTo("a"); + assertThat(auth.headers()[0].value()).isEqualTo("1"); + assertThat(auth.claims()).isNotNull(); + } + + @WithMockJwt(name = "truc", authorities = { "machin", "chose" }, headers = { + @Property(name = "a", value = "1") }, claims = { + @Property(name = "audience", value = "test audience", parser = "org.springframework.security.test.context.support.oauth2.properties.StringListPropertyParser"), + @Property(name = "issuer", value = "https://test-issuer.org", parser = "org.springframework.security.test.context.support.oauth2.properties.UrlPropertyParser"), + @Property(name = "issuedAt", value = "2019-03-03T22:35:00.0", parser = "org.springframework.security.test.context.support.oauth2.properties.InstantPropertyParser"), + @Property(name = "expiresAt", value = "2019-03-04T22:35:00.0", parser = "org.springframework.security.test.context.support.oauth2.properties.InstantPropertyParser"), + @Property(name = "notBefore", value = "2019-03-03T22:36:00.0", parser = "org.springframework.security.test.context.support.oauth2.properties.InstantPropertyParser"), + @Property(name = "jti", value = "test ID") }) + private static class Custom { + } +} diff --git a/oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/properties/PropertyParsersHelperTest.java b/oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/properties/PropertyParsersHelperTest.java new file mode 100644 index 00000000000..8f76cb975a8 --- /dev/null +++ b/oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/properties/PropertyParsersHelperTest.java @@ -0,0 +1,149 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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 org.springframework.security.test.context.support.oauth2.properties; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.net.URL; +import java.time.Instant; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.junit.Test; + +public class PropertyParsersHelperTest { + + @Test + public void helperWithDefaultParsers() { + final PropertyParsersHelper actual = PropertyParsersHelper.withDefaultParsers( + "org.springframework.security.test.context.support.oauth2.properties.PropertyParsersHelperTest$SomeTypePropertyParser", + "org.springframework.security.test.context.support.oauth2.properties.PropertyParsersHelperTest$OtherTypePropertyParser"); + + assertThat(actual.getParser(NoOpPropertyParser.class.getName())) + .isInstanceOf(NoOpPropertyParser.class); + assertThat(actual.getParser(BooleanPropertyParser.class.getName())) + .isInstanceOf(BooleanPropertyParser.class); + assertThat(actual.getParser(DoublePropertyParser.class.getName())) + .isInstanceOf(DoublePropertyParser.class); + assertThat(actual.getParser(InstantPropertyParser.class.getName())) + .isInstanceOf(InstantPropertyParser.class); + assertThat(actual.getParser(IntegerPropertyParser.class.getName())) + .isInstanceOf(IntegerPropertyParser.class); + assertThat(actual.getParser(LongPropertyParser.class.getName())) + .isInstanceOf(LongPropertyParser.class); + assertThat(actual.getParser(StringListPropertyParser.class.getName())) + .isInstanceOf(StringListPropertyParser.class); + assertThat(actual.getParser(StringSetPropertyParser.class.getName())) + .isInstanceOf(StringSetPropertyParser.class); + assertThat(actual.getParser(UrlPropertyParser.class.getName())) + .isInstanceOf(UrlPropertyParser.class); + assertThat(actual.getParser(SomeTypePropertyParser.class.getName())) + .isInstanceOf(SomeTypePropertyParser.class); + assertThat(actual.getParser(OtherTypePropertyParser.class.getName())) + .isInstanceOf(OtherTypePropertyParser.class); + } + + @Test + public void helperWithoutDefaultParsers() { + final PropertyParsersHelper actual = PropertyParsersHelper.withoutDefaultParsers( + "org.springframework.security.test.context.support.oauth2.properties.PropertyParsersHelperTest$SomeTypePropertyParser", + "org.springframework.security.test.context.support.oauth2.properties.PropertyParsersHelperTest$OtherTypePropertyParser"); + assertThat(actual.getParser(String.class.getName())).isNull(); + assertThat(actual.getParser(Boolean.class.getName())).isNull(); + assertThat(actual.getParser(Double.class.getName())).isNull(); + assertThat(actual.getParser(Instant.class.getName())).isNull(); + assertThat(actual.getParser(Integer.class.getName())).isNull(); + assertThat(actual.getParser(Long.class.getName())).isNull(); + assertThat(actual.getParser(List.class.getName())).isNull(); + assertThat(actual.getParser(Set.class.getName())).isNull(); + assertThat(actual.getParser(URL.class.getName())).isNull(); + assertThat(actual.getParser(SomeTypePropertyParser.class.getName())) + .isInstanceOf(SomeTypePropertyParser.class); + assertThat(actual.getParser(OtherTypePropertyParser.class.getName())) + .isInstanceOf(OtherTypePropertyParser.class); + } + + @Test + public void parsePropertiesWithDistinctNames() { + final PropertyParsersHelper helper = PropertyParsersHelper.withoutDefaultParsers( + "org.springframework.security.test.context.support.oauth2.properties.PropertyParsersHelperTest$SomeTypePropertyParser"); + + final Property propertyAnnotationA = mock(Property.class); + when(propertyAnnotationA.name()).thenReturn("a"); + when(propertyAnnotationA.value()).thenReturn("bidule"); + when(propertyAnnotationA.parser()) + .thenReturn(SomeTypePropertyParser.class.getName()); + + final Property propertyAnnotationB = mock(Property.class); + when(propertyAnnotationB.name()).thenReturn("b"); + when(propertyAnnotationB.value()).thenReturn("chose"); + when(propertyAnnotationB.parser()) + .thenReturn(SomeTypePropertyParser.class.getName()); + + final Map actual = helper.parse(propertyAnnotationA, + propertyAnnotationB); + assertThat(actual).hasSize(2); + assertThat(actual.get("a")).isInstanceOf(String.class); + assertThat(actual.get("b")).isInstanceOf(String.class); + + } + + @SuppressWarnings("unchecked") + @Test + public void parsePropertiesWithSameNameAccumulatesValues() { + final PropertyParsersHelper helper = PropertyParsersHelper.withDefaultParsers(); + + final Property propertyAnnotationA = mock(Property.class); + when(propertyAnnotationA.name()).thenReturn("a"); + when(propertyAnnotationA.value()).thenReturn("bidule"); + when(propertyAnnotationA.parser()) + .thenReturn(StringListPropertyParser.class.getName()); + + final Property propertyAnnotationB = mock(Property.class); + when(propertyAnnotationB.name()).thenReturn("a"); + when(propertyAnnotationB.value()).thenReturn("chose"); + when(propertyAnnotationB.parser()) + .thenReturn(StringListPropertyParser.class.getName()); + + final Map actual = helper.parse(propertyAnnotationA, + propertyAnnotationB); + assertThat(actual).hasSize(1); + assertThat(actual.get("a")).isInstanceOf(List.class); + assertThat((List) actual.get("a")).hasSize(2); + assertThat((List) actual.get("a")).contains("bidule", "chose"); + + } + + static final class SomeTypePropertyParser implements PropertyParser { + @Override + public String parse(final String value) { + return value; + } + } + + static final class OtherTypePropertyParser + implements PropertyParser> { + @Override + public Collection parse(final String value) { + return Collections.singletonList(value); + } + } +} diff --git a/oauth2-test/template.mf b/oauth2-test/template.mf new file mode 100644 index 00000000000..01a76852085 --- /dev/null +++ b/oauth2-test/template.mf @@ -0,0 +1,19 @@ +Implementation-Title: org.springframework.security.oauth2.test +Implementation-Version: ${version} +Bundle-SymbolicName: org.springframework.security.oauth2.test +Bundle-Name: Spring Security OAuth2 Test +Bundle-Vendor: SpringSource +Bundle-Version: ${version} +Bundle-ManifestVersion: 2 +Ignored-Existing-Headers: + Import-Package, + Export-Package +Import-Template: + org.apache.commons.logging.*;version="${cloggingRange}", + org.springframework.security.core.*;version="${secRange}", + org.springframework.security.authentication.*;version="${secRange}", + org.springframework.security.oauth2.server.resource.*;version="${secRange}", + org.springframework.security.web.*;version="${secRange}", + org.springframework.beans.factory;version="${springRange}", + org.springframework.util;version="${springRange}", + javax.servlet.*;version="0" diff --git a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/OAuth2IntrospectionClaimNames.java b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/OAuth2IntrospectionClaimNames.java index 178f08f33e4..7ee1cd4aa27 100644 --- a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/OAuth2IntrospectionClaimNames.java +++ b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/OAuth2IntrospectionClaimNames.java @@ -22,7 +22,7 @@ * @author Josh Cummings * @since 5.2 */ -interface OAuth2IntrospectionClaimNames { +public interface OAuth2IntrospectionClaimNames { /** * {@code active} - Indicator whether or not the token is currently active diff --git a/samples/boot/oauth2resourceserver/spring-security-samples-boot-oauth2resourceserver.gradle b/samples/boot/oauth2resourceserver/spring-security-samples-boot-oauth2resourceserver.gradle index 2135bb0af66..1ba15ac36cf 100644 --- a/samples/boot/oauth2resourceserver/spring-security-samples-boot-oauth2resourceserver.gradle +++ b/samples/boot/oauth2resourceserver/spring-security-samples-boot-oauth2resourceserver.gradle @@ -9,5 +9,6 @@ dependencies { compile 'com.squareup.okhttp3:mockwebserver' testCompile project(':spring-security-test') + testCompile project(':spring-security-oauth2-resource-server-test') testCompile 'org.springframework.boot:spring-boot-starter-test' } diff --git a/samples/boot/oauth2resourceserver/src/test/java/sample/OAuth2ResourceServerControllerTest.java b/samples/boot/oauth2resourceserver/src/test/java/sample/OAuth2ResourceServerControllerTest.java new file mode 100644 index 00000000000..5b09a09f544 --- /dev/null +++ b/samples/boot/oauth2resourceserver/src/test/java/sample/OAuth2ResourceServerControllerTest.java @@ -0,0 +1,60 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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 sample; + +import static org.hamcrest.CoreMatchers.is; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.security.oauth2.jwt.JwtDecoder; +import org.springframework.security.test.context.support.oauth2.WithMockJwt; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.servlet.MockMvc; + +/** + * + * @author Jérôme Wacongne <ch4mp@c4-soft.com> + * @since 5.2.0 + * + */ +@RunWith(SpringRunner.class) +@WebMvcTest(OAuth2ResourceServerController.class) +public class OAuth2ResourceServerControllerTest { + + @Autowired + MockMvc mockMvc; + + //TODO: find a way to remove that (JwtDecoder is useless and a stub one could be registered by test framework) + @MockBean + JwtDecoder jwtDecoder; + + @Test + @WithMockJwt() + public void test() throws Exception { + mockMvc.perform(get("/")) + .andDo(print()) + .andExpect(status().isOk()) + .andExpect(content().string(is("Hello, " + WithMockJwt.DEFAULT_AUTH_NAME + "!"))); + } + +} From eb2b2a663e67b3725e19a454abe558e6fc832b34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Wacongne?= Date: Mon, 18 Mar 2019 22:05:08 +0100 Subject: [PATCH 2/9] Polish * short names for parsers reference (class "simple name" in addition to class "name") * better security-context factory unit tests coverage and readability * rename from @Property to @Attribute (better matches usage as tokens attributes, claims and headers) * rename parser interface from PropertyParser to Parser * rename parsers implementations from *PropertyParser to *StringParser * set default authorities to { "ROLE_USER" } instead of {} * merge authorities and scopes --- .../support/oauth2/AnnotationHelper.java | 56 ++++ .../support/oauth2/AuthoritiesAndScopes.java | 118 ++++++++ .../support/oauth2/StringObjectMapHelper.java | 53 ---- .../support/oauth2/StringParsingHelper.java | 61 ---- .../context/support/oauth2/WithMockJwt.java | 100 ++++--- .../WithMockJwtSecurityContextFactory.java | 60 ++-- .../support/oauth2/attributes/Attribute.java | 78 ++++++ .../attributes/AttributeParsersHelper.java | 221 +++++++++++++++ .../attributes/BooleanStringParser.java | 37 +++ .../oauth2/attributes/DoubleStringParser.java | 37 +++ .../attributes/InstantStringParser.java | 39 +++ .../attributes/IntegerStringParser.java | 37 +++ .../oauth2/attributes/LongStringParser.java | 37 +++ .../oauth2/attributes/NoOpStringParser.java | 32 +++ .../support/oauth2/attributes/Parser.java | 32 +++ .../StringListStringParser.java} | 19 +- .../StringSetStringParser.java} | 19 +- .../oauth2/attributes/UrlStringParser.java | 44 +++ .../properties/BooleanPropertyParser.java | 35 --- .../properties/DoublePropertyParser.java | 35 --- .../properties/InstantPropertyParser.java | 37 --- .../properties/IntegerPropertyParser.java | 35 --- .../oauth2/properties/LongPropertyParser.java | 35 --- .../oauth2/properties/NoOpPropertyParser.java | 35 --- .../support/oauth2/properties/Property.java | 83 ------ .../oauth2/properties/PropertyParser.java | 37 --- .../properties/PropertyParsersHelper.java | 261 ------------------ .../oauth2/properties/UrlPropertyParser.java | 43 --- .../support/oauth2/AnnotationHelperTest.java | 94 +++++++ .../oauth2/AuthoritiesAndScopesTest.java | 101 +++++++ ...ithMockJwtSecurityContextFactoryTests.java | 247 ++++++++++++----- .../support/oauth2/WithMockJwtTests.java | 64 ++--- .../AttributeParsersHelperTest.java | 192 +++++++++++++ .../properties/PropertyParsersHelperTest.java | 149 ---------- 34 files changed, 1451 insertions(+), 1112 deletions(-) create mode 100644 oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/AnnotationHelper.java create mode 100644 oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/AuthoritiesAndScopes.java delete mode 100644 oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/StringObjectMapHelper.java delete mode 100644 oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/StringParsingHelper.java create mode 100644 oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/Attribute.java create mode 100644 oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/AttributeParsersHelper.java create mode 100644 oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/BooleanStringParser.java create mode 100644 oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/DoubleStringParser.java create mode 100644 oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/InstantStringParser.java create mode 100644 oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/IntegerStringParser.java create mode 100644 oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/LongStringParser.java create mode 100644 oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/NoOpStringParser.java create mode 100644 oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/Parser.java rename oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/{properties/StringListPropertyParser.java => attributes/StringListStringParser.java} (54%) rename oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/{properties/StringSetPropertyParser.java => attributes/StringSetStringParser.java} (54%) create mode 100644 oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/UrlStringParser.java delete mode 100644 oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/properties/BooleanPropertyParser.java delete mode 100644 oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/properties/DoublePropertyParser.java delete mode 100644 oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/properties/InstantPropertyParser.java delete mode 100644 oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/properties/IntegerPropertyParser.java delete mode 100644 oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/properties/LongPropertyParser.java delete mode 100644 oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/properties/NoOpPropertyParser.java delete mode 100644 oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/properties/Property.java delete mode 100644 oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/properties/PropertyParser.java delete mode 100644 oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/properties/PropertyParsersHelper.java delete mode 100644 oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/properties/UrlPropertyParser.java create mode 100644 oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/AnnotationHelperTest.java create mode 100644 oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/AuthoritiesAndScopesTest.java create mode 100644 oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/attributes/AttributeParsersHelperTest.java delete mode 100644 oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/properties/PropertyParsersHelperTest.java diff --git a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/AnnotationHelper.java b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/AnnotationHelper.java new file mode 100644 index 00000000000..d121c10b2f7 --- /dev/null +++ b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/AnnotationHelper.java @@ -0,0 +1,56 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. 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 org.springframework.security.test.context.support.oauth2; + +import java.util.Collection; +import java.util.Map; +import java.util.stream.Stream; + +import org.springframework.util.StringUtils; + +/** + * + * @author Jérôme Wacongne <ch4mp@c4-soft.com> + * @since 5.2.0 + * + */ +class AnnotationHelper { + + public static String nullIfEmpty(final String str) { + return StringUtils.isEmpty(str) ? null : str; + } + + public static final Map + putIfNotEmpty(final String key, final String value, final Map map) { + if (value != null && !value.isEmpty()) { + map.put(key, value); + } + return map; + } + + public static final Map + putIfNotEmpty(final String key, final Collection value, final Map map) { + if (value != null && !value.isEmpty()) { + map.put(key, value); + } + return map; + } + + public static Stream stringStream(final String... values) { + if (values == null || values.length == 0) { + return Stream.empty(); + } + return Stream.of(values).map(AnnotationHelper::nullIfEmpty).filter(a -> a != null); + } + +} diff --git a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/AuthoritiesAndScopes.java b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/AuthoritiesAndScopes.java new file mode 100644 index 00000000000..c146c243a34 --- /dev/null +++ b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/AuthoritiesAndScopes.java @@ -0,0 +1,118 @@ +package org.springframework.security.test.context.support.oauth2; + +import static org.springframework.security.test.context.support.oauth2.AnnotationHelper.putIfNotEmpty; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.springframework.security.core.authority.SimpleGrantedAuthority; + +final class AuthoritiesAndScopes { + public final Set authorities; + public final Set scopes; + public final Optional scopeAttributeName; + + private AuthoritiesAndScopes( + final Set authorities, + final Set scopes, + final Optional scopeAttributeName) { + this.authorities = Collections.unmodifiableSet(authorities); + this.scopes = Collections.unmodifiableSet(scopes); + this.scopeAttributeName = scopeAttributeName; + } + + /** + *

+ * Merges {@code authorities} and {@code scope}. + *

+ *

+ * Scopes are searched for in attributes with keys "scope", "scp" and "scopes", first entry found being used and + * others ignored. + *

+ * + *
+	 * @Foo(
+	 * 		authorities = { "ROLE_R", "SCOPE_s1" },
+	 * 		scopes = { "s2" },
+	 * 		attributes = @Attribute(name = "scope", value = "s3", parser = "StringSetStringParser"))
+	 * static final class Decorated {
+	 * }
+	 *
+	 * @Test
+	 * public void testScopesAndAuthorities() {
+	 * 	final Foo annotation = AnnotationUtils.findAnnotation(Decorated.class, Foo.class);
+	 * 	final Map attributes =
+	 * 			new HashMap<>(AttributeParsersHelper.withDefaultParsers().parse(annotation.attributes()));
+	 *
+	 * 	Set scopeAttributeBeforeGet = (Set) attributes.get("scope");
+	 * 	assertThat(scopeAttributeAfterGet).hasSize(1);
+	 * 	assertThat(scopeAttributeAfterGet).contains("s3");
+	 *
+	 * 	AuthoritiesAndScopes actual =
+	 * 			AuthoritiesAndScopes.get(annotation.authorities(), annotation.scopes(), attributes);
+	 *
+	 * 	assertThat(actual.scopes).hasSize(3);
+	 * 	assertThat(actual.scopes).contains("s1"); // from "SCOPE_s1" authority
+	 * 	assertThat(actual.scopes).contains("s2"); // from "Foo::scopes"
+	 * 	assertThat(actual.scopes).contains("s3"); // from scope attribute
+	 *
+	 * 	assertThat(actual.authorities).hasSize(4);
+	 * 	assertThat(actual.authorities).contains("ROLE_R"); // from authorities
+	 * 	assertThat(actual.authorities).contains("SCOPE_s1"); // from authorities
+	 * 	assertThat(actual.authorities).contains("SCOPE_s2"); // from "Foo::scopes"
+	 * 	assertThat(actual.authorities).contains("SCOPE_s3"); // from scope attribute
+	 *
+	 * 	Set scopeAttributeAfterGet = (Set) attributes.get("scope");
+	 * 	assertThat(scopeAttributeAfterGet).hasSize(3);
+	 * 	assertThat(scopeAttributeAfterGet).contains("s1");
+	 * 	assertThat(scopeAttributeAfterGet).contains("s2");
+	 * 	assertThat(scopeAttributeAfterGet).contains("s3");
+	 * }
+	 * 
+ * + * @param annotatedAuthorities authorities array (probably from an annotation {@code authorities()}) + * @param annotatedScopes scopes array (probably from an annotation {@code scopes()}) + * @param attributes attributes /!\ mutable /!\ map (probably from an annotation {@code attributes()} or + * {@code claims()}) + * @return a structure containing merged granted authorities and scopes + */ + public static AuthoritiesAndScopes get( + final String[] annotatedAuthorities, + final String[] annotatedScopes, + final Map attributes) { + final Optional scopeAttributeName = attributes.keySet() + .stream() + .filter(k -> "scope".equals(k) || "scp".equals(k) || "scopes".equals(k)) + .sorted() + .findFirst(); + + final Stream attributesScopes = + scopeAttributeName.map(attributes::get).map(AuthoritiesAndScopes::stream).orElse(Stream.empty()); + + final Stream authoritiesScopes = + Stream.of(annotatedAuthorities).filter(a -> a.startsWith("SCOPE_")).map(a -> a.substring(6)); + + final Set allScopes = + Stream.concat(Stream.of(annotatedScopes), Stream.concat(authoritiesScopes, attributesScopes)) + .collect(Collectors.toSet()); + + putIfNotEmpty(scopeAttributeName.orElse("scope"), allScopes, attributes); + + final Set allAuthorities = + Stream.concat(Stream.of(annotatedAuthorities), allScopes.stream().map(scope -> "SCOPE_" + scope)) + .map(SimpleGrantedAuthority::new) + .collect(Collectors.toSet()); + + return new AuthoritiesAndScopes(allAuthorities, allScopes, scopeAttributeName); + } + + @SuppressWarnings("unchecked") + private static Stream stream(final Object col) { + return col == null ? Stream.empty() : ((Collection) col).stream(); + } +} \ No newline at end of file diff --git a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/StringObjectMapHelper.java b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/StringObjectMapHelper.java deleted file mode 100644 index 5111a20cc65..00000000000 --- a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/StringObjectMapHelper.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * 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 org.springframework.security.test.context.support.oauth2; - -import java.util.List; -import java.util.Map; - -/** - * - * @author Jérôme Wacongne <ch4mp@c4-soft.com> - * @since 5.2.0 - * - */ -class StringObjectMapHelper { - - public static final Map putIfNotEmpty(String key, String value, - Map map) { - if (value != null && !value.isEmpty()) { - map.put(key, value); - } - return map; - } - - public static final Map putIfNotEmpty(String key, List value, - Map map) { - if (value != null && !value.isEmpty()) { - map.put(key, value); - } - return map; - } - - public static final Map putIfNotEmpty(String key, T value, - Map map) { - if (value != null) { - map.put(key, value); - } - return map; - } - -} diff --git a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/StringParsingHelper.java b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/StringParsingHelper.java deleted file mode 100644 index e21e3f37ae7..00000000000 --- a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/StringParsingHelper.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * 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 org.springframework.security.test.context.support.oauth2; - -import java.net.MalformedURLException; -import java.net.URL; -import java.time.Instant; -import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import org.springframework.security.core.authority.SimpleGrantedAuthority; - -/** - * - * @author Jérôme Wacongne <ch4mp@c4-soft.com> - * @since 5.2.0 - * - */ -class StringParsingHelper { - - public static String nullIfEmpty(String str) { - return str == null || str.isEmpty() ? null : str; - } - - public static Instant intant(String str) { - return str == null || str.isEmpty() ? null : Instant.parse(str); - } - - public static URL url(String str) throws MalformedURLException { - return str == null || str.isEmpty() ? null : new URL(str); - } - - public static List stringList(String[] stringArr) { - return Stream.of(stringArr).collect(Collectors.toList()); - } - - public static Set stringSet(String[] stringArr) { - return Stream.of(stringArr).collect(Collectors.toSet()); - } - - public static Set grantedAuthorities(String[] stringArr) { - return Stream.of(stringArr).map(SimpleGrantedAuthority::new) - .collect(Collectors.toSet()); - } - -} \ No newline at end of file diff --git a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/WithMockJwt.java b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/WithMockJwt.java index e589a274efd..cae2a4765de 100644 --- a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/WithMockJwt.java +++ b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/WithMockJwt.java @@ -1,17 +1,14 @@ /* * Copyright 2002-2019 the original author or authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * 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 + * 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. + * 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.test.context.support.oauth2; @@ -31,41 +28,43 @@ import org.springframework.security.test.context.support.TestExecutionEvent; import org.springframework.security.test.context.support.WithSecurityContext; import org.springframework.security.test.context.support.WithSecurityContextTestExecutionListener; -import org.springframework.security.test.context.support.oauth2.properties.Property; -import org.springframework.security.test.context.support.oauth2.properties.PropertyParser; -import org.springframework.security.test.context.support.oauth2.properties.PropertyParsersHelper; +import org.springframework.security.test.context.support.oauth2.attributes.Attribute; +import org.springframework.security.test.context.support.oauth2.attributes.AttributeParsersHelper; +import org.springframework.security.test.context.support.oauth2.attributes.Parser; import org.springframework.test.context.TestContext; import org.springframework.test.web.servlet.MockMvc; /** *

- * A lot like - * {@link org.springframework.security.test.context.support.WithMockUser @WithMockUser}: - * when used with {@link WithSecurityContextTestExecutionListener} this annotation can be - * added to a test method to emulate running with a mocked authentication created out of a - * {@link Jwt JWT}. + * A lot like {@link org.springframework.security.test.context.support.WithMockUser @WithMockUser}: when used with + * {@link WithSecurityContextTestExecutionListener} this annotation can be added to a test method to emulate running + * with a mocked authentication created out of a {@link Jwt JWT}. *

*

* Main steps are: *

*
    - *
  • A {@link Jwt JWT} is created as per this annotation {@code name} (forces - * {@code subject} claim), {@code headers} and {@code claims}
  • - *
  • A {@link JwtAuthenticationToken JwtAuthenticationToken} is then created and fed - * with this new JWT token
  • - *
  • An empty {@link SecurityContext} is instantiated and populated with this - * {@code JwtAuthenticationToken}
  • + *
  • A {@link Jwt JWT} is created as per this annotation {@code name} (forces {@code subject} claim), {@code headers} + * and {@code claims}
  • + *
  • A {@link JwtAuthenticationToken JwtAuthenticationToken} is then created and fed with this new JWT token
  • + *
  • An empty {@link SecurityContext} is instantiated and populated with this {@code JwtAuthenticationToken}
  • *
*

- * As a result, the {@link Authentication} {@link MockMvc} gets from security context will - * have the following properties: + * As a result, the {@link Authentication} {@link MockMvc} gets from security context will have the following + * properties: *

*
    *
  • {@link Authentication#getPrincipal() getPrincipal()} returns a {@link Jwt}
  • - *
  • {@link Authentication#getName() getName()} returns the JWT {@code subject} claim - * (set from this annotation {@code name} value)
  • - *
  • {@link Authentication#getAuthorities() authorities} will be a collection of - * {@link SimpleGrantedAuthority} as defined by this annotation {@code authorities}
  • + *
  • {@link Authentication#getName() getName()} returns the JWT {@code subject} claim, set from this annotation + * {@code name} value ({@code "user"} by default)
  • + *
  • {@link Authentication#getAuthorities() authorities} will be a collection of {@link SimpleGrantedAuthority} as + * defined by this annotation {@link #authorities()} ( + * + *
    + * { "ROLE_USER" }
    + * 
    + * + * by default
  • *
* * Sample Usage: @@ -74,30 +73,29 @@ * @WithMockJwt * @Test * public void testSomethingWithDefaultJwtAuthentication() { - * //no authority - * //single {@link DEFAULT_HEADER_NAME} header (can't be empty) - * //"sub" claim (subject) with {@link DEFAULT_AUTH_NAME} as value + * //identified as {@code "user"} with {@code "ROLE_USER"} + * //claims contain {@code "sub"} (subject) with {@code "ch4mpy"} as value + * //headers can't be empty, so a default one is set * ... * } * * @WithMockJwt( * authorities = {"ROLE_USER", "ROLE_ADMIN"}, - * name = "user", - * headers = { @Property(name = "foo", value = "bar") }, - * claims = { @Property(name = "machin", value = "chose") }) + * name = "ch4mpy", + * headers = { @Attribute(name = "foo", value = "bar") }, + * claims = { @Attribute(name = "machin", value = "chose") }) * @Test * public void testSomethingWithCustomJwtAuthentication() { - * //two authorities - * //single "foo" header with "bar" as value - * //"machin" claim with "chose" as value - * //"sub" claim (subject) with "user" as value + * //identified as {@code "ch4mpy"} with {@code "ROLE_USER"} and {@code "ROLE_ADMIN"} + * //claims are {@code "machin"} with {@code "chose"} as value and {@code "sub"} (subject) with {@code "ch4mpy"} as value + * //single {@code "foo"} header with {@code "bar"} as value * ... * } * * - * @see Property - * @see PropertyParser - * @see PropertyParsersHelper#DEFAULT_PARSERS + * @see Attribute + * @see Parser + * @see AttributeParsersHelper#DEFAULT_PARSERS * * @author Jérôme Wacongne <ch4mp@c4-soft.com> * @since 5.2.0 @@ -118,18 +116,17 @@ * @return Authorities the client is to be granted */ @AliasFor("authorities") - String[] value() default {}; + String[] value() default { "ROLE_USER" }; /** * Alias for value * @return Authorities the client is to be granted */ @AliasFor("value") - String[] authorities() default {}; + String[] authorities() default { "ROLE_USER" }; /** - * To be used both as authentication {@code Principal} name and token {@code username} - * attribute. + * To be used both as authentication {@code Principal} name and token {@code username} attribute. * @return Resource owner name */ String name() default DEFAULT_AUTH_NAME; @@ -137,22 +134,21 @@ /** * @return JWT claims */ - Property[] claims() default {}; + Attribute[] claims() default {}; /** * Of little use at unit test time... * @return JWT headers */ - Property[] headers() default { - @Property(name = DEFAULT_HEADER_NAME, value = DEFAULT_HEADER_VALUE) }; + Attribute[] headers() default { @Attribute(name = DEFAULT_HEADER_NAME, value = DEFAULT_HEADER_VALUE) }; /** - * {@link PropertyParsersHelper#DEFAULT_PARSERS Defaulted parsers} are provided for - * most common value types. + * {@link AttributeParsersHelper#DEFAULT_PARSERS Defaulted parsers} are provided for most common value types. Those + * will be added to defaults (and override defaults when retrieved by simple name) * * @return parsers to add to default ones (or override) * - * @see PropertyParsersHelper#DEFAULT_PARSERS + * @see AttributeParsersHelper#DEFAULT_PARSERS */ String[] additionalParsers() default {}; diff --git a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/WithMockJwtSecurityContextFactory.java b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/WithMockJwtSecurityContextFactory.java index f6d5dd07ebb..0976643721e 100644 --- a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/WithMockJwtSecurityContextFactory.java +++ b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/WithMockJwtSecurityContextFactory.java @@ -1,22 +1,18 @@ /* * Copyright 2002-2019 the original author or authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * 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 + * 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. + * 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.test.context.support.oauth2; -import static org.springframework.security.test.context.support.oauth2.StringObjectMapHelper.putIfNotEmpty; -import static org.springframework.security.test.context.support.oauth2.StringParsingHelper.grantedAuthorities; +import static org.springframework.security.test.context.support.oauth2.AnnotationHelper.putIfNotEmpty; import java.time.Instant; import java.util.HashMap; @@ -28,46 +24,48 @@ import org.springframework.security.oauth2.jwt.JwtClaimNames; import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; import org.springframework.security.test.context.support.WithSecurityContextFactory; -import org.springframework.security.test.context.support.oauth2.properties.PropertyParsersHelper; +import org.springframework.security.test.context.support.oauth2.attributes.AttributeParsersHelper; /** - * Create a {@link org.springframework.security.core.context.SecurityContext - * SecurityContext} populated with a + * Create a {@link org.springframework.security.core.context.SecurityContext SecurityContext} populated with a * {@link org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken - * JwtAuthenticationToken} containing a {@link org.springframework.security.oauth2.jwt.Jwt - * JWT} as described by + * JwtAuthenticationToken} containing a {@link org.springframework.security.oauth2.jwt.Jwt JWT} as described by * {@link org.springframework.security.test.context.support.oauth2.WithMockJwt @WithMockJwt} * * @author Jérôme Wacongne <ch4mp@c4-soft.com> * @since 5.2.0 * */ -public final class WithMockJwtSecurityContextFactory - implements WithSecurityContextFactory { +public final class WithMockJwtSecurityContextFactory implements WithSecurityContextFactory { public static final String DEFAULT_TOKEN_VALUE = "test.jwt.value"; @Override public SecurityContext createSecurityContext(final WithMockJwt annotation) { - final PropertyParsersHelper propertyParsers = PropertyParsersHelper - .withDefaultParsers(annotation.additionalParsers()); + final AttributeParsersHelper parsersHelper = + AttributeParsersHelper.withDefaultParsers(annotation.additionalParsers()); - final Map headers = propertyParsers.parse(annotation.headers()); + final Map headers = parsersHelper.parse(annotation.headers()); - Map claims = propertyParsers.parse(annotation.claims()); + final Map claims = new HashMap<>(parsersHelper.parse(annotation.claims())); if (claims.containsKey(JwtClaimNames.SUB)) { - throw new RuntimeException(JwtClaimNames.SUB - + " claim is not configurable (forced to @WithMockJwt.name)"); - } - else { - claims = new HashMap<>(claims); + throw new RuntimeException(JwtClaimNames.SUB + " claim is not configurable (forced to @WithMockJwt.name)"); + } else { putIfNotEmpty(JwtClaimNames.SUB, annotation.name(), claims); } + final AuthoritiesAndScopes authoritiesAndScopes = + AuthoritiesAndScopes.get(annotation.authorities(), new String[] {}, claims); + final SecurityContext context = SecurityContextHolder.createEmptyContext(); - context.setAuthentication(new JwtAuthenticationToken( - new Jwt(DEFAULT_TOKEN_VALUE, (Instant) claims.get(JwtClaimNames.IAT), - (Instant) claims.get(JwtClaimNames.EXP), headers, claims), - grantedAuthorities(annotation.authorities()))); + context.setAuthentication( + new JwtAuthenticationToken( + new Jwt( + DEFAULT_TOKEN_VALUE, + (Instant) claims.get(JwtClaimNames.IAT), + (Instant) claims.get(JwtClaimNames.EXP), + headers, + claims), + authoritiesAndScopes.authorities)); return context; } diff --git a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/Attribute.java b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/Attribute.java new file mode 100644 index 00000000000..193c67d72f8 --- /dev/null +++ b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/Attribute.java @@ -0,0 +1,78 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. 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 org.springframework.security.test.context.support.oauth2.attributes; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + *

+ * Annotation to create an entry in a {@link java.util.Map Map<String, Object>} such as + * {@link org.springframework.security.oauth2.jwt.Jwt JWT} headers or claims. + *

+ *

+ * {@link #parser()} can be referenced either by fully qualified name or simple name. + *

+ *

+ * See {@link Parser} and its already provided implementations: {@link AttributeParsersHelper#DEFAULT_PARSERS} + *

+ * Sample usage:
+ * + *
+ * @WithMockJwt(
+ *   claims = {
+ *     @Attribute(name = JwtClaimNames.AUD, value = "first audience", parser = "StringListStringParser"),
+ *     @Attribute(name = JwtClaimNames.AUD, value = "second audience", parser = "StringListStringParser"),
+ *     @Attribute(name = JwtClaimNames.ISS, value = "https://test-issuer.org", parser = "UrlStringParser"),
+ *     @Attribute(name = "machin", value = "chose"),
+ *     @Attribute(name = "truc", value = "bidule", parser = "your.fancy.ParserImpl")})
+ * 
+ * + * This will create + *
    + *
  • an {@code audience} claim with a value being a {@code List} with two entries
  • + *
  • an {@code issuer} claim with a value being a {@code java.net.URL} instance
  • + *
  • a {@code machin} claim with {@code chose} String as value (default parser is + * {@link org.springframework.security.test.context.support.oauth2.attributes.NoOpStringParser NoOpStringParser})
  • + *
  • a {@code truc} claim whith an instance of what {@code your.fancy.ParserImpl} is designed to build from + * {@code bidule} string as value
  • + *
+ * + * @author Jérôme Wacongne <ch4mp@c4-soft.com> + * @since 5.2.0 + * + */ +@Target({ ElementType.METHOD, ElementType.TYPE }) +@Retention(RetentionPolicy.RUNTIME) +public @interface Attribute { + + /** + * @return the key in the {@link java.util.Map Map<String, Object>} + */ + String name(); + + /** + * @return a value to be transformed using "parser" before being put as value in {@link java.util.Map Map<String, + * Object>} + */ + String value(); + + /** + * @return a {@link org.springframework.security.test.context.support.oauth2.attributes.Parser Parser} + * implementation class name + */ + String parser() default "NoOpStringParser"; + +} diff --git a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/AttributeParsersHelper.java b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/AttributeParsersHelper.java new file mode 100644 index 00000000000..a39b2e707a9 --- /dev/null +++ b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/AttributeParsersHelper.java @@ -0,0 +1,221 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. 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 org.springframework.security.test.context.support.oauth2.attributes; + +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.BinaryOperator; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * Helps turn a {@link org.springframework.security.test.context.support.oauth2.attributes.Attribute @Attribute} array + * into a {@link java.util.Map Map<String, Object>} + * + * @author Jérôme Wacongne <ch4mp@c4-soft.com> + * @since 5.2.0 + * + */ +public class AttributeParsersHelper { + /** + *
    + *
  • {@link NoOpStringParser} => keeps value as is.
  • + *
  • {@link BooleanStringParser} => Boolean
  • + *
  • {@link DoubleStringParser} => Double
  • + *
  • {@link InstantStringParser} => {@link java.time.Instant Instant}
  • + *
  • {@link IntegerStringParser} => Integer
  • + *
  • {@link LongStringParser} => Long
  • + *
  • {@link StringListStringParser} => List<String>
  • + *
  • {@link StringSetStringParser} => Set<String>
  • + *
  • {@link UrlStringParser} => URL
  • + *
+ */ + public static final Set> DEFAULT_PARSERS = new HashSet<>(); + + static { + DEFAULT_PARSERS.add(new NoOpStringParser()); + DEFAULT_PARSERS.add(new BooleanStringParser()); + DEFAULT_PARSERS.add(new DoubleStringParser()); + DEFAULT_PARSERS.add(new InstantStringParser()); + DEFAULT_PARSERS.add(new IntegerStringParser()); + DEFAULT_PARSERS.add(new LongStringParser()); + DEFAULT_PARSERS.add(new StringListStringParser()); + DEFAULT_PARSERS.add(new StringSetStringParser()); + DEFAULT_PARSERS.add(new UrlStringParser()); + } + + private final Map> parsers; + + @SuppressWarnings("unchecked") + private AttributeParsersHelper(final Set> baseParsers, final String... additionalParserNames) { + this.parsers = new HashMap<>(2 * DEFAULT_PARSERS.size() + 2 * additionalParserNames.length); + final Stream> additionalParsers = Stream.of(additionalParserNames).distinct().map(t -> { + try { + return Class.forName(t); + } catch (final ClassNotFoundException e) { + throw new RuntimeException(e); + } + }).map(c -> { + try { + return (Parser) c.getDeclaredConstructor().newInstance(); + } catch (InstantiationException | IllegalAccessException | IllegalArgumentException + | InvocationTargetException | NoSuchMethodException | SecurityException e) { + throw new RuntimeException("Missing public no-arg constructor on " + c.getName()); + } + }); + + Stream.concat(baseParsers.stream(), additionalParsers).forEachOrdered(p -> { + this.parsers.put(p.getClass().getName(), p); + this.parsers.put(p.getClass().getSimpleName(), p); + }); + } + + /** + * @param parserClassName {@code Parser.class.getName()} + * @return Parser instance + */ + public Parser getParser(final String parserClassName) { + return this.parsers.get(parserClassName); + } + + private ParsedProperty parse(final Attribute p) { + final Parser parser = getParser(p.parser()); + if (parser == null) { + throw new RuntimeException("No registered Parser implementation for " + p.parser()); + } + + return new ParsedProperty<>(p.name(), parser.parse(p.value())); + } + + /** + *

+ * Turns a {@link org.springframework.security.test.context.support.oauth2.attributes.Attribute @Attribute} array + * into a {@link java.util.Map Map<String, Object>} as required for + * {@link org.springframework.security.oauth2.jwt.Jwt JWT} headers and claims. + *

+ *

+ * Process highlights: + *

+ *
    + *
  • each {@link org.springframework.security.test.context.support.oauth2.attributes.Attribute#value() value()} is + * parsed according to {@link org.springframework.security.test.context.support.oauth2.attributes.Attribute#parser() + * parser()}
  • + *
  • obtained values are associated with + * {@link org.springframework.security.test.context.support.oauth2.attributes.Attribute#name() name()}
  • + *
  • values with same name are accumulated in the same collection
  • + *
+ * + * @param properties to be transformed + * @return processed properties + */ + @SuppressWarnings("unchecked") + public Map parse(final Attribute... properties) { + return Stream.of(properties) + .map(this::parse) + .collect(Collectors.toMap(ParsedProperty::getName, ParsedProperty::getValue, (v1, v2) -> { + if (!(v1 instanceof Collection) || !(v2 instanceof Collection)) { + throw new UnsupportedOperationException( + "@Attribute values can be accumuleted only if instance of Collection"); + } + if (v1 instanceof Map) { + if (v2 instanceof Map) { + return MAP_ACCUMULATOR.apply((Map) v1, (Map) v2); + } + throw new UnsupportedOperationException( + "@Attribute \"Map\" values can only be accumulated with Maps"); + } + if (v2 instanceof Map) { + throw new UnsupportedOperationException( + "@Attribute \"Map\" values can only be accumulated with Maps"); + } + if (v1 instanceof List) { + return LIST_ACCUMULATOR.apply((List) v1, (Collection) v2); + } + return SET_ACCUMULATOR.apply((Collection) v1, (Collection) v2); + })); + } + + /** + * Instantiates default {@link org.springframework.security.test.context.support.oauth2.attributes.Parser Parser}s + * plus all provided ones (using default constructor) + * + * @param additionalParserNames {@link org.springframework.security.test.context.support.oauth2.attributes.Parser + * Parser} implementations class names to add to + * {@link org.springframework.security.test.context.support.oauth2.attributes.AttributeParsersHelper#DEFAULT_PARSERS + * default ones} + * @return helper instance with provided parsers plus default ones + */ + public static AttributeParsersHelper withDefaultParsers(final String... additionalParserNames) { + return new AttributeParsersHelper(DEFAULT_PARSERS, additionalParserNames); + } + + /** + * Instantiates all provided {@link org.springframework.security.test.context.support.oauth2.attributes.Parser + * Parser}s using default constructor + * + * @param allParserNames {@link org.springframework.security.test.context.support.oauth2.attributes.Parser Parser} + * implementations class names + * @return helper instance with provided parsers only + */ + public static AttributeParsersHelper withoutDefaultParsers(final String... allParserNames) { + return new AttributeParsersHelper(Collections.emptySet(), allParserNames); + } + + private static final class ParsedProperty { + private final String name; + private final T value; + + public ParsedProperty(final String name, final T value) { + super(); + this.name = name; + this.value = value; + } + + public String getName() { + return name; + } + + public T getValue() { + return value; + } + + } + + private static BinaryOperator> SET_ACCUMULATOR = (v1, v2) -> { + final HashSet all = new HashSet<>(v1.size() + v2.size()); + all.addAll(v1); + all.addAll(v2); + return all; + }; + + private static BinaryOperator> LIST_ACCUMULATOR = (v1, v2) -> { + final ArrayList all = new ArrayList<>(v1.size() + v2.size()); + all.addAll(v1); + all.addAll(v2); + return all; + }; + + private static BinaryOperator> MAP_ACCUMULATOR = (v1, v2) -> { + final HashMap all = new HashMap<>(v1.size() + v2.size()); + all.putAll(v1); + all.putAll(v2); + return all; + }; +} diff --git a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/BooleanStringParser.java b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/BooleanStringParser.java new file mode 100644 index 00000000000..4eff10ef271 --- /dev/null +++ b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/BooleanStringParser.java @@ -0,0 +1,37 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. 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 org.springframework.security.test.context.support.oauth2.attributes; + +import org.springframework.util.StringUtils; + +/** + * Turns an annotation String value into a Boolean + * + * @author Jérôme Wacongne <ch4mp@c4-soft.com> + * @since 5.2.0 + * + */ +public class BooleanStringParser implements Parser { + + /** + * {@inheritDoc} + */ + @Override + public Boolean parse(final String value) { + if (StringUtils.isEmpty(value)) { + return null; + } + return Boolean.valueOf(value); + } + +} diff --git a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/DoubleStringParser.java b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/DoubleStringParser.java new file mode 100644 index 00000000000..d12ccba842d --- /dev/null +++ b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/DoubleStringParser.java @@ -0,0 +1,37 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. 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 org.springframework.security.test.context.support.oauth2.attributes; + +import org.springframework.util.StringUtils; + +/** + * Turns an annotation String value into a Double + * + * @author Jérôme Wacongne <ch4mp@c4-soft.com> + * @since 5.2.0 + * + */ +public class DoubleStringParser implements Parser { + + /** + * {@inheritDoc} + */ + @Override + public Double parse(final String value) { + if (StringUtils.isEmpty(value)) { + return null; + } + return Double.valueOf(value); + } + +} diff --git a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/InstantStringParser.java b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/InstantStringParser.java new file mode 100644 index 00000000000..cba7f6513f4 --- /dev/null +++ b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/InstantStringParser.java @@ -0,0 +1,39 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. 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 org.springframework.security.test.context.support.oauth2.attributes; + +import java.time.Instant; + +import org.springframework.util.StringUtils; + +/** + * Turns an annotation String value into an {@link java.time.Instant Instant} + * + * @author Jérôme Wacongne <ch4mp@c4-soft.com> + * @since 5.2.0 + * + */ +public class InstantStringParser implements Parser { + + /** + * {@inheritDoc} + */ + @Override + public Instant parse(final String value) { + if (StringUtils.isEmpty(value)) { + return null; + } + return Instant.parse(value); + } + +} diff --git a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/IntegerStringParser.java b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/IntegerStringParser.java new file mode 100644 index 00000000000..d8300d2f56e --- /dev/null +++ b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/IntegerStringParser.java @@ -0,0 +1,37 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. 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 org.springframework.security.test.context.support.oauth2.attributes; + +import org.springframework.util.StringUtils; + +/** + * Turns an annotation String value into an Integer + * + * @author Jérôme Wacongne <ch4mp@c4-soft.com> + * @since 5.2.0 + * + */ +public class IntegerStringParser implements Parser { + + /** + * {@inheritDoc} + */ + @Override + public Integer parse(final String value) { + if (StringUtils.isEmpty(value)) { + return null; + } + return Integer.valueOf(value); + } + +} diff --git a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/LongStringParser.java b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/LongStringParser.java new file mode 100644 index 00000000000..94a5b0f6412 --- /dev/null +++ b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/LongStringParser.java @@ -0,0 +1,37 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. 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 org.springframework.security.test.context.support.oauth2.attributes; + +import org.springframework.util.StringUtils; + +/** + * Turns an annotation String value into a Long + * + * @author Jérôme Wacongne <ch4mp@c4-soft.com> + * @since 5.2.0 + * + */ +public class LongStringParser implements Parser { + + /** + * {@inheritDoc} + */ + @Override + public Long parse(final String value) { + if (StringUtils.isEmpty(value)) { + return null; + } + return Long.valueOf(value); + } + +} diff --git a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/NoOpStringParser.java b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/NoOpStringParser.java new file mode 100644 index 00000000000..bbd27d5208f --- /dev/null +++ b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/NoOpStringParser.java @@ -0,0 +1,32 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. 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 org.springframework.security.test.context.support.oauth2.attributes; + +/** + * Leaves an annotation String value untouched + * + * @author Jérôme Wacongne <ch4mp@c4-soft.com> + * @since 5.2.0 + * + */ +public class NoOpStringParser implements Parser { + + /** + * {@inheritDoc} + */ + @Override + public String parse(final String value) { + return value; + } + +} diff --git a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/Parser.java b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/Parser.java new file mode 100644 index 00000000000..1f362031386 --- /dev/null +++ b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/Parser.java @@ -0,0 +1,32 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. 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 org.springframework.security.test.context.support.oauth2.attributes; + +/** + * + * @author Jérôme Wacongne <ch4mp@c4-soft.com> + * @since 5.2.0 + * + * @param type before parsing (source) + * @param type after parsing (target) + * + */ +public interface Parser { + + /** + * @param value to de-serialize + * @return an Object + */ + TO_TYPE parse(FROM_TYPE value); + +} diff --git a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/properties/StringListPropertyParser.java b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/StringListStringParser.java similarity index 54% rename from oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/properties/StringListPropertyParser.java rename to oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/StringListStringParser.java index 884ef7688e8..aeca5ad1ccd 100644 --- a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/properties/StringListPropertyParser.java +++ b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/StringListStringParser.java @@ -1,19 +1,16 @@ /* * Copyright 2002-2019 the original author or authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * 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 + * 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. + * 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.test.context.support.oauth2.properties; +package org.springframework.security.test.context.support.oauth2.attributes; import java.util.Collections; import java.util.List; @@ -25,7 +22,7 @@ * @since 5.2.0 * */ -public class StringListPropertyParser implements PropertyParser> { +public class StringListStringParser implements Parser> { /** * {@inheritDoc} diff --git a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/properties/StringSetPropertyParser.java b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/StringSetStringParser.java similarity index 54% rename from oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/properties/StringSetPropertyParser.java rename to oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/StringSetStringParser.java index 4f781fd4c8e..41d5136d0d3 100644 --- a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/properties/StringSetPropertyParser.java +++ b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/StringSetStringParser.java @@ -1,19 +1,16 @@ /* * Copyright 2002-2019 the original author or authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * 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 + * 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. + * 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.test.context.support.oauth2.properties; +package org.springframework.security.test.context.support.oauth2.attributes; import java.util.Collections; import java.util.Set; @@ -25,7 +22,7 @@ * @since 5.2.0 * */ -public class StringSetPropertyParser implements PropertyParser> { +public class StringSetStringParser implements Parser> { /** * {@inheritDoc} diff --git a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/UrlStringParser.java b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/UrlStringParser.java new file mode 100644 index 00000000000..1e7a18cffbd --- /dev/null +++ b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/UrlStringParser.java @@ -0,0 +1,44 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. 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 org.springframework.security.test.context.support.oauth2.attributes; + +import java.net.MalformedURLException; +import java.net.URL; + +import org.springframework.util.StringUtils; + +/** + * Turns an annotation String value into an {@link java.net.URL URL} + * + * @author Jérôme Wacongne <ch4mp@c4-soft.com> + * @since 5.2.0 + * + */ +public class UrlStringParser implements Parser { + + /** + * {@inheritDoc} + */ + @Override + public URL parse(final String value) { + if (StringUtils.isEmpty(value)) { + return null; + } + try { + return new URL(value); + } catch (final MalformedURLException e) { + throw new RuntimeException(e); + } + } + +} diff --git a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/properties/BooleanPropertyParser.java b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/properties/BooleanPropertyParser.java deleted file mode 100644 index 023f88b4406..00000000000 --- a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/properties/BooleanPropertyParser.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * 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 org.springframework.security.test.context.support.oauth2.properties; - -/** - * Turns an annotation String value into a Boolean - * - * @author Jérôme Wacongne <ch4mp@c4-soft.com> - * @since 5.2.0 - * - */ -public class BooleanPropertyParser implements PropertyParser { - - /** - * {@inheritDoc} - */ - @Override - public Boolean parse(String value) { - return Boolean.valueOf(value); - } - -} diff --git a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/properties/DoublePropertyParser.java b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/properties/DoublePropertyParser.java deleted file mode 100644 index 0d2a4d2cfdf..00000000000 --- a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/properties/DoublePropertyParser.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * 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 org.springframework.security.test.context.support.oauth2.properties; - -/** - * Turns an annotation String value into a Double - * - * @author Jérôme Wacongne <ch4mp@c4-soft.com> - * @since 5.2.0 - * - */ -public class DoublePropertyParser implements PropertyParser { - - /** - * {@inheritDoc} - */ - @Override - public Double parse(String value) { - return Double.valueOf(value); - } - -} diff --git a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/properties/InstantPropertyParser.java b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/properties/InstantPropertyParser.java deleted file mode 100644 index 2317227ffe2..00000000000 --- a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/properties/InstantPropertyParser.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * 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 org.springframework.security.test.context.support.oauth2.properties; - -import java.time.Instant; - -/** - * Turns an annotation String value into an {@link java.time.Instant Instant} - * - * @author Jérôme Wacongne <ch4mp@c4-soft.com> - * @since 5.2.0 - * - */ -public class InstantPropertyParser implements PropertyParser { - - /** - * {@inheritDoc} - */ - @Override - public Instant parse(String value) { - return Instant.parse(value); - } - -} diff --git a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/properties/IntegerPropertyParser.java b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/properties/IntegerPropertyParser.java deleted file mode 100644 index 767fa2184df..00000000000 --- a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/properties/IntegerPropertyParser.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * 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 org.springframework.security.test.context.support.oauth2.properties; - -/** - * Turns an annotation String value into an Integer - * - * @author Jérôme Wacongne <ch4mp@c4-soft.com> - * @since 5.2.0 - * - */ -public class IntegerPropertyParser implements PropertyParser { - - /** - * {@inheritDoc} - */ - @Override - public Integer parse(String value) { - return Integer.valueOf(value); - } - -} diff --git a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/properties/LongPropertyParser.java b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/properties/LongPropertyParser.java deleted file mode 100644 index 2771cf72e86..00000000000 --- a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/properties/LongPropertyParser.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * 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 org.springframework.security.test.context.support.oauth2.properties; - -/** - * Turns an annotation String value into a Long - * - * @author Jérôme Wacongne <ch4mp@c4-soft.com> - * @since 5.2.0 - * - */ -public class LongPropertyParser implements PropertyParser { - - /** - * {@inheritDoc} - */ - @Override - public Long parse(String value) { - return Long.valueOf(value); - } - -} diff --git a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/properties/NoOpPropertyParser.java b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/properties/NoOpPropertyParser.java deleted file mode 100644 index 261ada9b3d6..00000000000 --- a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/properties/NoOpPropertyParser.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * 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 org.springframework.security.test.context.support.oauth2.properties; - -/** - * Leaves an annotation String value untouched - * - * @author Jérôme Wacongne <ch4mp@c4-soft.com> - * @since 5.2.0 - * - */ -public class NoOpPropertyParser implements PropertyParser { - - /** - * {@inheritDoc} - */ - @Override - public String parse(String value) { - return value; - } - -} diff --git a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/properties/Property.java b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/properties/Property.java deleted file mode 100644 index 4b22cdd5c1a..00000000000 --- a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/properties/Property.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * 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 org.springframework.security.test.context.support.oauth2.properties; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - *

- * Annotation to create an entry in a {@link java.util.Map Map<String, Object>} such - * as {@link org.springframework.security.oauth2.jwt.Jwt JWT} headers or claims. - *

- *

- * See - * {@link org.springframework.security.test.context.support.oauth2.properties.PropertyParser - * PropertyParser} and its already provided implementations - *

- * Sample usage:
- * - *
- * @WithMockJwt(
- *   claims = {
- *     @Property(name = "audience", value = "first audience", parser = "org.springframework.security.test.context.support.oauth2.properties.StringListPropertyParser"),
- *     @Property(name = "audience", value = "second audience", parser = "org.springframework.security.test.context.support.oauth2.properties.StringListPropertyParser"),
- *     @Property(name = "issuer", value = "https://test-issuer.org", parser = "org.springframework.security.test.context.support.oauth2.properties.UrlPropertyParser"),
- *     @Property(name = "machin", value = "chose"),
- *     @Property(name = "truc", value = "bidule", parser = "your.fancy.ParserImpl")})
- * 
- * - * This will create - *
    - *
  • an {@code audience} claim with a value being a {@code List} with two - * entries
  • - *
  • an {@code issuer} claim with a value being a {@code java.net.URL} instance
  • - *
  • a {@code machin} claim with {@code chose} String as value (default parser is - * {@link org.springframework.security.test.context.support.oauth2.properties.NoOpPropertyParser - * NoOpPropertyParser})
  • - *
  • a {@code truc} claim whith an instance of what {@code your.fancy.ParserImpl} is - * designed to build from {@code bidule} string as value
  • - *
- * - * @author Jérôme Wacongne <ch4mp@c4-soft.com> - * @since 5.2.0 - * - */ -@Target({ ElementType.METHOD, ElementType.TYPE }) -@Retention(RetentionPolicy.RUNTIME) -public @interface Property { - - /** - * @return the key in the {@link java.util.Map Map<String, Object>} - */ - String name(); - - /** - * @return a value to be transformed using "parser" before being put as value in - * {@link java.util.Map Map<String, Object>} - */ - String value(); - - /** - * @return a - * {@link org.springframework.security.test.context.support.oauth2.properties.PropertyParser - * PropertyParser} implementation class name - */ - String parser() default "org.springframework.security.test.context.support.oauth2.properties.NoOpPropertyParser"; - -} diff --git a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/properties/PropertyParser.java b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/properties/PropertyParser.java deleted file mode 100644 index 272705200da..00000000000 --- a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/properties/PropertyParser.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * 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 org.springframework.security.test.context.support.oauth2.properties; - -/** - * De-serializes an annotation String value into an Object of type T - * - * @author Jérôme Wacongne <ch4mp@c4-soft.com> - * @since 5.2.0 - * - * @param what - * {@link org.springframework.security.test.context.support.oauth2.properties.Property#value() @Property.value} - * should be turned into - * - */ -public interface PropertyParser { - - /** - * @param value String to de-serialize - * @return an Object - */ - T parse(String value); - -} diff --git a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/properties/PropertyParsersHelper.java b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/properties/PropertyParsersHelper.java deleted file mode 100644 index 297bbb2ce06..00000000000 --- a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/properties/PropertyParsersHelper.java +++ /dev/null @@ -1,261 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * 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 org.springframework.security.test.context.support.oauth2.properties; - -import java.lang.reflect.InvocationTargetException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.function.BinaryOperator; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -/** - * Helps turn a - * {@link org.springframework.security.test.context.support.oauth2.properties.Property @Property} - * array into a {@link java.util.Map Map<String, Object>} - * - * @author Jérôme Wacongne <ch4mp@c4-soft.com> - * @since 5.2.0 - * - */ -public class PropertyParsersHelper { - /** - *
    - *
  • {@link org.springframework.security.test.context.support.oauth2.properties.NoOpPropertyParser - * NoOpPropertyParser} => keeps value as is.
  • - *
  • {@link org.springframework.security.test.context.support.oauth2.properties.BooleanPropertyParser - * BooleanPropertyParser} => Boolean
  • - *
  • {@link org.springframework.security.test.context.support.oauth2.properties.DoublePropertyParser - * DoublePropertyParser} => Double
  • - *
  • {@link org.springframework.security.test.context.support.oauth2.properties.InstantPropertyParser - * InstantPropertyParser} => {@link java.time.Instant Instant}
  • - *
  • {@link org.springframework.security.test.context.support.oauth2.properties.IntegerPropertyParser - * IntegerPropertyParser} => Integer
  • - *
  • {@link org.springframework.security.test.context.support.oauth2.properties.LongPropertyParser - * LongPropertyParser} => Long
  • - *
  • {@link org.springframework.security.test.context.support.oauth2.properties.StringListPropertyParser - * StringListPropertyParser} => List<String>
  • - *
  • {@link org.springframework.security.test.context.support.oauth2.properties.StringSetPropertyParser - * StringSetPropertyParser} => Set<String>
  • - *
  • {@link org.springframework.security.test.context.support.oauth2.properties.UrlPropertyParser - * UrlPropertyParser} => URL
  • - *
- */ - public static final Set DEFAULT_PARSERS = new HashSet<>(); - - static { - DEFAULT_PARSERS.add( - "org.springframework.security.test.context.support.oauth2.properties.NoOpPropertyParser"); - DEFAULT_PARSERS.add( - "org.springframework.security.test.context.support.oauth2.properties.BooleanPropertyParser"); - DEFAULT_PARSERS.add( - "org.springframework.security.test.context.support.oauth2.properties.DoublePropertyParser"); - DEFAULT_PARSERS.add( - "org.springframework.security.test.context.support.oauth2.properties.InstantPropertyParser"); - DEFAULT_PARSERS.add( - "org.springframework.security.test.context.support.oauth2.properties.IntegerPropertyParser"); - DEFAULT_PARSERS.add( - "org.springframework.security.test.context.support.oauth2.properties.LongPropertyParser"); - DEFAULT_PARSERS.add( - "org.springframework.security.test.context.support.oauth2.properties.StringListPropertyParser"); - DEFAULT_PARSERS.add( - "org.springframework.security.test.context.support.oauth2.properties.StringSetPropertyParser"); - DEFAULT_PARSERS.add( - "org.springframework.security.test.context.support.oauth2.properties.UrlPropertyParser"); - } - - private final Map> parsers; - - private PropertyParsersHelper(final Set parsers) { - this.parsers = parsers.stream().map(t -> { - try { - return Class.forName(t); - } - catch (final ClassNotFoundException e) { - throw new RuntimeException(e); - } - }).map(c -> { - try { - return (PropertyParser) c.getDeclaredConstructor().newInstance(); - } - catch (InstantiationException | IllegalAccessException - | IllegalArgumentException | InvocationTargetException - | NoSuchMethodException | SecurityException e) { - throw new RuntimeException( - "Missing public no-arg constructor on " + c.getName()); - } - }).collect(Collectors.toMap(p -> p.getClass().getName(), p -> p)); - } - - /** - * @param parserClassName {@code Parser.class.getName()} - * @return Parser instance - */ - public PropertyParser getParser(final String parserClassName) { - return this.parsers.get(parserClassName); - } - - private ParsedProperty parse(final Property p) { - final PropertyParser parser = getParser(p.parser()); - if (parser == null) { - throw new RuntimeException( - "No registered PropertyParser implementation for " + p.parser()); - } - - return new ParsedProperty<>(p.name(), parser.parse(p.value())); - } - - /** - *

- * Turns a - * {@link org.springframework.security.test.context.support.oauth2.properties.Property @Property} - * array into a {@link java.util.Map Map<String, Object>} as required for - * {@link org.springframework.security.oauth2.jwt.Jwt JWT} headers and claims. - *

- *

- * Process highlights: - *

- *
    - *
  • each - * {@link org.springframework.security.test.context.support.oauth2.properties.Property#value() - * value()} is parsed according to - * {@link org.springframework.security.test.context.support.oauth2.properties.Property#parser() - * parser()}
  • - *
  • obtained values are associated with - * {@link org.springframework.security.test.context.support.oauth2.properties.Property#name() - * name()}
  • - *
  • values with same name are accumulated in the same collection
  • - *
- * - * @param properties to be transformed - * @return processed properties - */ - @SuppressWarnings("unchecked") - public Map parse(final Property... properties) { - return Stream.of(properties).map(this::parse).collect(Collectors - .toMap(ParsedProperty::getName, ParsedProperty::getValue, (v1, v2) -> { - if (!(v1 instanceof Collection) || !(v2 instanceof Collection)) { - throw new UnsupportedOperationException( - "@Property values can be accumuleted only if instance of Collection"); - } - if (v1 instanceof Map) { - if (v2 instanceof Map) { - return MAP_ACCUMULATOR.apply((Map) v1, - (Map) v2); - } - throw new UnsupportedOperationException( - "@Property \"Map\" values can only be accumulated with Maps"); - } - if (v2 instanceof Map) { - throw new UnsupportedOperationException( - "@Property \"Map\" values can only be accumulated with Maps"); - } - if (v1 instanceof List) { - return LIST_ACCUMULATOR.apply((List) v1, - (Collection) v2); - } - return SET_ACCUMULATOR.apply((Collection) v1, - (Collection) v2); - })); - } - - private static Set defaultParserNamesPlus( - final String... additionalParserNames) { - final Set allParserNames = new HashSet<>(DEFAULT_PARSERS); - allParserNames - .addAll(Stream.of(additionalParserNames).collect(Collectors.toSet())); - return allParserNames; - } - - /** - * Instantiates default - * {@link org.springframework.security.test.context.support.oauth2.properties.PropertyParser - * PropertyParser}s plus all provided ones (using default constructor) - * - * @param additionalParserNames - * {@link org.springframework.security.test.context.support.oauth2.properties.PropertyParser - * PropertyParser} implementations class names to add to - * {@link org.springframework.security.test.context.support.oauth2.properties.PropertyParsersHelper#DEFAULT_PARSERS - * default ones} - * @return helper instance with provided parsers plus default ones - */ - public static PropertyParsersHelper withDefaultParsers( - final String... additionalParserNames) { - return new PropertyParsersHelper(defaultParserNamesPlus(additionalParserNames)); - } - - /** - * Instantiates all provided - * {@link org.springframework.security.test.context.support.oauth2.properties.PropertyParser - * PropertyParser}s using default constructor - * - * @param allParserNames - * {@link org.springframework.security.test.context.support.oauth2.properties.PropertyParser - * PropertyParser} implementations class names - * @return helper instance with provided parsers only - */ - public static PropertyParsersHelper withoutDefaultParsers( - final String... allParserNames) { - return new PropertyParsersHelper( - Stream.of(allParserNames).collect(Collectors.toSet())); - } - - private static final class ParsedProperty { - private final String name; - private final T value; - - public ParsedProperty(final String name, final T value) { - super(); - this.name = name; - this.value = value; - } - - public String getName() { - return name; - } - - public T getValue() { - return value; - } - - } - - private static BinaryOperator> SET_ACCUMULATOR = (v1, v2) -> { - final HashSet all = new HashSet<>(v1.size() + v2.size()); - all.addAll(v1); - all.addAll(v2); - return all; - }; - - private static BinaryOperator> LIST_ACCUMULATOR = (v1, v2) -> { - final ArrayList all = new ArrayList<>(v1.size() + v2.size()); - all.addAll(v1); - all.addAll(v2); - return all; - }; - - private static BinaryOperator> MAP_ACCUMULATOR = (v1, v2) -> { - final HashMap all = new HashMap<>(v1.size() + v2.size()); - all.putAll(v1); - all.putAll(v2); - return all; - }; -} diff --git a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/properties/UrlPropertyParser.java b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/properties/UrlPropertyParser.java deleted file mode 100644 index 47f2085410d..00000000000 --- a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/properties/UrlPropertyParser.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * 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 org.springframework.security.test.context.support.oauth2.properties; - -import java.net.MalformedURLException; -import java.net.URL; - -/** - * Turns an annotation String value into an {@link java.net.URL URL} - * - * @author Jérôme Wacongne <ch4mp@c4-soft.com> - * @since 5.2.0 - * - */ -public class UrlPropertyParser implements PropertyParser { - - /** - * {@inheritDoc} - */ - @Override - public URL parse(String value) { - try { - return new URL(value); - } - catch (MalformedURLException e) { - throw new RuntimeException(e); - } - } - -} diff --git a/oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/AnnotationHelperTest.java b/oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/AnnotationHelperTest.java new file mode 100644 index 00000000000..1551ea8101d --- /dev/null +++ b/oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/AnnotationHelperTest.java @@ -0,0 +1,94 @@ +package org.springframework.security.test.context.support.oauth2; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.security.test.context.support.oauth2.AnnotationHelper.nullIfEmpty; +import static org.springframework.security.test.context.support.oauth2.AnnotationHelper.putIfNotEmpty; +import static org.springframework.security.test.context.support.oauth2.AnnotationHelper.stringStream; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.stream.Stream; + +import org.junit.Test; + +public class AnnotationHelperTest { + + @Test + public void nullIfEmptyReturnsNullForNullString() { + assertThat(nullIfEmpty(null)).isNull(); + } + + @Test + public void nullIfEmptyReturnsNullForEmptyString() { + assertThat(nullIfEmpty("")).isNull(); + } + + @Test + public void nullIfEmptyReturnsNonNullForSpace() { + assertThat(nullIfEmpty(" ")).isEqualTo(" "); + } + + @Test + public void nullIfEmptyReturnsNonNullForToto() { + assertThat(nullIfEmpty("Toto")).isEqualTo("Toto"); + } + + @Test + public void putIfNotEmptyDoesNothingForNullString() { + assertThat(putIfNotEmpty("foo", (String) null, new HashMap<>())).isEmpty(); + } + + @Test + public void putIfNotEmptyDoesNothingForEmptyString() { + assertThat(putIfNotEmpty("foo", "", new HashMap<>())).isEmpty(); + } + + @Test + public void putIfNotEmptyInsertsSpace() { + assertThat(putIfNotEmpty("foo", " ", new HashMap<>()).get("foo")).isEqualTo(" "); + } + + @Test + public void putIfNotEmptyInsertsToto() { + assertThat(putIfNotEmpty("foo", "Toto", new HashMap<>()).get("foo")).isEqualTo("Toto"); + } + + @Test + public void putIfNotEmptyDoesNothingForNullList() { + assertThat(putIfNotEmpty("foo", (List) null, new HashMap<>())).isEmpty(); + } + + @Test + public void putIfNotEmptyDoesNothingForEmptyList() { + assertThat(putIfNotEmpty("foo", Collections.emptyList(), new HashMap<>())).isEmpty(); + } + + @Test + public void putIfNotEmptyInsertsNonEmptyList() { + @SuppressWarnings("unchecked") + final List actual = + (List) (putIfNotEmpty("foo", Collections.singletonList("Toto"), new HashMap<>()).get("foo")); + assertThat(actual).hasSize(1); + assertThat(actual).contains("Toto"); + } + + @Test + public void stringStreamReturnsEmptyStreamForNullArray() { + final Stream actual = stringStream((String[]) null); + assertThat(actual).isEmpty(); + } + + @Test + public void stringStreamReturnsEmptyStreamForEmptyArray() { + final Stream actual = stringStream(new String[] {}); + assertThat(actual).isEmpty(); + } + + @Test + public void stringStreamSkipsNullAndEmptyStrings() { + final Stream actual = stringStream(new String[] { null, "", "Toto" }); + assertThat(actual).allMatch("Toto"::equals); + } + +} diff --git a/oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/AuthoritiesAndScopesTest.java b/oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/AuthoritiesAndScopesTest.java new file mode 100644 index 00000000000..f0f36c3c7b2 --- /dev/null +++ b/oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/AuthoritiesAndScopesTest.java @@ -0,0 +1,101 @@ +package org.springframework.security.test.context.support.oauth2; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +import org.junit.Test; +import org.springframework.security.core.authority.SimpleGrantedAuthority; + +public class AuthoritiesAndScopesTest { + + @Test + public void testScopeAttribute() { + final Map attributes = new HashMap<>(); + attributes.put("scope", Collections.singleton("c")); + final AuthoritiesAndScopes actual = + AuthoritiesAndScopes.get(new String[] { "AUTHORITY_A", "SCOPE_a" }, new String[] { "b" }, attributes); + + assertThat(actual.authorities).hasSize(4); + assertThat(actual.authorities).contains(new SimpleGrantedAuthority("AUTHORITY_A")); + assertThat(actual.authorities).contains(new SimpleGrantedAuthority("SCOPE_a")); + assertThat(actual.authorities).contains(new SimpleGrantedAuthority("SCOPE_b")); + assertThat(actual.authorities).contains(new SimpleGrantedAuthority("SCOPE_c")); + + assertThat(actual.scopes).hasSize(3); + assertThat(actual.scopes).contains("a"); + assertThat(actual.scopes).contains("b"); + assertThat(actual.scopes).contains("c"); + + assertThat(actual.scopeAttributeName).isEqualTo(Optional.of("scope")); + } + + @Test + public void testScpAttribute() { + final Map attributes = new HashMap<>(); + attributes.put("scp", Collections.singleton("c")); + final AuthoritiesAndScopes actual = + AuthoritiesAndScopes.get(new String[] { "AUTHORITY_A", "SCOPE_a" }, new String[] { "b" }, attributes); + + assertThat(actual.authorities).hasSize(4); + assertThat(actual.authorities).contains(new SimpleGrantedAuthority("AUTHORITY_A")); + assertThat(actual.authorities).contains(new SimpleGrantedAuthority("SCOPE_a")); + assertThat(actual.authorities).contains(new SimpleGrantedAuthority("SCOPE_b")); + assertThat(actual.authorities).contains(new SimpleGrantedAuthority("SCOPE_c")); + + assertThat(actual.scopes).hasSize(3); + assertThat(actual.scopes).contains("a"); + assertThat(actual.scopes).contains("b"); + assertThat(actual.scopes).contains("c"); + + assertThat(actual.scopeAttributeName).isEqualTo(Optional.of("scp")); + } + + @Test + public void testScopesAttribute() { + final Map attributes = new HashMap<>(); + attributes.put("scopes", Collections.singleton("c")); + final AuthoritiesAndScopes actual = + AuthoritiesAndScopes.get(new String[] { "AUTHORITY_A", "SCOPE_a" }, new String[] { "b" }, attributes); + + assertThat(actual.authorities).hasSize(4); + assertThat(actual.authorities).contains(new SimpleGrantedAuthority("AUTHORITY_A")); + assertThat(actual.authorities).contains(new SimpleGrantedAuthority("SCOPE_a")); + assertThat(actual.authorities).contains(new SimpleGrantedAuthority("SCOPE_b")); + assertThat(actual.authorities).contains(new SimpleGrantedAuthority("SCOPE_c")); + + assertThat(actual.scopes).hasSize(3); + assertThat(actual.scopes).contains("a"); + assertThat(actual.scopes).contains("b"); + assertThat(actual.scopes).contains("c"); + + assertThat(actual.scopeAttributeName).isEqualTo(Optional.of("scopes")); + } + + @Test + public void testAttributeCollision() { + final Map attributes = new HashMap<>(3); + attributes.put("scopes", Collections.singleton("c")); + attributes.put("scope", Collections.singleton("d")); + attributes.put("scp", Collections.singleton("e")); + final AuthoritiesAndScopes actual = + AuthoritiesAndScopes.get(new String[] { "AUTHORITY_A", "SCOPE_a" }, new String[] { "b" }, attributes); + + assertThat(actual.authorities).hasSize(4); + assertThat(actual.authorities).contains(new SimpleGrantedAuthority("AUTHORITY_A")); + assertThat(actual.authorities).contains(new SimpleGrantedAuthority("SCOPE_a")); + assertThat(actual.authorities).contains(new SimpleGrantedAuthority("SCOPE_b")); + assertThat(actual.authorities).contains(new SimpleGrantedAuthority("SCOPE_d")); + + assertThat(actual.scopes).hasSize(3); + assertThat(actual.scopes).contains("a"); + assertThat(actual.scopes).contains("b"); + assertThat(actual.scopes).contains("d"); + + assertThat(actual.scopeAttributeName).isEqualTo(Optional.of("scope")); + } + +} diff --git a/oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/WithMockJwtSecurityContextFactoryTests.java b/oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/WithMockJwtSecurityContextFactoryTests.java index 4a812da982d..11105179a5e 100644 --- a/oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/WithMockJwtSecurityContextFactoryTests.java +++ b/oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/WithMockJwtSecurityContextFactoryTests.java @@ -1,71 +1,90 @@ /* * Copyright 2002-2019 the original author or authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * 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 + * 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. + * 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.test.context.support.oauth2; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.when; +import java.net.URL; +import java.time.Instant; import java.util.Map; import org.junit.Before; import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.junit.MockitoJUnitRunner; +import org.springframework.core.annotation.AnnotationUtils; import org.springframework.security.core.Authentication; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.oauth2.jwt.Jwt; -import org.springframework.security.test.context.support.oauth2.properties.NoOpPropertyParser; -import org.springframework.security.test.context.support.oauth2.properties.Property; +import org.springframework.security.oauth2.jwt.JwtClaimNames; +import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; +import org.springframework.security.test.context.support.oauth2.attributes.Attribute; +import org.springframework.security.test.context.support.oauth2.attributes.Parser; -@RunWith(MockitoJUnitRunner.class) public class WithMockJwtSecurityContextFactoryTests { - @Mock - private WithMockJwt authAnnotation; - - @Mock - private Property defaultHeaderAnnotation; - private WithMockJwtSecurityContextFactory factory; @Before public void setup() { factory = new WithMockJwtSecurityContextFactory(); - when(defaultHeaderAnnotation.name()).thenReturn(WithMockJwt.DEFAULT_HEADER_NAME); - when(defaultHeaderAnnotation.value()) - .thenReturn(WithMockJwt.DEFAULT_HEADER_VALUE); - when(defaultHeaderAnnotation.parser()) - .thenReturn(NoOpPropertyParser.class.getName()); + } + + @WithMockJwt + private static class Default { + } + + @WithMockJwt("ROLE_ADMIN") + private static class CustomMini { + } + + @WithMockJwt(name = "Test User", authorities = { "ROLE_USER", "ROLE_ADMIN" }) + private static class CustomFrequent { + } + + @WithMockJwt( + name = "Test User", + authorities = { "ROLE_USER", "ROLE_ADMIN" }, + claims = { @Attribute(name = "custom-claim", value = "foo") }) + private static class CustomAdvanced { + } + + @WithMockJwt( + name = "truc", + authorities = { "machin", "chose" }, + headers = { @Attribute(name = "a", value = "1") }, + claims = { + @Attribute(name = JwtClaimNames.AUD, value = "test audience", parser = "StringListStringParser"), + @Attribute(name = JwtClaimNames.AUD, value = "other audience", parser = "StringListStringParser"), + @Attribute(name = JwtClaimNames.ISS, value = "https://test-issuer.org", parser = "UrlStringParser"), + @Attribute( + name = JwtClaimNames.IAT, + value = "2019-03-03T22:35:00.0Z", + parser = "InstantStringParser"), + @Attribute(name = JwtClaimNames.JTI, value = "test ID"), + @Attribute(name = "custom-claim", value = "foo") }, + additionalParsers = { + "org.springframework.security.test.context.support.oauth2.WithMockJwtSecurityContextFactoryTests$FooParser" }) + private static class CustomFull { } @Test public void defaults() { - when(authAnnotation.name()).thenReturn(WithMockJwt.DEFAULT_AUTH_NAME); - when(authAnnotation.authorities()).thenReturn(new String[] {}); - when(authAnnotation.headers()) - .thenReturn(new Property[] { defaultHeaderAnnotation }); - when(authAnnotation.claims()).thenReturn(new Property[] {}); - when(authAnnotation.additionalParsers()).thenReturn(new String[] {}); + final WithMockJwt annotation = AnnotationUtils.findAnnotation(Default.class, WithMockJwt.class); - final Authentication auth = factory.createSecurityContext(authAnnotation) - .getAuthentication(); + final Authentication auth = factory.createSecurityContext(annotation).getAuthentication(); assertThat(auth.getName()).isEqualTo(WithMockJwt.DEFAULT_AUTH_NAME); - assertThat(auth.getAuthorities()).isEmpty(); + assertThat(auth.getAuthorities()).hasSize(1); + assertThat(auth.getAuthorities().contains(new SimpleGrantedAuthority("ROLE_USER"))).isTrue(); assertThat(auth.getPrincipal()).isInstanceOf(Jwt.class); final Jwt jwt = (Jwt) auth.getPrincipal(); @@ -73,8 +92,7 @@ public void defaults() { assertThat(auth.getCredentials()).isEqualTo(jwt); assertThat(auth.getDetails()).isNull(); - assertThat(jwt.getTokenValue()) - .isEqualTo(WithMockJwtSecurityContextFactory.DEFAULT_TOKEN_VALUE); + assertThat(jwt.getTokenValue()).isEqualTo(WithMockJwtSecurityContextFactory.DEFAULT_TOKEN_VALUE); assertThat(jwt.getSubject()).isEqualTo(WithMockJwt.DEFAULT_AUTH_NAME); assertThat(jwt.getAudience()).isNull(); assertThat(jwt.getIssuer()).isNull(); @@ -85,32 +103,87 @@ public void defaults() { final Map headers = jwt.getHeaders(); assertThat(headers).hasSize(1); - assertThat(headers.get(WithMockJwt.DEFAULT_HEADER_NAME)) - .isEqualTo(WithMockJwt.DEFAULT_HEADER_VALUE); + assertThat(headers.get(WithMockJwt.DEFAULT_HEADER_NAME)).isEqualTo(WithMockJwt.DEFAULT_HEADER_VALUE); } @Test - public void custom() throws Exception { - final SimpleGrantedAuthority machinAuthority = new SimpleGrantedAuthority( - "machin"); - final SimpleGrantedAuthority choseAuthority = new SimpleGrantedAuthority("chose"); + public void customMini() { + final WithMockJwt annotation = AnnotationUtils.findAnnotation(CustomMini.class, WithMockJwt.class); + + final Authentication auth = factory.createSecurityContext(annotation).getAuthentication(); + + assertThat(auth.getName()).isEqualTo(WithMockJwt.DEFAULT_AUTH_NAME); + assertThat(auth.getAuthorities().contains(new SimpleGrantedAuthority("ROLE_ADMIN"))).isTrue(); + assertThat(auth.getPrincipal()).isInstanceOf(Jwt.class); + + final Jwt jwt = (Jwt) auth.getPrincipal(); + + assertThat(auth.getCredentials()).isEqualTo(jwt); + assertThat(auth.getDetails()).isNull(); + + assertThat(jwt.getTokenValue()).isEqualTo(WithMockJwtSecurityContextFactory.DEFAULT_TOKEN_VALUE); + assertThat(jwt.getSubject()).isEqualTo(WithMockJwt.DEFAULT_AUTH_NAME); + assertThat(jwt.getAudience()).isNull(); + assertThat(jwt.getIssuer()).isNull(); + assertThat(jwt.getIssuedAt()).isNull(); + assertThat(jwt.getExpiresAt()).isNull(); + assertThat(jwt.getNotBefore()).isNull(); + assertThat(jwt.getId()).isNull(); + + final Map headers = jwt.getHeaders(); + assertThat(headers).hasSize(1); + assertThat(headers.get(WithMockJwt.DEFAULT_HEADER_NAME)).isEqualTo(WithMockJwt.DEFAULT_HEADER_VALUE); + } + + @Test + public void customFrequent() { + final WithMockJwt annotation = AnnotationUtils.findAnnotation(CustomFrequent.class, WithMockJwt.class); + + final Authentication auth = factory.createSecurityContext(annotation).getAuthentication(); + + assertThat(auth.getName()).isEqualTo("Test User"); + assertThat(auth.getAuthorities()).hasSize(2); + assertThat( + auth.getAuthorities() + .stream() + .allMatch( + a -> a.equals(new SimpleGrantedAuthority("ROLE_ADMIN")) + || a.equals(new SimpleGrantedAuthority("ROLE_USER")))).isTrue(); + assertThat(auth.getPrincipal()).isInstanceOf(Jwt.class); - when(authAnnotation.name()).thenReturn("bidule"); - when(authAnnotation.authorities()).thenReturn(new String[] { - machinAuthority.getAuthority(), choseAuthority.getAuthority() }); - when(authAnnotation.headers()) - .thenReturn(new Property[] { defaultHeaderAnnotation }); - when(authAnnotation.claims()).thenReturn(new Property[] {}); - when(authAnnotation.additionalParsers()).thenReturn(new String[] {}); + final Jwt jwt = (Jwt) auth.getPrincipal(); - final Authentication auth = factory.createSecurityContext(authAnnotation) - .getAuthentication(); + assertThat(auth.getCredentials()).isEqualTo(jwt); + assertThat(auth.getDetails()).isNull(); - assertThat(auth.getName()).isEqualTo("bidule"); + assertThat(jwt.getTokenValue()).isEqualTo(WithMockJwtSecurityContextFactory.DEFAULT_TOKEN_VALUE); + assertThat(jwt.getSubject()).isEqualTo("Test User"); + assertThat(jwt.getAudience()).isNull(); + assertThat(jwt.getIssuer()).isNull(); + assertThat(jwt.getIssuedAt()).isNull(); + assertThat(jwt.getExpiresAt()).isNull(); + assertThat(jwt.getNotBefore()).isNull(); + assertThat(jwt.getId()).isNull(); + + final Map headers = jwt.getHeaders(); + assertThat(headers).hasSize(1); + assertThat(headers.get(WithMockJwt.DEFAULT_HEADER_NAME)).isEqualTo(WithMockJwt.DEFAULT_HEADER_VALUE); + } + + @Test + public void customAdvanced() { + final WithMockJwt annotation = AnnotationUtils.findAnnotation(CustomAdvanced.class, WithMockJwt.class); + + final Authentication auth = factory.createSecurityContext(annotation).getAuthentication(); + + assertThat(auth.getName()).isEqualTo("Test User"); assertThat(auth.getAuthorities()).hasSize(2); - assertThat(auth.getAuthorities().stream() - .allMatch(a -> a.equals(machinAuthority) || a.equals(choseAuthority))) - .isTrue(); + assertThat( + auth.getAuthorities() + .stream() + .allMatch( + a -> a.equals(new SimpleGrantedAuthority("ROLE_ADMIN")) + || a.equals(new SimpleGrantedAuthority("ROLE_USER")))).isTrue(); assertThat(auth.getPrincipal()).isInstanceOf(Jwt.class); final Jwt jwt = (Jwt) auth.getPrincipal(); @@ -118,14 +191,64 @@ public void custom() throws Exception { assertThat(auth.getCredentials()).isEqualTo(jwt); assertThat(auth.getDetails()).isNull(); - assertThat(jwt.getTokenValue()) - .isEqualTo(WithMockJwtSecurityContextFactory.DEFAULT_TOKEN_VALUE); - assertThat(jwt.getSubject()).isEqualTo("bidule"); + assertThat(jwt.getTokenValue()).isEqualTo(WithMockJwtSecurityContextFactory.DEFAULT_TOKEN_VALUE); + assertThat(jwt.getSubject()).isEqualTo("Test User"); + assertThat(jwt.getAudience()).isNull(); + assertThat(jwt.getIssuer()).isNull(); + assertThat(jwt.getIssuedAt()).isNull(); + assertThat(jwt.getExpiresAt()).isNull(); + assertThat(jwt.getNotBefore()).isNull(); + assertThat(jwt.getId()).isNull(); + assertThat(jwt.getClaimAsString("custom-claim")).isEqualTo("foo"); final Map headers = jwt.getHeaders(); assertThat(headers).hasSize(1); - assertThat(headers.get(WithMockJwt.DEFAULT_HEADER_NAME)) - .isEqualTo(WithMockJwt.DEFAULT_HEADER_VALUE); + assertThat(headers.get(WithMockJwt.DEFAULT_HEADER_NAME)).isEqualTo(WithMockJwt.DEFAULT_HEADER_VALUE); + } + + @Test + public void custom() throws Exception { + final SimpleGrantedAuthority machinAuthority = new SimpleGrantedAuthority("machin"); + final SimpleGrantedAuthority choseAuthority = new SimpleGrantedAuthority("chose"); + + final WithMockJwt annotation = AnnotationUtils.findAnnotation(CustomFull.class, WithMockJwt.class); + + final JwtAuthenticationToken auth = + (JwtAuthenticationToken) factory.createSecurityContext(annotation).getAuthentication(); + final Jwt principal = (Jwt) auth.getPrincipal(); + + assertThat(auth.getAuthorities()).hasSize(2); + assertThat(auth.getAuthorities().stream().allMatch(a -> a.equals(machinAuthority) || a.equals(choseAuthority))) + .isTrue(); + + assertThat(auth.getCredentials()).isEqualTo(principal); + + assertThat(auth.getDetails()).isNull(); + + assertThat(auth.getName()).isEqualTo("truc"); + + assertThat(principal.getAudience()).hasSize(2); + assertThat(principal.getAudience()).contains("test audience", "other audience"); + assertThat(principal.getExpiresAt()).isNull(); + assertThat(principal.getHeaders()).hasSize(1); + assertThat(principal.getHeaders().get("a")).isEqualTo("1"); + assertThat(principal.getId()).isEqualTo("test ID"); + assertThat(principal.getIssuedAt()).isEqualTo(Instant.parse("2019-03-03T22:35:00.0Z")); + assertThat(principal.getIssuer()).isEqualTo(new URL("https://test-issuer.org")); + assertThat(principal.getSubject()).isEqualTo("truc"); + assertThat(principal.getNotBefore()).isNull(); + assertThat(principal.getTokenValue()).isEqualTo(WithMockJwtSecurityContextFactory.DEFAULT_TOKEN_VALUE); + assertThat(principal.getClaims().get("custom-claim")).isEqualTo("foo"); + + } + + public static final class FooParser implements Parser { + + @Override + public String parse(final String value) { + return "foo"; + } + } } diff --git a/oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/WithMockJwtTests.java b/oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/WithMockJwtTests.java index bbebfc7a358..6dc60b25226 100644 --- a/oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/WithMockJwtTests.java +++ b/oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/WithMockJwtTests.java @@ -1,17 +1,14 @@ /* * Copyright 2002-2019 the original author or authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * 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 + * 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. + * 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.test.context.support.oauth2; @@ -22,22 +19,21 @@ import org.springframework.core.annotation.AnnotationUtils; import org.springframework.security.test.context.support.TestExecutionEvent; import org.springframework.security.test.context.support.WithSecurityContext; -import org.springframework.security.test.context.support.WithUserDetails; -import org.springframework.security.test.context.support.oauth2.properties.Property; +import org.springframework.security.test.context.support.oauth2.attributes.Attribute; public class WithMockJwtTests { @Test public void defaults() { - final WithMockJwt auth = AnnotationUtils.findAnnotation(Annotated.class, - WithMockJwt.class); + final WithMockJwt auth = AnnotationUtils.findAnnotation(Annotated.class, WithMockJwt.class); assertThat(auth.name()).isEqualTo(WithMockJwt.DEFAULT_AUTH_NAME); - assertThat(auth.authorities()).isEmpty(); - assertThat(auth.headers()).hasAtLeastOneElementOfType(Property.class); + assertThat(auth.authorities()).hasSize(1); + assertThat(auth.authorities()).contains("ROLE_USER"); + assertThat(auth.headers()).hasAtLeastOneElementOfType(Attribute.class); assertThat(auth.claims()).isNotNull(); - final WithSecurityContext context = AnnotatedElementUtils - .findMergedAnnotation(Annotated.class, WithSecurityContext.class); + final WithSecurityContext context = + AnnotatedElementUtils.findMergedAnnotation(Annotated.class, WithSecurityContext.class); assertThat(context.setupBefore()).isEqualTo(TestExecutionEvent.TEST_METHOD); } @@ -48,20 +44,20 @@ private static class Annotated { @Test public void findMergedAnnotationWhenSetupExplicitThenOverridden() { - final WithSecurityContext context = AnnotatedElementUtils - .findMergedAnnotation(SetupExplicit.class, WithSecurityContext.class); + final WithSecurityContext context = + AnnotatedElementUtils.findMergedAnnotation(SetupExplicit.class, WithSecurityContext.class); assertThat(context.setupBefore()).isEqualTo(TestExecutionEvent.TEST_METHOD); } - @WithUserDetails(setupBefore = TestExecutionEvent.TEST_METHOD) + @WithMockJwt(setupBefore = TestExecutionEvent.TEST_METHOD) private class SetupExplicit { } @Test public void findMergedAnnotationWhenSetupOverriddenThenOverridden() { - final WithSecurityContext context = AnnotatedElementUtils - .findMergedAnnotation(SetupOverridden.class, WithSecurityContext.class); + final WithSecurityContext context = + AnnotatedElementUtils.findMergedAnnotation(SetupOverridden.class, WithSecurityContext.class); assertThat(context.setupBefore()).isEqualTo(TestExecutionEvent.TEST_EXECUTION); } @@ -69,28 +65,4 @@ public void findMergedAnnotationWhenSetupOverriddenThenOverridden() { @WithMockJwt(setupBefore = TestExecutionEvent.TEST_EXECUTION) private class SetupOverridden { } - - @Test - public void custom() { - final WithMockJwt auth = AnnotationUtils.findAnnotation(Custom.class, - WithMockJwt.class); - assertThat(auth.name()).isEqualTo("truc"); - assertThat(auth.authorities()).hasSize(2); - assertThat(auth.authorities()).contains("machin", "chose"); - assertThat(auth.headers()).hasSize(1); - assertThat(auth.headers()[0].name()).isEqualTo("a"); - assertThat(auth.headers()[0].value()).isEqualTo("1"); - assertThat(auth.claims()).isNotNull(); - } - - @WithMockJwt(name = "truc", authorities = { "machin", "chose" }, headers = { - @Property(name = "a", value = "1") }, claims = { - @Property(name = "audience", value = "test audience", parser = "org.springframework.security.test.context.support.oauth2.properties.StringListPropertyParser"), - @Property(name = "issuer", value = "https://test-issuer.org", parser = "org.springframework.security.test.context.support.oauth2.properties.UrlPropertyParser"), - @Property(name = "issuedAt", value = "2019-03-03T22:35:00.0", parser = "org.springframework.security.test.context.support.oauth2.properties.InstantPropertyParser"), - @Property(name = "expiresAt", value = "2019-03-04T22:35:00.0", parser = "org.springframework.security.test.context.support.oauth2.properties.InstantPropertyParser"), - @Property(name = "notBefore", value = "2019-03-03T22:36:00.0", parser = "org.springframework.security.test.context.support.oauth2.properties.InstantPropertyParser"), - @Property(name = "jti", value = "test ID") }) - private static class Custom { - } } diff --git a/oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/attributes/AttributeParsersHelperTest.java b/oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/attributes/AttributeParsersHelperTest.java new file mode 100644 index 00000000000..691a398534e --- /dev/null +++ b/oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/attributes/AttributeParsersHelperTest.java @@ -0,0 +1,192 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. 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 org.springframework.security.test.context.support.oauth2.attributes; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.time.Instant; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import org.junit.Test; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.security.test.context.support.oauth2.attributes.Attribute; +import org.springframework.security.test.context.support.oauth2.attributes.AttributeParsersHelper; +import org.springframework.security.test.context.support.oauth2.attributes.BooleanStringParser; +import org.springframework.security.test.context.support.oauth2.attributes.DoubleStringParser; +import org.springframework.security.test.context.support.oauth2.attributes.IntegerStringParser; +import org.springframework.security.test.context.support.oauth2.attributes.LongStringParser; +import org.springframework.security.test.context.support.oauth2.attributes.NoOpStringParser; +import org.springframework.security.test.context.support.oauth2.attributes.Parser; +import org.springframework.security.test.context.support.oauth2.attributes.StringListStringParser; +import org.springframework.security.test.context.support.oauth2.attributes.StringSetStringParser; +import org.springframework.security.test.context.support.oauth2.attributes.UrlStringParser; + +public class AttributeParsersHelperTest { + + @Test + public void helperWithDefaultParsers() { + final AttributeParsersHelper actual = AttributeParsersHelper.withDefaultParsers( + "org.springframework.security.test.context.support.oauth2.attributes.AttributeParsersHelperTest$SomeTypeParser", + "org.springframework.security.test.context.support.oauth2.attributes.AttributeParsersHelperTest$OtherTypeParser"); + + assertThat(actual.getParser(NoOpStringParser.class.getName())).isInstanceOf(NoOpStringParser.class); + assertThat(actual.getParser("NoOpStringParser")).isInstanceOf(NoOpStringParser.class); + assertThat(actual.getParser(BooleanStringParser.class.getName())).isInstanceOf(BooleanStringParser.class); + assertThat(actual.getParser("BooleanStringParser")).isInstanceOf(BooleanStringParser.class); + assertThat(actual.getParser(DoubleStringParser.class.getName())).isInstanceOf(DoubleStringParser.class); + assertThat(actual.getParser("DoubleStringParser")).isInstanceOf(DoubleStringParser.class); + assertThat( + actual.getParser( + org.springframework.security.test.context.support.oauth2.attributes.InstantStringParser.class + .getName())).isInstanceOf( + org.springframework.security.test.context.support.oauth2.attributes.InstantStringParser.class); + assertThat(actual.getParser("InstantStringParser")).isInstanceOf( + org.springframework.security.test.context.support.oauth2.attributes.InstantStringParser.class); + assertThat(actual.getParser(IntegerStringParser.class.getName())).isInstanceOf(IntegerStringParser.class); + assertThat(actual.getParser("IntegerStringParser")).isInstanceOf(IntegerStringParser.class); + assertThat(actual.getParser(LongStringParser.class.getName())).isInstanceOf(LongStringParser.class); + assertThat(actual.getParser("LongStringParser")).isInstanceOf(LongStringParser.class); + assertThat(actual.getParser(StringListStringParser.class.getName())).isInstanceOf(StringListStringParser.class); + assertThat(actual.getParser("StringListStringParser")).isInstanceOf(StringListStringParser.class); + assertThat(actual.getParser(StringSetStringParser.class.getName())).isInstanceOf(StringSetStringParser.class); + assertThat(actual.getParser("StringSetStringParser")).isInstanceOf(StringSetStringParser.class); + assertThat(actual.getParser(UrlStringParser.class.getName())).isInstanceOf(UrlStringParser.class); + assertThat(actual.getParser("UrlStringParser")).isInstanceOf(UrlStringParser.class); + assertThat(actual.getParser(SomeTypeParser.class.getName())).isInstanceOf(SomeTypeParser.class); + assertThat(actual.getParser("SomeTypeParser")).isInstanceOf(SomeTypeParser.class); + assertThat(actual.getParser(OtherTypeParser.class.getName())).isInstanceOf(OtherTypeParser.class); + assertThat(actual.getParser("OtherTypeParser")).isInstanceOf(OtherTypeParser.class); + } + + @Test + public void helperWithoutDefaultParsers() { + final AttributeParsersHelper actual = AttributeParsersHelper.withoutDefaultParsers( + "org.springframework.security.test.context.support.oauth2.attributes.AttributeParsersHelperTest$SomeTypeParser", + "org.springframework.security.test.context.support.oauth2.attributes.AttributeParsersHelperTest$OtherTypeParser"); + assertThat(actual.getParser(NoOpStringParser.class.getName())).isNull(); + assertThat(actual.getParser("NoOpStringParser")).isNull(); + assertThat(actual.getParser(BooleanStringParser.class.getName())).isNull(); + assertThat(actual.getParser("BooleanStringParser")).isNull(); + assertThat(actual.getParser(DoubleStringParser.class.getName())).isNull(); + assertThat(actual.getParser("DoubleStringParser")).isNull(); + assertThat(actual.getParser(InstantStringParser.class.getName())).isNull(); + assertThat(actual.getParser("InstantStringParser")).isNull(); + assertThat(actual.getParser(IntegerStringParser.class.getName())).isNull(); + assertThat(actual.getParser("IntegerStringParser")).isNull(); + assertThat(actual.getParser(LongStringParser.class.getName())).isNull(); + assertThat(actual.getParser("LongStringParser")).isNull(); + assertThat(actual.getParser(StringListStringParser.class.getName())).isNull(); + assertThat(actual.getParser("StringListStringParser")).isNull(); + assertThat(actual.getParser(StringSetStringParser.class.getName())).isNull(); + assertThat(actual.getParser("StringSetStringParser")).isNull(); + assertThat(actual.getParser(UrlStringParser.class.getName())).isNull(); + assertThat(actual.getParser("UrlStringParser")).isNull(); + assertThat(actual.getParser(SomeTypeParser.class.getName())).isInstanceOf(SomeTypeParser.class); + assertThat(actual.getParser("SomeTypeParser")).isInstanceOf(SomeTypeParser.class); + assertThat(actual.getParser(OtherTypeParser.class.getName())).isInstanceOf(OtherTypeParser.class); + assertThat(actual.getParser("OtherTypeParser")).isInstanceOf(OtherTypeParser.class); + } + + @Attribute(name = "a", value = "bidule", parser = "SomeTypeParser") + private static final class AProperty { + } + + @Attribute( + name = "b", + value = "chose", + parser = "org.springframework.security.test.context.support.oauth2.attributes.AttributeParsersHelperTest$SomeTypeParser") + private static final class BProperty { + } + + @Test + public void parsePropertiesWithDistinctNames() { + final AttributeParsersHelper helper = AttributeParsersHelper.withoutDefaultParsers( + "org.springframework.security.test.context.support.oauth2.attributes.AttributeParsersHelperTest$SomeTypeParser"); + final Attribute propertyAnnotationA = AnnotationUtils.findAnnotation(AProperty.class, Attribute.class); + final Attribute propertyAnnotationB = AnnotationUtils.findAnnotation(BProperty.class, Attribute.class); + + final Map actual = helper.parse(propertyAnnotationA, propertyAnnotationB); + assertThat(actual).hasSize(2); + assertThat(actual.get("a")).isInstanceOf(String.class); + assertThat(actual.get("b")).isInstanceOf(String.class); + + } + + @Attribute(name = "a", value = "bidule", parser = "StringListStringParser") + private static final class CProperty { + } + + @Attribute(name = "a", value = "chose", parser = "StringListStringParser") + private static final class DProperty { + } + + @SuppressWarnings("unchecked") + @Test + public void parsePropertiesWithSameNameAccumulatesValues() { + final AttributeParsersHelper helper = AttributeParsersHelper.withDefaultParsers(); + final Attribute propertyAnnotationC = AnnotationUtils.findAnnotation(CProperty.class, Attribute.class); + final Attribute propertyAnnotationD = AnnotationUtils.findAnnotation(DProperty.class, Attribute.class); + + final Map actual = helper.parse(propertyAnnotationC, propertyAnnotationD); + assertThat(actual).hasSize(1); + assertThat(actual.get("a")).isInstanceOf(List.class); + assertThat((List) actual.get("a")).hasSize(2); + assertThat((List) actual.get("a")).contains("bidule", "chose"); + + } + + @Attribute(name = "instant-millis", value = "12345678", parser = "InstantStringParser") + private static final class EProperty { + } + + @Test + public void parsePropertiesUsesParseroverrides() { + final AttributeParsersHelper helper = AttributeParsersHelper.withDefaultParsers( + "org.springframework.security.test.context.support.oauth2.attributes.AttributeParsersHelperTest$InstantStringParser"); + + final Attribute propertyAnnotation = AnnotationUtils.findAnnotation(EProperty.class, Attribute.class); + + final Map actual = helper.parse(propertyAnnotation); + assertThat(actual).hasSize(1); + assertThat(actual.get("instant-millis")).isInstanceOf(Instant.class); + assertThat((Instant) actual.get("instant-millis")).isEqualTo(Instant.ofEpochMilli(12345678L)); + + } + + public static final class SomeTypeParser implements Parser { + @Override + public String parse(final String value) { + return value; + } + } + + public static final class OtherTypeParser implements Parser> { + @Override + public Collection parse(final String value) { + return Collections.singletonList(value); + } + } + + /** + * custom Instant mapper designed to override default one + */ + public static final class InstantStringParser implements Parser { + @Override + public Instant parse(final String value) { + return Instant.ofEpochMilli(Long.valueOf(value)); + } + } +} diff --git a/oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/properties/PropertyParsersHelperTest.java b/oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/properties/PropertyParsersHelperTest.java deleted file mode 100644 index 8f76cb975a8..00000000000 --- a/oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/properties/PropertyParsersHelperTest.java +++ /dev/null @@ -1,149 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * 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 org.springframework.security.test.context.support.oauth2.properties; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import java.net.URL; -import java.time.Instant; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import org.junit.Test; - -public class PropertyParsersHelperTest { - - @Test - public void helperWithDefaultParsers() { - final PropertyParsersHelper actual = PropertyParsersHelper.withDefaultParsers( - "org.springframework.security.test.context.support.oauth2.properties.PropertyParsersHelperTest$SomeTypePropertyParser", - "org.springframework.security.test.context.support.oauth2.properties.PropertyParsersHelperTest$OtherTypePropertyParser"); - - assertThat(actual.getParser(NoOpPropertyParser.class.getName())) - .isInstanceOf(NoOpPropertyParser.class); - assertThat(actual.getParser(BooleanPropertyParser.class.getName())) - .isInstanceOf(BooleanPropertyParser.class); - assertThat(actual.getParser(DoublePropertyParser.class.getName())) - .isInstanceOf(DoublePropertyParser.class); - assertThat(actual.getParser(InstantPropertyParser.class.getName())) - .isInstanceOf(InstantPropertyParser.class); - assertThat(actual.getParser(IntegerPropertyParser.class.getName())) - .isInstanceOf(IntegerPropertyParser.class); - assertThat(actual.getParser(LongPropertyParser.class.getName())) - .isInstanceOf(LongPropertyParser.class); - assertThat(actual.getParser(StringListPropertyParser.class.getName())) - .isInstanceOf(StringListPropertyParser.class); - assertThat(actual.getParser(StringSetPropertyParser.class.getName())) - .isInstanceOf(StringSetPropertyParser.class); - assertThat(actual.getParser(UrlPropertyParser.class.getName())) - .isInstanceOf(UrlPropertyParser.class); - assertThat(actual.getParser(SomeTypePropertyParser.class.getName())) - .isInstanceOf(SomeTypePropertyParser.class); - assertThat(actual.getParser(OtherTypePropertyParser.class.getName())) - .isInstanceOf(OtherTypePropertyParser.class); - } - - @Test - public void helperWithoutDefaultParsers() { - final PropertyParsersHelper actual = PropertyParsersHelper.withoutDefaultParsers( - "org.springframework.security.test.context.support.oauth2.properties.PropertyParsersHelperTest$SomeTypePropertyParser", - "org.springframework.security.test.context.support.oauth2.properties.PropertyParsersHelperTest$OtherTypePropertyParser"); - assertThat(actual.getParser(String.class.getName())).isNull(); - assertThat(actual.getParser(Boolean.class.getName())).isNull(); - assertThat(actual.getParser(Double.class.getName())).isNull(); - assertThat(actual.getParser(Instant.class.getName())).isNull(); - assertThat(actual.getParser(Integer.class.getName())).isNull(); - assertThat(actual.getParser(Long.class.getName())).isNull(); - assertThat(actual.getParser(List.class.getName())).isNull(); - assertThat(actual.getParser(Set.class.getName())).isNull(); - assertThat(actual.getParser(URL.class.getName())).isNull(); - assertThat(actual.getParser(SomeTypePropertyParser.class.getName())) - .isInstanceOf(SomeTypePropertyParser.class); - assertThat(actual.getParser(OtherTypePropertyParser.class.getName())) - .isInstanceOf(OtherTypePropertyParser.class); - } - - @Test - public void parsePropertiesWithDistinctNames() { - final PropertyParsersHelper helper = PropertyParsersHelper.withoutDefaultParsers( - "org.springframework.security.test.context.support.oauth2.properties.PropertyParsersHelperTest$SomeTypePropertyParser"); - - final Property propertyAnnotationA = mock(Property.class); - when(propertyAnnotationA.name()).thenReturn("a"); - when(propertyAnnotationA.value()).thenReturn("bidule"); - when(propertyAnnotationA.parser()) - .thenReturn(SomeTypePropertyParser.class.getName()); - - final Property propertyAnnotationB = mock(Property.class); - when(propertyAnnotationB.name()).thenReturn("b"); - when(propertyAnnotationB.value()).thenReturn("chose"); - when(propertyAnnotationB.parser()) - .thenReturn(SomeTypePropertyParser.class.getName()); - - final Map actual = helper.parse(propertyAnnotationA, - propertyAnnotationB); - assertThat(actual).hasSize(2); - assertThat(actual.get("a")).isInstanceOf(String.class); - assertThat(actual.get("b")).isInstanceOf(String.class); - - } - - @SuppressWarnings("unchecked") - @Test - public void parsePropertiesWithSameNameAccumulatesValues() { - final PropertyParsersHelper helper = PropertyParsersHelper.withDefaultParsers(); - - final Property propertyAnnotationA = mock(Property.class); - when(propertyAnnotationA.name()).thenReturn("a"); - when(propertyAnnotationA.value()).thenReturn("bidule"); - when(propertyAnnotationA.parser()) - .thenReturn(StringListPropertyParser.class.getName()); - - final Property propertyAnnotationB = mock(Property.class); - when(propertyAnnotationB.name()).thenReturn("a"); - when(propertyAnnotationB.value()).thenReturn("chose"); - when(propertyAnnotationB.parser()) - .thenReturn(StringListPropertyParser.class.getName()); - - final Map actual = helper.parse(propertyAnnotationA, - propertyAnnotationB); - assertThat(actual).hasSize(1); - assertThat(actual.get("a")).isInstanceOf(List.class); - assertThat((List) actual.get("a")).hasSize(2); - assertThat((List) actual.get("a")).contains("bidule", "chose"); - - } - - static final class SomeTypePropertyParser implements PropertyParser { - @Override - public String parse(final String value) { - return value; - } - } - - static final class OtherTypePropertyParser - implements PropertyParser> { - @Override - public Collection parse(final String value) { - return Collections.singletonList(value); - } - } -} From 37e9641370167ad2e941d562f38902112ec1a3a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Wacongne?= Date: Tue, 19 Mar 2019 15:05:09 +0100 Subject: [PATCH 3/9] Developper friendly @Attribute value parsers --- .../support/oauth2/AuthoritiesAndScopes.java | 4 +- .../context/support/oauth2/WithMockJwt.java | 4 +- .../support/oauth2/attributes/Attribute.java | 30 ++-- .../attributes/AttributeParsersHelper.java | 128 +++++++++++------- ...{Parser.java => AttributeValueParser.java} | 5 +- ...anStringParser.java => BooleanParser.java} | 2 +- ...bleStringParser.java => DoubleParser.java} | 2 +- ...ntStringParser.java => InstantParser.java} | 2 +- ...erStringParser.java => IntegerParser.java} | 2 +- ...{LongStringParser.java => LongParser.java} | 2 +- ...{NoOpStringParser.java => NoOpParser.java} | 2 +- ...tringParser.java => StringListParser.java} | 2 +- ...StringParser.java => StringSetParser.java} | 2 +- .../{UrlStringParser.java => UrlParser.java} | 2 +- ...ithMockJwtSecurityContextFactoryTests.java | 13 +- .../AttributeParsersHelperTest.java | 94 ++----------- 16 files changed, 134 insertions(+), 162 deletions(-) rename oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/{Parser.java => AttributeValueParser.java} (87%) rename oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/{BooleanStringParser.java => BooleanParser.java} (94%) rename oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/{DoubleStringParser.java => DoubleParser.java} (94%) rename oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/{InstantStringParser.java => InstantParser.java} (94%) rename oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/{IntegerStringParser.java => IntegerParser.java} (94%) rename oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/{LongStringParser.java => LongParser.java} (94%) rename oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/{NoOpStringParser.java => NoOpParser.java} (93%) rename oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/{StringListStringParser.java => StringListParser.java} (94%) rename oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/{StringSetStringParser.java => StringSetParser.java} (94%) rename oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/{UrlStringParser.java => UrlParser.java} (95%) diff --git a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/AuthoritiesAndScopes.java b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/AuthoritiesAndScopes.java index c146c243a34..f8ffbeac431 100644 --- a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/AuthoritiesAndScopes.java +++ b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/AuthoritiesAndScopes.java @@ -34,12 +34,12 @@ private AuthoritiesAndScopes( * Scopes are searched for in attributes with keys "scope", "scp" and "scopes", first entry found being used and * others ignored. *

- * + * *
 	 * @Foo(
 	 * 		authorities = { "ROLE_R", "SCOPE_s1" },
 	 * 		scopes = { "s2" },
-	 * 		attributes = @Attribute(name = "scope", value = "s3", parser = "StringSetStringParser"))
+	 * 		attributes = @Attribute(name = "scope", value = "s3", parseTo = TargetType.STRING_SET))
 	 * static final class Decorated {
 	 * }
 	 *
diff --git a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/WithMockJwt.java b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/WithMockJwt.java
index cae2a4765de..27d7ecd441e 100644
--- a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/WithMockJwt.java
+++ b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/WithMockJwt.java
@@ -30,7 +30,7 @@
 import org.springframework.security.test.context.support.WithSecurityContextTestExecutionListener;
 import org.springframework.security.test.context.support.oauth2.attributes.Attribute;
 import org.springframework.security.test.context.support.oauth2.attributes.AttributeParsersHelper;
-import org.springframework.security.test.context.support.oauth2.attributes.Parser;
+import org.springframework.security.test.context.support.oauth2.attributes.AttributeValueParser;
 import org.springframework.test.context.TestContext;
 import org.springframework.test.web.servlet.MockMvc;
 
@@ -94,7 +94,7 @@
  * 
* * @see Attribute - * @see Parser + * @see AttributeValueParser * @see AttributeParsersHelper#DEFAULT_PARSERS * * @author Jérôme Wacongne <ch4mp@c4-soft.com> diff --git a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/Attribute.java b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/Attribute.java index 193c67d72f8..f1e1208abae 100644 --- a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/Attribute.java +++ b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/Attribute.java @@ -17,27 +17,30 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.springframework.security.test.context.support.oauth2.attributes.AttributeParsersHelper.TargetType; + /** *

* Annotation to create an entry in a {@link java.util.Map Map<String, Object>} such as * {@link org.springframework.security.oauth2.jwt.Jwt JWT} headers or claims. *

*

- * {@link #parser()} can be referenced either by fully qualified name or simple name. + * {@link #parserOverride()} can be referenced either by fully qualified name or simple name. *

*

- * See {@link Parser} and its already provided implementations: {@link AttributeParsersHelper#DEFAULT_PARSERS} + * See {@link AttributeValueParser} and its already provided implementations: + * {@link AttributeParsersHelper#DEFAULT_PARSERS} *

* Sample usage:
* *
  * @WithMockJwt(
  *   claims = {
- *     @Attribute(name = JwtClaimNames.AUD, value = "first audience", parser = "StringListStringParser"),
- *     @Attribute(name = JwtClaimNames.AUD, value = "second audience", parser = "StringListStringParser"),
- *     @Attribute(name = JwtClaimNames.ISS, value = "https://test-issuer.org", parser = "UrlStringParser"),
+ *     @Attribute(name = JwtClaimNames.AUD, value = "first audience", parseTo = TargetType.STRING_LIST),
+ *     @Attribute(name = JwtClaimNames.AUD, value = "second audience",parseTo = TargetType.STRING_LIST),
+ *     @Attribute(name = JwtClaimNames.ISS, value = "https://test-issuer.org", parseTo = TargetType.URL),
  *     @Attribute(name = "machin", value = "chose"),
- *     @Attribute(name = "truc", value = "bidule", parser = "your.fancy.ParserImpl")})
+ *     @Attribute(name = "truc", value = "bidule", parserOverride = "your.fancy.ParserImpl")})
  * 
* * This will create @@ -45,7 +48,7 @@ *
  • an {@code audience} claim with a value being a {@code List} with two entries
  • *
  • an {@code issuer} claim with a value being a {@code java.net.URL} instance
  • *
  • a {@code machin} claim with {@code chose} String as value (default parser is - * {@link org.springframework.security.test.context.support.oauth2.attributes.NoOpStringParser NoOpStringParser})
  • + * {@link org.springframework.security.test.context.support.oauth2.attributes.NoOpParser NoOpParser}) *
  • a {@code truc} claim whith an instance of what {@code your.fancy.ParserImpl} is designed to build from * {@code bidule} string as value
  • * @@ -70,9 +73,16 @@ String value(); /** - * @return a {@link org.springframework.security.test.context.support.oauth2.attributes.Parser Parser} - * implementation class name + * Determines the {@link AttributeValueParser} to use.
    + * If empty or {@link TargetType#OTHER}, {@link #parserOverride()} must be provided too. + * @return the type attribute value should be turned into + */ + TargetType parseTo() default TargetType.STRING; + + /** + * If provided, {@link #parseTo()} is ignored + * @return a {@link AttributeValueParser} implementation class name */ - String parser() default "NoOpStringParser"; + String parserOverride() default ""; } diff --git a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/AttributeParsersHelper.java b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/AttributeParsersHelper.java index a39b2e707a9..4c90d9e2bdb 100644 --- a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/AttributeParsersHelper.java +++ b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/AttributeParsersHelper.java @@ -20,11 +20,14 @@ import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.function.BinaryOperator; import java.util.stream.Collectors; import java.util.stream.Stream; +import org.springframework.util.StringUtils; + /** * Helps turn a {@link org.springframework.security.test.context.support.oauth2.attributes.Attribute @Attribute} array * into a {@link java.util.Map Map<String, Object>} @@ -34,39 +37,44 @@ * */ public class AttributeParsersHelper { + public enum TargetType { + STRING, BOOLEAN, DOUBLE, INSTANT, INTEGER, LONG, STRING_LIST, STRING_SET, URL, OTHER; + } + /** *
      - *
    • {@link NoOpStringParser} => keeps value as is.
    • - *
    • {@link BooleanStringParser} => Boolean
    • - *
    • {@link DoubleStringParser} => Double
    • - *
    • {@link InstantStringParser} => {@link java.time.Instant Instant}
    • - *
    • {@link IntegerStringParser} => Integer
    • - *
    • {@link LongStringParser} => Long
    • - *
    • {@link StringListStringParser} => List<String>
    • - *
    • {@link StringSetStringParser} => Set<String>
    • - *
    • {@link UrlStringParser} => URL
    • + *
    • {@link NoOpParser} => keeps value as is.
    • + *
    • {@link BooleanParser} => Boolean
    • + *
    • {@link DoubleParser} => Double
    • + *
    • {@link InstantParser} => {@link java.time.Instant Instant}
    • + *
    • {@link IntegerParser} => Integer
    • + *
    • {@link LongParser} => Long
    • + *
    • {@link StringListParser} => List<String>
    • + *
    • {@link StringSetParser} => Set<String>
    • + *
    • {@link UrlParser} => URL
    • *
    */ - public static final Set> DEFAULT_PARSERS = new HashSet<>(); + public static final Set> DEFAULT_PARSERS = new HashSet<>(); static { - DEFAULT_PARSERS.add(new NoOpStringParser()); - DEFAULT_PARSERS.add(new BooleanStringParser()); - DEFAULT_PARSERS.add(new DoubleStringParser()); - DEFAULT_PARSERS.add(new InstantStringParser()); - DEFAULT_PARSERS.add(new IntegerStringParser()); - DEFAULT_PARSERS.add(new LongStringParser()); - DEFAULT_PARSERS.add(new StringListStringParser()); - DEFAULT_PARSERS.add(new StringSetStringParser()); - DEFAULT_PARSERS.add(new UrlStringParser()); + DEFAULT_PARSERS.add(new NoOpParser()); + DEFAULT_PARSERS.add(new BooleanParser()); + DEFAULT_PARSERS.add(new DoubleParser()); + DEFAULT_PARSERS.add(new InstantParser()); + DEFAULT_PARSERS.add(new IntegerParser()); + DEFAULT_PARSERS.add(new LongParser()); + DEFAULT_PARSERS.add(new StringListParser()); + DEFAULT_PARSERS.add(new StringSetParser()); + DEFAULT_PARSERS.add(new UrlParser()); } - private final Map> parsers; + private final Map> attributeValueParsers; - @SuppressWarnings("unchecked") - private AttributeParsersHelper(final Set> baseParsers, final String... additionalParserNames) { - this.parsers = new HashMap<>(2 * DEFAULT_PARSERS.size() + 2 * additionalParserNames.length); - final Stream> additionalParsers = Stream.of(additionalParserNames).distinct().map(t -> { + private AttributeParsersHelper( + final Set> baseParsers, + final String... additionalParserNames) { + this.attributeValueParsers = new HashMap<>(2 * DEFAULT_PARSERS.size() + 2 * additionalParserNames.length); + final Stream> additionalParsers = Stream.of(additionalParserNames).distinct().map(t -> { try { return Class.forName(t); } catch (final ClassNotFoundException e) { @@ -74,7 +82,7 @@ private AttributeParsersHelper(final Set> baseParsers, final S } }).map(c -> { try { - return (Parser) c.getDeclaredConstructor().newInstance(); + return (AttributeValueParser) c.getDeclaredConstructor().newInstance(); } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException e) { throw new RuntimeException("Missing public no-arg constructor on " + c.getName()); @@ -82,23 +90,46 @@ private AttributeParsersHelper(final Set> baseParsers, final S }); Stream.concat(baseParsers.stream(), additionalParsers).forEachOrdered(p -> { - this.parsers.put(p.getClass().getName(), p); - this.parsers.put(p.getClass().getSimpleName(), p); + this.attributeValueParsers.put(p.getClass().getName(), p); + this.attributeValueParsers.put(p.getClass().getSimpleName(), p); }); } - /** - * @param parserClassName {@code Parser.class.getName()} - * @return Parser instance - */ - public Parser getParser(final String parserClassName) { - return this.parsers.get(parserClassName); + private AttributeValueParser getParser(final TargetType targetType, final String parserOverrideClassName) { + final Optional> parserOverride = + Optional.ofNullable(StringUtils.isEmpty(parserOverrideClassName) ? null : parserOverrideClassName) + .map(this.attributeValueParsers::get); + + switch (targetType) { + case STRING: + return parserOverride.orElse(new NoOpParser()); + case BOOLEAN: + return parserOverride.orElse(new BooleanParser()); + case DOUBLE: + return parserOverride.orElse(new DoubleParser()); + case INSTANT: + return parserOverride.orElse(new InstantParser()); + case INTEGER: + return parserOverride.orElse(new IntegerParser()); + case LONG: + return parserOverride.orElse(new LongParser()); + case STRING_LIST: + return parserOverride.orElse(new StringListParser()); + case STRING_SET: + return parserOverride.orElse(new StringSetParser()); + case URL: + return parserOverride.orElse(new UrlParser()); + default: + assert (!StringUtils.isEmpty(parserOverrideClassName)); + return parserOverride.get(); + } + } private ParsedProperty parse(final Attribute p) { - final Parser parser = getParser(p.parser()); + final AttributeValueParser parser = getParser(p.parseTo(), p.parserOverride()); if (parser == null) { - throw new RuntimeException("No registered Parser implementation for " + p.parser()); + throw new RuntimeException("No registered AttributeValueParser implementation for " + p.parserOverride()); } return new ParsedProperty<>(p.name(), parser.parse(p.value())); @@ -115,7 +146,8 @@ private ParsedProperty parse(final Attribute p) { *

    *
      *
    • each {@link org.springframework.security.test.context.support.oauth2.attributes.Attribute#value() value()} is - * parsed according to {@link org.springframework.security.test.context.support.oauth2.attributes.Attribute#parser() + * parsed according to + * {@link org.springframework.security.test.context.support.oauth2.attributes.Attribute#parserOverride() * parser()}
    • *
    • obtained values are associated with * {@link org.springframework.security.test.context.support.oauth2.attributes.Attribute#name() name()}
    • @@ -153,26 +185,30 @@ public Map parse(final Attribute... properties) { } /** - * Instantiates default {@link org.springframework.security.test.context.support.oauth2.attributes.Parser Parser}s - * plus all provided ones (using default constructor) + * Instantiates default + * {@link org.springframework.security.test.context.support.oauth2.attributes.AttributeValueParser + * AttributeValueParser}s plus all provided ones (using default constructor) * - * @param additionalParserNames {@link org.springframework.security.test.context.support.oauth2.attributes.Parser - * Parser} implementations class names to add to + * @param additionalParserNames + * {@link org.springframework.security.test.context.support.oauth2.attributes.AttributeValueParser + * AttributeValueParser} implementations class names to add to * {@link org.springframework.security.test.context.support.oauth2.attributes.AttributeParsersHelper#DEFAULT_PARSERS * default ones} - * @return helper instance with provided parsers plus default ones + * @return helper instance with provided attributeValueParsers plus default ones */ public static AttributeParsersHelper withDefaultParsers(final String... additionalParserNames) { return new AttributeParsersHelper(DEFAULT_PARSERS, additionalParserNames); } /** - * Instantiates all provided {@link org.springframework.security.test.context.support.oauth2.attributes.Parser - * Parser}s using default constructor + * Instantiates all provided + * {@link org.springframework.security.test.context.support.oauth2.attributes.AttributeValueParser + * AttributeValueParser}s using default constructor * - * @param allParserNames {@link org.springframework.security.test.context.support.oauth2.attributes.Parser Parser} - * implementations class names - * @return helper instance with provided parsers only + * @param allParserNames + * {@link org.springframework.security.test.context.support.oauth2.attributes.AttributeValueParser + * AttributeValueParser} implementations class names + * @return helper instance with provided attributeValueParsers only */ public static AttributeParsersHelper withoutDefaultParsers(final String... allParserNames) { return new AttributeParsersHelper(Collections.emptySet(), allParserNames); diff --git a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/Parser.java b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/AttributeValueParser.java similarity index 87% rename from oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/Parser.java rename to oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/AttributeValueParser.java index 1f362031386..2a9941cf054 100644 --- a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/Parser.java +++ b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/AttributeValueParser.java @@ -17,16 +17,15 @@ * @author Jérôme Wacongne <ch4mp@c4-soft.com> * @since 5.2.0 * - * @param type before parsing (source) * @param type after parsing (target) * */ -public interface Parser { +public interface AttributeValueParser { /** * @param value to de-serialize * @return an Object */ - TO_TYPE parse(FROM_TYPE value); + TO_TYPE parse(String value); } diff --git a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/BooleanStringParser.java b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/BooleanParser.java similarity index 94% rename from oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/BooleanStringParser.java rename to oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/BooleanParser.java index 4eff10ef271..2db0fa31920 100644 --- a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/BooleanStringParser.java +++ b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/BooleanParser.java @@ -21,7 +21,7 @@ * @since 5.2.0 * */ -public class BooleanStringParser implements Parser { +public class BooleanParser implements AttributeValueParser { /** * {@inheritDoc} diff --git a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/DoubleStringParser.java b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/DoubleParser.java similarity index 94% rename from oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/DoubleStringParser.java rename to oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/DoubleParser.java index d12ccba842d..da7ead4db3c 100644 --- a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/DoubleStringParser.java +++ b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/DoubleParser.java @@ -21,7 +21,7 @@ * @since 5.2.0 * */ -public class DoubleStringParser implements Parser { +public class DoubleParser implements AttributeValueParser { /** * {@inheritDoc} diff --git a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/InstantStringParser.java b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/InstantParser.java similarity index 94% rename from oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/InstantStringParser.java rename to oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/InstantParser.java index cba7f6513f4..a2c3c6798ff 100644 --- a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/InstantStringParser.java +++ b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/InstantParser.java @@ -23,7 +23,7 @@ * @since 5.2.0 * */ -public class InstantStringParser implements Parser { +public class InstantParser implements AttributeValueParser { /** * {@inheritDoc} diff --git a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/IntegerStringParser.java b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/IntegerParser.java similarity index 94% rename from oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/IntegerStringParser.java rename to oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/IntegerParser.java index d8300d2f56e..f4bd76f8c00 100644 --- a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/IntegerStringParser.java +++ b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/IntegerParser.java @@ -21,7 +21,7 @@ * @since 5.2.0 * */ -public class IntegerStringParser implements Parser { +public class IntegerParser implements AttributeValueParser { /** * {@inheritDoc} diff --git a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/LongStringParser.java b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/LongParser.java similarity index 94% rename from oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/LongStringParser.java rename to oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/LongParser.java index 94a5b0f6412..405b614fe85 100644 --- a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/LongStringParser.java +++ b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/LongParser.java @@ -21,7 +21,7 @@ * @since 5.2.0 * */ -public class LongStringParser implements Parser { +public class LongParser implements AttributeValueParser { /** * {@inheritDoc} diff --git a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/NoOpStringParser.java b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/NoOpParser.java similarity index 93% rename from oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/NoOpStringParser.java rename to oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/NoOpParser.java index bbd27d5208f..6f6f27bb2a5 100644 --- a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/NoOpStringParser.java +++ b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/NoOpParser.java @@ -19,7 +19,7 @@ * @since 5.2.0 * */ -public class NoOpStringParser implements Parser { +public class NoOpParser implements AttributeValueParser { /** * {@inheritDoc} diff --git a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/StringListStringParser.java b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/StringListParser.java similarity index 94% rename from oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/StringListStringParser.java rename to oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/StringListParser.java index aeca5ad1ccd..c50d85e9ee3 100644 --- a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/StringListStringParser.java +++ b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/StringListParser.java @@ -22,7 +22,7 @@ * @since 5.2.0 * */ -public class StringListStringParser implements Parser> { +public class StringListParser implements AttributeValueParser> { /** * {@inheritDoc} diff --git a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/StringSetStringParser.java b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/StringSetParser.java similarity index 94% rename from oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/StringSetStringParser.java rename to oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/StringSetParser.java index 41d5136d0d3..7dcf89a92ab 100644 --- a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/StringSetStringParser.java +++ b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/StringSetParser.java @@ -22,7 +22,7 @@ * @since 5.2.0 * */ -public class StringSetStringParser implements Parser> { +public class StringSetParser implements AttributeValueParser> { /** * {@inheritDoc} diff --git a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/UrlStringParser.java b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/UrlParser.java similarity index 95% rename from oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/UrlStringParser.java rename to oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/UrlParser.java index 1e7a18cffbd..83a02ddcb04 100644 --- a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/UrlStringParser.java +++ b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/UrlParser.java @@ -24,7 +24,7 @@ * @since 5.2.0 * */ -public class UrlStringParser implements Parser { +public class UrlParser implements AttributeValueParser { /** * {@inheritDoc} diff --git a/oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/WithMockJwtSecurityContextFactoryTests.java b/oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/WithMockJwtSecurityContextFactoryTests.java index 11105179a5e..0219ad81ba6 100644 --- a/oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/WithMockJwtSecurityContextFactoryTests.java +++ b/oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/WithMockJwtSecurityContextFactoryTests.java @@ -27,7 +27,8 @@ import org.springframework.security.oauth2.jwt.JwtClaimNames; import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; import org.springframework.security.test.context.support.oauth2.attributes.Attribute; -import org.springframework.security.test.context.support.oauth2.attributes.Parser; +import org.springframework.security.test.context.support.oauth2.attributes.AttributeParsersHelper.TargetType; +import org.springframework.security.test.context.support.oauth2.attributes.AttributeValueParser; public class WithMockJwtSecurityContextFactoryTests { @@ -62,13 +63,13 @@ private static class CustomAdvanced { authorities = { "machin", "chose" }, headers = { @Attribute(name = "a", value = "1") }, claims = { - @Attribute(name = JwtClaimNames.AUD, value = "test audience", parser = "StringListStringParser"), - @Attribute(name = JwtClaimNames.AUD, value = "other audience", parser = "StringListStringParser"), - @Attribute(name = JwtClaimNames.ISS, value = "https://test-issuer.org", parser = "UrlStringParser"), + @Attribute(name = JwtClaimNames.AUD, value = "test audience", parseTo = TargetType.STRING_LIST), + @Attribute(name = JwtClaimNames.AUD, value = "other audience", parseTo = TargetType.STRING_LIST), + @Attribute(name = JwtClaimNames.ISS, value = "https://test-issuer.org", parseTo = TargetType.URL), @Attribute( name = JwtClaimNames.IAT, value = "2019-03-03T22:35:00.0Z", - parser = "InstantStringParser"), + parseTo = TargetType.INSTANT), @Attribute(name = JwtClaimNames.JTI, value = "test ID"), @Attribute(name = "custom-claim", value = "foo") }, additionalParsers = { @@ -242,7 +243,7 @@ public void custom() throws Exception { } - public static final class FooParser implements Parser { + public static final class FooParser implements AttributeValueParser { @Override public String parse(final String value) { diff --git a/oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/attributes/AttributeParsersHelperTest.java b/oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/attributes/AttributeParsersHelperTest.java index 691a398534e..f5e8a93f4ee 100644 --- a/oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/attributes/AttributeParsersHelperTest.java +++ b/oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/attributes/AttributeParsersHelperTest.java @@ -22,92 +22,18 @@ import org.junit.Test; import org.springframework.core.annotation.AnnotationUtils; -import org.springframework.security.test.context.support.oauth2.attributes.Attribute; -import org.springframework.security.test.context.support.oauth2.attributes.AttributeParsersHelper; -import org.springframework.security.test.context.support.oauth2.attributes.BooleanStringParser; -import org.springframework.security.test.context.support.oauth2.attributes.DoubleStringParser; -import org.springframework.security.test.context.support.oauth2.attributes.IntegerStringParser; -import org.springframework.security.test.context.support.oauth2.attributes.LongStringParser; -import org.springframework.security.test.context.support.oauth2.attributes.NoOpStringParser; -import org.springframework.security.test.context.support.oauth2.attributes.Parser; -import org.springframework.security.test.context.support.oauth2.attributes.StringListStringParser; -import org.springframework.security.test.context.support.oauth2.attributes.StringSetStringParser; -import org.springframework.security.test.context.support.oauth2.attributes.UrlStringParser; +import org.springframework.security.test.context.support.oauth2.attributes.AttributeParsersHelper.TargetType; public class AttributeParsersHelperTest { - @Test - public void helperWithDefaultParsers() { - final AttributeParsersHelper actual = AttributeParsersHelper.withDefaultParsers( - "org.springframework.security.test.context.support.oauth2.attributes.AttributeParsersHelperTest$SomeTypeParser", - "org.springframework.security.test.context.support.oauth2.attributes.AttributeParsersHelperTest$OtherTypeParser"); - - assertThat(actual.getParser(NoOpStringParser.class.getName())).isInstanceOf(NoOpStringParser.class); - assertThat(actual.getParser("NoOpStringParser")).isInstanceOf(NoOpStringParser.class); - assertThat(actual.getParser(BooleanStringParser.class.getName())).isInstanceOf(BooleanStringParser.class); - assertThat(actual.getParser("BooleanStringParser")).isInstanceOf(BooleanStringParser.class); - assertThat(actual.getParser(DoubleStringParser.class.getName())).isInstanceOf(DoubleStringParser.class); - assertThat(actual.getParser("DoubleStringParser")).isInstanceOf(DoubleStringParser.class); - assertThat( - actual.getParser( - org.springframework.security.test.context.support.oauth2.attributes.InstantStringParser.class - .getName())).isInstanceOf( - org.springframework.security.test.context.support.oauth2.attributes.InstantStringParser.class); - assertThat(actual.getParser("InstantStringParser")).isInstanceOf( - org.springframework.security.test.context.support.oauth2.attributes.InstantStringParser.class); - assertThat(actual.getParser(IntegerStringParser.class.getName())).isInstanceOf(IntegerStringParser.class); - assertThat(actual.getParser("IntegerStringParser")).isInstanceOf(IntegerStringParser.class); - assertThat(actual.getParser(LongStringParser.class.getName())).isInstanceOf(LongStringParser.class); - assertThat(actual.getParser("LongStringParser")).isInstanceOf(LongStringParser.class); - assertThat(actual.getParser(StringListStringParser.class.getName())).isInstanceOf(StringListStringParser.class); - assertThat(actual.getParser("StringListStringParser")).isInstanceOf(StringListStringParser.class); - assertThat(actual.getParser(StringSetStringParser.class.getName())).isInstanceOf(StringSetStringParser.class); - assertThat(actual.getParser("StringSetStringParser")).isInstanceOf(StringSetStringParser.class); - assertThat(actual.getParser(UrlStringParser.class.getName())).isInstanceOf(UrlStringParser.class); - assertThat(actual.getParser("UrlStringParser")).isInstanceOf(UrlStringParser.class); - assertThat(actual.getParser(SomeTypeParser.class.getName())).isInstanceOf(SomeTypeParser.class); - assertThat(actual.getParser("SomeTypeParser")).isInstanceOf(SomeTypeParser.class); - assertThat(actual.getParser(OtherTypeParser.class.getName())).isInstanceOf(OtherTypeParser.class); - assertThat(actual.getParser("OtherTypeParser")).isInstanceOf(OtherTypeParser.class); - } - - @Test - public void helperWithoutDefaultParsers() { - final AttributeParsersHelper actual = AttributeParsersHelper.withoutDefaultParsers( - "org.springframework.security.test.context.support.oauth2.attributes.AttributeParsersHelperTest$SomeTypeParser", - "org.springframework.security.test.context.support.oauth2.attributes.AttributeParsersHelperTest$OtherTypeParser"); - assertThat(actual.getParser(NoOpStringParser.class.getName())).isNull(); - assertThat(actual.getParser("NoOpStringParser")).isNull(); - assertThat(actual.getParser(BooleanStringParser.class.getName())).isNull(); - assertThat(actual.getParser("BooleanStringParser")).isNull(); - assertThat(actual.getParser(DoubleStringParser.class.getName())).isNull(); - assertThat(actual.getParser("DoubleStringParser")).isNull(); - assertThat(actual.getParser(InstantStringParser.class.getName())).isNull(); - assertThat(actual.getParser("InstantStringParser")).isNull(); - assertThat(actual.getParser(IntegerStringParser.class.getName())).isNull(); - assertThat(actual.getParser("IntegerStringParser")).isNull(); - assertThat(actual.getParser(LongStringParser.class.getName())).isNull(); - assertThat(actual.getParser("LongStringParser")).isNull(); - assertThat(actual.getParser(StringListStringParser.class.getName())).isNull(); - assertThat(actual.getParser("StringListStringParser")).isNull(); - assertThat(actual.getParser(StringSetStringParser.class.getName())).isNull(); - assertThat(actual.getParser("StringSetStringParser")).isNull(); - assertThat(actual.getParser(UrlStringParser.class.getName())).isNull(); - assertThat(actual.getParser("UrlStringParser")).isNull(); - assertThat(actual.getParser(SomeTypeParser.class.getName())).isInstanceOf(SomeTypeParser.class); - assertThat(actual.getParser("SomeTypeParser")).isInstanceOf(SomeTypeParser.class); - assertThat(actual.getParser(OtherTypeParser.class.getName())).isInstanceOf(OtherTypeParser.class); - assertThat(actual.getParser("OtherTypeParser")).isInstanceOf(OtherTypeParser.class); - } - - @Attribute(name = "a", value = "bidule", parser = "SomeTypeParser") + @Attribute(name = "a", value = "bidule", parserOverride = "SomeTypeParser") private static final class AProperty { } @Attribute( name = "b", value = "chose", - parser = "org.springframework.security.test.context.support.oauth2.attributes.AttributeParsersHelperTest$SomeTypeParser") + parserOverride = "org.springframework.security.test.context.support.oauth2.attributes.AttributeParsersHelperTest$SomeTypeParser") private static final class BProperty { } @@ -125,11 +51,11 @@ public void parsePropertiesWithDistinctNames() { } - @Attribute(name = "a", value = "bidule", parser = "StringListStringParser") + @Attribute(name = "a", value = "bidule", parseTo = TargetType.STRING_LIST) private static final class CProperty { } - @Attribute(name = "a", value = "chose", parser = "StringListStringParser") + @Attribute(name = "a", value = "chose", parseTo = TargetType.STRING_LIST) private static final class DProperty { } @@ -148,14 +74,14 @@ public void parsePropertiesWithSameNameAccumulatesValues() { } - @Attribute(name = "instant-millis", value = "12345678", parser = "InstantStringParser") + @Attribute(name = "instant-millis", value = "12345678", parserOverride = "InstantParser") private static final class EProperty { } @Test public void parsePropertiesUsesParseroverrides() { final AttributeParsersHelper helper = AttributeParsersHelper.withDefaultParsers( - "org.springframework.security.test.context.support.oauth2.attributes.AttributeParsersHelperTest$InstantStringParser"); + "org.springframework.security.test.context.support.oauth2.attributes.AttributeParsersHelperTest$InstantParser"); final Attribute propertyAnnotation = AnnotationUtils.findAnnotation(EProperty.class, Attribute.class); @@ -166,14 +92,14 @@ public void parsePropertiesUsesParseroverrides() { } - public static final class SomeTypeParser implements Parser { + public static final class SomeTypeParser implements AttributeValueParser { @Override public String parse(final String value) { return value; } } - public static final class OtherTypeParser implements Parser> { + public static final class OtherTypeParser implements AttributeValueParser> { @Override public Collection parse(final String value) { return Collections.singletonList(value); @@ -183,7 +109,7 @@ public Collection parse(final String value) { /** * custom Instant mapper designed to override default one */ - public static final class InstantStringParser implements Parser { + public static final class InstantParser implements AttributeValueParser { @Override public Instant parse(final String value) { return Instant.ofEpochMilli(Long.valueOf(value)); From c0ce0adfd13eb25223a27d4844482e4474ffc24e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Wacongne?= Date: Tue, 19 Mar 2019 15:57:44 +0100 Subject: [PATCH 4/9] missing comments --- .../support/oauth2/AuthoritiesAndScopes.java | 19 +++++++++++++++++++ .../support/oauth2/AnnotationHelperTest.java | 18 ++++++++++++++++++ .../oauth2/AuthoritiesAndScopesTest.java | 18 ++++++++++++++++++ ...ithMockJwtSecurityContextFactoryTests.java | 6 ++++++ .../support/oauth2/WithMockJwtTests.java | 6 ++++++ .../AttributeParsersHelperTest.java | 6 ++++++ 6 files changed, 73 insertions(+) diff --git a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/AuthoritiesAndScopes.java b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/AuthoritiesAndScopes.java index f8ffbeac431..9d90e862281 100644 --- a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/AuthoritiesAndScopes.java +++ b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/AuthoritiesAndScopes.java @@ -1,3 +1,15 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. 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 org.springframework.security.test.context.support.oauth2; import static org.springframework.security.test.context.support.oauth2.AnnotationHelper.putIfNotEmpty; @@ -12,6 +24,13 @@ import org.springframework.security.core.authority.SimpleGrantedAuthority; +/** + * Helps merging {@code authorities} and {@code scope}. + * + * @author Jérôme Wacongne <ch4mp@c4-soft.com> + * @since 5.2.0 + * + */ final class AuthoritiesAndScopes { public final Set authorities; public final Set scopes; diff --git a/oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/AnnotationHelperTest.java b/oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/AnnotationHelperTest.java index 1551ea8101d..2953e476156 100644 --- a/oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/AnnotationHelperTest.java +++ b/oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/AnnotationHelperTest.java @@ -1,3 +1,15 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. 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 org.springframework.security.test.context.support.oauth2; import static org.assertj.core.api.Assertions.assertThat; @@ -12,6 +24,12 @@ import org.junit.Test; +/** + * + * @author Jérôme Wacongne <ch4mp@c4-soft.com> + * @since 5.2.0 + * + */ public class AnnotationHelperTest { @Test diff --git a/oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/AuthoritiesAndScopesTest.java b/oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/AuthoritiesAndScopesTest.java index f0f36c3c7b2..2d10ec9938c 100644 --- a/oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/AuthoritiesAndScopesTest.java +++ b/oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/AuthoritiesAndScopesTest.java @@ -1,3 +1,15 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. 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 org.springframework.security.test.context.support.oauth2; import static org.assertj.core.api.Assertions.assertThat; @@ -10,6 +22,12 @@ import org.junit.Test; import org.springframework.security.core.authority.SimpleGrantedAuthority; +/** + * + * @author Jérôme Wacongne <ch4mp@c4-soft.com> + * @since 5.2.0 + * + */ public class AuthoritiesAndScopesTest { @Test diff --git a/oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/WithMockJwtSecurityContextFactoryTests.java b/oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/WithMockJwtSecurityContextFactoryTests.java index 0219ad81ba6..3ab510a11da 100644 --- a/oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/WithMockJwtSecurityContextFactoryTests.java +++ b/oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/WithMockJwtSecurityContextFactoryTests.java @@ -30,6 +30,12 @@ import org.springframework.security.test.context.support.oauth2.attributes.AttributeParsersHelper.TargetType; import org.springframework.security.test.context.support.oauth2.attributes.AttributeValueParser; +/** + * + * @author Jérôme Wacongne <ch4mp@c4-soft.com> + * @since 5.2.0 + * + */ public class WithMockJwtSecurityContextFactoryTests { private WithMockJwtSecurityContextFactory factory; diff --git a/oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/WithMockJwtTests.java b/oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/WithMockJwtTests.java index 6dc60b25226..d3893fd4668 100644 --- a/oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/WithMockJwtTests.java +++ b/oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/WithMockJwtTests.java @@ -21,6 +21,12 @@ import org.springframework.security.test.context.support.WithSecurityContext; import org.springframework.security.test.context.support.oauth2.attributes.Attribute; +/** + * + * @author Jérôme Wacongne <ch4mp@c4-soft.com> + * @since 5.2.0 + * + */ public class WithMockJwtTests { @Test diff --git a/oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/attributes/AttributeParsersHelperTest.java b/oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/attributes/AttributeParsersHelperTest.java index f5e8a93f4ee..73b7acb327c 100644 --- a/oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/attributes/AttributeParsersHelperTest.java +++ b/oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/attributes/AttributeParsersHelperTest.java @@ -24,6 +24,12 @@ import org.springframework.core.annotation.AnnotationUtils; import org.springframework.security.test.context.support.oauth2.attributes.AttributeParsersHelper.TargetType; +/** + * + * @author Jérôme Wacongne <ch4mp@c4-soft.com> + * @since 5.2.0 + * + */ public class AttributeParsersHelperTest { @Attribute(name = "a", value = "bidule", parserOverride = "SomeTypeParser") From 39f846492a8997c7b3d1bc2f6971179ba4ca3a73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Wacongne?= Date: Tue, 19 Mar 2019 16:09:18 +0100 Subject: [PATCH 5/9] remove unused code --- .../support/oauth2/AnnotationHelper.java | 9 +-------- .../support/oauth2/AnnotationHelperTest.java | 20 ------------------- 2 files changed, 1 insertion(+), 28 deletions(-) diff --git a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/AnnotationHelper.java b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/AnnotationHelper.java index d121c10b2f7..6ed83c36730 100644 --- a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/AnnotationHelper.java +++ b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/AnnotationHelper.java @@ -14,11 +14,11 @@ import java.util.Collection; import java.util.Map; -import java.util.stream.Stream; import org.springframework.util.StringUtils; /** + * Useful functions to manipulate annotation values * * @author Jérôme Wacongne <ch4mp@c4-soft.com> * @since 5.2.0 @@ -46,11 +46,4 @@ public static String nullIfEmpty(final String str) { return map; } - public static Stream stringStream(final String... values) { - if (values == null || values.length == 0) { - return Stream.empty(); - } - return Stream.of(values).map(AnnotationHelper::nullIfEmpty).filter(a -> a != null); - } - } diff --git a/oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/AnnotationHelperTest.java b/oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/AnnotationHelperTest.java index 2953e476156..87fc69d305d 100644 --- a/oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/AnnotationHelperTest.java +++ b/oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/AnnotationHelperTest.java @@ -15,12 +15,10 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.security.test.context.support.oauth2.AnnotationHelper.nullIfEmpty; import static org.springframework.security.test.context.support.oauth2.AnnotationHelper.putIfNotEmpty; -import static org.springframework.security.test.context.support.oauth2.AnnotationHelper.stringStream; import java.util.Collections; import java.util.HashMap; import java.util.List; -import java.util.stream.Stream; import org.junit.Test; @@ -91,22 +89,4 @@ public void putIfNotEmptyInsertsNonEmptyList() { assertThat(actual).contains("Toto"); } - @Test - public void stringStreamReturnsEmptyStreamForNullArray() { - final Stream actual = stringStream((String[]) null); - assertThat(actual).isEmpty(); - } - - @Test - public void stringStreamReturnsEmptyStreamForEmptyArray() { - final Stream actual = stringStream(new String[] {}); - assertThat(actual).isEmpty(); - } - - @Test - public void stringStreamSkipsNullAndEmptyStrings() { - final Stream actual = stringStream(new String[] { null, "", "Toto" }); - assertThat(actual).allMatch("Toto"::equals); - } - } From 0f185ba2fdfb8bade59fffbf539cae9f1170ca7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Wacongne?= Date: Tue, 19 Mar 2019 17:47:38 +0100 Subject: [PATCH 6/9] AttributeParsersHelper shouldn't build new default parser instance each time it is accessed --- .../attributes/AttributeParsersHelper.java | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/AttributeParsersHelper.java b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/AttributeParsersHelper.java index 4c90d9e2bdb..239cb81a9f8 100644 --- a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/AttributeParsersHelper.java +++ b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/AttributeParsersHelper.java @@ -68,12 +68,12 @@ public enum TargetType { DEFAULT_PARSERS.add(new UrlParser()); } - private final Map> attributeValueParsers; + private final Map> parsers; private AttributeParsersHelper( final Set> baseParsers, final String... additionalParserNames) { - this.attributeValueParsers = new HashMap<>(2 * DEFAULT_PARSERS.size() + 2 * additionalParserNames.length); + this.parsers = new HashMap<>(2 * DEFAULT_PARSERS.size() + 2 * additionalParserNames.length); final Stream> additionalParsers = Stream.of(additionalParserNames).distinct().map(t -> { try { return Class.forName(t); @@ -90,35 +90,35 @@ private AttributeParsersHelper( }); Stream.concat(baseParsers.stream(), additionalParsers).forEachOrdered(p -> { - this.attributeValueParsers.put(p.getClass().getName(), p); - this.attributeValueParsers.put(p.getClass().getSimpleName(), p); + this.parsers.put(p.getClass().getName(), p); + this.parsers.put(p.getClass().getSimpleName(), p); }); } private AttributeValueParser getParser(final TargetType targetType, final String parserOverrideClassName) { final Optional> parserOverride = Optional.ofNullable(StringUtils.isEmpty(parserOverrideClassName) ? null : parserOverrideClassName) - .map(this.attributeValueParsers::get); + .map(parsers::get); switch (targetType) { case STRING: - return parserOverride.orElse(new NoOpParser()); + return parserOverride.orElse(parsers.get("NoOpParser")); case BOOLEAN: - return parserOverride.orElse(new BooleanParser()); + return parserOverride.orElse(parsers.get("BooleanParser")); case DOUBLE: - return parserOverride.orElse(new DoubleParser()); + return parserOverride.orElse(parsers.get("DoubleParser")); case INSTANT: - return parserOverride.orElse(new InstantParser()); + return parserOverride.orElse(parsers.get("InstantParser")); case INTEGER: - return parserOverride.orElse(new IntegerParser()); + return parserOverride.orElse(parsers.get("IntegerParser")); case LONG: - return parserOverride.orElse(new LongParser()); + return parserOverride.orElse(parsers.get("LongParser")); case STRING_LIST: - return parserOverride.orElse(new StringListParser()); + return parserOverride.orElse(parsers.get("StringListParser")); case STRING_SET: - return parserOverride.orElse(new StringSetParser()); + return parserOverride.orElse(parsers.get("StringSetParser")); case URL: - return parserOverride.orElse(new UrlParser()); + return parserOverride.orElse(parsers.get("UrlParser")); default: assert (!StringUtils.isEmpty(parserOverrideClassName)); return parserOverride.get(); @@ -194,7 +194,7 @@ public Map parse(final Attribute... properties) { * AttributeValueParser} implementations class names to add to * {@link org.springframework.security.test.context.support.oauth2.attributes.AttributeParsersHelper#DEFAULT_PARSERS * default ones} - * @return helper instance with provided attributeValueParsers plus default ones + * @return helper instance with provided parsers plus default ones */ public static AttributeParsersHelper withDefaultParsers(final String... additionalParserNames) { return new AttributeParsersHelper(DEFAULT_PARSERS, additionalParserNames); @@ -208,7 +208,7 @@ public static AttributeParsersHelper withDefaultParsers(final String... addition * @param allParserNames * {@link org.springframework.security.test.context.support.oauth2.attributes.AttributeValueParser * AttributeValueParser} implementations class names - * @return helper instance with provided attributeValueParsers only + * @return helper instance with provided parsers only */ public static AttributeParsersHelper withoutDefaultParsers(final String... allParserNames) { return new AttributeParsersHelper(Collections.emptySet(), allParserNames); From 771fe7c14da38ce969513e9ee72e0055f680be0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Wacongne?= Date: Tue, 19 Mar 2019 23:10:21 +0100 Subject: [PATCH 7/9] Lambdas instead of default parsers named classes --- .../oauth2/{attributes => }/Attribute.java | 11 +- .../AttributeParsersHelper.java | 111 +++++++++--------- .../AttributeValueParser.java | 2 +- .../context/support/oauth2/WithMockJwt.java | 16 ++- .../WithMockJwtSecurityContextFactory.java | 1 - .../oauth2/attributes/BooleanParser.java | 37 ------ .../oauth2/attributes/DoubleParser.java | 37 ------ .../oauth2/attributes/InstantParser.java | 39 ------ .../oauth2/attributes/IntegerParser.java | 37 ------ .../support/oauth2/attributes/LongParser.java | 37 ------ .../support/oauth2/attributes/NoOpParser.java | 32 ----- .../oauth2/attributes/StringListParser.java | 35 ------ .../oauth2/attributes/StringSetParser.java | 34 ------ .../support/oauth2/attributes/UrlParser.java | 44 ------- .../AttributeParsersHelperTest.java | 13 +- ...ithMockJwtSecurityContextFactoryTests.java | 4 +- .../support/oauth2/WithMockJwtTests.java | 1 - 17 files changed, 77 insertions(+), 414 deletions(-) rename oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/{attributes => }/Attribute.java (90%) rename oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/{attributes => }/AttributeParsersHelper.java (72%) rename oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/{attributes => }/AttributeValueParser.java (98%) delete mode 100644 oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/BooleanParser.java delete mode 100644 oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/DoubleParser.java delete mode 100644 oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/InstantParser.java delete mode 100644 oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/IntegerParser.java delete mode 100644 oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/LongParser.java delete mode 100644 oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/NoOpParser.java delete mode 100644 oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/StringListParser.java delete mode 100644 oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/StringSetParser.java delete mode 100644 oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/UrlParser.java rename oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/{attributes => }/AttributeParsersHelperTest.java (88%) diff --git a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/Attribute.java b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/Attribute.java similarity index 90% rename from oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/Attribute.java rename to oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/Attribute.java index f1e1208abae..d75242b8465 100644 --- a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/Attribute.java +++ b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/Attribute.java @@ -10,14 +10,14 @@ * 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.test.context.support.oauth2.attributes; +package org.springframework.security.test.context.support.oauth2; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.springframework.security.test.context.support.oauth2.attributes.AttributeParsersHelper.TargetType; +import org.springframework.security.test.context.support.oauth2.AttributeParsersHelper.TargetType; /** *

      @@ -28,8 +28,8 @@ * {@link #parserOverride()} can be referenced either by fully qualified name or simple name. *

      *

      - * See {@link AttributeValueParser} and its already provided implementations: - * {@link AttributeParsersHelper#DEFAULT_PARSERS} + * See {@link AttributeValueParser}. Implementations for any {@link TargetType} but {@link TargetType#OTHER} are + * provided out of the box (can be overriden, just provide a parser with same name) *

      * Sample usage:
      * @@ -47,8 +47,7 @@ *
        *
      • an {@code audience} claim with a value being a {@code List} with two entries
      • *
      • an {@code issuer} claim with a value being a {@code java.net.URL} instance
      • - *
      • a {@code machin} claim with {@code chose} String as value (default parser is - * {@link org.springframework.security.test.context.support.oauth2.attributes.NoOpParser NoOpParser})
      • + *
      • a {@code machin} claim with {@code chose} String as value (default parser is {@code NoOpParser})
      • *
      • a {@code truc} claim whith an instance of what {@code your.fancy.ParserImpl} is designed to build from * {@code bidule} string as value
      • *
      diff --git a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/AttributeParsersHelper.java b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/AttributeParsersHelper.java similarity index 72% rename from oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/AttributeParsersHelper.java rename to oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/AttributeParsersHelper.java index 239cb81a9f8..c4fc8e833bf 100644 --- a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/AttributeParsersHelper.java +++ b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/AttributeParsersHelper.java @@ -10,9 +10,12 @@ * 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.test.context.support.oauth2.attributes; +package org.springframework.security.test.context.support.oauth2; import java.lang.reflect.InvocationTargetException; +import java.net.MalformedURLException; +import java.net.URL; +import java.time.Instant; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -21,7 +24,6 @@ import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.Set; import java.util.function.BinaryOperator; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -29,52 +31,26 @@ import org.springframework.util.StringUtils; /** - * Helps turn a {@link org.springframework.security.test.context.support.oauth2.attributes.Attribute @Attribute} array - * into a {@link java.util.Map Map<String, Object>} + * Helps turn a {@link org.springframework.security.test.context.support.oauth2.Attribute @Attribute} array into a + * {@link java.util.Map Map<String, Object>} * * @author Jérôme Wacongne <ch4mp@c4-soft.com> * @since 5.2.0 * */ -public class AttributeParsersHelper { +class AttributeParsersHelper { public enum TargetType { STRING, BOOLEAN, DOUBLE, INSTANT, INTEGER, LONG, STRING_LIST, STRING_SET, URL, OTHER; } - /** - *
        - *
      • {@link NoOpParser} => keeps value as is.
      • - *
      • {@link BooleanParser} => Boolean
      • - *
      • {@link DoubleParser} => Double
      • - *
      • {@link InstantParser} => {@link java.time.Instant Instant}
      • - *
      • {@link IntegerParser} => Integer
      • - *
      • {@link LongParser} => Long
      • - *
      • {@link StringListParser} => List<String>
      • - *
      • {@link StringSetParser} => Set<String>
      • - *
      • {@link UrlParser} => URL
      • - *
      - */ - public static final Set> DEFAULT_PARSERS = new HashSet<>(); - - static { - DEFAULT_PARSERS.add(new NoOpParser()); - DEFAULT_PARSERS.add(new BooleanParser()); - DEFAULT_PARSERS.add(new DoubleParser()); - DEFAULT_PARSERS.add(new InstantParser()); - DEFAULT_PARSERS.add(new IntegerParser()); - DEFAULT_PARSERS.add(new LongParser()); - DEFAULT_PARSERS.add(new StringListParser()); - DEFAULT_PARSERS.add(new StringSetParser()); - DEFAULT_PARSERS.add(new UrlParser()); - } - private final Map> parsers; private AttributeParsersHelper( - final Set> baseParsers, + final Map> baseParsers, final String... additionalParserNames) { - this.parsers = new HashMap<>(2 * DEFAULT_PARSERS.size() + 2 * additionalParserNames.length); - final Stream> additionalParsers = Stream.of(additionalParserNames).distinct().map(t -> { + this.parsers = new HashMap<>(baseParsers); + + Stream.of(additionalParserNames).distinct().map(t -> { try { return Class.forName(t); } catch (final ClassNotFoundException e) { @@ -87,9 +63,7 @@ private AttributeParsersHelper( | InvocationTargetException | NoSuchMethodException | SecurityException e) { throw new RuntimeException("Missing public no-arg constructor on " + c.getName()); } - }); - - Stream.concat(baseParsers.stream(), additionalParsers).forEachOrdered(p -> { + }).forEachOrdered(p -> { this.parsers.put(p.getClass().getName(), p); this.parsers.put(p.getClass().getSimpleName(), p); }); @@ -137,20 +111,19 @@ private ParsedProperty parse(final Attribute p) { /** *

      - * Turns a {@link org.springframework.security.test.context.support.oauth2.attributes.Attribute @Attribute} array - * into a {@link java.util.Map Map<String, Object>} as required for + * Turns a {@link org.springframework.security.test.context.support.oauth2.Attribute @Attribute} array into a + * {@link java.util.Map Map<String, Object>} as required for * {@link org.springframework.security.oauth2.jwt.Jwt JWT} headers and claims. *

      *

      * Process highlights: *

      *
        - *
      • each {@link org.springframework.security.test.context.support.oauth2.attributes.Attribute#value() value()} is - * parsed according to - * {@link org.springframework.security.test.context.support.oauth2.attributes.Attribute#parserOverride() + *
      • each {@link org.springframework.security.test.context.support.oauth2.Attribute#value() value()} is parsed + * according to {@link org.springframework.security.test.context.support.oauth2.Attribute#parserOverride() * parser()}
      • *
      • obtained values are associated with - * {@link org.springframework.security.test.context.support.oauth2.attributes.Attribute#name() name()}
      • + * {@link org.springframework.security.test.context.support.oauth2.Attribute#name() name()} *
      • values with same name are accumulated in the same collection
      • *
      * @@ -185,33 +158,59 @@ public Map parse(final Attribute... properties) { } /** - * Instantiates default - * {@link org.springframework.security.test.context.support.oauth2.attributes.AttributeValueParser + * Instantiates default {@link org.springframework.security.test.context.support.oauth2.AttributeValueParser * AttributeValueParser}s plus all provided ones (using default constructor) * - * @param additionalParserNames - * {@link org.springframework.security.test.context.support.oauth2.attributes.AttributeValueParser + * @param additionalParserNames {@link org.springframework.security.test.context.support.oauth2.AttributeValueParser * AttributeValueParser} implementations class names to add to - * {@link org.springframework.security.test.context.support.oauth2.attributes.AttributeParsersHelper#DEFAULT_PARSERS - * default ones} + * {@link org.springframework.security.test.context.support.oauth2.AttributeParsersHelper#DEFAULT_PARSERS default + * ones} * @return helper instance with provided parsers plus default ones */ public static AttributeParsersHelper withDefaultParsers(final String... additionalParserNames) { - return new AttributeParsersHelper(DEFAULT_PARSERS, additionalParserNames); + final Map> baseParsers = new HashMap<>(9); + + baseParsers.put("NoOpParser", (final String value) -> value); + baseParsers.put( + "BooleanParser", + (final String value) -> StringUtils.isEmpty(value) ? null : Boolean.valueOf(value)); + baseParsers + .put("DoubleParser", (final String value) -> StringUtils.isEmpty(value) ? null : Double.valueOf(value)); + baseParsers + .put("InstantParser", (final String value) -> StringUtils.isEmpty(value) ? null : Instant.parse(value)); + baseParsers.put( + "IntegerParser", + (final String value) -> StringUtils.isEmpty(value) ? null : Integer.valueOf(value)); + baseParsers.put("LongParser", (final String value) -> StringUtils.isEmpty(value) ? null : Long.valueOf(value)); + baseParsers.put( + "StringListParser", + (final String value) -> StringUtils.isEmpty(value) ? Collections.emptyList() + : Collections.singletonList(value)); + baseParsers.put( + "StringSetParser", + (final String value) -> StringUtils.isEmpty(value) ? Collections.emptySet() + : Collections.singleton(value)); + baseParsers.put("UrlParser", (final String value) -> { + try { + return (StringUtils.isEmpty(value)) ? null : new URL(value); + } catch (final MalformedURLException e) { + throw new RuntimeException(e); + } + }); + + return new AttributeParsersHelper(baseParsers, additionalParserNames); } /** - * Instantiates all provided - * {@link org.springframework.security.test.context.support.oauth2.attributes.AttributeValueParser + * Instantiates all provided {@link org.springframework.security.test.context.support.oauth2.AttributeValueParser * AttributeValueParser}s using default constructor * - * @param allParserNames - * {@link org.springframework.security.test.context.support.oauth2.attributes.AttributeValueParser + * @param allParserNames {@link org.springframework.security.test.context.support.oauth2.AttributeValueParser * AttributeValueParser} implementations class names * @return helper instance with provided parsers only */ public static AttributeParsersHelper withoutDefaultParsers(final String... allParserNames) { - return new AttributeParsersHelper(Collections.emptySet(), allParserNames); + return new AttributeParsersHelper(Collections.emptyMap(), allParserNames); } private static final class ParsedProperty { diff --git a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/AttributeValueParser.java b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/AttributeValueParser.java similarity index 98% rename from oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/AttributeValueParser.java rename to oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/AttributeValueParser.java index 2a9941cf054..67270a67206 100644 --- a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/AttributeValueParser.java +++ b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/AttributeValueParser.java @@ -10,7 +10,7 @@ * 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.test.context.support.oauth2.attributes; +package org.springframework.security.test.context.support.oauth2; /** * diff --git a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/WithMockJwt.java b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/WithMockJwt.java index 27d7ecd441e..b02e7ea13b9 100644 --- a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/WithMockJwt.java +++ b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/WithMockJwt.java @@ -28,9 +28,7 @@ import org.springframework.security.test.context.support.TestExecutionEvent; import org.springframework.security.test.context.support.WithSecurityContext; import org.springframework.security.test.context.support.WithSecurityContextTestExecutionListener; -import org.springframework.security.test.context.support.oauth2.attributes.Attribute; -import org.springframework.security.test.context.support.oauth2.attributes.AttributeParsersHelper; -import org.springframework.security.test.context.support.oauth2.attributes.AttributeValueParser; +import org.springframework.security.test.context.support.oauth2.AttributeParsersHelper.TargetType; import org.springframework.test.context.TestContext; import org.springframework.test.web.servlet.MockMvc; @@ -59,11 +57,11 @@ * {@code name} value ({@code "user"} by default) *
    • {@link Authentication#getAuthorities() authorities} will be a collection of {@link SimpleGrantedAuthority} as * defined by this annotation {@link #authorities()} ( - * + * *
        * { "ROLE_USER" }
        * 
      - * + * * by default
    • * * @@ -95,7 +93,7 @@ * * @see Attribute * @see AttributeValueParser - * @see AttributeParsersHelper#DEFAULT_PARSERS + * @see TargetType * * @author Jérôme Wacongne <ch4mp@c4-soft.com> * @since 5.2.0 @@ -143,12 +141,12 @@ Attribute[] headers() default { @Attribute(name = DEFAULT_HEADER_NAME, value = DEFAULT_HEADER_VALUE) }; /** - * {@link AttributeParsersHelper#DEFAULT_PARSERS Defaulted parsers} are provided for most common value types. Those - * will be added to defaults (and override defaults when retrieved by simple name) + * Parsers are provided for for all {@link TargetType} but {@link TargetType#OTHER}. Those will be added to parse + * more types or override defaults with same {@code SimpleName}. * * @return parsers to add to default ones (or override) * - * @see AttributeParsersHelper#DEFAULT_PARSERS + * @see TargetType */ String[] additionalParsers() default {}; diff --git a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/WithMockJwtSecurityContextFactory.java b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/WithMockJwtSecurityContextFactory.java index 0976643721e..a610e93f4ab 100644 --- a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/WithMockJwtSecurityContextFactory.java +++ b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/WithMockJwtSecurityContextFactory.java @@ -24,7 +24,6 @@ import org.springframework.security.oauth2.jwt.JwtClaimNames; import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; import org.springframework.security.test.context.support.WithSecurityContextFactory; -import org.springframework.security.test.context.support.oauth2.attributes.AttributeParsersHelper; /** * Create a {@link org.springframework.security.core.context.SecurityContext SecurityContext} populated with a diff --git a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/BooleanParser.java b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/BooleanParser.java deleted file mode 100644 index 2db0fa31920..00000000000 --- a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/BooleanParser.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. 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 org.springframework.security.test.context.support.oauth2.attributes; - -import org.springframework.util.StringUtils; - -/** - * Turns an annotation String value into a Boolean - * - * @author Jérôme Wacongne <ch4mp@c4-soft.com> - * @since 5.2.0 - * - */ -public class BooleanParser implements AttributeValueParser { - - /** - * {@inheritDoc} - */ - @Override - public Boolean parse(final String value) { - if (StringUtils.isEmpty(value)) { - return null; - } - return Boolean.valueOf(value); - } - -} diff --git a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/DoubleParser.java b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/DoubleParser.java deleted file mode 100644 index da7ead4db3c..00000000000 --- a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/DoubleParser.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. 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 org.springframework.security.test.context.support.oauth2.attributes; - -import org.springframework.util.StringUtils; - -/** - * Turns an annotation String value into a Double - * - * @author Jérôme Wacongne <ch4mp@c4-soft.com> - * @since 5.2.0 - * - */ -public class DoubleParser implements AttributeValueParser { - - /** - * {@inheritDoc} - */ - @Override - public Double parse(final String value) { - if (StringUtils.isEmpty(value)) { - return null; - } - return Double.valueOf(value); - } - -} diff --git a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/InstantParser.java b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/InstantParser.java deleted file mode 100644 index a2c3c6798ff..00000000000 --- a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/InstantParser.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. 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 org.springframework.security.test.context.support.oauth2.attributes; - -import java.time.Instant; - -import org.springframework.util.StringUtils; - -/** - * Turns an annotation String value into an {@link java.time.Instant Instant} - * - * @author Jérôme Wacongne <ch4mp@c4-soft.com> - * @since 5.2.0 - * - */ -public class InstantParser implements AttributeValueParser { - - /** - * {@inheritDoc} - */ - @Override - public Instant parse(final String value) { - if (StringUtils.isEmpty(value)) { - return null; - } - return Instant.parse(value); - } - -} diff --git a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/IntegerParser.java b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/IntegerParser.java deleted file mode 100644 index f4bd76f8c00..00000000000 --- a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/IntegerParser.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. 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 org.springframework.security.test.context.support.oauth2.attributes; - -import org.springframework.util.StringUtils; - -/** - * Turns an annotation String value into an Integer - * - * @author Jérôme Wacongne <ch4mp@c4-soft.com> - * @since 5.2.0 - * - */ -public class IntegerParser implements AttributeValueParser { - - /** - * {@inheritDoc} - */ - @Override - public Integer parse(final String value) { - if (StringUtils.isEmpty(value)) { - return null; - } - return Integer.valueOf(value); - } - -} diff --git a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/LongParser.java b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/LongParser.java deleted file mode 100644 index 405b614fe85..00000000000 --- a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/LongParser.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. 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 org.springframework.security.test.context.support.oauth2.attributes; - -import org.springframework.util.StringUtils; - -/** - * Turns an annotation String value into a Long - * - * @author Jérôme Wacongne <ch4mp@c4-soft.com> - * @since 5.2.0 - * - */ -public class LongParser implements AttributeValueParser { - - /** - * {@inheritDoc} - */ - @Override - public Long parse(final String value) { - if (StringUtils.isEmpty(value)) { - return null; - } - return Long.valueOf(value); - } - -} diff --git a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/NoOpParser.java b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/NoOpParser.java deleted file mode 100644 index 6f6f27bb2a5..00000000000 --- a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/NoOpParser.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. 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 org.springframework.security.test.context.support.oauth2.attributes; - -/** - * Leaves an annotation String value untouched - * - * @author Jérôme Wacongne <ch4mp@c4-soft.com> - * @since 5.2.0 - * - */ -public class NoOpParser implements AttributeValueParser { - - /** - * {@inheritDoc} - */ - @Override - public String parse(final String value) { - return value; - } - -} diff --git a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/StringListParser.java b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/StringListParser.java deleted file mode 100644 index c50d85e9ee3..00000000000 --- a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/StringListParser.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. 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 org.springframework.security.test.context.support.oauth2.attributes; - -import java.util.Collections; -import java.util.List; - -/** - * Turns an annotation String value into a {@link java.util.List List<String>} - * - * @author Jérôme Wacongne <ch4mp@c4-soft.com> - * @since 5.2.0 - * - */ -public class StringListParser implements AttributeValueParser> { - - /** - * {@inheritDoc} - */ - @Override - public List parse(final String value) { - return Collections.singletonList(value); - } - -} diff --git a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/StringSetParser.java b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/StringSetParser.java deleted file mode 100644 index 7dcf89a92ab..00000000000 --- a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/StringSetParser.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. 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 org.springframework.security.test.context.support.oauth2.attributes; - -import java.util.Collections; -import java.util.Set; - -/** - * Turns an annotation String value into a {@link java.util.Set Set<String>} - * - * @author Jérôme Wacongne <ch4mp@c4-soft.com> - * @since 5.2.0 - * - */ -public class StringSetParser implements AttributeValueParser> { - - /** - * {@inheritDoc} - */ - @Override - public Set parse(final String value) { - return Collections.singleton(value); - } -} diff --git a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/UrlParser.java b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/UrlParser.java deleted file mode 100644 index 83a02ddcb04..00000000000 --- a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/attributes/UrlParser.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. 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 org.springframework.security.test.context.support.oauth2.attributes; - -import java.net.MalformedURLException; -import java.net.URL; - -import org.springframework.util.StringUtils; - -/** - * Turns an annotation String value into an {@link java.net.URL URL} - * - * @author Jérôme Wacongne <ch4mp@c4-soft.com> - * @since 5.2.0 - * - */ -public class UrlParser implements AttributeValueParser { - - /** - * {@inheritDoc} - */ - @Override - public URL parse(final String value) { - if (StringUtils.isEmpty(value)) { - return null; - } - try { - return new URL(value); - } catch (final MalformedURLException e) { - throw new RuntimeException(e); - } - } - -} diff --git a/oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/attributes/AttributeParsersHelperTest.java b/oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/AttributeParsersHelperTest.java similarity index 88% rename from oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/attributes/AttributeParsersHelperTest.java rename to oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/AttributeParsersHelperTest.java index 73b7acb327c..7a664f5ce23 100644 --- a/oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/attributes/AttributeParsersHelperTest.java +++ b/oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/AttributeParsersHelperTest.java @@ -10,7 +10,7 @@ * 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.test.context.support.oauth2.attributes; +package org.springframework.security.test.context.support.oauth2; import static org.assertj.core.api.Assertions.assertThat; @@ -22,7 +22,10 @@ import org.junit.Test; import org.springframework.core.annotation.AnnotationUtils; -import org.springframework.security.test.context.support.oauth2.attributes.AttributeParsersHelper.TargetType; +import org.springframework.security.test.context.support.oauth2.Attribute; +import org.springframework.security.test.context.support.oauth2.AttributeParsersHelper; +import org.springframework.security.test.context.support.oauth2.AttributeValueParser; +import org.springframework.security.test.context.support.oauth2.AttributeParsersHelper.TargetType; /** * @@ -39,14 +42,14 @@ private static final class AProperty { @Attribute( name = "b", value = "chose", - parserOverride = "org.springframework.security.test.context.support.oauth2.attributes.AttributeParsersHelperTest$SomeTypeParser") + parserOverride = "org.springframework.security.test.context.support.oauth2.AttributeParsersHelperTest$SomeTypeParser") private static final class BProperty { } @Test public void parsePropertiesWithDistinctNames() { final AttributeParsersHelper helper = AttributeParsersHelper.withoutDefaultParsers( - "org.springframework.security.test.context.support.oauth2.attributes.AttributeParsersHelperTest$SomeTypeParser"); + "org.springframework.security.test.context.support.oauth2.AttributeParsersHelperTest$SomeTypeParser"); final Attribute propertyAnnotationA = AnnotationUtils.findAnnotation(AProperty.class, Attribute.class); final Attribute propertyAnnotationB = AnnotationUtils.findAnnotation(BProperty.class, Attribute.class); @@ -87,7 +90,7 @@ private static final class EProperty { @Test public void parsePropertiesUsesParseroverrides() { final AttributeParsersHelper helper = AttributeParsersHelper.withDefaultParsers( - "org.springframework.security.test.context.support.oauth2.attributes.AttributeParsersHelperTest$InstantParser"); + "org.springframework.security.test.context.support.oauth2.AttributeParsersHelperTest$InstantParser"); final Attribute propertyAnnotation = AnnotationUtils.findAnnotation(EProperty.class, Attribute.class); diff --git a/oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/WithMockJwtSecurityContextFactoryTests.java b/oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/WithMockJwtSecurityContextFactoryTests.java index 3ab510a11da..3b8f5bb7e52 100644 --- a/oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/WithMockJwtSecurityContextFactoryTests.java +++ b/oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/WithMockJwtSecurityContextFactoryTests.java @@ -26,9 +26,7 @@ import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.security.oauth2.jwt.JwtClaimNames; import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; -import org.springframework.security.test.context.support.oauth2.attributes.Attribute; -import org.springframework.security.test.context.support.oauth2.attributes.AttributeParsersHelper.TargetType; -import org.springframework.security.test.context.support.oauth2.attributes.AttributeValueParser; +import org.springframework.security.test.context.support.oauth2.AttributeParsersHelper.TargetType; /** * diff --git a/oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/WithMockJwtTests.java b/oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/WithMockJwtTests.java index d3893fd4668..9397b03dd1d 100644 --- a/oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/WithMockJwtTests.java +++ b/oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/WithMockJwtTests.java @@ -19,7 +19,6 @@ import org.springframework.core.annotation.AnnotationUtils; import org.springframework.security.test.context.support.TestExecutionEvent; import org.springframework.security.test.context.support.WithSecurityContext; -import org.springframework.security.test.context.support.oauth2.attributes.Attribute; /** * From bc286f0a1099c6821b74be634754a5ce57ea0db9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Wacongne?= Date: Wed, 20 Mar 2019 13:48:18 +0100 Subject: [PATCH 8/9] TargetType enum must be public --- .../context/support/oauth2/Attribute.java | 2 - .../oauth2/AttributeParsersHelper.java | 4 -- .../context/support/oauth2/TargetType.java | 23 ++++++++ .../context/support/oauth2/WithMockJwt.java | 1 - .../oauth2/AttributeParsersHelperTest.java | 1 - ...ithMockJwtSecurityContextFactoryTests.java | 1 - .../OAuth2ResourceServerControllerTest.java | 52 +++++++++++++------ 7 files changed, 60 insertions(+), 24 deletions(-) create mode 100644 oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/TargetType.java diff --git a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/Attribute.java b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/Attribute.java index d75242b8465..17a55311609 100644 --- a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/Attribute.java +++ b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/Attribute.java @@ -17,8 +17,6 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.springframework.security.test.context.support.oauth2.AttributeParsersHelper.TargetType; - /** *

      * Annotation to create an entry in a {@link java.util.Map Map<String, Object>} such as diff --git a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/AttributeParsersHelper.java b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/AttributeParsersHelper.java index c4fc8e833bf..2ee10870dd4 100644 --- a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/AttributeParsersHelper.java +++ b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/AttributeParsersHelper.java @@ -39,10 +39,6 @@ * */ class AttributeParsersHelper { - public enum TargetType { - STRING, BOOLEAN, DOUBLE, INSTANT, INTEGER, LONG, STRING_LIST, STRING_SET, URL, OTHER; - } - private final Map> parsers; private AttributeParsersHelper( diff --git a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/TargetType.java b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/TargetType.java new file mode 100644 index 00000000000..4dbaa057775 --- /dev/null +++ b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/TargetType.java @@ -0,0 +1,23 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. 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 org.springframework.security.test.context.support.oauth2; + +/** + * + * @author Jérôme Wacongne <ch4mp@c4-soft.com> + * @since 5.2.0 + * + */ +public enum TargetType { + STRING, BOOLEAN, DOUBLE, INSTANT, INTEGER, LONG, STRING_LIST, STRING_SET, URL, OTHER; +} \ No newline at end of file diff --git a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/WithMockJwt.java b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/WithMockJwt.java index b02e7ea13b9..940a5008800 100644 --- a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/WithMockJwt.java +++ b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/WithMockJwt.java @@ -28,7 +28,6 @@ import org.springframework.security.test.context.support.TestExecutionEvent; import org.springframework.security.test.context.support.WithSecurityContext; import org.springframework.security.test.context.support.WithSecurityContextTestExecutionListener; -import org.springframework.security.test.context.support.oauth2.AttributeParsersHelper.TargetType; import org.springframework.test.context.TestContext; import org.springframework.test.web.servlet.MockMvc; diff --git a/oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/AttributeParsersHelperTest.java b/oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/AttributeParsersHelperTest.java index 7a664f5ce23..675b451e358 100644 --- a/oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/AttributeParsersHelperTest.java +++ b/oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/AttributeParsersHelperTest.java @@ -25,7 +25,6 @@ import org.springframework.security.test.context.support.oauth2.Attribute; import org.springframework.security.test.context.support.oauth2.AttributeParsersHelper; import org.springframework.security.test.context.support.oauth2.AttributeValueParser; -import org.springframework.security.test.context.support.oauth2.AttributeParsersHelper.TargetType; /** * diff --git a/oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/WithMockJwtSecurityContextFactoryTests.java b/oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/WithMockJwtSecurityContextFactoryTests.java index 3b8f5bb7e52..18baa697e6a 100644 --- a/oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/WithMockJwtSecurityContextFactoryTests.java +++ b/oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/WithMockJwtSecurityContextFactoryTests.java @@ -26,7 +26,6 @@ import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.security.oauth2.jwt.JwtClaimNames; import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; -import org.springframework.security.test.context.support.oauth2.AttributeParsersHelper.TargetType; /** * diff --git a/samples/boot/oauth2resourceserver/src/test/java/sample/OAuth2ResourceServerControllerTest.java b/samples/boot/oauth2resourceserver/src/test/java/sample/OAuth2ResourceServerControllerTest.java index 5b09a09f544..7208d433db1 100644 --- a/samples/boot/oauth2resourceserver/src/test/java/sample/OAuth2ResourceServerControllerTest.java +++ b/samples/boot/oauth2resourceserver/src/test/java/sample/OAuth2ResourceServerControllerTest.java @@ -1,17 +1,14 @@ /* * Copyright 2002-2019 the original author or authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * 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 + * 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. + * 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 sample; @@ -27,6 +24,8 @@ import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.security.oauth2.jwt.JwtDecoder; +import org.springframework.security.test.context.support.oauth2.Attribute; +import org.springframework.security.test.context.support.oauth2.TargetType; import org.springframework.security.test.context.support.oauth2.WithMockJwt; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.web.servlet.MockMvc; @@ -44,17 +43,40 @@ public class OAuth2ResourceServerControllerTest { @Autowired MockMvc mockMvc; - //TODO: find a way to remove that (JwtDecoder is useless and a stub one could be registered by test framework) @MockBean JwtDecoder jwtDecoder; @Test - @WithMockJwt() - public void test() throws Exception { + @WithMockJwt(name = "ch4mpy") + public void testIndex() throws Exception { mockMvc.perform(get("/")) - .andDo(print()) - .andExpect(status().isOk()) - .andExpect(content().string(is("Hello, " + WithMockJwt.DEFAULT_AUTH_NAME + "!"))); + .andDo(print()) + .andExpect(status().isOk()) + .andExpect(content().string(is("Hello, ch4mpy!"))); + } + + @Test + @WithMockJwt("SCOPE_message:read") + public void testMessageIsAcciessibleWithCorrectScopeAuthority() throws Exception { + mockMvc.perform(get("/message")) + .andDo(print()) + .andExpect(status().isOk()) + .andExpect(content().string(is("secret message"))); + } + + @Test + @WithMockJwt(claims = @Attribute(name = "scope", value = "message:read", parseTo = TargetType.STRING_SET)) + public void testMessageIsAcciessibleWithCorrectScopeClaim() throws Exception { + mockMvc.perform(get("/message")) + .andDo(print()) + .andExpect(status().isOk()) + .andExpect(content().string(is("secret message"))); + } + + @Test + @WithMockJwt + public void testMessageIsNotAcciessibleWithDefaultAuthority() throws Exception { + mockMvc.perform(get("/message")).andDo(print()).andExpect(status().isForbidden()); } } From 6865e0111c664e45926837059add96cd5294b850 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Wacongne?= Date: Sat, 23 Mar 2019 12:27:43 +0100 Subject: [PATCH 9/9] JWT request post-processor Also * enable scope collection extracting from "scope" claim if single space separated string * expose "scopes" at annotation level --- ...ecurity-oauth2-resource-server-test.gradle | 4 + .../support/oauth2/AuthoritiesAndScopes.java | 137 ------------------ .../WithMockJwtSecurityContextFactory.java | 71 --------- .../oauth2/{ => annotations}/Attribute.java | 19 ++- .../AttributeParsersSupport.java} | 84 +++++------ .../AttributeValueParser.java | 17 ++- .../oauth2/{ => annotations}/TargetType.java | 17 ++- .../oauth2/{ => annotations}/WithMockJwt.java | 65 ++++++--- .../AbstractOAuth2RequestPostProcessor.java | 116 +++++++++++++++ .../request/JwtRequestPostProcessor.java | 76 ++++++++++ .../OAuth2MockMvcRequestPostProcessors.java | 28 ++++ .../oauth2/support/AuthoritiesAndScopes.java | 117 +++++++++++++++ .../CollectionsSupport.java} | 34 +++-- .../support/oauth2/support/JwtSupport.java | 65 +++++++++ .../AttributeParsersSupportTest.java} | 34 ++--- ...ithMockJwtSecurityContextFactoryTests.java | 47 +++--- .../{ => annotations}/WithMockJwtTests.java | 22 ++- .../AuthoritiesAndScopesTest.java | 53 +++++-- .../CollectionsSupportTest.java} | 23 +-- .../OAuth2ResourceServerControllerTest.java | 37 ++++- ...ityContextRequestPostProcessorSupport.java | 126 ++++++++++++++++ .../SecurityMockMvcRequestPostProcessors.java | 96 ------------ 22 files changed, 819 insertions(+), 469 deletions(-) delete mode 100644 oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/AuthoritiesAndScopes.java delete mode 100644 oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/WithMockJwtSecurityContextFactory.java rename oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/{ => annotations}/Attribute.java (86%) rename oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/{AttributeParsersHelper.java => annotations/AttributeParsersSupport.java} (76%) rename oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/{ => annotations}/AttributeValueParser.java (55%) rename oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/{ => annotations}/TargetType.java (51%) rename oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/{ => annotations}/WithMockJwt.java (70%) create mode 100644 oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/request/AbstractOAuth2RequestPostProcessor.java create mode 100644 oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/request/JwtRequestPostProcessor.java create mode 100644 oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/request/OAuth2MockMvcRequestPostProcessors.java create mode 100644 oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/support/AuthoritiesAndScopes.java rename oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/{AnnotationHelper.java => support/CollectionsSupport.java} (51%) create mode 100644 oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/support/JwtSupport.java rename oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/{AttributeParsersHelperTest.java => annotations/AttributeParsersSupportTest.java} (74%) rename oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/{ => annotations}/WithMockJwtSecurityContextFactoryTests.java (81%) rename oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/{ => annotations}/WithMockJwtTests.java (73%) rename oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/{ => support}/AuthoritiesAndScopesTest.java (67%) rename oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/{AnnotationHelperTest.java => support/CollectionsSupportTest.java} (79%) create mode 100644 test/src/main/java/org/springframework/security/test/web/servlet/request/SecurityContextRequestPostProcessorSupport.java diff --git a/oauth2-test/spring-security-oauth2-resource-server-test.gradle b/oauth2-test/spring-security-oauth2-resource-server-test.gradle index 1395227adff..efc3156cd4e 100644 --- a/oauth2-test/spring-security-oauth2-resource-server-test.gradle +++ b/oauth2-test/spring-security-oauth2-resource-server-test.gradle @@ -4,4 +4,8 @@ dependencies { compile project(':spring-security-test') compile project(':spring-security-oauth2-resource-server') compile project(':spring-security-oauth2-jose') + + compile 'org.springframework:spring-test' + + provided 'javax.servlet:javax.servlet-api' } diff --git a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/AuthoritiesAndScopes.java b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/AuthoritiesAndScopes.java deleted file mode 100644 index 9d90e862281..00000000000 --- a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/AuthoritiesAndScopes.java +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. 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 org.springframework.security.test.context.support.oauth2; - -import static org.springframework.security.test.context.support.oauth2.AnnotationHelper.putIfNotEmpty; - -import java.util.Collection; -import java.util.Collections; -import java.util.Map; -import java.util.Optional; -import java.util.Set; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import org.springframework.security.core.authority.SimpleGrantedAuthority; - -/** - * Helps merging {@code authorities} and {@code scope}. - * - * @author Jérôme Wacongne <ch4mp@c4-soft.com> - * @since 5.2.0 - * - */ -final class AuthoritiesAndScopes { - public final Set authorities; - public final Set scopes; - public final Optional scopeAttributeName; - - private AuthoritiesAndScopes( - final Set authorities, - final Set scopes, - final Optional scopeAttributeName) { - this.authorities = Collections.unmodifiableSet(authorities); - this.scopes = Collections.unmodifiableSet(scopes); - this.scopeAttributeName = scopeAttributeName; - } - - /** - *

      - * Merges {@code authorities} and {@code scope}. - *

      - *

      - * Scopes are searched for in attributes with keys "scope", "scp" and "scopes", first entry found being used and - * others ignored. - *

      - * - *
      -	 * @Foo(
      -	 * 		authorities = { "ROLE_R", "SCOPE_s1" },
      -	 * 		scopes = { "s2" },
      -	 * 		attributes = @Attribute(name = "scope", value = "s3", parseTo = TargetType.STRING_SET))
      -	 * static final class Decorated {
      -	 * }
      -	 *
      -	 * @Test
      -	 * public void testScopesAndAuthorities() {
      -	 * 	final Foo annotation = AnnotationUtils.findAnnotation(Decorated.class, Foo.class);
      -	 * 	final Map attributes =
      -	 * 			new HashMap<>(AttributeParsersHelper.withDefaultParsers().parse(annotation.attributes()));
      -	 *
      -	 * 	Set scopeAttributeBeforeGet = (Set) attributes.get("scope");
      -	 * 	assertThat(scopeAttributeAfterGet).hasSize(1);
      -	 * 	assertThat(scopeAttributeAfterGet).contains("s3");
      -	 *
      -	 * 	AuthoritiesAndScopes actual =
      -	 * 			AuthoritiesAndScopes.get(annotation.authorities(), annotation.scopes(), attributes);
      -	 *
      -	 * 	assertThat(actual.scopes).hasSize(3);
      -	 * 	assertThat(actual.scopes).contains("s1"); // from "SCOPE_s1" authority
      -	 * 	assertThat(actual.scopes).contains("s2"); // from "Foo::scopes"
      -	 * 	assertThat(actual.scopes).contains("s3"); // from scope attribute
      -	 *
      -	 * 	assertThat(actual.authorities).hasSize(4);
      -	 * 	assertThat(actual.authorities).contains("ROLE_R"); // from authorities
      -	 * 	assertThat(actual.authorities).contains("SCOPE_s1"); // from authorities
      -	 * 	assertThat(actual.authorities).contains("SCOPE_s2"); // from "Foo::scopes"
      -	 * 	assertThat(actual.authorities).contains("SCOPE_s3"); // from scope attribute
      -	 *
      -	 * 	Set scopeAttributeAfterGet = (Set) attributes.get("scope");
      -	 * 	assertThat(scopeAttributeAfterGet).hasSize(3);
      -	 * 	assertThat(scopeAttributeAfterGet).contains("s1");
      -	 * 	assertThat(scopeAttributeAfterGet).contains("s2");
      -	 * 	assertThat(scopeAttributeAfterGet).contains("s3");
      -	 * }
      -	 * 
      - * - * @param annotatedAuthorities authorities array (probably from an annotation {@code authorities()}) - * @param annotatedScopes scopes array (probably from an annotation {@code scopes()}) - * @param attributes attributes /!\ mutable /!\ map (probably from an annotation {@code attributes()} or - * {@code claims()}) - * @return a structure containing merged granted authorities and scopes - */ - public static AuthoritiesAndScopes get( - final String[] annotatedAuthorities, - final String[] annotatedScopes, - final Map attributes) { - final Optional scopeAttributeName = attributes.keySet() - .stream() - .filter(k -> "scope".equals(k) || "scp".equals(k) || "scopes".equals(k)) - .sorted() - .findFirst(); - - final Stream attributesScopes = - scopeAttributeName.map(attributes::get).map(AuthoritiesAndScopes::stream).orElse(Stream.empty()); - - final Stream authoritiesScopes = - Stream.of(annotatedAuthorities).filter(a -> a.startsWith("SCOPE_")).map(a -> a.substring(6)); - - final Set allScopes = - Stream.concat(Stream.of(annotatedScopes), Stream.concat(authoritiesScopes, attributesScopes)) - .collect(Collectors.toSet()); - - putIfNotEmpty(scopeAttributeName.orElse("scope"), allScopes, attributes); - - final Set allAuthorities = - Stream.concat(Stream.of(annotatedAuthorities), allScopes.stream().map(scope -> "SCOPE_" + scope)) - .map(SimpleGrantedAuthority::new) - .collect(Collectors.toSet()); - - return new AuthoritiesAndScopes(allAuthorities, allScopes, scopeAttributeName); - } - - @SuppressWarnings("unchecked") - private static Stream stream(final Object col) { - return col == null ? Stream.empty() : ((Collection) col).stream(); - } -} \ No newline at end of file diff --git a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/WithMockJwtSecurityContextFactory.java b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/WithMockJwtSecurityContextFactory.java deleted file mode 100644 index a610e93f4ab..00000000000 --- a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/WithMockJwtSecurityContextFactory.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. 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 org.springframework.security.test.context.support.oauth2; - -import static org.springframework.security.test.context.support.oauth2.AnnotationHelper.putIfNotEmpty; - -import java.time.Instant; -import java.util.HashMap; -import java.util.Map; - -import org.springframework.security.core.context.SecurityContext; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.oauth2.jwt.Jwt; -import org.springframework.security.oauth2.jwt.JwtClaimNames; -import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; -import org.springframework.security.test.context.support.WithSecurityContextFactory; - -/** - * Create a {@link org.springframework.security.core.context.SecurityContext SecurityContext} populated with a - * {@link org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken - * JwtAuthenticationToken} containing a {@link org.springframework.security.oauth2.jwt.Jwt JWT} as described by - * {@link org.springframework.security.test.context.support.oauth2.WithMockJwt @WithMockJwt} - * - * @author Jérôme Wacongne <ch4mp@c4-soft.com> - * @since 5.2.0 - * - */ -public final class WithMockJwtSecurityContextFactory implements WithSecurityContextFactory { - public static final String DEFAULT_TOKEN_VALUE = "test.jwt.value"; - - @Override - public SecurityContext createSecurityContext(final WithMockJwt annotation) { - final AttributeParsersHelper parsersHelper = - AttributeParsersHelper.withDefaultParsers(annotation.additionalParsers()); - - final Map headers = parsersHelper.parse(annotation.headers()); - - final Map claims = new HashMap<>(parsersHelper.parse(annotation.claims())); - if (claims.containsKey(JwtClaimNames.SUB)) { - throw new RuntimeException(JwtClaimNames.SUB + " claim is not configurable (forced to @WithMockJwt.name)"); - } else { - putIfNotEmpty(JwtClaimNames.SUB, annotation.name(), claims); - } - - final AuthoritiesAndScopes authoritiesAndScopes = - AuthoritiesAndScopes.get(annotation.authorities(), new String[] {}, claims); - - final SecurityContext context = SecurityContextHolder.createEmptyContext(); - context.setAuthentication( - new JwtAuthenticationToken( - new Jwt( - DEFAULT_TOKEN_VALUE, - (Instant) claims.get(JwtClaimNames.IAT), - (Instant) claims.get(JwtClaimNames.EXP), - headers, - claims), - authoritiesAndScopes.authorities)); - - return context; - } -} \ No newline at end of file diff --git a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/Attribute.java b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/annotations/Attribute.java similarity index 86% rename from oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/Attribute.java rename to oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/annotations/Attribute.java index 17a55311609..cb529fa4dd4 100644 --- a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/Attribute.java +++ b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/annotations/Attribute.java @@ -1,16 +1,19 @@ /* * Copyright 2002-2019 the original author or authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at + * 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 + * 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. + * 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.test.context.support.oauth2; +package org.springframework.security.test.context.support.oauth2.annotations; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; @@ -41,7 +44,7 @@ * @Attribute(name = "truc", value = "bidule", parserOverride = "your.fancy.ParserImpl")}) * * - * This will create + * This would create *
        *
      • an {@code audience} claim with a value being a {@code List} with two entries
      • *
      • an {@code issuer} claim with a value being a {@code java.net.URL} instance
      • diff --git a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/AttributeParsersHelper.java b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/annotations/AttributeParsersSupport.java similarity index 76% rename from oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/AttributeParsersHelper.java rename to oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/annotations/AttributeParsersSupport.java index 2ee10870dd4..f4d13286ad4 100644 --- a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/AttributeParsersHelper.java +++ b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/annotations/AttributeParsersSupport.java @@ -1,18 +1,23 @@ /* * Copyright 2002-2019 the original author or authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at + * 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 + * 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. + * 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.test.context.support.oauth2; +package org.springframework.security.test.context.support.oauth2.annotations; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; -import java.lang.reflect.InvocationTargetException; import java.net.MalformedURLException; import java.net.URL; import java.time.Instant; @@ -31,33 +36,27 @@ import org.springframework.util.StringUtils; /** - * Helps turn a {@link org.springframework.security.test.context.support.oauth2.Attribute @Attribute} array into a + * Helps turn a {@link org.springframework.security.test.context.support.oauth2.annotations.Attribute @Attribute} array into a * {@link java.util.Map Map<String, Object>} * * @author Jérôme Wacongne <ch4mp@c4-soft.com> * @since 5.2.0 * */ -class AttributeParsersHelper { +class AttributeParsersSupport { private final Map> parsers; - private AttributeParsersHelper( + private AttributeParsersSupport( final Map> baseParsers, final String... additionalParserNames) { this.parsers = new HashMap<>(baseParsers); - Stream.of(additionalParserNames).distinct().map(t -> { + Stream.of(additionalParserNames).distinct().map(parserName -> { try { - return Class.forName(t); - } catch (final ClassNotFoundException e) { - throw new RuntimeException(e); - } - }).map(c -> { - try { - return (AttributeValueParser) c.getDeclaredConstructor().newInstance(); - } catch (InstantiationException | IllegalAccessException | IllegalArgumentException - | InvocationTargetException | NoSuchMethodException | SecurityException e) { - throw new RuntimeException("Missing public no-arg constructor on " + c.getName()); + final Class clazz = Class.forName(parserName); + return (AttributeValueParser) clazz.getDeclaredConstructor().newInstance(); + } catch (final Exception e) { + throw new RuntimeException("Missing public no-arg constructor on " + parserName); } }).forEachOrdered(p -> { this.parsers.put(p.getClass().getName(), p); @@ -65,10 +64,9 @@ private AttributeParsersHelper( }); } - private AttributeValueParser getParser(final TargetType targetType, final String parserOverrideClassName) { + private AttributeValueParser getParser(final TargetType targetType, final String parserName) { final Optional> parserOverride = - Optional.ofNullable(StringUtils.isEmpty(parserOverrideClassName) ? null : parserOverrideClassName) - .map(parsers::get); + Optional.ofNullable(StringUtils.isEmpty(parserName) ? null : parserName).map(parsers::get); switch (targetType) { case STRING: @@ -90,7 +88,12 @@ private AttributeValueParser getParser(final TargetType targetType, final Str case URL: return parserOverride.orElse(parsers.get("UrlParser")); default: - assert (!StringUtils.isEmpty(parserOverrideClassName)); + assertFalse( + "parserOverride must benon empty when parseTo = \"" + targetType + "\"", + StringUtils.isEmpty(parserName)); + assertTrue( + "No registered AttributeValueParser implementation for " + parserName, + parserOverride.isPresent()); return parserOverride.get(); } @@ -98,16 +101,13 @@ private AttributeValueParser getParser(final TargetType targetType, final Str private ParsedProperty parse(final Attribute p) { final AttributeValueParser parser = getParser(p.parseTo(), p.parserOverride()); - if (parser == null) { - throw new RuntimeException("No registered AttributeValueParser implementation for " + p.parserOverride()); - } return new ParsedProperty<>(p.name(), parser.parse(p.value())); } /** *

        - * Turns a {@link org.springframework.security.test.context.support.oauth2.Attribute @Attribute} array into a + * Turns a {@link org.springframework.security.test.context.support.oauth2.annotations.Attribute @Attribute} array into a * {@link java.util.Map Map<String, Object>} as required for * {@link org.springframework.security.oauth2.jwt.Jwt JWT} headers and claims. *

        @@ -115,11 +115,11 @@ private ParsedProperty parse(final Attribute p) { * Process highlights: *

        *
          - *
        • each {@link org.springframework.security.test.context.support.oauth2.Attribute#value() value()} is parsed - * according to {@link org.springframework.security.test.context.support.oauth2.Attribute#parserOverride() + *
        • each {@link org.springframework.security.test.context.support.oauth2.annotations.Attribute#value() value()} is parsed + * according to {@link org.springframework.security.test.context.support.oauth2.annotations.Attribute#parserOverride() * parser()}
        • *
        • obtained values are associated with - * {@link org.springframework.security.test.context.support.oauth2.Attribute#name() name()}
        • + * {@link org.springframework.security.test.context.support.oauth2.annotations.Attribute#name() name()} *
        • values with same name are accumulated in the same collection
        • *
        * @@ -154,16 +154,16 @@ public Map parse(final Attribute... properties) { } /** - * Instantiates default {@link org.springframework.security.test.context.support.oauth2.AttributeValueParser + * Instantiates default {@link org.springframework.security.test.context.support.oauth2.annotations.AttributeValueParser * AttributeValueParser}s plus all provided ones (using default constructor) * - * @param additionalParserNames {@link org.springframework.security.test.context.support.oauth2.AttributeValueParser + * @param additionalParserNames {@link org.springframework.security.test.context.support.oauth2.annotations.AttributeValueParser * AttributeValueParser} implementations class names to add to - * {@link org.springframework.security.test.context.support.oauth2.AttributeParsersHelper#DEFAULT_PARSERS default + * {@link org.springframework.security.test.context.support.oauth2.annotations.AttributeParsersSupport#DEFAULT_PARSERS default * ones} * @return helper instance with provided parsers plus default ones */ - public static AttributeParsersHelper withDefaultParsers(final String... additionalParserNames) { + public static AttributeParsersSupport withDefaultParsers(final String... additionalParserNames) { final Map> baseParsers = new HashMap<>(9); baseParsers.put("NoOpParser", (final String value) -> value); @@ -194,19 +194,19 @@ public static AttributeParsersHelper withDefaultParsers(final String... addition } }); - return new AttributeParsersHelper(baseParsers, additionalParserNames); + return new AttributeParsersSupport(baseParsers, additionalParserNames); } /** - * Instantiates all provided {@link org.springframework.security.test.context.support.oauth2.AttributeValueParser + * Instantiates all provided {@link org.springframework.security.test.context.support.oauth2.annotations.AttributeValueParser * AttributeValueParser}s using default constructor * - * @param allParserNames {@link org.springframework.security.test.context.support.oauth2.AttributeValueParser + * @param allParserNames {@link org.springframework.security.test.context.support.oauth2.annotations.AttributeValueParser * AttributeValueParser} implementations class names * @return helper instance with provided parsers only */ - public static AttributeParsersHelper withoutDefaultParsers(final String... allParserNames) { - return new AttributeParsersHelper(Collections.emptyMap(), allParserNames); + public static AttributeParsersSupport withoutDefaultParsers(final String... allParserNames) { + return new AttributeParsersSupport(Collections.emptyMap(), allParserNames); } private static final class ParsedProperty { diff --git a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/AttributeValueParser.java b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/annotations/AttributeValueParser.java similarity index 55% rename from oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/AttributeValueParser.java rename to oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/annotations/AttributeValueParser.java index 67270a67206..cb38f3de941 100644 --- a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/AttributeValueParser.java +++ b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/annotations/AttributeValueParser.java @@ -1,16 +1,19 @@ /* * Copyright 2002-2019 the original author or authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at + * 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 + * 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. + * 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.test.context.support.oauth2; +package org.springframework.security.test.context.support.oauth2.annotations; /** * diff --git a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/TargetType.java b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/annotations/TargetType.java similarity index 51% rename from oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/TargetType.java rename to oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/annotations/TargetType.java index 4dbaa057775..25f0b557888 100644 --- a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/TargetType.java +++ b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/annotations/TargetType.java @@ -1,16 +1,19 @@ /* * Copyright 2002-2019 the original author or authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at + * 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 + * 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. + * 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.test.context.support.oauth2; +package org.springframework.security.test.context.support.oauth2.annotations; /** * diff --git a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/WithMockJwt.java b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/annotations/WithMockJwt.java similarity index 70% rename from oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/WithMockJwt.java rename to oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/annotations/WithMockJwt.java index 940a5008800..87779839089 100644 --- a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/WithMockJwt.java +++ b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/annotations/WithMockJwt.java @@ -1,16 +1,21 @@ /* * Copyright 2002-2019 the original author or authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at + * 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 + * 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. + * 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.test.context.support.oauth2; +package org.springframework.security.test.context.support.oauth2.annotations; + +import static org.springframework.security.test.context.support.oauth2.support.CollectionsSupport.asSet; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; @@ -23,11 +28,15 @@ import org.springframework.security.core.Authentication; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; import org.springframework.security.test.context.support.TestExecutionEvent; import org.springframework.security.test.context.support.WithSecurityContext; +import org.springframework.security.test.context.support.WithSecurityContextFactory; import org.springframework.security.test.context.support.WithSecurityContextTestExecutionListener; +import org.springframework.security.test.context.support.oauth2.annotations.WithMockJwt.WithMockJwtSecurityContextFactory; +import org.springframework.security.test.context.support.oauth2.support.JwtSupport; import org.springframework.test.context.TestContext; import org.springframework.test.web.servlet.MockMvc; @@ -55,13 +64,7 @@ *
      • {@link Authentication#getName() getName()} returns the JWT {@code subject} claim, set from this annotation * {@code name} value ({@code "user"} by default)
      • *
      • {@link Authentication#getAuthorities() authorities} will be a collection of {@link SimpleGrantedAuthority} as - * defined by this annotation {@link #authorities()} ( - * - *
        - * { "ROLE_USER" }
        - * 
        - * - * by default
      • + * defined by this annotation {@link #authorities()} ({@code "ROLE_USER" } by default) * * * Sample Usage: @@ -104,16 +107,13 @@ @Documented @WithSecurityContext(factory = WithMockJwtSecurityContextFactory.class) public @interface WithMockJwt { - public static final String DEFAULT_AUTH_NAME = "user"; - public static final String DEFAULT_HEADER_NAME = "alg"; - public static final String DEFAULT_HEADER_VALUE = "test-algorythm"; /** * Alias for authorities * @return Authorities the client is to be granted */ @AliasFor("authorities") - String[] value() default { "ROLE_USER" }; + String[] value() default { "ROLE_USER" };// TODO: use default value in JwtSupport /** * Alias for value @@ -122,11 +122,16 @@ @AliasFor("value") String[] authorities() default { "ROLE_USER" }; + /** + * @return Scopes the client is to be granted (added to "scope" claim, and authorities with "SCOPE_" prefix) + */ + String[] scopes() default {}; + /** * To be used both as authentication {@code Principal} name and token {@code username} attribute. * @return Resource owner name */ - String name() default DEFAULT_AUTH_NAME; + String name() default JwtSupport.DEFAULT_AUTH_NAME; /** * @return JWT claims @@ -137,7 +142,8 @@ * Of little use at unit test time... * @return JWT headers */ - Attribute[] headers() default { @Attribute(name = DEFAULT_HEADER_NAME, value = DEFAULT_HEADER_VALUE) }; + Attribute[] headers() default { + @Attribute(name = JwtSupport.DEFAULT_HEADER_NAME, value = JwtSupport.DEFAULT_HEADER_VALUE) }; /** * Parsers are provided for for all {@link TargetType} but {@link TargetType#OTHER}. Those will be added to parse @@ -157,4 +163,23 @@ */ @AliasFor(annotation = WithSecurityContext.class) TestExecutionEvent setupBefore() default TestExecutionEvent.TEST_METHOD; + + public final class WithMockJwtSecurityContextFactory implements WithSecurityContextFactory { + @Override + public SecurityContext createSecurityContext(final WithMockJwt annotation) { + final AttributeParsersSupport parsersHelper = + AttributeParsersSupport.withDefaultParsers(annotation.additionalParsers()); + + final SecurityContext context = SecurityContextHolder.createEmptyContext(); + context.setAuthentication( + JwtSupport.authentication( + annotation.name(), + asSet(annotation.authorities()), + asSet(annotation.scopes()), + parsersHelper.parse(annotation.claims()), + parsersHelper.parse(annotation.headers()))); + + return context; + } + } } diff --git a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/request/AbstractOAuth2RequestPostProcessor.java b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/request/AbstractOAuth2RequestPostProcessor.java new file mode 100644 index 00000000000..c08de5ca49f --- /dev/null +++ b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/request/AbstractOAuth2RequestPostProcessor.java @@ -0,0 +1,116 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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.test.context.support.oauth2.request; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.springframework.security.test.context.support.oauth2.support.CollectionsSupport.asSet; + +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.stream.Stream; + +import org.springframework.test.web.servlet.request.RequestPostProcessor; + +/** + * @author Jérôme Wacongne <ch4mp@c4-soft.com> + * @since 5.2.0 + */ +public abstract class AbstractOAuth2RequestPostProcessor> + implements + RequestPostProcessor { + + private static final String ROLE_PREFIX = "ROLE_"; + + private static final String SCOPE_PREFIX = "SCOPE_"; + + protected String name; + + protected final Collection authorities; + + protected boolean isAuthoritiesSet = false; + + protected final Collection scopes = new HashSet<>(); + + protected final Map attributes = new HashMap<>(); + + protected AbstractOAuth2RequestPostProcessor(final String defaultName, final String[] defaultAuthorities) { + this.name = defaultName; + this.authorities = new HashSet<>(asSet(defaultAuthorities)); + } + + public T name(final String name) { + this.name = name; + return downCast(); + } + + public T authority(final String authority) { + assertNotNull(authority); + this.isAuthoritiesSet = true; + this.authorities.add(authority); + if (authority.startsWith(SCOPE_PREFIX)) { + this.scopes.add(authority.substring(SCOPE_PREFIX.length())); + } + return downCast(); + } + + public T authorities(final String... authorities) { + Stream.of(authorities).forEach(this::authority); + return downCast(); + } + + public T role(final String role) { + assertNotNull(role); + assertFalse(role.startsWith(ROLE_PREFIX)); + return authority(ROLE_PREFIX + role); + } + + public T roles(final String... roles) { + Stream.of(roles).forEach(this::role); + return downCast(); + } + + public T scope(final String role) { + assertNotNull(role); + assertFalse(role.startsWith(SCOPE_PREFIX)); + return authority(SCOPE_PREFIX + role); + } + + public T scopes(final String... scope) { + Stream.of(scope).forEach(this::role); + return downCast(); + } + + public T attributes(final Map attributes) { + assertNotNull(attributes); + attributes.entrySet().stream().forEach(e -> this.attribute(e.getKey(), e.getValue())); + return downCast(); + } + + public T attribute(final String name, final Object value) { + assertNotNull(name); + this.attributes.put(name, value); + return downCast(); + } + + @SuppressWarnings("unchecked") + private T downCast() { + return (T) this; + } + +} diff --git a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/request/JwtRequestPostProcessor.java b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/request/JwtRequestPostProcessor.java new file mode 100644 index 00000000000..a828b127b1d --- /dev/null +++ b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/request/JwtRequestPostProcessor.java @@ -0,0 +1,76 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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.test.context.support.oauth2.request; + +import static org.junit.Assert.assertNotNull; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.security.core.Authentication; +import org.springframework.security.test.context.support.oauth2.support.JwtSupport; +import org.springframework.security.test.web.servlet.request.SecurityContextRequestPostProcessorSupport; + +/** + * @author Jérôme Wacongne <ch4mp@c4-soft.com> + * @since 5.2.0 + */ +public class JwtRequestPostProcessor extends AbstractOAuth2RequestPostProcessor { + + private static Map DEFAULT_HEADERS = + Collections.singletonMap(JwtSupport.DEFAULT_HEADER_NAME, JwtSupport.DEFAULT_HEADER_VALUE); + + private Map headers = DEFAULT_HEADERS; + + private boolean isHeadersSet = false; + + public JwtRequestPostProcessor() { + super(JwtSupport.DEFAULT_AUTH_NAME, JwtSupport.DEFAULT_AUTHORITIES); + } + + @Override + public MockHttpServletRequest postProcessRequest(final MockHttpServletRequest request) { + final Authentication authentication = JwtSupport.authentication(name, authorities, scopes, attributes, headers); + SecurityContextRequestPostProcessorSupport.createSecurityContext(authentication, request); + return request; + } + + public JwtRequestPostProcessor claims(final Map claims) { + return attributes(claims); + } + + public JwtRequestPostProcessor claim(final String name, final Object value) { + return attribute(name, value); + } + + public JwtRequestPostProcessor headers(final Map headers) { + headers.entrySet().stream().forEach(e -> this.header(e.getKey(), e.getValue())); + return this; + } + + public JwtRequestPostProcessor header(final String name, final Object value) { + assertNotNull(name); + if (this.isHeadersSet == false) { + this.headers = new HashMap<>(); + this.isHeadersSet = true; + } + this.headers.put(name, value); + return this; + } + +} diff --git a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/request/OAuth2MockMvcRequestPostProcessors.java b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/request/OAuth2MockMvcRequestPostProcessors.java new file mode 100644 index 00000000000..2be77e0cf1e --- /dev/null +++ b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/request/OAuth2MockMvcRequestPostProcessors.java @@ -0,0 +1,28 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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.test.context.support.oauth2.request; + +/** + * @author Jérôme Wacongne <ch4mp@c4-soft.com> + * @since 5.2.0 + */ +public final class OAuth2MockMvcRequestPostProcessors { + + public static JwtRequestPostProcessor jwt() { + return new JwtRequestPostProcessor(); + } + +} diff --git a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/support/AuthoritiesAndScopes.java b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/support/AuthoritiesAndScopes.java new file mode 100644 index 00000000000..94323cbd704 --- /dev/null +++ b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/support/AuthoritiesAndScopes.java @@ -0,0 +1,117 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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.test.context.support.oauth2.support; + +import static org.springframework.security.test.context.support.oauth2.support.CollectionsSupport.putIfNotEmpty; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.springframework.security.core.authority.SimpleGrantedAuthority; + +/** + * Helps merging {@code authorities} and {@code scope}. + * + * @author Jérôme Wacongne <ch4mp@c4-soft.com> + * @since 5.2.0 + * + */ +final class AuthoritiesAndScopes { + public final Set authorities; + public final Set scopes; + public final Optional scopeAttributeName; + + private AuthoritiesAndScopes( + final Set authorities, + final Set scopes, + final Optional scopeAttributeName) { + this.authorities = Collections.unmodifiableSet(authorities); + this.scopes = Collections.unmodifiableSet(scopes); + this.scopeAttributeName = scopeAttributeName; + } + + /** + * Merges {@code authorities} and {@code scope}. Scopes are scanned for in: + *
          + *
        • scopes of course
        • + *
        • authorities ("SCOPE_" prefix)
        • + *
        • claims with keys "scope", "scp" and "scopes", first entry found being used and others ignored
        • + *
        + *

        + * All scopes are merged and set in claims, authorities and allScopes + *

        + * + * @param authorities authorities array (probably from an annotation {@code authorities()}) + * @param scopes scopes array (probably from an annotation {@code scopes()}) + * @param attributes attributes /!\ mutable /!\ map (probably from an annotation {@code attributes()} or + * {@code claims()}) + * @return a structure containing merged granted authorities and scopes + */ + public static AuthoritiesAndScopes get( + final Collection authorities, + final Collection scopes, + final Map attributes) { + + final Optional scopeAttributeName = attributes.keySet() + .stream() + .filter(k -> "scope".equals(k) || "scp".equals(k) || "scopes".equals(k)) + .sorted() + .findFirst(); + + final Optional scopeAttribute = scopeAttributeName.map(attributes::get); + + final boolean scopeIsString = scopeAttribute.map(s -> s instanceof String).orElse(false); + + final Stream attributesScopes = scopeAttribute.map(s -> { + if (scopeIsString) { + return Stream.of(scopeAttribute.get().toString().split(" ")); + } + return AuthoritiesAndScopes.asStringStream(scopeAttribute.get()); + }).orElse(Stream.empty()); + + final Stream authoritiesScopes = + authorities.stream().filter(a -> a.startsWith("SCOPE_")).map(a -> a.substring(6)); + + final Set allScopes = Stream.concat(scopes.stream(), Stream.concat(authoritiesScopes, attributesScopes)) + .collect(Collectors.toSet()); + + if (scopeIsString) { + putIfNotEmpty( + scopeAttributeName.orElse("scope"), + allScopes.stream().collect(Collectors.joining(" ")), + attributes); + } else { + putIfNotEmpty(scopeAttributeName.orElse("scope"), allScopes, attributes); + } + + final Set allAuthorities = + Stream.concat(authorities.stream(), allScopes.stream().map(scope -> "SCOPE_" + scope)) + .map(SimpleGrantedAuthority::new) + .collect(Collectors.toSet()); + + return new AuthoritiesAndScopes(allAuthorities, allScopes, scopeAttributeName); + } + + @SuppressWarnings("unchecked") + private static Stream asStringStream(final Object col) { + return col == null ? Stream.empty() : ((Collection) col).stream(); + } +} \ No newline at end of file diff --git a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/AnnotationHelper.java b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/support/CollectionsSupport.java similarity index 51% rename from oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/AnnotationHelper.java rename to oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/support/CollectionsSupport.java index 6ed83c36730..1d3d4752850 100644 --- a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/AnnotationHelper.java +++ b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/support/CollectionsSupport.java @@ -1,30 +1,36 @@ /* * Copyright 2002-2019 the original author or authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at + * 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 + * 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. + * 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.test.context.support.oauth2; +package org.springframework.security.test.context.support.oauth2.support; import java.util.Collection; +import java.util.Collections; +import java.util.List; import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; import org.springframework.util.StringUtils; /** - * Useful functions to manipulate annotation values - * * @author Jérôme Wacongne <ch4mp@c4-soft.com> * @since 5.2.0 * */ -class AnnotationHelper { +public class CollectionsSupport { public static String nullIfEmpty(final String str) { return StringUtils.isEmpty(str) ? null : str; @@ -46,4 +52,12 @@ public static String nullIfEmpty(final String str) { return map; } + public static final Set asSet(final String... values) { + return values.length == 0 ? Collections.emptySet() : Stream.of(values).collect(Collectors.toSet()); + } + + public static final List asList(final String... values) { + return values.length == 0 ? Collections.emptyList() : Stream.of(values).collect(Collectors.toList()); + } + } diff --git a/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/support/JwtSupport.java b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/support/JwtSupport.java new file mode 100644 index 00000000000..dfc2548fb1e --- /dev/null +++ b/oauth2-test/src/main/java/org/springframework/security/test/context/support/oauth2/support/JwtSupport.java @@ -0,0 +1,65 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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.test.context.support.oauth2.support; + +import static org.springframework.security.test.context.support.oauth2.support.CollectionsSupport.putIfNotEmpty; + +import java.time.Instant; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +import org.springframework.security.oauth2.jwt.Jwt; +import org.springframework.security.oauth2.jwt.JwtClaimNames; +import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; + +/** + * @author Jérôme Wacongne <ch4mp@c4-soft.com> + * @since 5.2.0 + */ +public class JwtSupport { + public static final String DEFAULT_TOKEN_VALUE = "test.jwt.value"; + public static final String DEFAULT_AUTH_NAME = "user"; + public static final String DEFAULT_HEADER_NAME = "test-header"; + public static final String DEFAULT_HEADER_VALUE = "abracadabra"; + public static final String[] DEFAULT_AUTHORITIES = { "ROLE_USER" }; + + public static JwtAuthenticationToken authentication( + final String name, + final Collection authorities, + final Collection scopes, + final Map claims, + final Map headers) { + final Map postrPocessedClaims = new HashMap<>(claims); + if (claims.containsKey(JwtClaimNames.SUB)) { + throw new RuntimeException(JwtClaimNames.SUB + " claim is not configurable (forced to \"name\")"); + } else { + putIfNotEmpty(JwtClaimNames.SUB, name, postrPocessedClaims); + } + + final AuthoritiesAndScopes authoritiesAndScopes = + AuthoritiesAndScopes.get(authorities, scopes, postrPocessedClaims); + + return new JwtAuthenticationToken( + new Jwt( + DEFAULT_TOKEN_VALUE, + (Instant) postrPocessedClaims.get(JwtClaimNames.IAT), + (Instant) postrPocessedClaims.get(JwtClaimNames.EXP), + headers, + postrPocessedClaims), + authoritiesAndScopes.authorities); + } +} diff --git a/oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/AttributeParsersHelperTest.java b/oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/annotations/AttributeParsersSupportTest.java similarity index 74% rename from oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/AttributeParsersHelperTest.java rename to oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/annotations/AttributeParsersSupportTest.java index 675b451e358..126fadafe42 100644 --- a/oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/AttributeParsersHelperTest.java +++ b/oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/annotations/AttributeParsersSupportTest.java @@ -1,16 +1,19 @@ /* * Copyright 2002-2019 the original author or authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at + * 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 + * 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. + * 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.test.context.support.oauth2; +package org.springframework.security.test.context.support.oauth2.annotations; import static org.assertj.core.api.Assertions.assertThat; @@ -22,9 +25,6 @@ import org.junit.Test; import org.springframework.core.annotation.AnnotationUtils; -import org.springframework.security.test.context.support.oauth2.Attribute; -import org.springframework.security.test.context.support.oauth2.AttributeParsersHelper; -import org.springframework.security.test.context.support.oauth2.AttributeValueParser; /** * @@ -32,7 +32,7 @@ * @since 5.2.0 * */ -public class AttributeParsersHelperTest { +public class AttributeParsersSupportTest { @Attribute(name = "a", value = "bidule", parserOverride = "SomeTypeParser") private static final class AProperty { @@ -41,14 +41,14 @@ private static final class AProperty { @Attribute( name = "b", value = "chose", - parserOverride = "org.springframework.security.test.context.support.oauth2.AttributeParsersHelperTest$SomeTypeParser") + parserOverride = "org.springframework.security.test.context.support.oauth2.annotations.AttributeParsersSupportTest$SomeTypeParser") private static final class BProperty { } @Test public void parsePropertiesWithDistinctNames() { - final AttributeParsersHelper helper = AttributeParsersHelper.withoutDefaultParsers( - "org.springframework.security.test.context.support.oauth2.AttributeParsersHelperTest$SomeTypeParser"); + final AttributeParsersSupport helper = AttributeParsersSupport.withoutDefaultParsers( + "org.springframework.security.test.context.support.oauth2.annotations.AttributeParsersSupportTest$SomeTypeParser"); final Attribute propertyAnnotationA = AnnotationUtils.findAnnotation(AProperty.class, Attribute.class); final Attribute propertyAnnotationB = AnnotationUtils.findAnnotation(BProperty.class, Attribute.class); @@ -70,7 +70,7 @@ private static final class DProperty { @SuppressWarnings("unchecked") @Test public void parsePropertiesWithSameNameAccumulatesValues() { - final AttributeParsersHelper helper = AttributeParsersHelper.withDefaultParsers(); + final AttributeParsersSupport helper = AttributeParsersSupport.withDefaultParsers(); final Attribute propertyAnnotationC = AnnotationUtils.findAnnotation(CProperty.class, Attribute.class); final Attribute propertyAnnotationD = AnnotationUtils.findAnnotation(DProperty.class, Attribute.class); @@ -88,8 +88,8 @@ private static final class EProperty { @Test public void parsePropertiesUsesParseroverrides() { - final AttributeParsersHelper helper = AttributeParsersHelper.withDefaultParsers( - "org.springframework.security.test.context.support.oauth2.AttributeParsersHelperTest$InstantParser"); + final AttributeParsersSupport helper = AttributeParsersSupport.withDefaultParsers( + "org.springframework.security.test.context.support.oauth2.annotations.AttributeParsersSupportTest$InstantParser"); final Attribute propertyAnnotation = AnnotationUtils.findAnnotation(EProperty.class, Attribute.class); diff --git a/oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/WithMockJwtSecurityContextFactoryTests.java b/oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/annotations/WithMockJwtSecurityContextFactoryTests.java similarity index 81% rename from oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/WithMockJwtSecurityContextFactoryTests.java rename to oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/annotations/WithMockJwtSecurityContextFactoryTests.java index 18baa697e6a..9bd3f50abde 100644 --- a/oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/WithMockJwtSecurityContextFactoryTests.java +++ b/oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/annotations/WithMockJwtSecurityContextFactoryTests.java @@ -1,16 +1,19 @@ /* * Copyright 2002-2019 the original author or authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at + * 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 + * 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. + * 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.test.context.support.oauth2; +package org.springframework.security.test.context.support.oauth2.annotations; import static org.assertj.core.api.Assertions.assertThat; @@ -26,6 +29,8 @@ import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.security.oauth2.jwt.JwtClaimNames; import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; +import org.springframework.security.test.context.support.oauth2.annotations.WithMockJwt.WithMockJwtSecurityContextFactory; +import org.springframework.security.test.context.support.oauth2.support.JwtSupport; /** * @@ -76,7 +81,7 @@ private static class CustomAdvanced { @Attribute(name = JwtClaimNames.JTI, value = "test ID"), @Attribute(name = "custom-claim", value = "foo") }, additionalParsers = { - "org.springframework.security.test.context.support.oauth2.WithMockJwtSecurityContextFactoryTests$FooParser" }) + "org.springframework.security.test.context.support.oauth2.annotations.WithMockJwtSecurityContextFactoryTests$FooParser" }) private static class CustomFull { } @@ -86,7 +91,7 @@ public void defaults() { final Authentication auth = factory.createSecurityContext(annotation).getAuthentication(); - assertThat(auth.getName()).isEqualTo(WithMockJwt.DEFAULT_AUTH_NAME); + assertThat(auth.getName()).isEqualTo(JwtSupport.DEFAULT_AUTH_NAME); assertThat(auth.getAuthorities()).hasSize(1); assertThat(auth.getAuthorities().contains(new SimpleGrantedAuthority("ROLE_USER"))).isTrue(); assertThat(auth.getPrincipal()).isInstanceOf(Jwt.class); @@ -96,8 +101,8 @@ public void defaults() { assertThat(auth.getCredentials()).isEqualTo(jwt); assertThat(auth.getDetails()).isNull(); - assertThat(jwt.getTokenValue()).isEqualTo(WithMockJwtSecurityContextFactory.DEFAULT_TOKEN_VALUE); - assertThat(jwt.getSubject()).isEqualTo(WithMockJwt.DEFAULT_AUTH_NAME); + assertThat(jwt.getTokenValue()).isEqualTo(JwtSupport.DEFAULT_TOKEN_VALUE); + assertThat(jwt.getSubject()).isEqualTo(JwtSupport.DEFAULT_AUTH_NAME); assertThat(jwt.getAudience()).isNull(); assertThat(jwt.getIssuer()).isNull(); assertThat(jwt.getIssuedAt()).isNull(); @@ -107,7 +112,7 @@ public void defaults() { final Map headers = jwt.getHeaders(); assertThat(headers).hasSize(1); - assertThat(headers.get(WithMockJwt.DEFAULT_HEADER_NAME)).isEqualTo(WithMockJwt.DEFAULT_HEADER_VALUE); + assertThat(headers.get(JwtSupport.DEFAULT_HEADER_NAME)).isEqualTo(JwtSupport.DEFAULT_HEADER_VALUE); } @Test @@ -116,7 +121,7 @@ public void customMini() { final Authentication auth = factory.createSecurityContext(annotation).getAuthentication(); - assertThat(auth.getName()).isEqualTo(WithMockJwt.DEFAULT_AUTH_NAME); + assertThat(auth.getName()).isEqualTo(JwtSupport.DEFAULT_AUTH_NAME); assertThat(auth.getAuthorities().contains(new SimpleGrantedAuthority("ROLE_ADMIN"))).isTrue(); assertThat(auth.getPrincipal()).isInstanceOf(Jwt.class); @@ -125,8 +130,8 @@ public void customMini() { assertThat(auth.getCredentials()).isEqualTo(jwt); assertThat(auth.getDetails()).isNull(); - assertThat(jwt.getTokenValue()).isEqualTo(WithMockJwtSecurityContextFactory.DEFAULT_TOKEN_VALUE); - assertThat(jwt.getSubject()).isEqualTo(WithMockJwt.DEFAULT_AUTH_NAME); + assertThat(jwt.getTokenValue()).isEqualTo(JwtSupport.DEFAULT_TOKEN_VALUE); + assertThat(jwt.getSubject()).isEqualTo(JwtSupport.DEFAULT_AUTH_NAME); assertThat(jwt.getAudience()).isNull(); assertThat(jwt.getIssuer()).isNull(); assertThat(jwt.getIssuedAt()).isNull(); @@ -136,7 +141,7 @@ public void customMini() { final Map headers = jwt.getHeaders(); assertThat(headers).hasSize(1); - assertThat(headers.get(WithMockJwt.DEFAULT_HEADER_NAME)).isEqualTo(WithMockJwt.DEFAULT_HEADER_VALUE); + assertThat(headers.get(JwtSupport.DEFAULT_HEADER_NAME)).isEqualTo(JwtSupport.DEFAULT_HEADER_VALUE); } @Test @@ -160,7 +165,7 @@ public void customFrequent() { assertThat(auth.getCredentials()).isEqualTo(jwt); assertThat(auth.getDetails()).isNull(); - assertThat(jwt.getTokenValue()).isEqualTo(WithMockJwtSecurityContextFactory.DEFAULT_TOKEN_VALUE); + assertThat(jwt.getTokenValue()).isEqualTo(JwtSupport.DEFAULT_TOKEN_VALUE); assertThat(jwt.getSubject()).isEqualTo("Test User"); assertThat(jwt.getAudience()).isNull(); assertThat(jwt.getIssuer()).isNull(); @@ -171,7 +176,7 @@ public void customFrequent() { final Map headers = jwt.getHeaders(); assertThat(headers).hasSize(1); - assertThat(headers.get(WithMockJwt.DEFAULT_HEADER_NAME)).isEqualTo(WithMockJwt.DEFAULT_HEADER_VALUE); + assertThat(headers.get(JwtSupport.DEFAULT_HEADER_NAME)).isEqualTo(JwtSupport.DEFAULT_HEADER_VALUE); } @Test @@ -195,7 +200,7 @@ public void customAdvanced() { assertThat(auth.getCredentials()).isEqualTo(jwt); assertThat(auth.getDetails()).isNull(); - assertThat(jwt.getTokenValue()).isEqualTo(WithMockJwtSecurityContextFactory.DEFAULT_TOKEN_VALUE); + assertThat(jwt.getTokenValue()).isEqualTo(JwtSupport.DEFAULT_TOKEN_VALUE); assertThat(jwt.getSubject()).isEqualTo("Test User"); assertThat(jwt.getAudience()).isNull(); assertThat(jwt.getIssuer()).isNull(); @@ -207,7 +212,7 @@ public void customAdvanced() { final Map headers = jwt.getHeaders(); assertThat(headers).hasSize(1); - assertThat(headers.get(WithMockJwt.DEFAULT_HEADER_NAME)).isEqualTo(WithMockJwt.DEFAULT_HEADER_VALUE); + assertThat(headers.get(JwtSupport.DEFAULT_HEADER_NAME)).isEqualTo(JwtSupport.DEFAULT_HEADER_VALUE); } @Test @@ -241,7 +246,7 @@ public void custom() throws Exception { assertThat(principal.getIssuer()).isEqualTo(new URL("https://test-issuer.org")); assertThat(principal.getSubject()).isEqualTo("truc"); assertThat(principal.getNotBefore()).isNull(); - assertThat(principal.getTokenValue()).isEqualTo(WithMockJwtSecurityContextFactory.DEFAULT_TOKEN_VALUE); + assertThat(principal.getTokenValue()).isEqualTo(JwtSupport.DEFAULT_TOKEN_VALUE); assertThat(principal.getClaims().get("custom-claim")).isEqualTo("foo"); } diff --git a/oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/WithMockJwtTests.java b/oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/annotations/WithMockJwtTests.java similarity index 73% rename from oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/WithMockJwtTests.java rename to oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/annotations/WithMockJwtTests.java index 9397b03dd1d..d8041da2fc8 100644 --- a/oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/WithMockJwtTests.java +++ b/oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/annotations/WithMockJwtTests.java @@ -1,16 +1,19 @@ /* * Copyright 2002-2019 the original author or authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at + * 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 + * 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. + * 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.test.context.support.oauth2; +package org.springframework.security.test.context.support.oauth2.annotations; import static org.assertj.core.api.Assertions.assertThat; @@ -19,6 +22,9 @@ import org.springframework.core.annotation.AnnotationUtils; import org.springframework.security.test.context.support.TestExecutionEvent; import org.springframework.security.test.context.support.WithSecurityContext; +import org.springframework.security.test.context.support.oauth2.annotations.Attribute; +import org.springframework.security.test.context.support.oauth2.annotations.WithMockJwt; +import org.springframework.security.test.context.support.oauth2.support.JwtSupport; /** * @@ -31,7 +37,7 @@ public class WithMockJwtTests { @Test public void defaults() { final WithMockJwt auth = AnnotationUtils.findAnnotation(Annotated.class, WithMockJwt.class); - assertThat(auth.name()).isEqualTo(WithMockJwt.DEFAULT_AUTH_NAME); + assertThat(auth.name()).isEqualTo(JwtSupport.DEFAULT_AUTH_NAME); assertThat(auth.authorities()).hasSize(1); assertThat(auth.authorities()).contains("ROLE_USER"); assertThat(auth.headers()).hasAtLeastOneElementOfType(Attribute.class); diff --git a/oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/AuthoritiesAndScopesTest.java b/oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/support/AuthoritiesAndScopesTest.java similarity index 67% rename from oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/AuthoritiesAndScopesTest.java rename to oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/support/AuthoritiesAndScopesTest.java index 2d10ec9938c..4fcc6920506 100644 --- a/oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/AuthoritiesAndScopesTest.java +++ b/oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/support/AuthoritiesAndScopesTest.java @@ -1,18 +1,22 @@ /* * Copyright 2002-2019 the original author or authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at + * 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 + * 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. + * 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.test.context.support.oauth2; +package org.springframework.security.test.context.support.oauth2.support; import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.security.test.context.support.oauth2.support.CollectionsSupport.asSet; import java.util.Collections; import java.util.HashMap; @@ -31,11 +35,11 @@ public class AuthoritiesAndScopesTest { @Test - public void testScopeAttribute() { + public void testScopeCollectionAttribute() { final Map attributes = new HashMap<>(); attributes.put("scope", Collections.singleton("c")); final AuthoritiesAndScopes actual = - AuthoritiesAndScopes.get(new String[] { "AUTHORITY_A", "SCOPE_a" }, new String[] { "b" }, attributes); + AuthoritiesAndScopes.get(asSet("AUTHORITY_A", "SCOPE_a"), asSet("b"), attributes); assertThat(actual.authorities).hasSize(4); assertThat(actual.authorities).contains(new SimpleGrantedAuthority("AUTHORITY_A")); @@ -51,12 +55,35 @@ public void testScopeAttribute() { assertThat(actual.scopeAttributeName).isEqualTo(Optional.of("scope")); } + @Test + public void testScopeStringAttribute() { + final Map attributes = new HashMap<>(); + attributes.put("scope", "c"); + final AuthoritiesAndScopes actual = + AuthoritiesAndScopes.get(asSet("AUTHORITY_A", "SCOPE_a"), asSet("b"), attributes); + + assertThat(actual.authorities).hasSize(4); + assertThat(actual.authorities).contains(new SimpleGrantedAuthority("AUTHORITY_A")); + assertThat(actual.authorities).contains(new SimpleGrantedAuthority("SCOPE_a")); + assertThat(actual.authorities).contains(new SimpleGrantedAuthority("SCOPE_b")); + assertThat(actual.authorities).contains(new SimpleGrantedAuthority("SCOPE_c")); + + assertThat(actual.scopes).hasSize(3); + assertThat(actual.scopes).contains("a"); + assertThat(actual.scopes).contains("b"); + assertThat(actual.scopes).contains("c"); + + assertThat(attributes.get("scope")).isEqualTo("a b c"); + + assertThat(actual.scopeAttributeName).isEqualTo(Optional.of("scope")); + } + @Test public void testScpAttribute() { final Map attributes = new HashMap<>(); attributes.put("scp", Collections.singleton("c")); final AuthoritiesAndScopes actual = - AuthoritiesAndScopes.get(new String[] { "AUTHORITY_A", "SCOPE_a" }, new String[] { "b" }, attributes); + AuthoritiesAndScopes.get(asSet("AUTHORITY_A", "SCOPE_a"), asSet("b"), attributes); assertThat(actual.authorities).hasSize(4); assertThat(actual.authorities).contains(new SimpleGrantedAuthority("AUTHORITY_A")); @@ -69,6 +96,8 @@ public void testScpAttribute() { assertThat(actual.scopes).contains("b"); assertThat(actual.scopes).contains("c"); + assertThat(attributes.get("scp")).isEqualTo(actual.scopes); + assertThat(actual.scopeAttributeName).isEqualTo(Optional.of("scp")); } @@ -77,7 +106,7 @@ public void testScopesAttribute() { final Map attributes = new HashMap<>(); attributes.put("scopes", Collections.singleton("c")); final AuthoritiesAndScopes actual = - AuthoritiesAndScopes.get(new String[] { "AUTHORITY_A", "SCOPE_a" }, new String[] { "b" }, attributes); + AuthoritiesAndScopes.get(asSet("AUTHORITY_A", "SCOPE_a"), asSet("b"), attributes); assertThat(actual.authorities).hasSize(4); assertThat(actual.authorities).contains(new SimpleGrantedAuthority("AUTHORITY_A")); @@ -100,7 +129,7 @@ public void testAttributeCollision() { attributes.put("scope", Collections.singleton("d")); attributes.put("scp", Collections.singleton("e")); final AuthoritiesAndScopes actual = - AuthoritiesAndScopes.get(new String[] { "AUTHORITY_A", "SCOPE_a" }, new String[] { "b" }, attributes); + AuthoritiesAndScopes.get(asSet("AUTHORITY_A", "SCOPE_a"), asSet("b"), attributes); assertThat(actual.authorities).hasSize(4); assertThat(actual.authorities).contains(new SimpleGrantedAuthority("AUTHORITY_A")); diff --git a/oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/AnnotationHelperTest.java b/oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/support/CollectionsSupportTest.java similarity index 79% rename from oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/AnnotationHelperTest.java rename to oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/support/CollectionsSupportTest.java index 87fc69d305d..b6c03ba5c70 100644 --- a/oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/AnnotationHelperTest.java +++ b/oauth2-test/src/test/java/org/springframework/security/test/context/support/oauth2/support/CollectionsSupportTest.java @@ -1,20 +1,23 @@ /* * Copyright 2002-2019 the original author or authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at + * 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 + * 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. + * 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.test.context.support.oauth2; +package org.springframework.security.test.context.support.oauth2.support; import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.security.test.context.support.oauth2.AnnotationHelper.nullIfEmpty; -import static org.springframework.security.test.context.support.oauth2.AnnotationHelper.putIfNotEmpty; +import static org.springframework.security.test.context.support.oauth2.support.CollectionsSupport.nullIfEmpty; +import static org.springframework.security.test.context.support.oauth2.support.CollectionsSupport.putIfNotEmpty; import java.util.Collections; import java.util.HashMap; @@ -28,7 +31,7 @@ * @since 5.2.0 * */ -public class AnnotationHelperTest { +public class CollectionsSupportTest { @Test public void nullIfEmptyReturnsNullForNullString() { diff --git a/samples/boot/oauth2resourceserver/src/test/java/sample/OAuth2ResourceServerControllerTest.java b/samples/boot/oauth2resourceserver/src/test/java/sample/OAuth2ResourceServerControllerTest.java index 7208d433db1..72bf55e9228 100644 --- a/samples/boot/oauth2resourceserver/src/test/java/sample/OAuth2ResourceServerControllerTest.java +++ b/samples/boot/oauth2resourceserver/src/test/java/sample/OAuth2ResourceServerControllerTest.java @@ -13,20 +13,23 @@ package sample; import static org.hamcrest.CoreMatchers.is; +import static org.springframework.security.test.context.support.oauth2.request.OAuth2MockMvcRequestPostProcessors.jwt; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import java.util.Collections; + import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.security.oauth2.jwt.JwtDecoder; -import org.springframework.security.test.context.support.oauth2.Attribute; -import org.springframework.security.test.context.support.oauth2.TargetType; -import org.springframework.security.test.context.support.oauth2.WithMockJwt; +import org.springframework.security.test.context.support.oauth2.annotations.Attribute; +import org.springframework.security.test.context.support.oauth2.annotations.TargetType; +import org.springframework.security.test.context.support.oauth2.annotations.WithMockJwt; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.web.servlet.MockMvc; @@ -79,4 +82,32 @@ public void testMessageIsNotAcciessibleWithDefaultAuthority() throws Exception { mockMvc.perform(get("/message")).andDo(print()).andExpect(status().isForbidden()); } + @Test + public void testRequestPostProcessor() throws Exception { + // No post-processor => no authorization => unauthorized + mockMvc.perform(get("/message")).andDo(print()).andExpect(status().isUnauthorized()); + + mockMvc.perform(get("/").with(jwt().name("ch4mpy"))) + .andDo(print()) + .andExpect(status().isOk()) + .andExpect(content().string(is("Hello, ch4mpy!"))); + + mockMvc.perform(get("/message").with(jwt().scope("message:read"))) + .andDo(print()) + .andExpect(status().isOk()) + .andExpect(content().string(is("secret message"))); + + mockMvc.perform(get("/message").with(jwt().authority("SCOPE_message:read"))) + .andDo(print()) + .andExpect(status().isOk()) + .andExpect(content().string(is("secret message"))); + + mockMvc.perform(get("/message").with(jwt().claim("scope", Collections.singletonList("message:read")))) + .andDo(print()) + .andExpect(status().isOk()) + .andExpect(content().string(is("secret message"))); + + mockMvc.perform(get("/message").with(jwt().name("ch4mpy"))).andDo(print()).andExpect(status().isForbidden()); + } + } diff --git a/test/src/main/java/org/springframework/security/test/web/servlet/request/SecurityContextRequestPostProcessorSupport.java b/test/src/main/java/org/springframework/security/test/web/servlet/request/SecurityContextRequestPostProcessorSupport.java new file mode 100644 index 00000000000..37c7f483b80 --- /dev/null +++ b/test/src/main/java/org/springframework/security/test/web/servlet/request/SecurityContextRequestPostProcessorSupport.java @@ -0,0 +1,126 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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.test.web.servlet.request; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.test.web.support.WebTestUtils; +import org.springframework.security.web.context.HttpRequestResponseHolder; +import org.springframework.security.web.context.SecurityContextRepository; +import org.springframework.test.web.servlet.request.RequestPostProcessor; + +/** + * Support class for {@link RequestPostProcessor}'s that establish a Spring Security context + * + * @author Rob Winch + * @since 4.0 + */ +public abstract class SecurityContextRequestPostProcessorSupport { + + public static SecurityContext + createSecurityContext(final Authentication authentication, final HttpServletRequest request) { + return save(authentication, request); + } + + /** + * Saves the specified {@link Authentication} into an empty {@link SecurityContext} using the + * {@link SecurityContextRepository}. + * + * @param authentication the {@link Authentication} to save + * @param request the {@link HttpServletRequest} to use + */ + static SecurityContext save(final Authentication authentication, final HttpServletRequest request) { + final SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); + securityContext.setAuthentication(authentication); + save(securityContext, request); + return securityContext; + } + + /** + * Saves the {@link SecurityContext} using the {@link SecurityContextRepository} + * + * @param securityContext the {@link SecurityContext} to save + * @param request the {@link HttpServletRequest} to use + */ + static void save(final SecurityContext securityContext, HttpServletRequest request) { + SecurityContextRepository securityContextRepository = WebTestUtils.getSecurityContextRepository(request); + final boolean isTestRepository = + securityContextRepository instanceof SecurityContextRequestPostProcessorSupport.TestSecurityContextRepository; + if (!isTestRepository) { + securityContextRepository = new TestSecurityContextRepository(securityContextRepository); + WebTestUtils.setSecurityContextRepository(request, securityContextRepository); + } + + HttpServletResponse response = new MockHttpServletResponse(); + + final HttpRequestResponseHolder requestResponseHolder = new HttpRequestResponseHolder(request, response); + securityContextRepository.loadContext(requestResponseHolder); + + request = requestResponseHolder.getRequest(); + response = requestResponseHolder.getResponse(); + + securityContextRepository.saveContext(securityContext, request, response); + } + + /** + * Used to wrap the SecurityContextRepository to provide support for testing in stateless mode + */ + static class TestSecurityContextRepository implements SecurityContextRepository { + private final static String ATTR_NAME = + SecurityContextRequestPostProcessorSupport.TestSecurityContextRepository.class.getName() + .concat(".REPO"); + + private final SecurityContextRepository delegate; + + private TestSecurityContextRepository(final SecurityContextRepository delegate) { + this.delegate = delegate; + } + + @Override + public SecurityContext loadContext(final HttpRequestResponseHolder requestResponseHolder) { + final SecurityContext result = getContext(requestResponseHolder.getRequest()); + // always load from the delegate to ensure the request/response in the + // holder are updated + // remember the SecurityContextRepository is used in many different + // locations + final SecurityContext delegateResult = this.delegate.loadContext(requestResponseHolder); + return result == null ? delegateResult : result; + } + + @Override + public void saveContext( + final SecurityContext context, + final HttpServletRequest request, + final HttpServletResponse response) { + request.setAttribute(ATTR_NAME, context); + this.delegate.saveContext(context, request, response); + } + + @Override + public boolean containsContext(final HttpServletRequest request) { + return getContext(request) != null || this.delegate.containsContext(request); + } + + static SecurityContext getContext(final HttpServletRequest request) { + return (SecurityContext) request.getAttribute(ATTR_NAME); + } + } +} \ No newline at end of file diff --git a/test/src/main/java/org/springframework/security/test/web/servlet/request/SecurityMockMvcRequestPostProcessors.java b/test/src/main/java/org/springframework/security/test/web/servlet/request/SecurityMockMvcRequestPostProcessors.java index ee13abffa4b..75498993cf6 100644 --- a/test/src/main/java/org/springframework/security/test/web/servlet/request/SecurityMockMvcRequestPostProcessors.java +++ b/test/src/main/java/org/springframework/security/test/web/servlet/request/SecurityMockMvcRequestPostProcessors.java @@ -48,9 +48,7 @@ import org.springframework.security.test.context.TestSecurityContextHolder; import org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers; import org.springframework.security.test.web.support.WebTestUtils; -import org.springframework.security.web.context.HttpRequestResponseHolder; import org.springframework.security.web.context.SecurityContextPersistenceFilter; -import org.springframework.security.web.context.SecurityContextRepository; import org.springframework.security.web.csrf.CsrfToken; import org.springframework.security.web.csrf.CsrfTokenRepository; import org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository; @@ -551,100 +549,6 @@ private static String md5Hex(String a2) { } } - /** - * Support class for {@link RequestPostProcessor}'s that establish a Spring Security - * context - */ - private static abstract class SecurityContextRequestPostProcessorSupport { - - /** - * Saves the specified {@link Authentication} into an empty - * {@link SecurityContext} using the {@link SecurityContextRepository}. - * - * @param authentication the {@link Authentication} to save - * @param request the {@link HttpServletRequest} to use - */ - final void save(Authentication authentication, HttpServletRequest request) { - SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); - securityContext.setAuthentication(authentication); - save(securityContext, request); - } - - /** - * Saves the {@link SecurityContext} using the {@link SecurityContextRepository} - * - * @param securityContext the {@link SecurityContext} to save - * @param request the {@link HttpServletRequest} to use - */ - final void save(SecurityContext securityContext, HttpServletRequest request) { - SecurityContextRepository securityContextRepository = WebTestUtils - .getSecurityContextRepository(request); - boolean isTestRepository = securityContextRepository instanceof TestSecurityContextRepository; - if (!isTestRepository) { - securityContextRepository = new TestSecurityContextRepository( - securityContextRepository); - WebTestUtils.setSecurityContextRepository(request, - securityContextRepository); - } - - HttpServletResponse response = new MockHttpServletResponse(); - - HttpRequestResponseHolder requestResponseHolder = new HttpRequestResponseHolder( - request, response); - securityContextRepository.loadContext(requestResponseHolder); - - request = requestResponseHolder.getRequest(); - response = requestResponseHolder.getResponse(); - - securityContextRepository.saveContext(securityContext, request, response); - } - - /** - * Used to wrap the SecurityContextRepository to provide support for testing in - * stateless mode - */ - static class TestSecurityContextRepository implements SecurityContextRepository { - private final static String ATTR_NAME = TestSecurityContextRepository.class - .getName().concat(".REPO"); - - private final SecurityContextRepository delegate; - - private TestSecurityContextRepository(SecurityContextRepository delegate) { - this.delegate = delegate; - } - - @Override - public SecurityContext loadContext( - HttpRequestResponseHolder requestResponseHolder) { - SecurityContext result = getContext(requestResponseHolder.getRequest()); - // always load from the delegate to ensure the request/response in the - // holder are updated - // remember the SecurityContextRepository is used in many different - // locations - SecurityContext delegateResult = this.delegate - .loadContext(requestResponseHolder); - return result == null ? delegateResult : result; - } - - @Override - public void saveContext(SecurityContext context, HttpServletRequest request, - HttpServletResponse response) { - request.setAttribute(ATTR_NAME, context); - this.delegate.saveContext(context, request, response); - } - - @Override - public boolean containsContext(HttpServletRequest request) { - return getContext(request) != null - || this.delegate.containsContext(request); - } - - private static SecurityContext getContext(HttpServletRequest request) { - return (SecurityContext) request.getAttribute(ATTR_NAME); - } - } - } - /** * Associates the {@link SecurityContext} found in * {@link TestSecurityContextHolder#getContext()} with the