From 0863cb24d5a19e26544d5465d8c18dfc733d0876 Mon Sep 17 00:00:00 2001 From: Pete Gautier Date: Fri, 15 Sep 2023 09:26:32 -0700 Subject: [PATCH 1/2] chore: add disposable token testing --- src/Momento.Sdk/Internal/ScsTokenClient.cs | 26 +- .../Momento.Sdk.Tests/AuthClientCacheTest.cs | 556 ++++++++++++++++++ .../Momento.Sdk.Tests/AuthClientTest.cs | 25 - .../Momento.Sdk.Tests/AuthClientTopicTest.cs | 344 +++++++++++ 4 files changed, 920 insertions(+), 31 deletions(-) create mode 100644 tests/Integration/Momento.Sdk.Tests/AuthClientCacheTest.cs delete mode 100644 tests/Integration/Momento.Sdk.Tests/AuthClientTest.cs create mode 100644 tests/Integration/Momento.Sdk.Tests/AuthClientTopicTest.cs diff --git a/src/Momento.Sdk/Internal/ScsTokenClient.cs b/src/Momento.Sdk/Internal/ScsTokenClient.cs index 75ea11e5..814b1a8b 100644 --- a/src/Momento.Sdk/Internal/ScsTokenClient.cs +++ b/src/Momento.Sdk/Internal/ScsTokenClient.cs @@ -29,7 +29,7 @@ public ScsTokenClient(IAuthConfiguration config, string authToken, string endpoi this._exceptionMapper = new CacheExceptionMapper(config.LoggerFactory); } - protected DateTime CalculateDeadline() + private DateTime CalculateDeadline() { return DateTime.UtcNow.Add(authClientOperationTimeout); } @@ -39,14 +39,28 @@ protected DateTime CalculateDeadline() public async Task GenerateDisposableToken( DisposableTokenScope scope, ExpiresIn expiresIn ) { - _GenerateDisposableTokenRequest request = new _GenerateDisposableTokenRequest + Permissions permissions; + try + { + permissions = PermissionsFromDisposableTokenScope(scope); + } + catch (ArgumentNullException e) { - Expires = new _GenerateDisposableTokenRequest.Types.Expires() { ValidForSeconds = (uint)expiresIn.Seconds() }, - AuthToken = this.authToken, - Permissions = PermissionsFromDisposableTokenScope(scope) - }; + return _logger.LogTraceAuthRequestError(RequestTypeAuthGenerateDisposableToken, + new GenerateDisposableTokenResponse.Error( + new InvalidArgumentException("Permissions parameters may not be null", null, e) + ) + ); + } + try { + _GenerateDisposableTokenRequest request = new _GenerateDisposableTokenRequest + { + Expires = new _GenerateDisposableTokenRequest.Types.Expires() { ValidForSeconds = (uint)expiresIn.Seconds() }, + AuthToken = this.authToken, + Permissions = permissions + }; _logger.LogTraceExecutingAuthRequest(RequestTypeAuthGenerateDisposableToken); var response = await grpcManager.Client.generateDisposableToken( request, new CallOptions(deadline: CalculateDeadline()) diff --git a/tests/Integration/Momento.Sdk.Tests/AuthClientCacheTest.cs b/tests/Integration/Momento.Sdk.Tests/AuthClientCacheTest.cs new file mode 100644 index 00000000..6fe5823f --- /dev/null +++ b/tests/Integration/Momento.Sdk.Tests/AuthClientCacheTest.cs @@ -0,0 +1,556 @@ +using System.ComponentModel.DataAnnotations; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.InteropServices; +using System.Threading.Tasks; +using Momento.Sdk.Auth; +using Momento.Sdk.Auth.AccessControl; +using Momento.Sdk.Config; +using Newtonsoft.Json.Serialization; + +namespace Momento.Sdk.Tests; + +[Collection("AuthClient")] +public class AuthClientCacheTest : IClassFixture, IClassFixture +{ + private readonly ICacheClient cacheClient; + private readonly IAuthClient authClient; + private readonly string cacheName; + private readonly string key = "my-key"; + private readonly string keyPrefix; + private readonly string value = "my-value"; + + public AuthClientCacheTest(CacheClientFixture cacheFixture, AuthClientFixture authFixture) + { + authClient = authFixture.Client; + cacheClient = cacheFixture.Client; + cacheName = cacheFixture.CacheName; + keyPrefix = key.Substring(0, 3); + } + + private async Task GetClientForTokenScope(DisposableTokenScope scope) + { + var response = await authClient.GenerateDisposableTokenAsync(scope, ExpiresIn.Minutes(2)); + Assert.True(response is GenerateDisposableTokenResponse.Success); + string authToken = ""; + if (response is GenerateDisposableTokenResponse.Success token) + { + Assert.False(String.IsNullOrEmpty(token.AuthToken)); + authToken = token.AuthToken; + } + var authProvider = new StringMomentoTokenProvider(authToken); + return new CacheClient(Configurations.Laptop.Latest(), authProvider, TimeSpan.FromSeconds(60)); + } + + private async Task SetCacheKey() + { + var setResponse = await cacheClient.SetAsync(cacheName, key, value); + Assert.True(setResponse is CacheSetResponse.Success); + } + + private async Task VerifyCacheKey() + { + var getResponse = await cacheClient.GetAsync(cacheName, key); + Assert.True(getResponse is CacheGetResponse.Hit); + if (getResponse is CacheGetResponse.Hit hit) + { + Assert.Equal(value, hit.ValueString); + } + } + + private void AssertPermissionError(TResp response) + { + Assert.True(response is TErrResp); + if (response is IError err) + { + Assert.Equal(err.ErrorCode, MomentoErrorCode.PERMISSION_ERROR); + } + } + + [Fact] + public async Task GenerateDisposableCacheAuthToken_HappyPath() + { + var response = await authClient.GenerateDisposableTokenAsync( + DisposableTokenScopes.CacheReadWrite("cache"), ExpiresIn.Minutes(10) + ); + Assert.True(response is GenerateDisposableTokenResponse.Success); + response = await authClient.GenerateDisposableTokenAsync( + DisposableTokenScopes.CacheReadWrite(CacheSelector.ByName("cache")), ExpiresIn.Minutes(10) + ); + Assert.True(response is GenerateDisposableTokenResponse.Success); + + response = await authClient.GenerateDisposableTokenAsync( + DisposableTokenScopes.CacheReadOnly("cache"), ExpiresIn.Minutes(10) + ); + Assert.True(response is GenerateDisposableTokenResponse.Success); + response = await authClient.GenerateDisposableTokenAsync( + DisposableTokenScopes.CacheReadOnly(CacheSelector.ByName("cache")), ExpiresIn.Minutes(10) + ); + Assert.True(response is GenerateDisposableTokenResponse.Success); + + response = await authClient.GenerateDisposableTokenAsync( + DisposableTokenScopes.CacheWriteOnly("cache"), ExpiresIn.Minutes(10) + ); + Assert.True(response is GenerateDisposableTokenResponse.Success); + response = await authClient.GenerateDisposableTokenAsync( + DisposableTokenScopes.CacheWriteOnly(CacheSelector.ByName("cache")), ExpiresIn.Minutes(10) + ); + Assert.True(response is GenerateDisposableTokenResponse.Success); + } + + [Theory] + [InlineData(null)] + public async Task GenerateDisposableCacheAuthToken_ErrorsOnNull(string cacheName) + { + var response = await authClient.GenerateDisposableTokenAsync( + DisposableTokenScopes.CacheReadWrite(cacheName), + ExpiresIn.Minutes(10) + ); + Assert.True(response is GenerateDisposableTokenResponse.Error); + Assert.Equal(MomentoErrorCode.INVALID_ARGUMENT_ERROR, + ((GenerateDisposableTokenResponse.Error)response).ErrorCode); + response = await authClient.GenerateDisposableTokenAsync( + DisposableTokenScopes.CacheReadOnly(cacheName), + ExpiresIn.Minutes(10) + ); + Assert.True(response is GenerateDisposableTokenResponse.Error); + Assert.Equal(MomentoErrorCode.INVALID_ARGUMENT_ERROR, + ((GenerateDisposableTokenResponse.Error)response).ErrorCode); + response = await authClient.GenerateDisposableTokenAsync( + DisposableTokenScopes.CacheWriteOnly(cacheName), + ExpiresIn.Minutes(10) + ); + Assert.True(response is GenerateDisposableTokenResponse.Error); + Assert.Equal(MomentoErrorCode.INVALID_ARGUMENT_ERROR, + ((GenerateDisposableTokenResponse.Error)response).ErrorCode); + } + + [Fact] + public async Task GenerateDisposableCacheAuthToken_ErrorsOnEmpty() + { + var response = await authClient.GenerateDisposableTokenAsync( + DisposableTokenScopes.CacheReadWrite(""), + ExpiresIn.Minutes(10) + ); + Assert.True(response is GenerateDisposableTokenResponse.Error); + Assert.Equal(MomentoErrorCode.INVALID_ARGUMENT_ERROR, + ((GenerateDisposableTokenResponse.Error)response).ErrorCode); + response = await authClient.GenerateDisposableTokenAsync( + DisposableTokenScopes.CacheReadOnly(""), + ExpiresIn.Minutes(10) + ); + Assert.True(response is GenerateDisposableTokenResponse.Error); + Assert.Equal(MomentoErrorCode.INVALID_ARGUMENT_ERROR, + ((GenerateDisposableTokenResponse.Error)response).ErrorCode); + response = await authClient.GenerateDisposableTokenAsync( + DisposableTokenScopes.CacheWriteOnly(""), + ExpiresIn.Minutes(10) + ); + Assert.True(response is GenerateDisposableTokenResponse.Error); + Assert.Equal(MomentoErrorCode.INVALID_ARGUMENT_ERROR, + ((GenerateDisposableTokenResponse.Error)response).ErrorCode); + } + + + [Fact] + public async Task GenerateDisposableCacheAuthToken_ReadWrite_HappyPath() + { + var readwriteCacheClient = await GetClientForTokenScope( + DisposableTokenScopes.CacheReadWrite(cacheName) + ); + var setResponse = await readwriteCacheClient.SetAsync(cacheName, key, value); + Assert.True(setResponse is CacheSetResponse.Success); + var getResponse = await readwriteCacheClient.GetAsync(cacheName, key); + Assert.True(getResponse is CacheGetResponse.Hit); + if (getResponse is CacheGetResponse.Hit hit) + { + Assert.Equal(value, hit.ValueString); + } + readwriteCacheClient = await GetClientForTokenScope( + DisposableTokenScopes.CacheReadWrite("someothercache") + ); + setResponse = await readwriteCacheClient.SetAsync(cacheName, key, value); + AssertPermissionError(setResponse); + getResponse = await readwriteCacheClient.GetAsync(cacheName, key); + AssertPermissionError(getResponse); + } + + [Fact] + public async Task GenerateDisposableCacheAuthToken_ReadOnly_HappyPath() + { + var readonlyCacheClient = await GetClientForTokenScope( + DisposableTokenScopes.CacheReadOnly(cacheName) + ); + var setResponse = await readonlyCacheClient.SetAsync(cacheName, key, value); + AssertPermissionError(setResponse); + await SetCacheKey(); + var getResponse = await readonlyCacheClient.GetAsync(cacheName, key); + Assert.True(getResponse is CacheGetResponse.Hit); + if (getResponse is CacheGetResponse.Hit hit) + { + Assert.Equal(value, hit.ValueString); + } + } + + [Fact] + public async Task GenerateDisposableCacheAuthToken_WriteOnly_HappyPath() + { + var writeonlyCacheClient = await GetClientForTokenScope( + DisposableTokenScopes.CacheWriteOnly(cacheName) + ); + var setResponse = await writeonlyCacheClient.SetAsync(cacheName, key, value); + Assert.True(setResponse is CacheSetResponse.Success); + var getResponse = await writeonlyCacheClient.GetAsync(cacheName, key); + AssertPermissionError(getResponse); + await VerifyCacheKey(); + writeonlyCacheClient = await GetClientForTokenScope( + DisposableTokenScopes.CacheWriteOnly("someothercache") + ); + setResponse = await writeonlyCacheClient.SetAsync(cacheName, key, value); + AssertPermissionError(setResponse); + } + + [Fact] + public async Task GenerateDisposableCacheKeyAuthToken_HappyPath() + { + var response = await authClient.GenerateDisposableTokenAsync( + DisposableTokenScopes.CacheKeyReadWrite("cache", "cacheKey"), ExpiresIn.Minutes(10) + ); + Assert.True(response is GenerateDisposableTokenResponse.Success); + response = await authClient.GenerateDisposableTokenAsync( + DisposableTokenScopes.CacheKeyReadWrite(CacheSelector.ByName("cache"), "cacheKey"), ExpiresIn.Minutes(10) + ); + Assert.True(response is GenerateDisposableTokenResponse.Success); + + response = await authClient.GenerateDisposableTokenAsync( + DisposableTokenScopes.CacheKeyReadOnly("cache", "cacheKey"), ExpiresIn.Minutes(10) + ); + Assert.True(response is GenerateDisposableTokenResponse.Success); + response = await authClient.GenerateDisposableTokenAsync( + DisposableTokenScopes.CacheKeyReadOnly(CacheSelector.ByName("cache"), "cacheKey"), ExpiresIn.Minutes(10) + ); + Assert.True(response is GenerateDisposableTokenResponse.Success); + + response = await authClient.GenerateDisposableTokenAsync( + DisposableTokenScopes.CacheKeyWriteOnly("cache", "cacheKey"), ExpiresIn.Minutes(10) + ); + Assert.True(response is GenerateDisposableTokenResponse.Success); + response = await authClient.GenerateDisposableTokenAsync( + DisposableTokenScopes.CacheKeyWriteOnly(CacheSelector.ByName("cache"), "cacheKey"), ExpiresIn.Minutes(10) + ); + Assert.True(response is GenerateDisposableTokenResponse.Success); + } + + [Theory] + [InlineData(null, "cacheKey")] + [InlineData("cache", null)] + public async Task GenerateDisposableCacheKeyAuthToken_ErrorsOnNull(string cacheName, string cacheKey) + { + var response = await authClient.GenerateDisposableTokenAsync( + DisposableTokenScopes.CacheKeyReadWrite(cacheName, cacheKey), + ExpiresIn.Minutes(10) + ); + Assert.True(response is GenerateDisposableTokenResponse.Error); + Assert.Equal(MomentoErrorCode.INVALID_ARGUMENT_ERROR, + ((GenerateDisposableTokenResponse.Error)response).ErrorCode); + response = await authClient.GenerateDisposableTokenAsync( + DisposableTokenScopes.CacheKeyReadOnly(cacheName, cacheKey), + ExpiresIn.Minutes(10) + ); + Assert.True(response is GenerateDisposableTokenResponse.Error); + Assert.Equal(MomentoErrorCode.INVALID_ARGUMENT_ERROR, + ((GenerateDisposableTokenResponse.Error)response).ErrorCode); + response = await authClient.GenerateDisposableTokenAsync( + DisposableTokenScopes.CacheKeyWriteOnly(cacheName, cacheKey), + ExpiresIn.Minutes(10) + ); + Assert.True(response is GenerateDisposableTokenResponse.Error); + Assert.Equal(MomentoErrorCode.INVALID_ARGUMENT_ERROR, + ((GenerateDisposableTokenResponse.Error)response).ErrorCode); + } + + [Theory] + [InlineData("", "cacheKey")] + [InlineData("cache", "")] + public async Task GenerateDisposableCacheKeyAuthToken_ErrorsOnEmpty(string cacheName, string cacheKey) + { + var response = await authClient.GenerateDisposableTokenAsync( + DisposableTokenScopes.CacheKeyReadWrite(cacheName, cacheKey), + ExpiresIn.Minutes(10) + ); + Assert.True(response is GenerateDisposableTokenResponse.Error); + Assert.Equal(MomentoErrorCode.INVALID_ARGUMENT_ERROR, + ((GenerateDisposableTokenResponse.Error)response).ErrorCode); + response = await authClient.GenerateDisposableTokenAsync( + DisposableTokenScopes.CacheKeyReadOnly(cacheName, cacheKey), + ExpiresIn.Minutes(10) + ); + Assert.True(response is GenerateDisposableTokenResponse.Error); + Assert.Equal(MomentoErrorCode.INVALID_ARGUMENT_ERROR, + ((GenerateDisposableTokenResponse.Error)response).ErrorCode); + response = await authClient.GenerateDisposableTokenAsync( + DisposableTokenScopes.CacheKeyWriteOnly(cacheName, cacheKey), + ExpiresIn.Minutes(10) + ); + Assert.True(response is GenerateDisposableTokenResponse.Error); + Assert.Equal(MomentoErrorCode.INVALID_ARGUMENT_ERROR, + ((GenerateDisposableTokenResponse.Error)response).ErrorCode); + } + + [Fact] + public async Task GenerateDisposableCacheKeyAuthToken_ReadWrite_HappyPath() + { + var readwriteCacheClient = await GetClientForTokenScope( + DisposableTokenScopes.CacheKeyReadWrite(cacheName, key) + ); + var setResponse = await readwriteCacheClient.SetAsync(cacheName, key, value); + Assert.True(setResponse is CacheSetResponse.Success); + var getResponse = await readwriteCacheClient.GetAsync(cacheName, key); + Assert.True(getResponse is CacheGetResponse.Hit); + if (getResponse is CacheGetResponse.Hit hit) + { + Assert.Equal(value, hit.ValueString); + } + readwriteCacheClient = await GetClientForTokenScope( + DisposableTokenScopes.CacheKeyReadWrite("othercache", key) + ); + setResponse = await readwriteCacheClient.SetAsync(cacheName, key, value); + AssertPermissionError(setResponse); + getResponse = await readwriteCacheClient.GetAsync(cacheName, key); + AssertPermissionError(getResponse); + readwriteCacheClient = await GetClientForTokenScope( + DisposableTokenScopes.CacheKeyReadWrite(cacheName, "otherkey") + ); + setResponse = await readwriteCacheClient.SetAsync(cacheName, key, value); + AssertPermissionError(setResponse); + getResponse = await readwriteCacheClient.GetAsync(cacheName, key); + AssertPermissionError(getResponse); + } + + [Fact] + public async Task GenerateDisposableCacheKeyAuthToken_ReadOnly_HappyPath() + { + var readonlyCacheClient = await GetClientForTokenScope( + DisposableTokenScopes.CacheKeyReadOnly(cacheName, key) + ); + var setResponse = await readonlyCacheClient.SetAsync(cacheName, key, value); + AssertPermissionError(setResponse); + await SetCacheKey(); + var getResponse = await readonlyCacheClient.GetAsync(cacheName, key); + Assert.True(getResponse is CacheGetResponse.Hit); + if (getResponse is CacheGetResponse.Hit hit) + { + Assert.Equal(value, hit.ValueString); + } + readonlyCacheClient = await GetClientForTokenScope( + DisposableTokenScopes.CacheKeyReadOnly("othercache", key) + ); + getResponse = await readonlyCacheClient.GetAsync(cacheName, key); + AssertPermissionError(getResponse); + readonlyCacheClient = await GetClientForTokenScope( + DisposableTokenScopes.CacheKeyReadOnly(cacheName, "otherkey") + ); + getResponse = await readonlyCacheClient.GetAsync(cacheName, key); + AssertPermissionError(getResponse); + } + + [Fact] + public async Task GenerateDisposableCacheKeyAuthToken_WriteOnly_HappyPath() + { + var writeonlyCacheClient = await GetClientForTokenScope( + DisposableTokenScopes.CacheKeyWriteOnly(cacheName, key) + ); + var setResponse = await writeonlyCacheClient.SetAsync(cacheName, key, value); + Assert.True(setResponse is CacheSetResponse.Success); + var getResponse = await writeonlyCacheClient.GetAsync(cacheName, key); + AssertPermissionError(getResponse); + await VerifyCacheKey(); + writeonlyCacheClient = await GetClientForTokenScope( + DisposableTokenScopes.CacheKeyWriteOnly("otherCache", key) + ); + setResponse = await writeonlyCacheClient.SetAsync(cacheName, key, value); + AssertPermissionError(setResponse); + writeonlyCacheClient = await GetClientForTokenScope( + DisposableTokenScopes.CacheKeyWriteOnly(cacheName, "otherkey") + ); + setResponse = await writeonlyCacheClient.SetAsync(cacheName, key, value); + AssertPermissionError(setResponse); + } + + [Fact] + public async Task GenerateDisposableCacheKeyPrefixAuthToken_HappyPath() + { + var response = await authClient.GenerateDisposableTokenAsync( + DisposableTokenScopes.CacheKeyPrefixReadWrite("cache", "cacheKey"), ExpiresIn.Minutes(10) + ); + Assert.True(response is GenerateDisposableTokenResponse.Success); + response = await authClient.GenerateDisposableTokenAsync( + DisposableTokenScopes.CacheKeyPrefixReadWrite(CacheSelector.ByName("cache"), "cacheKey"), + ExpiresIn.Minutes(10) + ); + Assert.True(response is GenerateDisposableTokenResponse.Success); + + response = await authClient.GenerateDisposableTokenAsync( + DisposableTokenScopes.CacheKeyPrefixReadOnly("cache", "cacheKey"), ExpiresIn.Minutes(10) + ); + Assert.True(response is GenerateDisposableTokenResponse.Success); + response = await authClient.GenerateDisposableTokenAsync( + DisposableTokenScopes.CacheKeyPrefixReadOnly(CacheSelector.ByName("cache"), "cacheKey"), + ExpiresIn.Minutes(10) + ); + Assert.True(response is GenerateDisposableTokenResponse.Success); + + response = await authClient.GenerateDisposableTokenAsync( + DisposableTokenScopes.CacheKeyPrefixWriteOnly("cache", "cacheKey"), ExpiresIn.Minutes(10) + ); + Assert.True(response is GenerateDisposableTokenResponse.Success); + response = await authClient.GenerateDisposableTokenAsync( + DisposableTokenScopes.CacheKeyPrefixWriteOnly(CacheSelector.ByName("cache"), "cacheKey"), + ExpiresIn.Minutes(10) + ); + Assert.True(response is GenerateDisposableTokenResponse.Success); + } + + [Theory] + [InlineData(null, "cacheKeyPrefix")] + [InlineData("cache", null)] + public async Task GenerateDisposableCacheKeyPrefixAuthToken_ErrorsOnNull(string cacheName, string cacheKeyPrefix) + { + var response = await authClient.GenerateDisposableTokenAsync( + DisposableTokenScopes.CacheKeyPrefixReadWrite(cacheName, cacheKeyPrefix), + ExpiresIn.Minutes(10) + ); + Assert.True(response is GenerateDisposableTokenResponse.Error); + Assert.Equal(MomentoErrorCode.INVALID_ARGUMENT_ERROR, + ((GenerateDisposableTokenResponse.Error)response).ErrorCode); + response = await authClient.GenerateDisposableTokenAsync( + DisposableTokenScopes.CacheKeyPrefixReadOnly(cacheName, cacheKeyPrefix), + ExpiresIn.Minutes(10) + ); + Assert.True(response is GenerateDisposableTokenResponse.Error); + Assert.Equal(MomentoErrorCode.INVALID_ARGUMENT_ERROR, + ((GenerateDisposableTokenResponse.Error)response).ErrorCode); + response = await authClient.GenerateDisposableTokenAsync( + DisposableTokenScopes.CacheKeyPrefixWriteOnly(cacheName, cacheKeyPrefix), + ExpiresIn.Minutes(10) + ); + Assert.True(response is GenerateDisposableTokenResponse.Error); + Assert.Equal(MomentoErrorCode.INVALID_ARGUMENT_ERROR, + ((GenerateDisposableTokenResponse.Error)response).ErrorCode); + } + + [Theory] + [InlineData("", "cacheKeyPrefix")] + [InlineData("cache", "")] + public async Task GenerateDisposableCacheKeyPrefixAuthToken_ErrorsOnEmpty(string cacheName, string cacheKeyPrefix) + { + var response = await authClient.GenerateDisposableTokenAsync( + DisposableTokenScopes.CacheKeyPrefixReadWrite(cacheName, cacheKeyPrefix), + ExpiresIn.Minutes(10) + ); + Assert.True(response is GenerateDisposableTokenResponse.Error); + Assert.Equal(MomentoErrorCode.INVALID_ARGUMENT_ERROR, + ((GenerateDisposableTokenResponse.Error)response).ErrorCode); + response = await authClient.GenerateDisposableTokenAsync( + DisposableTokenScopes.CacheKeyPrefixReadOnly(cacheName, cacheKeyPrefix), + ExpiresIn.Minutes(10) + ); + Assert.True(response is GenerateDisposableTokenResponse.Error); + Assert.Equal(MomentoErrorCode.INVALID_ARGUMENT_ERROR, + ((GenerateDisposableTokenResponse.Error)response).ErrorCode); + response = await authClient.GenerateDisposableTokenAsync( + DisposableTokenScopes.CacheKeyPrefixWriteOnly(cacheName, cacheKeyPrefix), + ExpiresIn.Minutes(10) + ); + Assert.True(response is GenerateDisposableTokenResponse.Error); + Assert.Equal(MomentoErrorCode.INVALID_ARGUMENT_ERROR, + ((GenerateDisposableTokenResponse.Error)response).ErrorCode); + } + + [Fact] + public async Task GenerateDisposableCacheKeyPrefixAuthToken_ReadWrite_HappyPath() + { + var readwriteCacheClient = await GetClientForTokenScope( + DisposableTokenScopes.CacheKeyPrefixReadWrite(cacheName, keyPrefix) + ); + var setResponse = await readwriteCacheClient.SetAsync(cacheName, key, value); + Assert.True(setResponse is CacheSetResponse.Success); + var getResponse = await readwriteCacheClient.GetAsync(cacheName, key); + Assert.True(getResponse is CacheGetResponse.Hit); + if (getResponse is CacheGetResponse.Hit hit) + { + Assert.Equal(value, hit.ValueString); + } + readwriteCacheClient = await GetClientForTokenScope( + DisposableTokenScopes.CacheKeyPrefixReadWrite(cacheName, "otherPrefix") + ); + setResponse = await readwriteCacheClient.SetAsync(cacheName, key, value); + AssertPermissionError(setResponse); + getResponse = await readwriteCacheClient.GetAsync(cacheName, key); + AssertPermissionError(getResponse); + readwriteCacheClient = await GetClientForTokenScope( + DisposableTokenScopes.CacheKeyPrefixReadWrite("otherCache", keyPrefix) + ); + setResponse = await readwriteCacheClient.SetAsync(cacheName, key, value); + AssertPermissionError(setResponse); + getResponse = await readwriteCacheClient.GetAsync(cacheName, key); + AssertPermissionError(getResponse); + } + + [Fact] + public async Task GenerateDisposableCacheKeyPrefixAuthToken_ReadOnly_HappyPath() + { + var readonlyCacheClient = await GetClientForTokenScope( + DisposableTokenScopes.CacheKeyPrefixReadOnly(cacheName, keyPrefix) + ); + var setResponse = await readonlyCacheClient.SetAsync(cacheName, key, value); + AssertPermissionError(setResponse); + await SetCacheKey(); + var getResponse = await readonlyCacheClient.GetAsync(cacheName, key); + Assert.True(getResponse is CacheGetResponse.Hit); + if (getResponse is CacheGetResponse.Hit hit) + { + Assert.Equal(value, hit.ValueString); + } + readonlyCacheClient = await GetClientForTokenScope( + DisposableTokenScopes.CacheKeyPrefixReadOnly("otherCache", keyPrefix) + ); + setResponse = await readonlyCacheClient.SetAsync(cacheName, key, value); + AssertPermissionError(setResponse); + getResponse = await readonlyCacheClient.GetAsync(cacheName, key); + AssertPermissionError(getResponse); + readonlyCacheClient = await GetClientForTokenScope( + DisposableTokenScopes.CacheKeyPrefixReadOnly(cacheName, "otherPrefix") + ); + setResponse = await readonlyCacheClient.SetAsync(cacheName, key, value); + AssertPermissionError(setResponse); + getResponse = await readonlyCacheClient.GetAsync(cacheName, key); + AssertPermissionError(getResponse); + } + + [Fact] + public async Task GenerateDisposableCacheKeyPrefixAuthToken_WriteOnly_HappyPath() + { + var writeonlyCacheClient = await GetClientForTokenScope( + DisposableTokenScopes.CacheKeyPrefixWriteOnly(cacheName, keyPrefix) + ); + var setResponse = await writeonlyCacheClient.SetAsync(cacheName, key, value); + Assert.True(setResponse is CacheSetResponse.Success); + var getResponse = await writeonlyCacheClient.GetAsync(cacheName, key); + AssertPermissionError(getResponse); + await VerifyCacheKey(); + writeonlyCacheClient = await GetClientForTokenScope( + DisposableTokenScopes.CacheKeyPrefixWriteOnly("otherCache", keyPrefix) + ); + setResponse = await writeonlyCacheClient.SetAsync(cacheName, key, value); + AssertPermissionError(setResponse); + getResponse = await writeonlyCacheClient.GetAsync(cacheName, key); + AssertPermissionError(getResponse); + writeonlyCacheClient = await GetClientForTokenScope( + DisposableTokenScopes.CacheKeyPrefixWriteOnly(cacheName, "otherPrefix") + ); + setResponse = await writeonlyCacheClient.SetAsync(cacheName, key, value); + AssertPermissionError(setResponse); + getResponse = await writeonlyCacheClient.GetAsync(cacheName, key); + AssertPermissionError(getResponse); + } +} diff --git a/tests/Integration/Momento.Sdk.Tests/AuthClientTest.cs b/tests/Integration/Momento.Sdk.Tests/AuthClientTest.cs deleted file mode 100644 index 76c470d7..00000000 --- a/tests/Integration/Momento.Sdk.Tests/AuthClientTest.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System.Threading.Tasks; -using Momento.Sdk.Auth.AccessControl; - -namespace Momento.Sdk.Tests.Integration; - -[Collection("AuthClient")] -public class AuthClientTest : IClassFixture -{ - private readonly IAuthClient authClient; - - public AuthClientTest(AuthClientFixture authFixture) - { - authClient = authFixture.Client; - } - - [Fact] - public async Task GenerateDisposableAuthToken_HappyPath() - { - var response = await authClient.GenerateDisposableTokenAsync( - DisposableTokenScopes.TopicPublishSubscribe("cache", "topic"), ExpiresIn.Minutes(10) - ); - Assert.True(response is GenerateDisposableTokenResponse.Success); - } - -} diff --git a/tests/Integration/Momento.Sdk.Tests/AuthClientTopicTest.cs b/tests/Integration/Momento.Sdk.Tests/AuthClientTopicTest.cs new file mode 100644 index 00000000..ec1e6793 --- /dev/null +++ b/tests/Integration/Momento.Sdk.Tests/AuthClientTopicTest.cs @@ -0,0 +1,344 @@ +#if NET6_0_OR_GREATER + +using System.Threading.Tasks; +using System.Threading; +using Momento.Sdk.Auth; +using Momento.Sdk.Auth.AccessControl; +using Momento.Sdk.Config; + +namespace Momento.Sdk.Tests; + +[Collection("AuthClient")] +public class AuthClientTopicTest : IClassFixture, IClassFixture, IClassFixture +{ + private readonly IAuthClient authClient; + private readonly ITopicClient topicClient; + private readonly string cacheName; + private readonly string topicName = "topic"; + + public AuthClientTopicTest( + CacheClientFixture cacheFixture, AuthClientFixture authFixture, TopicClientFixture topicFixture + ) + { + authClient = authFixture.Client; + cacheName = cacheFixture.CacheName; + topicClient = topicFixture.Client; + } + + private async Task GetClientForTokenScope(DisposableTokenScope scope) + { + var response = await authClient.GenerateDisposableTokenAsync(scope, ExpiresIn.Minutes(2)); + Assert.True(response is GenerateDisposableTokenResponse.Success); + string authToken = ""; + if (response is GenerateDisposableTokenResponse.Success token) + { + Assert.False(string.IsNullOrEmpty(token.AuthToken)); + authToken = token.AuthToken; + } + var authProvider = new StringMomentoTokenProvider(authToken); + return new TopicClient(TopicConfigurations.Laptop.latest(), authProvider); + } + + private async Task PublishToTopic(string cache, string topic, string value, ITopicClient? client = null) + { + client ??= topicClient; + var response = await client.PublishAsync(cache, topic, value); + Assert.True(response is TopicPublishResponse.Success); + } + + private async Task ExpectTextFromSubscription( + TopicSubscribeResponse.Subscription subscription, string expectedText + ) + { + var cts = new CancellationTokenSource(); + cts.CancelAfter(5000); + var cancellableSubscription = subscription.WithCancellation(cts.Token); + var gotText = false; + await foreach (var message in cancellableSubscription) + { + Assert.True(message is TopicMessage.Text); + if (message is TopicMessage.Text textMsg) { + Assert.Equal(expectedText, textMsg.Value); + gotText = true; + } + cts.Cancel(); + break; + } + if (!gotText) { + Assert.True(false, "didn't recieve expected text: " + expectedText); + } + } + + private void AssertPermissionError(TResp response) + { + Assert.True(response is TErrResp); + if (response is IError err) + { + Assert.Equal(err.ErrorCode, MomentoErrorCode.PERMISSION_ERROR); + } + } + + [Fact] + public async Task GenerateDisposableTopicAuthToken_HappyPath() + { + var response = await authClient.GenerateDisposableTokenAsync( + DisposableTokenScopes.TopicPublishSubscribe("cache", "topic"), ExpiresIn.Minutes(10) + ); + Assert.True(response is GenerateDisposableTokenResponse.Success); + response = await authClient.GenerateDisposableTokenAsync( + DisposableTokenScopes.TopicPublishSubscribe( + CacheSelector.ByName("cache"), "topic"), + ExpiresIn.Minutes(10) + ); + Assert.True(response is GenerateDisposableTokenResponse.Success); + response = await authClient.GenerateDisposableTokenAsync( + DisposableTokenScopes.TopicPublishSubscribe( + "cache", TopicSelector.ByName("topic")), + ExpiresIn.Minutes(10) + ); + Assert.True(response is GenerateDisposableTokenResponse.Success); + response = await authClient.GenerateDisposableTokenAsync( + DisposableTokenScopes.TopicPublishSubscribe( + CacheSelector.ByName("cache"), TopicSelector.ByName("topic")), + ExpiresIn.Minutes(10) + ); + Assert.True(response is GenerateDisposableTokenResponse.Success); + + response = await authClient.GenerateDisposableTokenAsync( + DisposableTokenScopes.TopicPublishOnly("cache", "topic"), ExpiresIn.Minutes(10) + ); + Assert.True(response is GenerateDisposableTokenResponse.Success); + response = await authClient.GenerateDisposableTokenAsync( + DisposableTokenScopes.TopicPublishOnly( + CacheSelector.ByName("cache"), "topic"), + ExpiresIn.Minutes(10) + ); + Assert.True(response is GenerateDisposableTokenResponse.Success); + response = await authClient.GenerateDisposableTokenAsync( + DisposableTokenScopes.TopicPublishOnly( + "cache", TopicSelector.ByName("topic")), + ExpiresIn.Minutes(10) + ); + Assert.True(response is GenerateDisposableTokenResponse.Success); + response = await authClient.GenerateDisposableTokenAsync( + DisposableTokenScopes.TopicPublishOnly( + CacheSelector.ByName("cache"), TopicSelector.ByName("topic")), + ExpiresIn.Minutes(10) + ); + Assert.True(response is GenerateDisposableTokenResponse.Success); + + response = await authClient.GenerateDisposableTokenAsync( + DisposableTokenScopes.TopicSubscribeOnly("cache", "topic"), ExpiresIn.Minutes(10) + ); + Assert.True(response is GenerateDisposableTokenResponse.Success); + response = await authClient.GenerateDisposableTokenAsync( + DisposableTokenScopes.TopicSubscribeOnly( + CacheSelector.ByName("cache"), "topic"), + ExpiresIn.Minutes(10) + ); + Assert.True(response is GenerateDisposableTokenResponse.Success); + response = await authClient.GenerateDisposableTokenAsync( + DisposableTokenScopes.TopicSubscribeOnly( + "cache", TopicSelector.ByName("topic")), + ExpiresIn.Minutes(10) + ); + Assert.True(response is GenerateDisposableTokenResponse.Success); + response = await authClient.GenerateDisposableTokenAsync( + DisposableTokenScopes.TopicSubscribeOnly( + CacheSelector.ByName("cache"), TopicSelector.ByName("topic")), + ExpiresIn.Minutes(10) + ); + Assert.True(response is GenerateDisposableTokenResponse.Success); + } + + [Theory] + [InlineData(null, "topic")] + [InlineData("cache", null)] + public async Task GenerateDisposableTopicAuthToken_ErrorsOnNull(string cacheName, string topicName) + { + var response = await authClient.GenerateDisposableTokenAsync( + DisposableTokenScopes.TopicPublishOnly(cacheName, topicName), + ExpiresIn.Minutes(10) + ); + Assert.True(response is GenerateDisposableTokenResponse.Error); + Assert.Equal(MomentoErrorCode.INVALID_ARGUMENT_ERROR, ((GenerateDisposableTokenResponse.Error)response).ErrorCode); + response = await authClient.GenerateDisposableTokenAsync( + DisposableTokenScopes.TopicSubscribeOnly(cacheName, topicName), + ExpiresIn.Minutes(10) + ); + Assert.True(response is GenerateDisposableTokenResponse.Error); + Assert.Equal(MomentoErrorCode.INVALID_ARGUMENT_ERROR, ((GenerateDisposableTokenResponse.Error)response).ErrorCode); + response = await authClient.GenerateDisposableTokenAsync( + DisposableTokenScopes.TopicPublishSubscribe(cacheName, topicName), + ExpiresIn.Minutes(10) + ); + Assert.True(response is GenerateDisposableTokenResponse.Error); + Assert.Equal(MomentoErrorCode.INVALID_ARGUMENT_ERROR, ((GenerateDisposableTokenResponse.Error)response).ErrorCode); + } + + [Theory] + [InlineData("", "topic")] + [InlineData("cache", "")] + public async Task GenerateDisposableTopicAuthToken_ErrorsOnEmpty(string cacheName, string topicName) + { + var response = await authClient.GenerateDisposableTokenAsync( + DisposableTokenScopes.TopicPublishOnly(cacheName, topicName), + ExpiresIn.Minutes(10) + ); + Assert.True(response is GenerateDisposableTokenResponse.Error); + Assert.Equal(MomentoErrorCode.INVALID_ARGUMENT_ERROR, ((GenerateDisposableTokenResponse.Error)response).ErrorCode); + response = await authClient.GenerateDisposableTokenAsync( + DisposableTokenScopes.TopicSubscribeOnly(cacheName, topicName), + ExpiresIn.Minutes(10) + ); + Assert.True(response is GenerateDisposableTokenResponse.Error); + Assert.Equal(MomentoErrorCode.INVALID_ARGUMENT_ERROR, ((GenerateDisposableTokenResponse.Error)response).ErrorCode); + response = await authClient.GenerateDisposableTokenAsync( + DisposableTokenScopes.TopicPublishSubscribe(cacheName, topicName), + ExpiresIn.Minutes(10) + ); + Assert.True(response is GenerateDisposableTokenResponse.Error); + Assert.Equal(MomentoErrorCode.INVALID_ARGUMENT_ERROR, ((GenerateDisposableTokenResponse.Error)response).ErrorCode); + } + + [Fact] + public async Task GenerateDisposableTopicAuthToken_ErrorsOnBadExpiry() + { + var response = await authClient.GenerateDisposableTokenAsync( + DisposableTokenScopes.TopicPublishSubscribe(cacheName, "foo"), ExpiresIn.Minutes(0) + ); + Assert.True(response is GenerateDisposableTokenResponse.Error); + if (response is GenerateDisposableTokenResponse.Error err) + { + Assert.Equal(MomentoErrorCode.INVALID_ARGUMENT_ERROR, err.ErrorCode); + } + response = await authClient.GenerateDisposableTokenAsync( + DisposableTokenScopes.TopicPublishSubscribe(cacheName, "foo"), ExpiresIn.Minutes(-50) + ); + Assert.True(response is GenerateDisposableTokenResponse.Error); + if (response is GenerateDisposableTokenResponse.Error err2) + { + Assert.Equal(MomentoErrorCode.INVALID_ARGUMENT_ERROR, err2.ErrorCode); + } + response = await authClient.GenerateDisposableTokenAsync( + DisposableTokenScopes.TopicPublishSubscribe(cacheName, "foo"), ExpiresIn.Days(365) + ); + Assert.True(response is GenerateDisposableTokenResponse.Error); + if (response is GenerateDisposableTokenResponse.Error err3) + { + Assert.Equal(MomentoErrorCode.INVALID_ARGUMENT_ERROR, err3.ErrorCode); + } + } + + [Fact] + public async Task GenerateDisposableTopicAuthToken_ReadWrite_HappyPath() + { + const string messageValue = "hello"; + var readwriteTopicClient = await GetClientForTokenScope( + DisposableTokenScopes.TopicPublishSubscribe(cacheName, topicName) + ); + var subscribeResponse = await readwriteTopicClient.SubscribeAsync(cacheName, topicName); + Assert.True(subscribeResponse is TopicSubscribeResponse.Subscription); + var publishResponse = await readwriteTopicClient.PublishAsync(cacheName, topicName, messageValue); + Assert.True(publishResponse is TopicPublishResponse.Success); + if (subscribeResponse is TopicSubscribeResponse.Subscription subscription) + { + await PublishToTopic(cacheName, topicName, messageValue, readwriteTopicClient); + await ExpectTextFromSubscription(subscription, messageValue); + } + } + + [Fact] + public async Task GenerateDisposableTopicAuthToken_ReadOnly_HappyPath() + { + const string messageValue = "hello"; + var readonlyTopicClient = await GetClientForTokenScope( + DisposableTokenScopes.TopicSubscribeOnly(cacheName, topicName) + ); + var subscribeResponse = await readonlyTopicClient.SubscribeAsync(cacheName, topicName); + Assert.True(subscribeResponse is TopicSubscribeResponse.Subscription); + var publishResponse = await readonlyTopicClient.PublishAsync(cacheName, topicName, messageValue); + AssertPermissionError(publishResponse); + await PublishToTopic(cacheName, topicName, messageValue); + if (subscribeResponse is TopicSubscribeResponse.Subscription subscription) + { + await ExpectTextFromSubscription(subscription, messageValue); + } + } + + [Fact(Skip = "Enable when SubscribeAsync returns an error")] + public async Task GenerateDisposableTopicAuthToken_WriteOnly_CantSubscribe() + { + var writeOnlyTopicClient = await GetClientForTokenScope( + DisposableTokenScopes.TopicPublishOnly(cacheName, topicName) + ); + var subscribeResponse = await writeOnlyTopicClient.SubscribeAsync(cacheName, topicName); + Assert.True(subscribeResponse is TopicSubscribeResponse.Error, $"expected error got {subscribeResponse}"); + } + + [Fact] + public async Task GenerateDisposableTopicAuthToken_WriteOnly_CanPublish() + { + // TODO: why am I failing intermittently? + const string messageValue = "hello"; + var writeOnlyTopicClient = await GetClientForTokenScope( + DisposableTokenScopes.TopicPublishOnly(cacheName, topicName) + ); + + var subscribeResponse = await topicClient.SubscribeAsync(cacheName, topicName); + Assert.True(subscribeResponse is TopicSubscribeResponse.Subscription); + + // `PublishToTopic` asserts a successful publish response + await PublishToTopic(cacheName, topicName, messageValue, writeOnlyTopicClient); + + if (subscribeResponse is TopicSubscribeResponse.Subscription subscription) + { + var subTask = ExpectTextFromSubscription(subscription, messageValue); + await PublishToTopic(cacheName, topicName, messageValue, writeOnlyTopicClient); + await subTask; + } + } + + [Fact] + public async Task GenerateDisposableTopicAuthToken_NoCachePerms_CantPublish() + { + var noCachePermsClient = await GetClientForTokenScope( + DisposableTokenScopes.TopicPublishSubscribe("notthecacheyourelookingfor", topicName) + ); + var response = await noCachePermsClient.PublishAsync(cacheName, topicName, "iamdoomed"); + AssertPermissionError(response); + } + + [Fact(Skip = "Enable when SubscribeAsync returns an error")] + public async Task GenerateDisposableTopicAuthToken_NoCachePerms_CantSubscribe() + { + Assert.True(false); + var noCachePermsClient = await GetClientForTokenScope( + DisposableTokenScopes.TopicPublishSubscribe("notthecacheyourelookingfor", topicName) + ); + var response = await noCachePermsClient.SubscribeAsync(cacheName, topicName); + AssertPermissionError(response); + } + + [Fact] + public async Task GenerateDisposableTopicAuthToken_NoTopicPerms_CantPublish() + { + var noCachePermsClient = await GetClientForTokenScope( + DisposableTokenScopes.TopicPublishSubscribe(cacheName, "notthetopicyourelookingfor") + ); + var response = await noCachePermsClient.PublishAsync(cacheName, topicName, "iamdoomed"); + AssertPermissionError(response); + } + + [Fact(Skip = "Enable when SubscribeAsync returns an error")] + public async Task GenerateDisposableTopicAuthToken_NoTopicPerms_CantSubscribe() + { + Assert.True(false); + var noCachePermsClient = await GetClientForTokenScope( + DisposableTokenScopes.TopicPublishSubscribe(cacheName, "notthetopicyourelookingfor") + ); + var response = await noCachePermsClient.SubscribeAsync(cacheName, topicName); + AssertPermissionError(response); + } +} +#endif From 26056b09ea72ae4a4d641664706b8ad0fbc60035 Mon Sep 17 00:00:00 2001 From: Pete Gautier Date: Tue, 19 Sep 2023 13:37:00 -0700 Subject: [PATCH 2/2] fix: remove stray comment --- tests/Integration/Momento.Sdk.Tests/AuthClientTopicTest.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/Integration/Momento.Sdk.Tests/AuthClientTopicTest.cs b/tests/Integration/Momento.Sdk.Tests/AuthClientTopicTest.cs index ec1e6793..fa56f3e6 100644 --- a/tests/Integration/Momento.Sdk.Tests/AuthClientTopicTest.cs +++ b/tests/Integration/Momento.Sdk.Tests/AuthClientTopicTest.cs @@ -279,7 +279,6 @@ public async Task GenerateDisposableTopicAuthToken_WriteOnly_CantSubscribe() [Fact] public async Task GenerateDisposableTopicAuthToken_WriteOnly_CanPublish() { - // TODO: why am I failing intermittently? const string messageValue = "hello"; var writeOnlyTopicClient = await GetClientForTokenScope( DisposableTokenScopes.TopicPublishOnly(cacheName, topicName)