diff --git a/pom.xml b/pom.xml index 069307ba..4e868162 100644 --- a/pom.xml +++ b/pom.xml @@ -30,11 +30,11 @@ io.gravitee gravitee-parent - 15 + 16 - 1.15.0 + 1.16.0-SNAPSHOT 1.5.0 1.1.0 1.15.0 diff --git a/src/main/java/io/gravitee/policy/oauth2/Oauth2Policy.java b/src/main/java/io/gravitee/policy/oauth2/Oauth2Policy.java index f3b5e1dd..2570ab7f 100644 --- a/src/main/java/io/gravitee/policy/oauth2/Oauth2Policy.java +++ b/src/main/java/io/gravitee/policy/oauth2/Oauth2Policy.java @@ -40,6 +40,7 @@ import java.util.*; import static io.gravitee.gateway.api.ExecutionContext.ATTR_USER; +import static io.gravitee.gateway.api.ExecutionContext.ATTR_USER_ROLES; /** * @author David BRASSELY (david.brassely at graviteesource.com) @@ -132,6 +133,7 @@ Handler handleResponse(PolicyChain policyChain, Request request, final OAuth2Resource oauth2 = executionContext.getComponent(ResourceManager.class).getResource( oAuth2PolicyConfiguration.getOauthResource(), OAuth2Resource.class); + // Extract user final String user = oauthResponseNode.path(oauth2.getUserClaim() == null ? OAUTH_PAYLOAD_SUB_NODE : oauth2.getUserClaim()).asText(); @@ -139,10 +141,16 @@ Handler handleResponse(PolicyChain policyChain, Request request, executionContext.setAttribute(ATTR_USER, user); } + // Extract scopes from introspection response + List scopes = extractScopes(oauthResponseNode, oauth2.getScopeSeparator()); + executionContext.setAttribute(ATTR_USER_ROLES, scopes); + // Check required scopes to access the resource if (oAuth2PolicyConfiguration.isCheckRequiredScopes()) { - if (! hasRequiredScopes(oauthResponseNode, oAuth2PolicyConfiguration.getRequiredScopes(), - oauth2.getScopeSeparator(), oAuth2PolicyConfiguration.isModeStrict())) { + if (! hasRequiredScopes( + scopes, + oAuth2PolicyConfiguration.getRequiredScopes(), + oAuth2PolicyConfiguration.isModeStrict())) { sendError(response, policyChain, "insufficient_scope", "The request requires higher privileges than provided by the access token."); return; @@ -196,15 +204,11 @@ private JsonNode readPayload(String oauthPayload) { } } - static boolean hasRequiredScopes(JsonNode oauthResponseNode, List requiredScopes, String scopeSeparator, - final boolean modeStrict) { - if (requiredScopes == null || requiredScopes.isEmpty()) { - return true; - } - + static List extractScopes(JsonNode oauthResponseNode, String scopeSeparator) { JsonNode scopesNode = oauthResponseNode.path(OAUTH_PAYLOAD_SCOPE_NODE); List scopes; + if (scopesNode instanceof ArrayNode) { Iterator scopeIterator = scopesNode.elements(); scopes = new ArrayList<>(scopesNode.size()); @@ -214,10 +218,23 @@ static boolean hasRequiredScopes(JsonNode oauthResponseNode, List requir scopes = Arrays.asList(scopesNode.asText().split(scopeSeparator)); } + return scopes; + } + + static boolean hasRequiredScopes(Collection tokenScopes, List requiredScopes, + final boolean modeStrict) { + if (requiredScopes == null || requiredScopes.isEmpty()) { + return true; + } + + if (tokenScopes == null || tokenScopes.isEmpty()) { + return false; + } + if (modeStrict) { - return scopes.containsAll(requiredScopes) && requiredScopes.containsAll(scopes); + return tokenScopes.containsAll(requiredScopes) && requiredScopes.containsAll(tokenScopes); } else { - return scopes.stream().anyMatch(requiredScopes::contains); + return tokenScopes.stream().anyMatch(requiredScopes::contains); } } } diff --git a/src/test/java/io/gravitee/policy/oauth2/OAuth2PolicyTest.java b/src/test/java/io/gravitee/policy/oauth2/OAuth2PolicyTest.java index 2caa1d4e..117cf587 100644 --- a/src/test/java/io/gravitee/policy/oauth2/OAuth2PolicyTest.java +++ b/src/test/java/io/gravitee/policy/oauth2/OAuth2PolicyTest.java @@ -31,6 +31,7 @@ import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentMatcher; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; @@ -38,14 +39,11 @@ import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.UUID; - -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.anyString; -import static org.mockito.Matchers.eq; +import java.util.*; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.*; /** @@ -191,70 +189,80 @@ public void shouldCallOAuthResourceAndHandleResult() throws Exception { @Test public void shouldValidScopes_noRequiredScopes() throws IOException { JsonNode jsonNode = readJsonResource("/io/gravitee/policy/oauth2/oauth2-response01.json"); - boolean valid = Oauth2Policy.hasRequiredScopes(jsonNode, null, DEFAULT_OAUTH_SCOPE_SEPARATOR, false); + Collection scopes = Oauth2Policy.extractScopes(jsonNode, DEFAULT_OAUTH_SCOPE_SEPARATOR); + boolean valid = Oauth2Policy.hasRequiredScopes(scopes, null, false); Assert.assertTrue(valid); } @Test public void shouldNotValidScopes_emptyOAuthResponse() throws IOException { JsonNode jsonNode = readJsonResource("/io/gravitee/policy/oauth2/oauth2-response01.json"); - boolean valid = Oauth2Policy.hasRequiredScopes(jsonNode, Collections.singletonList("read"), DEFAULT_OAUTH_SCOPE_SEPARATOR, false); + Collection scopes = Oauth2Policy.extractScopes(jsonNode, DEFAULT_OAUTH_SCOPE_SEPARATOR); + boolean valid = Oauth2Policy.hasRequiredScopes(scopes, Collections.singletonList("read"), false); Assert.assertFalse(valid); } @Test public void shouldValidScopes_emptyOAuthResponse() throws IOException { JsonNode jsonNode = readJsonResource("/io/gravitee/policy/oauth2/oauth2-response02.json"); - boolean valid = Oauth2Policy.hasRequiredScopes(jsonNode, Collections.singletonList("read"), DEFAULT_OAUTH_SCOPE_SEPARATOR, false); + Collection scopes = Oauth2Policy.extractScopes(jsonNode, DEFAULT_OAUTH_SCOPE_SEPARATOR); + boolean valid = Oauth2Policy.hasRequiredScopes(scopes, Collections.singletonList("read"), false); Assert.assertTrue(valid); } @Test public void shouldValidScopes_stringOfScopes() throws IOException { JsonNode jsonNode = readJsonResource("/io/gravitee/policy/oauth2/oauth2-response04.json"); - boolean valid = Oauth2Policy.hasRequiredScopes(jsonNode, Collections.singletonList("read"), DEFAULT_OAUTH_SCOPE_SEPARATOR, false); + Collection scopes = Oauth2Policy.extractScopes(jsonNode, DEFAULT_OAUTH_SCOPE_SEPARATOR); + boolean valid = Oauth2Policy.hasRequiredScopes(scopes, Collections.singletonList("read"), false); Assert.assertTrue(valid); } @Test public void shouldValidScopes_stringOfScopes_customSeparator() throws IOException { JsonNode jsonNode = readJsonResource("/io/gravitee/policy/oauth2/oauth2-response06.json"); - boolean valid = Oauth2Policy.hasRequiredScopes(jsonNode, Collections.singletonList("read"), ",", false); + Collection scopes = Oauth2Policy.extractScopes(jsonNode, ","); + boolean valid = Oauth2Policy.hasRequiredScopes(scopes, Collections.singletonList("read"), false); Assert.assertTrue(valid); } @Test public void shouldValidScopes_arrayOfScope() throws IOException { JsonNode jsonNode = readJsonResource("/io/gravitee/policy/oauth2/oauth2-response05.json"); - boolean valid = Oauth2Policy.hasRequiredScopes(jsonNode, Collections.singletonList("read"), DEFAULT_OAUTH_SCOPE_SEPARATOR, false); + Collection scopes = Oauth2Policy.extractScopes(jsonNode, DEFAULT_OAUTH_SCOPE_SEPARATOR); + boolean valid = Oauth2Policy.hasRequiredScopes(scopes, Collections.singletonList("read"), false); Assert.assertTrue(valid); } @Test public void shouldValidScopes_arrayOfScopes() throws IOException { JsonNode jsonNode = readJsonResource("/io/gravitee/policy/oauth2/oauth2-response07.json"); - boolean valid = Oauth2Policy.hasRequiredScopes(jsonNode, Arrays.asList("read", "write"), DEFAULT_OAUTH_SCOPE_SEPARATOR, false); + Collection scopes = Oauth2Policy.extractScopes(jsonNode, DEFAULT_OAUTH_SCOPE_SEPARATOR); + boolean valid = Oauth2Policy.hasRequiredScopes(scopes, Arrays.asList("read", "write"), false); Assert.assertTrue(valid); } @Test public void shouldValidScopes_arrayOfScopes_strict() throws IOException { JsonNode jsonNode = readJsonResource("/io/gravitee/policy/oauth2/oauth2-response05.json"); - boolean valid = Oauth2Policy.hasRequiredScopes(jsonNode, Arrays.asList("read", "write", "admin"), DEFAULT_OAUTH_SCOPE_SEPARATOR, true); + Collection scopes = Oauth2Policy.extractScopes(jsonNode, DEFAULT_OAUTH_SCOPE_SEPARATOR); + boolean valid = Oauth2Policy.hasRequiredScopes(scopes, Arrays.asList("read", "write", "admin"), true); Assert.assertTrue(valid); } @Test public void shouldInvalidScopes_arrayOfScope_strict() throws IOException { JsonNode jsonNode = readJsonResource("/io/gravitee/policy/oauth2/oauth2-response05.json"); - boolean valid = Oauth2Policy.hasRequiredScopes(jsonNode, Collections.singletonList("read"), DEFAULT_OAUTH_SCOPE_SEPARATOR, true); + Collection scopes = Oauth2Policy.extractScopes(jsonNode, DEFAULT_OAUTH_SCOPE_SEPARATOR); + boolean valid = Oauth2Policy.hasRequiredScopes(scopes, Collections.singletonList("read"), true); Assert.assertFalse(valid); } @Test public void shouldInvalidScopes_arrayOfScopes_strict() throws IOException { JsonNode jsonNode = readJsonResource("/io/gravitee/policy/oauth2/oauth2-response05.json"); - boolean valid = Oauth2Policy.hasRequiredScopes(jsonNode, Arrays.asList("read", "write"), DEFAULT_OAUTH_SCOPE_SEPARATOR, true); + Collection scopes = Oauth2Policy.extractScopes(jsonNode, DEFAULT_OAUTH_SCOPE_SEPARATOR); + boolean valid = Oauth2Policy.hasRequiredScopes(scopes, Arrays.asList("read", "write"), true); Assert.assertFalse(valid); } @@ -312,6 +320,7 @@ public void shouldFail_goodIntrospection_noClientId() throws IOException { when(oAuth2PolicyConfiguration.getOauthResource()).thenReturn("oauth2"); when(mockExecutionContext.getComponent(ResourceManager.class)).thenReturn(resourceManager); when(resourceManager.getResource(oAuth2PolicyConfiguration.getOauthResource(), OAuth2Resource.class)).thenReturn(customOAuth2Resource); + when(customOAuth2Resource.getScopeSeparator()).thenReturn(DEFAULT_OAUTH_SCOPE_SEPARATOR); Handler handler = policy.handleResponse(mockPolicychain, mockRequest, mockResponse, mockExecutionContext); String payload = readResource("/io/gravitee/policy/oauth2/oauth2-response03.json"); @@ -329,6 +338,8 @@ public void shouldValidate_goodIntrospection_withClientId() throws IOException { Oauth2Policy policy = new Oauth2Policy(oAuth2PolicyConfiguration); when(mockExecutionContext.getComponent(ResourceManager.class)).thenReturn(resourceManager); when(resourceManager.getResource(oAuth2PolicyConfiguration.getOauthResource(), OAuth2Resource.class)).thenReturn(customOAuth2Resource); + when(customOAuth2Resource.getScopeSeparator()).thenReturn(DEFAULT_OAUTH_SCOPE_SEPARATOR); + Handler handler = policy.handleResponse(mockPolicychain, mockRequest, mockResponse, mockExecutionContext); String payload = readResource("/io/gravitee/policy/oauth2/oauth2-response04.json"); @@ -336,6 +347,15 @@ public void shouldValidate_goodIntrospection_withClientId() throws IOException { verify(mockExecutionContext).setAttribute(Oauth2Policy.CONTEXT_ATTRIBUTE_CLIENT_ID, "my-client-id"); verify(mockExecutionContext).setAttribute(Oauth2Policy.CONTEXT_ATTRIBUTE_OAUTH_PAYLOAD, payload); + verify(mockExecutionContext).setAttribute(eq(ExecutionContext.ATTR_USER_ROLES), argThat(new ArgumentMatcher>() { + @Override + public boolean matches(List scopes) { + return + scopes.get(0).equals("read") && + scopes.get(1).equals("write") && + scopes.get(2).equals("admin"); + } + })); verify(mockPolicychain).doNext(mockRequest, mockResponse); }