Skip to content

Commit

Permalink
feat(role): Inject scopes as role in execution context
Browse files Browse the repository at this point in the history
  • Loading branch information
brasseld authored and aelamrani committed May 13, 2019
1 parent 99d0ff6 commit 4508606
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 30 deletions.
4 changes: 2 additions & 2 deletions pom.xml
Expand Up @@ -30,11 +30,11 @@
<parent>
<groupId>io.gravitee</groupId>
<artifactId>gravitee-parent</artifactId>
<version>15</version>
<version>16</version>
</parent>

<properties>
<gravitee-gateway-api.version>1.15.0</gravitee-gateway-api.version>
<gravitee-gateway-api.version>1.16.0-SNAPSHOT</gravitee-gateway-api.version>
<gravitee-policy-api.version>1.5.0</gravitee-policy-api.version>
<gravitee-resource-api.version>1.1.0</gravitee-resource-api.version>
<gravitee-common.version>1.15.0</gravitee-common.version>
Expand Down
37 changes: 27 additions & 10 deletions src/main/java/io/gravitee/policy/oauth2/Oauth2Policy.java
Expand Up @@ -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)
Expand Down Expand Up @@ -132,17 +133,24 @@ Handler<OAuth2Response> 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();
if (user != null && !user.trim().isEmpty()) {
executionContext.setAttribute(ATTR_USER, user);
}

// Extract scopes from introspection response
List<String> 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;
Expand Down Expand Up @@ -196,15 +204,11 @@ private JsonNode readPayload(String oauthPayload) {
}
}

static boolean hasRequiredScopes(JsonNode oauthResponseNode, List<String> requiredScopes, String scopeSeparator,
final boolean modeStrict) {
if (requiredScopes == null || requiredScopes.isEmpty()) {
return true;
}

static List<String> extractScopes(JsonNode oauthResponseNode, String scopeSeparator) {
JsonNode scopesNode = oauthResponseNode.path(OAUTH_PAYLOAD_SCOPE_NODE);

List<String> scopes;

if (scopesNode instanceof ArrayNode) {
Iterator<JsonNode> scopeIterator = scopesNode.elements();
scopes = new ArrayList<>(scopesNode.size());
Expand All @@ -214,10 +218,23 @@ static boolean hasRequiredScopes(JsonNode oauthResponseNode, List<String> requir
scopes = Arrays.asList(scopesNode.asText().split(scopeSeparator));
}

return scopes;
}

static boolean hasRequiredScopes(Collection<String> tokenScopes, List<String> 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);
}
}
}
56 changes: 38 additions & 18 deletions src/test/java/io/gravitee/policy/oauth2/OAuth2PolicyTest.java
Expand Up @@ -31,21 +31,19 @@
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;

import java.io.ByteArrayOutputStream;
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.*;

/**
Expand Down Expand Up @@ -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<String> 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<String> 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<String> 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<String> 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<String> 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<String> 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<String> 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<String> 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<String> 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<String> scopes = Oauth2Policy.extractScopes(jsonNode, DEFAULT_OAUTH_SCOPE_SEPARATOR);
boolean valid = Oauth2Policy.hasRequiredScopes(scopes, Arrays.asList("read", "write"), true);
Assert.assertFalse(valid);
}

Expand Down Expand Up @@ -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<OAuth2Response> handler = policy.handleResponse(mockPolicychain, mockRequest, mockResponse, mockExecutionContext);

String payload = readResource("/io/gravitee/policy/oauth2/oauth2-response03.json");
Expand All @@ -329,13 +338,24 @@ 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<OAuth2Response> handler = policy.handleResponse(mockPolicychain, mockRequest, mockResponse, mockExecutionContext);

String payload = readResource("/io/gravitee/policy/oauth2/oauth2-response04.json");
handler.handle(new OAuth2Response(true, payload));

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<List<String>>() {
@Override
public boolean matches(List<String> scopes) {
return
scopes.get(0).equals("read") &&
scopes.get(1).equals("write") &&
scopes.get(2).equals("admin");
}
}));
verify(mockPolicychain).doNext(mockRequest, mockResponse);
}

Expand Down

0 comments on commit 4508606

Please sign in to comment.