From e8dd1325fdccc25b8768404876462a04d44c6a2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89douard=20Hue?= Date: Wed, 10 Jul 2019 21:48:18 +0200 Subject: [PATCH] Fixed misleading OAuth2 error messages Error messages sent by BearerTokenAccessDeniedHandler included information about the scopes of the rejected token instead of the scopes required by the resource. * Removal of token scopes from error_description attribute. * Removal of scope attribute from WWW-Authenticate response header. Fixes gh-7089 --- .../OAuth2ResourceServerConfigurerTests.java | 18 ++- .../BearerTokenAccessDeniedHandler.java | 53 ++----- .../BearerTokenServerAccessDeniedHandler.java | 52 ++----- .../BearerTokenAccessDeniedHandlerTests.java | 135 +----------------- ...erTokenServerAccessDeniedHandlerTests.java | 127 +--------------- 5 files changed, 39 insertions(+), 346 deletions(-) diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurerTests.java index 2bf95f5fa9..ccf8e67897 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurerTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurerTests.java @@ -108,7 +108,6 @@ import org.springframework.test.web.servlet.ResultMatcher; import org.springframework.test.web.servlet.request.RequestPostProcessor; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; -import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; @@ -397,7 +396,7 @@ public class OAuth2ResourceServerConfigurerTests { this.mvc.perform(get("/requires-read-scope") .with(bearerToken(token))) .andExpect(status().isForbidden()) - .andExpect(insufficientScopeHeader("")); + .andExpect(insufficientScopeHeader()); } @Test @@ -411,7 +410,7 @@ public class OAuth2ResourceServerConfigurerTests { this.mvc.perform(get("/requires-read-scope") .with(bearerToken(token))) .andExpect(status().isForbidden()) - .andExpect(insufficientScopeHeader("message:write")); + .andExpect(insufficientScopeHeader()); } @Test @@ -497,7 +496,7 @@ public class OAuth2ResourceServerConfigurerTests { this.mvc.perform(get("/ms-requires-read-scope") .with(bearerToken(token))) .andExpect(status().isForbidden()) - .andExpect(insufficientScopeHeader("")); + .andExpect(insufficientScopeHeader()); } @@ -512,7 +511,7 @@ public class OAuth2ResourceServerConfigurerTests { this.mvc.perform(get("/ms-requires-read-scope") .with(bearerToken(token))) .andExpect(status().isForbidden()) - .andExpect(insufficientScopeHeader("message:write")); + .andExpect(insufficientScopeHeader()); } @Test @@ -526,7 +525,7 @@ public class OAuth2ResourceServerConfigurerTests { this.mvc.perform(get("/ms-deny") .with(bearerToken(token))) .andExpect(status().isForbidden()) - .andExpect(insufficientScopeHeader("message:read")); + .andExpect(insufficientScopeHeader()); } // -- Resource Server should not engage csrf @@ -2230,12 +2229,11 @@ public class OAuth2ResourceServerConfigurerTests { ); } - private static ResultMatcher insufficientScopeHeader(String scope) { + private static ResultMatcher insufficientScopeHeader() { return header().string(HttpHeaders.WWW_AUTHENTICATE, "Bearer " + "error=\"insufficient_scope\"" + - ", error_description=\"The token provided has insufficient scope [" + scope + "] for this request\"" + - ", error_uri=\"https://tools.ietf.org/html/rfc6750#section-3.1\"" + - (StringUtils.hasText(scope) ? ", scope=\"" + scope + "\"" : "")); + ", error_description=\"The request requires higher privileges than provided by the access token.\"" + + ", error_uri=\"https://tools.ietf.org/html/rfc6750#section-3.1\""); } private void mockWebServer(String response) { diff --git a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/web/access/BearerTokenAccessDeniedHandler.java b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/web/access/BearerTokenAccessDeniedHandler.java index ff59f4bf22..620aa64027 100644 --- a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/web/access/BearerTokenAccessDeniedHandler.java +++ b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/web/access/BearerTokenAccessDeniedHandler.java @@ -16,16 +16,6 @@ package org.springframework.security.oauth2.server.resource.web.access; -import java.io.IOException; -import java.util.Arrays; -import java.util.Collection; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.stream.Collectors; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.security.access.AccessDeniedException; @@ -33,7 +23,14 @@ import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.server.resource.BearerTokenErrorCodes; import org.springframework.security.oauth2.server.resource.authentication.AbstractOAuth2TokenAuthenticationToken; import org.springframework.security.web.access.AccessDeniedHandler; -import org.springframework.util.StringUtils; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.stream.Collectors; /** * Translates any {@link AccessDeniedException} into an HTTP response in accordance with @@ -48,9 +45,6 @@ import org.springframework.util.StringUtils; */ public final class BearerTokenAccessDeniedHandler implements AccessDeniedHandler { - private static final Collection WELL_KNOWN_SCOPE_ATTRIBUTE_NAMES = - Arrays.asList("scope", "scp"); - private String realmName; /** @@ -75,19 +69,9 @@ public final class BearerTokenAccessDeniedHandler implements AccessDeniedHandler } if (request.getUserPrincipal() instanceof AbstractOAuth2TokenAuthenticationToken) { - AbstractOAuth2TokenAuthenticationToken token = - (AbstractOAuth2TokenAuthenticationToken) request.getUserPrincipal(); - - String scope = getScope(token); - parameters.put("error", BearerTokenErrorCodes.INSUFFICIENT_SCOPE); - parameters.put("error_description", - String.format("The token provided has insufficient scope [%s] for this request", scope)); + parameters.put("error_description", "The request requires higher privileges than provided by the access token."); parameters.put("error_uri", "https://tools.ietf.org/html/rfc6750#section-3.1"); - - if (StringUtils.hasText(scope)) { - parameters.put("scope", scope); - } } String wwwAuthenticate = computeWWWAuthenticateHeaderValue(parameters); @@ -105,25 +89,6 @@ public final class BearerTokenAccessDeniedHandler implements AccessDeniedHandler this.realmName = realmName; } - private static String getScope(AbstractOAuth2TokenAuthenticationToken token) { - - Map attributes = token.getTokenAttributes(); - - for (String attributeName : WELL_KNOWN_SCOPE_ATTRIBUTE_NAMES) { - Object scopes = attributes.get(attributeName); - if (scopes instanceof String) { - return (String) scopes; - } else if (scopes instanceof Collection) { - Collection coll = (Collection) scopes; - return (String) coll.stream() - .map(String::valueOf) - .collect(Collectors.joining(" ")); - } - } - - return ""; - } - private static String computeWWWAuthenticateHeaderValue(Map parameters) { String wwwAuthenticate = "Bearer"; if (!parameters.isEmpty()) { diff --git a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/web/access/server/BearerTokenServerAccessDeniedHandler.java b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/web/access/server/BearerTokenServerAccessDeniedHandler.java index db1cfee0b7..a96a9a311a 100644 --- a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/web/access/server/BearerTokenServerAccessDeniedHandler.java +++ b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/web/access/server/BearerTokenServerAccessDeniedHandler.java @@ -16,14 +16,6 @@ package org.springframework.security.oauth2.server.resource.web.access.server; -import java.util.Arrays; -import java.util.Collection; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.stream.Collectors; - -import reactor.core.publisher.Mono; - import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.security.access.AccessDeniedException; @@ -31,8 +23,14 @@ import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.server.resource.BearerTokenErrorCodes; import org.springframework.security.oauth2.server.resource.authentication.AbstractOAuth2TokenAuthenticationToken; import org.springframework.security.web.server.authorization.ServerAccessDeniedHandler; -import org.springframework.util.StringUtils; import org.springframework.web.server.ServerWebExchange; +import reactor.core.publisher.Mono; + +import java.util.Arrays; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.stream.Collectors; /** * Translates any {@link AccessDeniedException} into an HTTP response in accordance with @@ -63,8 +61,7 @@ public class BearerTokenServerAccessDeniedHandler implements ServerAccessDeniedH return exchange.getPrincipal() .filter(AbstractOAuth2TokenAuthenticationToken.class::isInstance) - .cast(AbstractOAuth2TokenAuthenticationToken.class) - .map(token -> errorMessageParameters(token, parameters)) + .map(token -> errorMessageParameters(parameters)) .switchIfEmpty(Mono.just(parameters)) .flatMap(params -> respond(exchange, params)); } @@ -78,21 +75,11 @@ public class BearerTokenServerAccessDeniedHandler implements ServerAccessDeniedH this.realmName = realmName; } - private static Map errorMessageParameters( - AbstractOAuth2TokenAuthenticationToken token, - Map parameters) { - - String scope = getScope(token); - + private static Map errorMessageParameters(Map parameters) { parameters.put("error", BearerTokenErrorCodes.INSUFFICIENT_SCOPE); - parameters.put("error_description", - String.format("The token provided has insufficient scope [%s] for this request", scope)); + parameters.put("error_description", "The request requires higher privileges than provided by the access token."); parameters.put("error_uri", "https://tools.ietf.org/html/rfc6750#section-3.1"); - if (StringUtils.hasText(scope)) { - parameters.put("scope", scope); - } - return parameters; } @@ -103,25 +90,6 @@ public class BearerTokenServerAccessDeniedHandler implements ServerAccessDeniedH return exchange.getResponse().setComplete(); } - private static String getScope(AbstractOAuth2TokenAuthenticationToken token) { - - Map attributes = token.getTokenAttributes(); - - for (String attributeName : WELL_KNOWN_SCOPE_ATTRIBUTE_NAMES) { - Object scopes = attributes.get(attributeName); - if (scopes instanceof String) { - return (String) scopes; - } else if (scopes instanceof Collection) { - Collection coll = (Collection) scopes; - return (String) coll.stream() - .map(String::valueOf) - .collect(Collectors.joining(" ")); - } - } - - return ""; - } - private static String computeWWWAuthenticateHeaderValue(Map parameters) { String wwwAuthenticate = "Bearer"; if (!parameters.isEmpty()) { diff --git a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/web/access/BearerTokenAccessDeniedHandlerTests.java b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/web/access/BearerTokenAccessDeniedHandlerTests.java index 118267c2d8..6e3f7ba5e9 100644 --- a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/web/access/BearerTokenAccessDeniedHandlerTests.java +++ b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/web/access/BearerTokenAccessDeniedHandlerTests.java @@ -16,14 +16,8 @@ package org.springframework.security.oauth2.server.resource.web.access; -import java.util.Arrays; -import java.util.Collections; -import java.util.Map; - -import org.assertj.core.util.Maps; import org.junit.Before; import org.junit.Test; - import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.security.authentication.TestingAuthenticationToken; @@ -31,6 +25,9 @@ import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.core.AbstractOAuth2Token; import org.springframework.security.oauth2.server.resource.authentication.AbstractOAuth2TokenAuthenticationToken; +import java.util.Collections; +import java.util.Map; + import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatCode; @@ -81,7 +78,7 @@ public class BearerTokenAccessDeniedHandlerTests { } @Test - public void handleWhenTokenHasNoScopesThenInsufficientScopeError() + public void handleWhenOAuth2AuthenticatedThenStatus403AndAuthHeaderWithInsufficientScopeErrorAttribute() throws Exception { MockHttpServletRequest request = new MockHttpServletRequest(); @@ -94,132 +91,10 @@ public class BearerTokenAccessDeniedHandlerTests { assertThat(response.getStatus()).isEqualTo(403); assertThat(response.getHeader("WWW-Authenticate")).isEqualTo("Bearer error=\"insufficient_scope\", " + - "error_description=\"The token provided has insufficient scope [] for this request\", " + + "error_description=\"The request requires higher privileges than provided by the access token.\", " + "error_uri=\"https://tools.ietf.org/html/rfc6750#section-3.1\""); } - - @Test - public void handleWhenTokenHasScopeAttributeThenInsufficientScopeErrorWithScopes() - throws Exception { - - MockHttpServletRequest request = new MockHttpServletRequest(); - MockHttpServletResponse response = new MockHttpServletResponse(); - - Map attributes = Maps.newHashMap("scope", "message:read message:write"); - Authentication token = new TestingOAuth2TokenAuthenticationToken(attributes); - request.setUserPrincipal(token); - - this.accessDeniedHandler.handle(request, response, null); - - assertThat(response.getStatus()).isEqualTo(403); - assertThat(response.getHeader("WWW-Authenticate")).isEqualTo("Bearer error=\"insufficient_scope\", " + - "error_description=\"The token provided has insufficient scope [message:read message:write] for this request\", " + - "error_uri=\"https://tools.ietf.org/html/rfc6750#section-3.1\", " + - "scope=\"message:read message:write\""); - } - - @Test - public void handleWhenTokenHasEmptyScopeAttributeThenInsufficientScopeError() - throws Exception { - - MockHttpServletRequest request = new MockHttpServletRequest(); - MockHttpServletResponse response = new MockHttpServletResponse(); - - Map attributes = Maps.newHashMap("scope", ""); - Authentication token = new TestingOAuth2TokenAuthenticationToken(attributes); - request.setUserPrincipal(token); - - this.accessDeniedHandler.handle(request, response, null); - - assertThat(response.getStatus()).isEqualTo(403); - assertThat(response.getHeader("WWW-Authenticate")).isEqualTo("Bearer error=\"insufficient_scope\", " + - "error_description=\"The token provided has insufficient scope [] for this request\", " + - "error_uri=\"https://tools.ietf.org/html/rfc6750#section-3.1\""); - } - - @Test - public void handleWhenTokenHasScpAttributeThenInsufficientScopeErrorWithScopes() - throws Exception { - - MockHttpServletRequest request = new MockHttpServletRequest(); - MockHttpServletResponse response = new MockHttpServletResponse(); - - Map attributes = Maps.newHashMap("scp", Arrays.asList("message:read", "message:write")); - Authentication token = new TestingOAuth2TokenAuthenticationToken(attributes); - request.setUserPrincipal(token); - - this.accessDeniedHandler.handle(request, response, null); - - assertThat(response.getStatus()).isEqualTo(403); - assertThat(response.getHeader("WWW-Authenticate")).isEqualTo("Bearer error=\"insufficient_scope\", " + - "error_description=\"The token provided has insufficient scope [message:read message:write] for this request\", " + - "error_uri=\"https://tools.ietf.org/html/rfc6750#section-3.1\", " + - "scope=\"message:read message:write\""); - } - - @Test - public void handleWhenTokenHasEmptyScpAttributeThenInsufficientScopeError() - throws Exception { - - MockHttpServletRequest request = new MockHttpServletRequest(); - MockHttpServletResponse response = new MockHttpServletResponse(); - - Map attributes = Maps.newHashMap("scp", Collections.emptyList()); - Authentication token = new TestingOAuth2TokenAuthenticationToken(attributes); - request.setUserPrincipal(token); - - this.accessDeniedHandler.handle(request, response, null); - - assertThat(response.getStatus()).isEqualTo(403); - assertThat(response.getHeader("WWW-Authenticate")).isEqualTo("Bearer error=\"insufficient_scope\", " + - "error_description=\"The token provided has insufficient scope [] for this request\", " + - "error_uri=\"https://tools.ietf.org/html/rfc6750#section-3.1\""); - } - - @Test - public void handleWhenTokenHasBothScopeAndScpAttributesTheInsufficientErrorBasedOnScopeAttribute() - throws Exception { - - MockHttpServletRequest request = new MockHttpServletRequest(); - MockHttpServletResponse response = new MockHttpServletResponse(); - - Map attributes = Maps.newHashMap("scp", Arrays.asList("message:read", "message:write")); - Authentication token = new TestingOAuth2TokenAuthenticationToken(attributes); - request.setUserPrincipal(token); - attributes.put("scope", "missive:read missive:write"); - - this.accessDeniedHandler.handle(request, response, null); - - assertThat(response.getStatus()).isEqualTo(403); - assertThat(response.getHeader("WWW-Authenticate")).isEqualTo("Bearer error=\"insufficient_scope\", " + - "error_description=\"The token provided has insufficient scope [missive:read missive:write] for this request\", " + - "error_uri=\"https://tools.ietf.org/html/rfc6750#section-3.1\", " + - "scope=\"missive:read missive:write\""); - } - - @Test - public void handleWhenTokenHasScopeAttributeAndRealmIsSetThenInsufficientScopeErrorWithScopesAndRealm() - throws Exception { - - MockHttpServletRequest request = new MockHttpServletRequest(); - MockHttpServletResponse response = new MockHttpServletResponse(); - - Map attributes = Maps.newHashMap("scope", "message:read message:write"); - Authentication token = new TestingOAuth2TokenAuthenticationToken(attributes); - request.setUserPrincipal(token); - - this.accessDeniedHandler.setRealmName("test"); - this.accessDeniedHandler.handle(request, response, null); - - assertThat(response.getStatus()).isEqualTo(403); - assertThat(response.getHeader("WWW-Authenticate")).isEqualTo("Bearer realm=\"test\", " + - "error=\"insufficient_scope\", " + - "error_description=\"The token provided has insufficient scope [message:read message:write] for this request\", " + - "error_uri=\"https://tools.ietf.org/html/rfc6750#section-3.1\", " + - "scope=\"message:read message:write\""); - } - @Test public void setRealmNameWhenNullRealmNameThenNoExceptionThrown() { assertThatCode(() -> this.accessDeniedHandler.setRealmName(null)) diff --git a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/web/access/server/BearerTokenServerAccessDeniedHandlerTests.java b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/web/access/server/BearerTokenServerAccessDeniedHandlerTests.java index 6a048b2c5a..04721e7538 100644 --- a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/web/access/server/BearerTokenServerAccessDeniedHandlerTests.java +++ b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/web/access/server/BearerTokenServerAccessDeniedHandlerTests.java @@ -16,15 +16,8 @@ package org.springframework.security.oauth2.server.resource.web.access.server; -import java.util.Arrays; -import java.util.Collections; -import java.util.Map; - -import org.assertj.core.util.Maps; import org.junit.Before; import org.junit.Test; -import reactor.core.publisher.Mono; - import org.springframework.http.HttpStatus; import org.springframework.mock.http.server.reactive.MockServerHttpResponse; import org.springframework.security.authentication.TestingAuthenticationToken; @@ -32,6 +25,11 @@ import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.core.AbstractOAuth2Token; import org.springframework.security.oauth2.server.resource.authentication.AbstractOAuth2TokenAuthenticationToken; import org.springframework.web.server.ServerWebExchange; +import reactor.core.publisher.Mono; + +import java.util.Arrays; +import java.util.Collections; +import java.util.Map; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatCode; @@ -78,7 +76,7 @@ public class BearerTokenServerAccessDeniedHandlerTests { } @Test - public void handleWhenTokenHasNoScopesThenInsufficientScopeError() { + public void handleWhenOAuth2AuthenticatedThenStatus403AndAuthHeaderWithInsufficientScopeErrorAttribute() { Authentication token = new TestingOAuth2TokenAuthenticationToken(Collections.emptyMap()); ServerWebExchange exchange = mock(ServerWebExchange.class); @@ -90,121 +88,10 @@ public class BearerTokenServerAccessDeniedHandlerTests { assertThat(exchange.getResponse().getStatusCode()).isEqualTo(HttpStatus.FORBIDDEN); assertThat(exchange.getResponse().getHeaders().get("WWW-Authenticate")).isEqualTo( Arrays.asList("Bearer error=\"insufficient_scope\", " + - "error_description=\"The token provided has insufficient scope [] for this request\", " + + "error_description=\"The request requires higher privileges than provided by the access token.\", " + "error_uri=\"https://tools.ietf.org/html/rfc6750#section-3.1\"")); } - - @Test - public void handleWhenTokenHasScopeAttributeThenInsufficientScopeErrorWithScopes() { - Map attributes = Maps.newHashMap("scope", "message:read message:write"); - Authentication token = new TestingOAuth2TokenAuthenticationToken(attributes); - ServerWebExchange exchange = mock(ServerWebExchange.class); - when(exchange.getPrincipal()).thenReturn(Mono.just(token)); - when(exchange.getResponse()).thenReturn(new MockServerHttpResponse()); - - this.accessDeniedHandler.handle(exchange, null).block(); - - assertThat(exchange.getResponse().getStatusCode()).isEqualTo(HttpStatus.FORBIDDEN); - assertThat(exchange.getResponse().getHeaders().get("WWW-Authenticate")).isEqualTo( - Arrays.asList("Bearer error=\"insufficient_scope\", " + - "error_description=\"The token provided has insufficient scope [message:read message:write] for this request\", " + - "error_uri=\"https://tools.ietf.org/html/rfc6750#section-3.1\", " + - "scope=\"message:read message:write\"")); - } - - @Test - public void handleWhenTokenHasEmptyScopeAttributeThenInsufficientScopeError() { - Map attributes = Maps.newHashMap("scope", ""); - Authentication token = new TestingOAuth2TokenAuthenticationToken(attributes); - ServerWebExchange exchange = mock(ServerWebExchange.class); - when(exchange.getPrincipal()).thenReturn(Mono.just(token)); - when(exchange.getResponse()).thenReturn(new MockServerHttpResponse()); - - this.accessDeniedHandler.handle(exchange, null).block(); - - assertThat(exchange.getResponse().getStatusCode()).isEqualTo(HttpStatus.FORBIDDEN); - assertThat(exchange.getResponse().getHeaders().get("WWW-Authenticate")).isEqualTo( - Arrays.asList("Bearer error=\"insufficient_scope\", " + - "error_description=\"The token provided has insufficient scope [] for this request\", " + - "error_uri=\"https://tools.ietf.org/html/rfc6750#section-3.1\"")); - } - - @Test - public void handleWhenTokenHasScpAttributeThenInsufficientScopeErrorWithScopes() { - Map attributes = Maps.newHashMap("scp", Arrays.asList("message:read", "message:write")); - Authentication token = new TestingOAuth2TokenAuthenticationToken(attributes); - ServerWebExchange exchange = mock(ServerWebExchange.class); - when(exchange.getPrincipal()).thenReturn(Mono.just(token)); - when(exchange.getResponse()).thenReturn(new MockServerHttpResponse()); - - this.accessDeniedHandler.handle(exchange, null).block(); - - assertThat(exchange.getResponse().getStatusCode()).isEqualTo(HttpStatus.FORBIDDEN); - assertThat(exchange.getResponse().getHeaders().get("WWW-Authenticate")).isEqualTo( - Arrays.asList("Bearer error=\"insufficient_scope\", " + - "error_description=\"The token provided has insufficient scope [message:read message:write] for this request\", " + - "error_uri=\"https://tools.ietf.org/html/rfc6750#section-3.1\", " + - "scope=\"message:read message:write\"")); - } - - @Test - public void handleWhenTokenHasEmptyScpAttributeThenInsufficientScopeError() { - - Map attributes = Maps.newHashMap("scp", Collections.emptyList()); - Authentication token = new TestingOAuth2TokenAuthenticationToken(attributes); - ServerWebExchange exchange = mock(ServerWebExchange.class); - when(exchange.getPrincipal()).thenReturn(Mono.just(token)); - when(exchange.getResponse()).thenReturn(new MockServerHttpResponse()); - - this.accessDeniedHandler.handle(exchange, null).block(); - - assertThat(exchange.getResponse().getStatusCode()).isEqualTo(HttpStatus.FORBIDDEN); - assertThat(exchange.getResponse().getHeaders().get("WWW-Authenticate")).isEqualTo( - Arrays.asList("Bearer error=\"insufficient_scope\", " + - "error_description=\"The token provided has insufficient scope [] for this request\", " + - "error_uri=\"https://tools.ietf.org/html/rfc6750#section-3.1\"")); - } - - @Test - public void handleWhenTokenHasBothScopeAndScpAttributesTheInsufficientErrorBasedOnScopeAttribute() { - Map attributes = Maps.newHashMap("scp", Arrays.asList("message:read", "message:write")); - attributes.put("scope", "missive:read missive:write"); - Authentication token = new TestingOAuth2TokenAuthenticationToken(attributes); - ServerWebExchange exchange = mock(ServerWebExchange.class); - when(exchange.getPrincipal()).thenReturn(Mono.just(token)); - when(exchange.getResponse()).thenReturn(new MockServerHttpResponse()); - - this.accessDeniedHandler.handle(exchange, null).block(); - - assertThat(exchange.getResponse().getStatusCode()).isEqualTo(HttpStatus.FORBIDDEN); - assertThat(exchange.getResponse().getHeaders().get("WWW-Authenticate")).isEqualTo( - Arrays.asList("Bearer error=\"insufficient_scope\", " + - "error_description=\"The token provided has insufficient scope [missive:read missive:write] for this request\", " + - "error_uri=\"https://tools.ietf.org/html/rfc6750#section-3.1\", " + - "scope=\"missive:read missive:write\"")); - } - - @Test - public void handleWhenTokenHasScopeAttributeAndRealmIsSetThenInsufficientScopeErrorWithScopesAndRealm() { - Map attributes = Maps.newHashMap("scope", "message:read message:write"); - Authentication token = new TestingOAuth2TokenAuthenticationToken(attributes); - ServerWebExchange exchange = mock(ServerWebExchange.class); - when(exchange.getPrincipal()).thenReturn(Mono.just(token)); - when(exchange.getResponse()).thenReturn(new MockServerHttpResponse()); - - this.accessDeniedHandler.setRealmName("test"); - this.accessDeniedHandler.handle(exchange, null).block(); - - assertThat(exchange.getResponse().getStatusCode()).isEqualTo(HttpStatus.FORBIDDEN); - assertThat(exchange.getResponse().getHeaders().get("WWW-Authenticate")) - .isEqualTo(Arrays.asList("Bearer realm=\"test\", " + - "error=\"insufficient_scope\", " + - "error_description=\"The token provided has insufficient scope [message:read message:write] for this request\", " + - "error_uri=\"https://tools.ietf.org/html/rfc6750#section-3.1\", " + - "scope=\"message:read message:write\"")); - } - @Test public void setRealmNameWhenNullRealmNameThenNoExceptionThrown() { assertThatCode(() -> this.accessDeniedHandler.setRealmName(null))