Przeglądaj źródła

Preserve ArrayListFromString Type

Closes gh-15165
Josh Cummings 1 rok temu
rodzic
commit
dd5edeb255

+ 16 - 2
oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/SpringOpaqueTokenIntrospector.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2023 the original author or authors.
+ * Copyright 2002-2024 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.
@@ -183,7 +183,7 @@ public class SpringOpaqueTokenIntrospector implements OpaqueTokenIntrospector {
 		return claims;
 	}
 
-	private OAuth2TokenIntrospectionClaimAccessor convertClaimsSet(Map<String, Object> claims) {
+	private ArrayListFromStringClaimAccessor convertClaimsSet(Map<String, Object> claims) {
 		Map<String, Object> converted = new LinkedHashMap<>(claims);
 		converted.computeIfPresent(OAuth2TokenIntrospectionClaimNames.AUD, (k, v) -> {
 			if (v instanceof String) {
@@ -277,4 +277,18 @@ public class SpringOpaqueTokenIntrospector implements OpaqueTokenIntrospector {
 
 	}
 
+	// gh-15165
+	private interface ArrayListFromStringClaimAccessor extends OAuth2TokenIntrospectionClaimAccessor {
+
+		@Override
+		default List<String> getScopes() {
+			Object value = getClaims().get(OAuth2TokenIntrospectionClaimNames.SCOPE);
+			if (value instanceof ArrayListFromString list) {
+				return list;
+			}
+			return OAuth2TokenIntrospectionClaimAccessor.super.getScopes();
+		}
+
+	}
+
 }

+ 16 - 2
oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/SpringReactiveOpaqueTokenIntrospector.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2023 the original author or authors.
+ * Copyright 2002-2024 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.
@@ -143,7 +143,7 @@ public class SpringReactiveOpaqueTokenIntrospector implements ReactiveOpaqueToke
 			.switchIfEmpty(Mono.error(() -> new BadOpaqueTokenException("Provided token isn't active")));
 	}
 
-	private OAuth2TokenIntrospectionClaimAccessor convertClaimsSet(Map<String, Object> claims) {
+	private ArrayListFromStringClaimAccessor convertClaimsSet(Map<String, Object> claims) {
 		Map<String, Object> converted = new LinkedHashMap<>(claims);
 		converted.computeIfPresent(OAuth2TokenIntrospectionClaimNames.AUD, (k, v) -> {
 			if (v instanceof String) {
@@ -231,4 +231,18 @@ public class SpringReactiveOpaqueTokenIntrospector implements ReactiveOpaqueToke
 
 	}
 
+	// gh-15165
+	private interface ArrayListFromStringClaimAccessor extends OAuth2TokenIntrospectionClaimAccessor {
+
+		@Override
+		default List<String> getScopes() {
+			Object value = getClaims().get(OAuth2TokenIntrospectionClaimNames.SCOPE);
+			if (value instanceof ArrayListFromString list) {
+				return list;
+			}
+			return OAuth2TokenIntrospectionClaimAccessor.super.getScopes();
+		}
+
+	}
+
 }

+ 17 - 1
oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/introspection/SpringOpaqueTokenIntrospectorTests.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2023 the original author or authors.
+ * Copyright 2002-2024 the original author or authors.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -39,6 +39,7 @@ import org.springframework.http.HttpStatus;
 import org.springframework.http.MediaType;
 import org.springframework.http.RequestEntity;
 import org.springframework.http.ResponseEntity;
+import org.springframework.security.core.authority.AuthorityUtils;
 import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal;
 import org.springframework.security.oauth2.core.OAuth2TokenIntrospectionClaimAccessor;
 import org.springframework.security.oauth2.core.OAuth2TokenIntrospectionClaimNames;
@@ -245,6 +246,21 @@ public class SpringOpaqueTokenIntrospectorTests {
 		assertThat(scope).containsExactly("read", "write", "dolphin");
 	}
 
+	// gh-15165
+	@Test
+	public void introspectWhenActiveThenMapsAuthorities() {
+		RestOperations restOperations = mock(RestOperations.class);
+		OpaqueTokenIntrospector introspectionClient = new SpringOpaqueTokenIntrospector(INTROSPECTION_URL,
+				restOperations);
+		given(restOperations.exchange(any(RequestEntity.class), eq(STRING_OBJECT_MAP))).willReturn(ACTIVE);
+		OAuth2AuthenticatedPrincipal principal = introspectionClient.introspect("token");
+		assertThat(principal.getAuthorities()).isNotEmpty();
+		Collection<String> scope = principal.getAttribute("scope");
+		assertThat(scope).containsExactly("read", "write", "dolphin");
+		Collection<String> authorities = AuthorityUtils.authorityListToSet(principal.getAuthorities());
+		assertThat(authorities).containsExactly("SCOPE_read", "SCOPE_write", "SCOPE_dolphin");
+	}
+
 	@Test
 	public void constructorWhenIntrospectionUriIsNullThenIllegalArgumentException() {
 		assertThatIllegalArgumentException()

+ 17 - 1
oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/introspection/SpringReactiveOpaqueTokenIntrospectorTests.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2023 the original author or authors.
+ * Copyright 2002-2024 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.
@@ -20,6 +20,7 @@ import java.io.IOException;
 import java.time.Instant;
 import java.util.Arrays;
 import java.util.Base64;
+import java.util.Collection;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Optional;
@@ -37,6 +38,7 @@ import org.springframework.core.convert.converter.Converter;
 import org.springframework.http.HttpHeaders;
 import org.springframework.http.HttpStatus;
 import org.springframework.http.MediaType;
+import org.springframework.security.core.authority.AuthorityUtils;
 import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal;
 import org.springframework.security.oauth2.core.OAuth2TokenIntrospectionClaimAccessor;
 import org.springframework.security.oauth2.core.OAuth2TokenIntrospectionClaimNames;
@@ -197,6 +199,20 @@ public class SpringReactiveOpaqueTokenIntrospectorTests {
 		// @formatter:on
 	}
 
+	// gh-15165
+	@Test
+	public void introspectWhenActiveThenMapsAuthorities() {
+		WebClient webClient = mockResponse(ACTIVE_RESPONSE);
+		SpringReactiveOpaqueTokenIntrospector introspectionClient = new SpringReactiveOpaqueTokenIntrospector(
+				INTROSPECTION_URL, webClient);
+		OAuth2AuthenticatedPrincipal principal = introspectionClient.introspect("token").block();
+		assertThat(principal.getAuthorities()).isNotEmpty();
+		Collection<String> scope = principal.getAttribute("scope");
+		assertThat(scope).containsExactly("read", "write", "dolphin");
+		Collection<String> authorities = AuthorityUtils.authorityListToSet(principal.getAuthorities());
+		assertThat(authorities).containsExactly("SCOPE_read", "SCOPE_write", "SCOPE_dolphin");
+	}
+
 	@Test
 	public void setAuthenticationConverterWhenConverterIsNullThenExceptionIsThrown() {
 		WebClient web = mock(WebClient.class);