-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Parse endpoints from auth token (#78)
* feat: Parse endpoints from auth token * add code comment * pull out endpoint resolution to a helper * update messages, clean up imports * fix formatting
- Loading branch information
1 parent
9ac6ca7
commit 0a3b890
Showing
7 changed files
with
228 additions
and
30 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
48 changes: 48 additions & 0 deletions
48
momento-sdk/src/intTest/java/momento/sdk/AuthTokenParserTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
package momento.sdk; | ||
|
||
import static org.junit.jupiter.api.Assertions.assertEquals; | ||
import static org.junit.jupiter.api.Assertions.assertFalse; | ||
import static org.junit.jupiter.api.Assertions.assertThrows; | ||
|
||
import momento.sdk.exceptions.ClientSdkException; | ||
import org.junit.jupiter.api.Test; | ||
|
||
final class AuthTokenParserTest { | ||
|
||
// These secrets have botched up signature section, so should be okay to have them in source | ||
// control. | ||
private static final String TEST_AUTH_TOKEN_NO_ENDPOINT = | ||
"eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJpbnRlZ3JhdGlvbiJ9.ZOgkTs"; | ||
private static final String TEST_AUTH_TOKEN_ENDPOINT = | ||
"eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJzcXVpcnJlbCIsImNwIjoiY29udHJvbCBwbGFuZSBlbmRwb2ludCIsImMiOiJkYXRhIHBsYW5lIGVuZHBvaW50In0.zsTsEXFawetTCZI"; | ||
|
||
@Test | ||
public void shouldParseAuthTokenWithNoEndpoints() { | ||
AuthTokenParser.Claims claims = AuthTokenParser.parse(TEST_AUTH_TOKEN_NO_ENDPOINT); | ||
assertFalse(claims.cacheEndpoint().isPresent()); | ||
assertFalse(claims.controlEndpoint().isPresent()); | ||
} | ||
|
||
@Test | ||
public void shouldParseAuthTokenWithEndpoints() { | ||
AuthTokenParser.Claims claims = AuthTokenParser.parse(TEST_AUTH_TOKEN_ENDPOINT); | ||
assertEquals("control plane endpoint", claims.controlEndpoint().get()); | ||
assertEquals("data plane endpoint", claims.cacheEndpoint().get()); | ||
} | ||
|
||
@Test | ||
public void throwExceptionWhenAuthTokenEmptyOrNull() { | ||
assertThrows(ClientSdkException.class, () -> AuthTokenParser.parse(null)); | ||
assertThrows(ClientSdkException.class, () -> AuthTokenParser.parse(" ")); | ||
} | ||
|
||
@Test | ||
public void throwExceptionForInvalidClaimsToken() { | ||
assertThrows(ClientSdkException.class, () -> AuthTokenParser.parse("abcd.effh.jdjjdjdj")); | ||
} | ||
|
||
@Test | ||
public void throwExceptionForMalformedToken() { | ||
assertThrows(ClientSdkException.class, () -> AuthTokenParser.parse("abcd")); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
62 changes: 62 additions & 0 deletions
62
momento-sdk/src/main/java/momento/sdk/AuthTokenParser.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
package momento.sdk; | ||
|
||
import io.jsonwebtoken.Header; | ||
import io.jsonwebtoken.Jwt; | ||
import io.jsonwebtoken.Jwts; | ||
import java.util.Optional; | ||
import momento.sdk.exceptions.ClientSdkException; | ||
|
||
final class AuthTokenParser { | ||
|
||
private AuthTokenParser() {} | ||
|
||
public static Claims parse(String authToken) { | ||
try { | ||
ensurePresent(authToken); | ||
Jwt<Header, io.jsonwebtoken.Claims> parsed = | ||
Jwts.parserBuilder().build().parseClaimsJwt(tokenWithOnlyHeaderAndClaims(authToken)); | ||
return new Claims(parsed.getBody()); | ||
} catch (Exception e) { | ||
throw new ClientSdkException("Failed to parse Auth Token", e); | ||
} | ||
} | ||
|
||
private static void ensurePresent(String authToken) { | ||
if (authToken == null || authToken.isEmpty()) { | ||
throw new IllegalArgumentException("Malformed Auth Token."); | ||
} | ||
} | ||
|
||
// https://github.com/jwtk/jjwt/issues/280 | ||
private static String tokenWithOnlyHeaderAndClaims(String authToken) { | ||
String[] splitToken = authToken.split("\\."); | ||
if (splitToken == null || splitToken.length < 2) { | ||
throw new IllegalArgumentException("Malformed Auth Token"); | ||
} | ||
return splitToken[0] + "." + splitToken[1] + "."; | ||
} | ||
|
||
static class Claims { | ||
|
||
private static final String CONTROL_ENDPOINT_CLAIM_NAME = "cp"; | ||
private static final String CACHE_ENDPOINT_CLAIM_NAME = "c"; | ||
|
||
private Optional<String> controlEndpoint; | ||
private Optional<String> cacheEndpoint; | ||
|
||
private Claims(io.jsonwebtoken.Claims claims) { | ||
controlEndpoint = | ||
Optional.ofNullable((String) claims.getOrDefault(CONTROL_ENDPOINT_CLAIM_NAME, null)); | ||
cacheEndpoint = | ||
Optional.ofNullable((String) claims.getOrDefault(CACHE_ENDPOINT_CLAIM_NAME, null)); | ||
} | ||
|
||
public Optional<String> controlEndpoint() { | ||
return controlEndpoint; | ||
} | ||
|
||
public Optional<String> cacheEndpoint() { | ||
return cacheEndpoint; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
75 changes: 75 additions & 0 deletions
75
momento-sdk/src/main/java/momento/sdk/MomentoEndpointsResolver.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
package momento.sdk; | ||
|
||
import java.util.Optional; | ||
import momento.sdk.exceptions.ClientSdkException; | ||
|
||
final class MomentoEndpointsResolver { | ||
|
||
private static final String CONTROL_ENDPOINT_PREFIX = "control."; | ||
private static final String CACHE_ENDPOINT_PREFIX = "cache."; | ||
|
||
public static MomentoEndpoints resolve(String authToken, Optional<String> hostedZone) { | ||
AuthTokenParser.Claims claims = AuthTokenParser.parse(authToken); | ||
String controlEndpoint = getControlEndpoint(claims, hostedZone); | ||
String cacheEndpoint = getCacheEndpoint(claims, hostedZone); | ||
return new MomentoEndpoints(controlEndpoint, cacheEndpoint); | ||
} | ||
|
||
private static String getControlEndpoint( | ||
AuthTokenParser.Claims claims, Optional<String> hostedZone) { | ||
return controlEndpointFromHostedZone(hostedZone) | ||
.orElseGet( | ||
() -> | ||
claims | ||
.controlEndpoint() | ||
.orElseThrow( | ||
() -> | ||
new ClientSdkException( | ||
"Failed to determine control endpoint from the auth token or an override"))); | ||
} | ||
|
||
private static String getCacheEndpoint( | ||
AuthTokenParser.Claims claims, Optional<String> hostedZone) { | ||
return cacheEndpointFromHostedZone(hostedZone) | ||
.orElseGet( | ||
() -> | ||
claims | ||
.cacheEndpoint() | ||
.orElseThrow( | ||
() -> | ||
new ClientSdkException( | ||
"Failed to determine cache endpoint from the auth token or an override"))); | ||
} | ||
|
||
private static Optional<String> controlEndpointFromHostedZone(Optional<String> hostedZone) { | ||
if (hostedZone.isPresent()) { | ||
return Optional.of(CONTROL_ENDPOINT_PREFIX + hostedZone.get()); | ||
} | ||
return Optional.empty(); | ||
} | ||
|
||
private static Optional<String> cacheEndpointFromHostedZone(Optional<String> hostedZone) { | ||
if (hostedZone.isPresent()) { | ||
return Optional.of(CACHE_ENDPOINT_PREFIX + hostedZone.get()); | ||
} | ||
return Optional.empty(); | ||
} | ||
|
||
static class MomentoEndpoints { | ||
private final String controlEndpoint; | ||
private final String cacheEndpoint; | ||
|
||
private MomentoEndpoints(String controlEndpoint, String cacheEndpoint) { | ||
this.cacheEndpoint = cacheEndpoint; | ||
this.controlEndpoint = controlEndpoint; | ||
} | ||
|
||
public String controlEndpoint() { | ||
return this.controlEndpoint; | ||
} | ||
|
||
public String cacheEndpoint() { | ||
return this.cacheEndpoint; | ||
} | ||
} | ||
} |