Skip to content

Commit

Permalink
feat: replace authToken with authProvider (#14)
Browse files Browse the repository at this point in the history
Co-authored-by: Pete Gautier <pete@momentohq.com>
  • Loading branch information
pgautier404 and pgautier404 authored Oct 13, 2022
1 parent 9e40217 commit 6852fbb
Show file tree
Hide file tree
Showing 7 changed files with 158 additions and 61 deletions.
27 changes: 27 additions & 0 deletions src/Auth/AuthUtils.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

namespace Momento\Auth;

use Momento\Cache\Errors\InvalidArgumentError;

class AuthUtils
{

private static function throwBadAuthToken() {
throw new InvalidArgumentError('Invalid Momento auth token.');
}

public static function parseAuthToken(string $authToken) : array {
$exploded = explode (".", $authToken);
if (count($exploded) != 3) {
self::throwBadAuthToken();
}
list($header, $payload, $signature) = $exploded;
$token = json_decode(base64_decode($payload), true);
if ($token === null) {
self::throwBadAuthToken();
}
return $token;
}

}
41 changes: 41 additions & 0 deletions src/Auth/EnvMomentoTokenProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php
namespace Momento\Auth;

use Momento\Auth\AuthUtils;
use Momento\Cache\Errors\InvalidArgumentError;
use function \Momento\Utilities\isNullOrEmpty;


class EnvMomentoTokenProvider implements ICredentialProvider
{
private string $authToken;
private string $controlEndpoint;
private string $cacheEndpoint;

public function __construct(string $envVariableName)
{
$authToken = getenv($envVariableName);
if (isNullOrEmpty($authToken)) {
throw new InvalidArgumentError("Environment variable $envVariableName is empty or null.");
}
$payload = AuthUtils::parseAuthToken($authToken);
$this->authToken = $authToken;
$this->controlEndpoint = $payload["cp"];
$this->cacheEndpoint = $payload["c"];
}

public function getAuthToken() : string
{
return $this->authToken;
}

public function getCacheEndpoint() : string
{
return $this->cacheEndpoint;
}

public function getControlEndpoint() : string
{
return $this->controlEndpoint;
}
}
9 changes: 9 additions & 0 deletions src/Auth/ICredentialProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php
namespace Momento\Auth;

interface ICredentialProvider
{
public function getAuthToken() : string;
public function getControlEndpoint() : string;
public function getCacheEndpoint() : string;
}
35 changes: 11 additions & 24 deletions src/Cache/SimpleCacheClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,43 +2,30 @@

namespace Momento\Cache;

use Momento\Cache\Errors\InvalidArgumentError;
use Momento\Auth\ICredentialProvider;

class SimpleCacheClient
{

private _ScsControlClient $controlClient;
private _ScsDataClient $dataClient;
/**
* @param string $authToken: momento JWT
* @param ICredentialProvider $authProvider: Momento credential provider
* @param int $defaultTtlSeconds: Default Time to Live for the item in Cache
* @param ?int $dataClientOperationTimeoutMs: msecs after which requests should be cancelled due to timeout
*/
function __construct(string $authToken, int $defaultTtlSeconds, ?int $dataClientOperationTimeoutMs=null)
{
$payload = $this->parseAuthToken($authToken);
$this->controlClient = new _ScsControlClient($authToken, $payload["cp"]);
function __construct(
ICredentialProvider $authProvider, int $defaultTtlSeconds, ?int $dataClientOperationTimeoutMs=null
) {
$this->controlClient = new _ScsControlClient($authProvider->getAuthToken(), $authProvider->getControlEndpoint());
$this->dataClient = new _ScsDataClient(
$authToken, $payload["c"], $defaultTtlSeconds, $dataClientOperationTimeoutMs
$authProvider->getAuthToken(),
$authProvider->getCacheEndpoint(),
$defaultTtlSeconds,
$dataClientOperationTimeoutMs
);
}

private function throwBadAuthToken() {
throw new InvalidArgumentError('Invalid Auth token.');
}

private function parseAuthToken(string $authToken) : array {
$exploded = explode (".", $authToken);
if (count($exploded) != 3) {
$this->throwBadAuthToken();
}
list($header, $payload, $signature) = $exploded;
$token = json_decode(base64_decode($payload), true);
if ($token === null) {
$this->throwBadAuthToken();
}
return $token;
}

public function createCache(string $cacheName) : CacheOperationTypes\CreateCacheResponse
{
return $this->controlClient->createCache($cacheName);
Expand Down
44 changes: 35 additions & 9 deletions src/Utilities/_DataValidation.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,59 @@

namespace Momento\Utilities;

use InvalidArgumentException;
use Momento\Cache\Errors\InvalidArgumentError;

if (!function_exists('validateTtl')) {
if (!function_exists('validateTtl'))
{
function validateTtl(int $ttlSeconds) : void
{
if (!is_int($ttlSeconds) || $ttlSeconds < 0) {
if (!is_int($ttlSeconds) || $ttlSeconds < 0)
{
throw new InvalidArgumentError("TTL Seconds must be a non-negative integer");
}
}
}

if (!function_exists('validateCacheName')) {
if (!function_exists('isNullOrEmpty'))
{
function isNullOrEmpty(string $str=null) : bool
{
return (is_null($str) || $str === "");
}
}

if (!function_exists('validateCacheName'))
{
function validateCacheName(string $cacheName) : void
{
if (!$cacheName || !is_string($cacheName)) {
if (isNullOrEmpty($cacheName))
{
throw new InvalidArgumentError("Cache name must be a non-empty string");
}
}
}

if (!function_exists('validateOperationTimeout')) {
function validateOperationTimeout(?int $operationTimeout=null) {
if ($operationTimeout === null) {
if (!function_exists('validateListName'))
{
function validateListName(string $listName) : void
{
if (isNullOrEmpty($listName))
{
throw new InvalidArgumentError("List name must be a non-empty string");
}
}
}

if (!function_exists('validateOperationTimeout'))
{
function validateOperationTimeout(?int $operationTimeout=null)
{
if ($operationTimeout === null)
{
return;
}
if ($operationTimeout <= 0) {
if ($operationTimeout <= 0)
{
throw new InvalidArgumentError("Request timeout must be greater than zero.");
}
}
Expand Down
12 changes: 6 additions & 6 deletions src/Utilities/_ErrorConverter.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ class _ErrorConverter {
Grpc\STATUS_RESOURCE_EXHAUSTED => \Momento\Cache\Errors\LimitExceededError::class,
Grpc\STATUS_ALREADY_EXISTS => \Momento\Cache\Errors\AlreadyExistsError::class,
Grpc\STATUS_NOT_FOUND => \Momento\Cache\Errors\NotFoundError::class,
Grpc\STATUS_UNKNOWN => \Momento\Cache\Errors\InternalServerError::class,
Grpc\STATUS_ABORTED => \Momento\Cache\Errors\InternalServerError::class,
Grpc\STATUS_INTERNAL => \Momento\Cache\Errors\InternalServerError::class,
Grpc\STATUS_UNAVAILABLE => \Momento\Cache\Errors\InternalServerError::class,
Grpc\STATUS_DATA_LOSS => \Momento\Cache\Errors\InternalServerError::class
Grpc\STATUS_UNKNOWN => InternalServerError::class,
Grpc\STATUS_ABORTED => InternalServerError::class,
Grpc\STATUS_INTERNAL => InternalServerError::class,
Grpc\STATUS_UNAVAILABLE => InternalServerError::class,
Grpc\STATUS_DATA_LOSS => InternalServerError::class
];

public static function convert(int $status, string $details) : \Exception
Expand All @@ -31,6 +31,6 @@ public static function convert(int $status, string $details) : \Exception
$class = self::$rpcToError[$status];
return new $class($details);
}
throw new \Momento\Cache\Errors\InternalServerError("CacheService failed an internal error");
throw new InternalServerError("CacheService failed an internal error");
}
}
51 changes: 29 additions & 22 deletions tests/Cache/CacheClientTest.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
<?php
namespace Momento\Tests\Cache;

use Momento\Auth\AuthUtils;
use Momento\Auth\EnvMomentoTokenProvider;
use Momento\Cache\CacheOperationTypes\CacheGetStatus;
use Momento\Cache\Errors\AlreadyExistsError;
use Momento\Cache\Errors\AuthenticationError;
Expand All @@ -18,35 +20,39 @@
*/
class CacheClientTest extends TestCase
{
private string $AUTH_TOKEN;
private EnvMomentoTokenProvider $authProvider;
private string $TEST_CACHE_NAME;
private string $BAD_AUTH_TOKEN = "eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJpbnRlZ3JhdGlvbiIsImNwIjoiY29udHJvbC5jZWxsLWFscGhhLWRldi5wcmVwcm9kLmEubW9tZW50b2hxLmNvbSIsImMiOiJjYWNoZS5jZWxsLWFscGhhLWRldi5wcmVwcm9kLmEubW9tZW50b2hxLmNvbSJ9.gdghdjjfjyehhdkkkskskmmls76573jnajhjjjhjdhnndy";
private int $DEFAULT_TTL_SECONDS = 60;
private SimpleCacheClient $client;

public function setUp() : void
{
$this->AUTH_TOKEN = getenv("TEST_AUTH_TOKEN");
if (!$this->AUTH_TOKEN) {
throw new RuntimeException(
"Integration tests require TEST_AUTH_TOKEN env var; see README for more details."
);
}
$this->authProvider = new EnvMomentoTokenProvider("TEST_AUTH_TOKEN");

$this->TEST_CACHE_NAME = getenv("TEST_CACHE_NAME");
if (!$this->TEST_CACHE_NAME) {
throw new RuntimeException(
"Integration tests require TEST_CACHE_NAME env var; see README for more details."
);
}
$this->client = new SimpleCacheClient($this->AUTH_TOKEN, $this->DEFAULT_TTL_SECONDS);
$this->client = new SimpleCacheClient($this->authProvider, $this->DEFAULT_TTL_SECONDS);

// Ensure test cache exists
try {
$this->client->createCache($this->TEST_CACHE_NAME);
} catch (AlreadyExistsError $e) {}
}

private function getBadAuthTokenClient() : SimpleCacheClient
{
$badEnvName = "_MOMENTO_BAD_AUTH_TOKEN";
putenv("{$badEnvName}={$this->BAD_AUTH_TOKEN}");
$authProvider = new EnvMomentoTokenProvider($badEnvName);
putenv($badEnvName);
return new SimpleCacheClient($authProvider, $this->DEFAULT_TTL_SECONDS);
}

// Happy path test
public function testCreateSetGetDelete() {
$cacheName = uniqid();
Expand All @@ -64,26 +70,26 @@ public function testCreateSetGetDelete() {
// Client initialization tests
public function testNegativeDefaultTtl() {
$this->expectExceptionMessage("TTL Seconds must be a non-negative integer");
$client = new SimpleCacheClient($this->AUTH_TOKEN, -1);
$client = new SimpleCacheClient($this->authProvider, -1);
}

public function testNonJwtTokens() {
$AUTH_TOKEN = "notanauthtoken";
$this->expectExceptionMessage("Invalid Auth token.");
$client = new SimpleCacheClient($AUTH_TOKEN, $this->DEFAULT_TTL_SECONDS);
$this->expectExceptionMessage("Invalid Momento auth token.");
AuthUtils::parseAuthToken($AUTH_TOKEN);
$AUTH_TOKEN = "not.anauth.token";
$this->expectExceptionMessage("Invalid Auth token.");
$client = new SimpleCacheClient($AUTH_TOKEN, $this->DEFAULT_TTL_SECONDS);
$this->expectExceptionMessage("Invalid Momento auth token.");
AuthUtils::parseAuthToken($AUTH_TOKEN);
}

public function testNegativeRequestTimeout() {
$this->expectExceptionMessage("Request timeout must be greater than zero.");
$client = new SimpleCacheClient($this->AUTH_TOKEN, $this->DEFAULT_TTL_SECONDS, -1);
$client = new SimpleCacheClient($this->authProvider, $this->DEFAULT_TTL_SECONDS, -1);
}

public function testZeroRequestTimeout() {
$this->expectExceptionMessage("Request timeout must be greater than zero.");
$client = new SimpleCacheClient($this->AUTH_TOKEN, $this->DEFAULT_TTL_SECONDS, 0);
$client = new SimpleCacheClient($this->authProvider, $this->DEFAULT_TTL_SECONDS, 0);
}

// Create cache tests
Expand All @@ -109,8 +115,8 @@ public function testCreateCacheBadName() {
}

public function testCreateCacheBadAuth() {
$client = $this->getBadAuthTokenClient();
$this->expectException(AuthenticationError::class);
$client = new SimpleCacheClient($this->BAD_AUTH_TOKEN, $this->DEFAULT_TTL_SECONDS);
$client->createCache(uniqid());
}

Expand Down Expand Up @@ -140,7 +146,7 @@ public function testDeleteEmptyCacheName() {
}

public function testDeleteCacheBadAuth() {
$client = new SimpleCacheClient($this->BAD_AUTH_TOKEN, $this->DEFAULT_TTL_SECONDS);
$client = $this->getBadAuthTokenClient();
$this->expectException(AuthenticationError::class);
$client->deleteCache(uniqid());
}
Expand All @@ -164,7 +170,7 @@ public function testListCaches() {
}

public function testListCachesBadAuth() {
$client = new SimpleCacheClient($this->BAD_AUTH_TOKEN, $this->DEFAULT_TTL_SECONDS);
$client = $this->getBadAuthTokenClient();
$this->expectException(AuthenticationError::class);
$client->listCaches();
}
Expand Down Expand Up @@ -197,7 +203,7 @@ public function testGetMiss() {
public function testExpiresAfterTtl() {
$key = uniqid();
$value = uniqid();
$client = new SimpleCacheClient($this->AUTH_TOKEN, 2);
$client = new SimpleCacheClient($this->authProvider, 2);
$client->set($this->TEST_CACHE_NAME, $key, $value);
$this->assertEquals(CacheGetStatus::HIT->name, $client->get($this->TEST_CACHE_NAME, $key)->status());
sleep(4);
Expand Down Expand Up @@ -276,9 +282,10 @@ public function testSetBadValue() {
}

public function testSetBadAuth() {
$client = new SimpleCacheClient($this->BAD_AUTH_TOKEN, $this->DEFAULT_TTL_SECONDS);
$client = $this->getBadAuthTokenClient();
$this->expectException(AuthenticationError::class);
$client->set($this->TEST_CACHE_NAME, "foo", "bar");
putenv($badEnvName);
}

// Get tests
Expand All @@ -304,13 +311,13 @@ public function testGetNullKey() {
}

public function testGetBadAuth() {
$client = new SimpleCacheClient($this->BAD_AUTH_TOKEN, $this->DEFAULT_TTL_SECONDS);
$client = $this->getBadAuthTokenClient();
$this->expectException(AuthenticationError::class);
$client->get($this->TEST_CACHE_NAME, "key");
}

public function testGetTimeout() {
$client = new SimpleCacheClient($this->AUTH_TOKEN, $this->DEFAULT_TTL_SECONDS, 1);
$client = new SimpleCacheClient($this->authProvider, $this->DEFAULT_TTL_SECONDS, 1);
$this->expectException(TimeoutError::class);
$client->get($this->TEST_CACHE_NAME, "key");
}
Expand Down

0 comments on commit 6852fbb

Please sign in to comment.