Skip to content

Commit a86db35

Browse files
authored
ENG-9494 expose roles in request context (#17)
expose roles in request context
1 parent eb6b0d1 commit a86db35

File tree

5 files changed

+81
-12
lines changed

5 files changed

+81
-12
lines changed

grpc-context-utils/src/main/java/org/hypertrace/core/grpcutils/context/Jwt.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package org.hypertrace.core.grpcutils.context;
22

3+
import java.util.List;
34
import java.util.Optional;
45

56
interface Jwt {
@@ -10,4 +11,6 @@ interface Jwt {
1011
Optional<String> getPictureUrl();
1112

1213
Optional<String> getEmail();
14+
15+
List<String> getRoles(String rolesClaim);
1316
}

grpc-context-utils/src/main/java/org/hypertrace/core/grpcutils/context/JwtParser.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
import com.auth0.jwt.interfaces.DecodedJWT;
55
import com.google.common.cache.Cache;
66
import com.google.common.cache.CacheBuilder;
7+
8+
import java.util.Collections;
9+
import java.util.List;
710
import java.util.Optional;
811
import java.util.concurrent.ExecutionException;
912
import java.util.concurrent.TimeUnit;
@@ -73,5 +76,14 @@ public Optional<String> getPictureUrl() {
7376
public Optional<String> getEmail() {
7477
return Optional.ofNullable(jwt.getClaim(EMAIL_CLAIM).asString());
7578
}
79+
80+
@Override
81+
public List<String> getRoles(String rolesClaim) {
82+
List<String> roles = jwt.getClaim(rolesClaim).asList(String.class);
83+
if (roles == null || roles.isEmpty()) {
84+
return Collections.emptyList();
85+
}
86+
return roles;
87+
}
7688
}
7789
}

grpc-context-utils/src/main/java/org/hypertrace/core/grpcutils/context/RequestContext.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
package org.hypertrace.core.grpcutils.context;
22

33
import io.grpc.Context;
4+
5+
import java.util.Collections;
46
import java.util.HashMap;
7+
import java.util.List;
58
import java.util.Map;
69
import java.util.Optional;
710
import java.util.concurrent.Callable;
@@ -44,6 +47,10 @@ public Optional<String> getEmail() {
4447
return getJwt().flatMap(Jwt::getEmail);
4548
}
4649

50+
public List<String> getRoles(String rolesClaim) {
51+
return getJwt().map(jwt -> jwt.getRoles(rolesClaim)).orElse(Collections.emptyList());
52+
}
53+
4754
private Optional<Jwt> getJwt() {
4855
return get(RequestContextConstants.AUTHORIZATION_HEADER).flatMap(jwtParser::fromAuthHeader);
4956
}

grpc-context-utils/src/test/java/org/hypertrace/core/grpcutils/context/JwtParserTest.java

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,24 @@
88
import static org.mockito.Mockito.verify;
99
import static org.mockito.Mockito.when;
1010

11+
import java.util.Collections;
12+
import java.util.List;
1113
import java.util.Optional;
14+
15+
import com.google.common.collect.ImmutableList;
1216
import org.junit.jupiter.api.Test;
1317
import org.mockito.ArgumentMatchers;
1418

1519
class JwtParserTest {
16-
private final String testJwt =
17-
"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJPbmxpbmUgSldUIEJ1aWxkZXIiLCJpYXQiOjE2MjEzNjM1OTcsImV4cCI6MTY1Mjg5OTU5NywiYXVkIjoid3d3LmV4YW1wbGUuY29tIiwic3ViIjoianJvY2tldEBleGFtcGxlLmNvbSIsIkdpdmVuTmFtZSI6IkpvaG5ueSIsIlN1cm5hbWUiOiJSb2NrZXQiLCJuYW1lIjoiSm9obm55IFJvY2tldCIsImVtYWlsIjoianJvY2tldEBleGFtcGxlLmNvbSIsInBpY3R1cmUiOiJ3d3cuZXhhbXBsZS5jb20iLCJSb2xlIjpbIk1hbmFnZXIiLCJQcm9qZWN0IEFkbWluaXN0cmF0b3IiXX0.aesOuNIamZkTMR30CBt0J9NMZZt9iLRETa5ayN_EcVs";
20+
private final String testJwt = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJPbmxpbmUgSldUIEJ1aWxkZXIiLCJpYXQiOjE2MjEzNjM1OTcsImV4cCI6MTY1Mjg5OTU5NywiYXVkIjoid3d3LmV4YW1wbGUuY29tIiwic3ViIjoianJvY2tldEBleGFtcGxlLmNvbSIsIkdpdmVuTmFtZSI6IkpvaG5ueSIsIlN1cm5hbWUiOiJSb2NrZXQiLCJuYW1lIjoiSm9obm55IFJvY2tldCIsImVtYWlsIjoianJvY2tldEBleGFtcGxlLmNvbSIsInBpY3R1cmUiOiJ3d3cuZXhhbXBsZS5jb20iLCJyb2xlcyI6WyJzdXBlcl91c2VyIiwidXNlciIsImJpbGxpbmdfYWRtaW4iXX0.lEDjPPCjr-Epv6pNslq-HK9vmxfstp1sY85GstlbU1I";
21+
private final String emptyRolesJwt = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJPbmxpbmUgSldUIEJ1aWxkZXIiLCJpYXQiOjE2MjEzNjM1OTcsImV4cCI6MTY1Mjg5OTU5NywiYXVkIjoid3d3LmV4YW1wbGUuY29tIiwic3ViIjoianJvY2tldEBleGFtcGxlLmNvbSIsIkdpdmVuTmFtZSI6IkpvaG5ueSIsIlN1cm5hbWUiOiJSb2NrZXQiLCJuYW1lIjoiSm9obm55IFJvY2tldCIsImVtYWlsIjoianJvY2tldEBleGFtcGxlLmNvbSIsInBpY3R1cmUiOiJ3d3cuZXhhbXBsZS5jb20iLCJodHRwczovL3RyYWNlYWJsZS5haS9yb2xlcyI6W119.sFUMZNyypj379xy5P4kqTbBXBOR5XvX2nhpKx6YiiwU";
22+
private final String noRolesJwt = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJPbmxpbmUgSldUIEJ1aWxkZXIiLCJpYXQiOjE2MjEzNjM1OTcsImV4cCI6MTY1Mjg5OTU5NywiYXVkIjoid3d3LmV4YW1wbGUuY29tIiwic3ViIjoianJvY2tldEBleGFtcGxlLmNvbSIsIkdpdmVuTmFtZSI6IkpvaG5ueSIsIlN1cm5hbWUiOiJSb2NrZXQiLCJuYW1lIjoiSm9obm55IFJvY2tldCIsImVtYWlsIjoianJvY2tldEBleGFtcGxlLmNvbSIsInBpY3R1cmUiOiJ3d3cuZXhhbXBsZS5jb20ifQ.Ui1Z2RhiVe3tq6uJPgcyjsfDBdeOeINs_gXEHC6cdpU";
1823
private final String testJwtUserId = "[email protected]";
1924
private final String testJwtName = "Johnny Rocket";
2025
private final String testJwtPictureUrl = "www.example.com";
2126
private final String testJwtEmail = "[email protected]";
27+
private final String testRolesClaim = "roles";
28+
private final List<String> testRoles = ImmutableList.of("super_user", "user", "billing_admin");
2229

2330
@Test
2431
void testGoodJwtParse() {
@@ -54,4 +61,25 @@ void testExtractBearerTokenReturnsEmptyOnMalformed() {
5461
assertEquals(Optional.empty(), parser.fromAuthHeader("Bad header"));
5562
verify(parser, times(0)).fromJwt(ArgumentMatchers.any());
5663
}
64+
65+
@Test
66+
void testRolesCanBeParsedFromToken() {
67+
JwtParser parser = new JwtParser();
68+
Optional<Jwt> jwt = parser.fromJwt(testJwt);
69+
assertEquals(Optional.of(testRoles), jwt.map(j -> j.getRoles(testRolesClaim)));
70+
}
71+
72+
@Test
73+
void testRolesAreEmptyIfRolesArrayIsEmptyInJwt() {
74+
JwtParser parser = new JwtParser();
75+
Optional<Jwt> jwt = parser.fromJwt(emptyRolesJwt);
76+
assertEquals(Optional.of(Collections.emptyList()), jwt.map(j -> j.getRoles(testRolesClaim)));
77+
}
78+
79+
@Test
80+
void testRolesAreEmptyIfRolesIfNoRolesClaimInToken() {
81+
JwtParser parser = new JwtParser();
82+
Optional<Jwt> jwt = parser.fromJwt(noRolesJwt);
83+
assertEquals(Optional.of(Collections.emptyList()), jwt.map(j -> j.getRoles(testRolesClaim)));
84+
}
5785
}
Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,40 @@
11
package org.hypertrace.core.grpcutils.context;
22

3+
import java.util.List;
34
import java.util.Map;
45
import java.util.Optional;
5-
import org.junit.jupiter.api.Assertions;
6+
7+
import com.google.common.collect.ImmutableList;
68
import org.junit.jupiter.api.Test;
79

10+
import static org.junit.jupiter.api.Assertions.assertEquals;
11+
812
/** Unit tests for {@link RequestContext} and utility methods in it. */
913
public class RequestContextTest {
1014
private static final String TENANT_ID = "example-tenant-id";
1115
private static final String TEST_AUTH_HEADER = "Bearer sample-auth-header";
1216

1317
@Test
14-
public void testTenantId() {
18+
void testTenantId() {
1519
RequestContext requestContext = new RequestContext();
1620
requestContext.add(RequestContextConstants.TENANT_ID_HEADER_KEY, TENANT_ID);
1721
Optional<String> tenantId = requestContext.getTenantId();
18-
Assertions.assertEquals(Optional.of(TENANT_ID), tenantId);
22+
assertEquals(Optional.of(TENANT_ID), tenantId);
1923

2024
requestContext = new RequestContext();
2125
tenantId = requestContext.getTenantId();
22-
Assertions.assertEquals(Optional.empty(), tenantId);
26+
assertEquals(Optional.empty(), tenantId);
2327
}
2428

2529
@Test
26-
public void testGetRequestHeaders() {
30+
void testGetRequestHeaders() {
2731
RequestContext requestContext = new RequestContext();
2832
requestContext.add(RequestContextConstants.AUTHORIZATION_HEADER, TEST_AUTH_HEADER);
2933
requestContext.add("x-some-tenant-header", "v1");
3034

3135
Map<String, String> requestHeaders = requestContext.getRequestHeaders();
3236

33-
Assertions.assertEquals(
37+
assertEquals(
3438
Map.of(
3539
RequestContextConstants.AUTHORIZATION_HEADER,
3640
TEST_AUTH_HEADER,
@@ -40,12 +44,27 @@ public void testGetRequestHeaders() {
4044
}
4145

4246
@Test
43-
public void testCreateForTenantId() {
47+
void testCreateForTenantId() {
4448
RequestContext requestContext = RequestContext.forTenantId(TENANT_ID);
45-
Assertions.assertEquals(Optional.of(TENANT_ID), requestContext.getTenantId());
46-
Assertions.assertEquals(
49+
assertEquals(Optional.of(TENANT_ID), requestContext.getTenantId());
50+
assertEquals(
4751
Optional.of(TENANT_ID), requestContext.get(RequestContextConstants.TENANT_ID_HEADER_KEY));
48-
Assertions.assertEquals(
52+
assertEquals(
4953
Map.of(RequestContextConstants.TENANT_ID_HEADER_KEY, TENANT_ID), requestContext.getAll());
5054
}
55+
56+
@Test
57+
void testRolesArePropagatedInRequestContext() {
58+
List<String> expectedRoles = ImmutableList.of("super_user", "user", "billing_admin");
59+
String jwt = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJPbmxpbmUgSldUIEJ1aWxkZXIiLCJpYXQiOjE2MjEzNjM1OTcsIm" +
60+
"V4cCI6MTY1Mjg5OTU5NywiYXVkIjoid3d3LmV4YW1wbGUuY29tIiwic3ViIjoianJvY2tldEBleGFtcGxlLmNvbSIsIkdpdmVuTmFtZSI6Ik" +
61+
"pvaG5ueSIsIlN1cm5hbWUiOiJSb2NrZXQiLCJuYW1lIjoiSm9obm55IFJvY2tldCIsImVtYWlsIjoianJvY2tldEBleGFtcGxlLmNvbSIsIn" +
62+
"BpY3R1cmUiOiJ3d3cuZXhhbXBsZS5jb20iLCJyb2xlcyI6WyJzdXBlcl91c2VyIiwidXNlciIsImJpbGxpbmdfYWRtaW4iXX0.lEDjPPCjr-" +
63+
"Epv6pNslq-HK9vmxfstp1sY85GstlbU1I";
64+
65+
RequestContext requestContext = new RequestContext();
66+
requestContext.add("authorization", "Bearer " + jwt);
67+
List<String> actualRoles = requestContext.getRoles("roles");
68+
assertEquals(expectedRoles, actualRoles);
69+
}
5170
}

0 commit comments

Comments
 (0)