diff --git a/Keen.NET.Test/AccessKeyMock.cs b/Keen.NET.Test/AccessKeyMock.cs new file mode 100644 index 0000000..682bfdd --- /dev/null +++ b/Keen.NET.Test/AccessKeyMock.cs @@ -0,0 +1,35 @@ +using Keen.Core.AccessKey; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Newtonsoft.Json.Linq; +using Keen.Core; + +namespace Keen.Net.Test +{ + /// + /// AccessKeyMock provides an implementation of IAccessKeys with a constructor that + /// accepts delegates for each of the interface methods. + /// The purpose of this is to allow test methods to set up a customized + /// IAccessKeys for each test. + /// + class AccessKeysMock : IAccessKeys + { + private readonly IProjectSettings _settings; + private readonly Func _createAccessKey; + + public AccessKeysMock(IProjectSettings projSettings, + Func createAccessKey = null) + { + _settings = projSettings; + _createAccessKey = createAccessKey ?? ((p, k) => new JObject()); + } + + public Task CreateAccessKey(AccessKey accesskey) + { + return Task.Run(() => _createAccessKey(accesskey, _settings)); + } + } +} diff --git a/Keen.NET.Test/AccessKeyTests.cs b/Keen.NET.Test/AccessKeyTests.cs new file mode 100644 index 0000000..4a2e301 --- /dev/null +++ b/Keen.NET.Test/AccessKeyTests.cs @@ -0,0 +1,91 @@ +using Keen.Core; +using Keen.Core.AccessKey; +using Keen.Core.Query; +using Newtonsoft.Json.Linq; +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Keen.Net.Test +{ + [TestFixture] + class AccessKeyTests : TestBase + { + [Test] + public void CreateAccessKey_Success() + { + var settings = new ProjectSettingsProvider(projectId: "X", masterKey: SettingsEnv.MasterKey); // Replace X with respective value + var client = new KeenClient(settings); + + if (UseMocks) + client.AccessKeys = new AccessKeysMock(settings, + createAccessKey: new Func((e, p) => + { + Assert.True(p == settings, "Incorrect Settings"); + Assert.NotNull(e.Name, "Expected a name for the newly created Key"); + Assert.NotNull(e.Permitted, "Expected a list of high level actions this key can perform"); + Assert.NotNull(e.Options, "Expected an object containing more details about the key’s permitted and restricted functionality"); + if ((p == settings) && (e.Name == "TestAccessKey") && (e.IsActive) && e.Permitted.First() == "queries" && e.Options.CachedQueries.Allowed.First() == "my_cached_query") + return new JObject(); + else + throw new Exception("Unexpected value"); + })); + + HashSet permissions = new HashSet() { "queries" }; + List qFilters = new List() { new QueryFilter("customer.id", QueryFilter.FilterOperator.Equals(), "asdf12345z") }; + CachedQueries cachedQueries = new CachedQueries(); + cachedQueries.Allowed = new HashSet() { "my_cached_query" }; + Options options = new Options() + { + Queries = new Core.AccessKey.Queries { Filters = qFilters }, + CachedQueries = cachedQueries + }; + + Assert.DoesNotThrow(() => client.CreateAccessKey(new AccessKey { Name = "TestAccessKey", IsActive = true, Options = options, Permitted = permissions })); + } + + + [Test] + public void CreateAccessKey_With_All_Properties_Given_As_Null_Success() + { + var settings = new ProjectSettingsProvider(projectId: "X", masterKey: SettingsEnv.MasterKey); // Replace X with respective value + var client = new KeenClient(settings); + + if (UseMocks) + client.AccessKeys = new AccessKeysMock(settings, + createAccessKey: new Func((e, p) => + { + Assert.True(p == settings, "Incorrect Settings"); + Assert.NotNull(e.Name, "Expected a name for the newly created Key"); + Assert.NotNull(e.Permitted, "Expected a list of high level actions this key can perform"); + Assert.NotNull(e.Options, "Expected an object containing more details about the key’s permitted and restricted functionality"); + if ((p == settings) && (e.Name == "TestAccessKey") && (e.IsActive) && e.Permitted.First() == "queries" && e.Options.CachedQueries.Allowed.First() == "my_cached_query") + return new JObject(); + else + throw new Exception("Unexpected value"); + })); + + HashSet permissions = new HashSet() { "queries" }; + List qFilters = new List() { new QueryFilter("customer.id", QueryFilter.FilterOperator.Equals(), "asdf12345z") }; + CachedQueries cachedQueries = new CachedQueries() { Allowed = null, Blocked = null }; + SavedQueries savedQuaries = new SavedQueries() { Allowed = null, Blocked = null, Filters = null }; + Datasets datasets = new Datasets() { Allowed = null, Blocked = null, Operations = null }; + Writes writes = new Writes() { Autofill = null }; + cachedQueries.Allowed = new HashSet() { "my_cached_query" }; + Options options = new Options() + { + Queries = new Core.AccessKey.Queries { Filters = qFilters }, + CachedQueries = cachedQueries, + SavedQueries = savedQuaries, + Datasets = datasets, + Writes = writes + }; + + Assert.DoesNotThrow(() => client.CreateAccessKey(new AccessKey { Name = "TestAccessKey", IsActive = true, Options = options, Permitted = permissions })); + } + + } +} diff --git a/Keen.NET.Test/Keen.NET.Test.csproj b/Keen.NET.Test/Keen.NET.Test.csproj index 3f05425..f8ad2ac 100644 --- a/Keen.NET.Test/Keen.NET.Test.csproj +++ b/Keen.NET.Test/Keen.NET.Test.csproj @@ -99,6 +99,8 @@ Properties\SharedVersionInfo.cs + + diff --git a/Keen.NET.Test/KeenClientTest.cs b/Keen.NET.Test/KeenClientTest.cs index 794d057..d76603f 100644 --- a/Keen.NET.Test/KeenClientTest.cs +++ b/Keen.NET.Test/KeenClientTest.cs @@ -1,4 +1,6 @@ using Keen.Core; +using Keen.Core.AccessKey; +using Keen.Core.Query; using Keen.Core.EventCache; using Moq; using Newtonsoft.Json.Linq; diff --git a/Keen/AccessKey/AccessKey.cs b/Keen/AccessKey/AccessKey.cs new file mode 100644 index 0000000..44eb3dd --- /dev/null +++ b/Keen/AccessKey/AccessKey.cs @@ -0,0 +1,83 @@ +using Keen.Core.Query; +using System; +using System.Collections.Generic; + +namespace Keen.Core.AccessKey +{ + /// + /// Model for AccessKey object + /// + public class AccessKey + { + public string Name { get; set; } + public bool IsActive { get; set; } + public ISet Permitted { get; set; } + public string Key { get; set; } + public Options Options { get; set; } + } + + /// + /// When SavedQueries are permitted, the Access Key will have access to run saved queries. + /// + public class SavedQueries + { + public ISet Blocked { get; set; } + public IEnumerable Filters { get; set; } + public ISet Allowed { get; set; } + } + + /// + /// When Queries are permitted, the Access Key will have the ability to do ad-hoc queries. + /// + public class Queries + { + public IEnumerable Filters { get; set; } + } + + /// + /// When Writes are permitted, the Access Key will have the ability to stream data to Keen. + /// + public class Writes + { + public dynamic Autofill { get; set; } // "customer": { "id": "93iskds39kd93id", "name": "Ada Corp." } + } + + /// + /// When Datasets are permitted, the Access Key will have access to getting a dataset definition, retrieving cached dataset results, and listing cached datasets definitions for a project. + /// + public class Datasets + { + public IEnumerable Operations { get; set; } + public IDictionary Allowed { get; set; } + public ISet Blocked { get; set; } + } + + /// + /// Optionals limiting of allowed datasets in the access key by index + /// + public class AllowedDatasetIndexes + { + public Tuple IndexBy { get; set;} + } + + /// + /// When CachedQueries are permitted, the Access Key will have access to retrieve results from cached queries. + /// + public class CachedQueries + { + public ISet Blocked { get; set; } + public ISet Allowed { get; set; } + } + + /// + /// An object containing more details about the key’s permitted and restricted functionality. + /// + public class Options + { + public SavedQueries SavedQueries { get; set; } + public Writes Writes { get; set; } + public Datasets Datasets { get; set; } + public CachedQueries CachedQueries { get; set; } + public Queries Queries { get; set; } + } +} diff --git a/Keen/AccessKey/AccessKeys.cs b/Keen/AccessKey/AccessKeys.cs new file mode 100644 index 0000000..7480cc5 --- /dev/null +++ b/Keen/AccessKey/AccessKeys.cs @@ -0,0 +1,100 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Newtonsoft.Json.Serialization; +using System; +using System.Threading.Tasks; + +namespace Keen.Core.AccessKey +{ + /// + /// AccessKeys implements the IAccessKeys interface which represents the Keen.IO Access Key API methods. + /// + public class AccessKeys : IAccessKeys + { + private readonly IKeenHttpClient _keenHttpClient; + private readonly string _accesKeyRelativeUrl; + private readonly string _readKey; + private readonly string _masterKey; + + internal AccessKeys(IProjectSettings prjSettings, + IKeenHttpClientProvider keenHttpClientProvider) + { + if (null == prjSettings) + { + throw new ArgumentNullException(nameof(prjSettings), + "Project Settings must be provided."); + } + + if (null == keenHttpClientProvider) + { + throw new ArgumentNullException(nameof(keenHttpClientProvider), + "A KeenHttpClient provider must be provided."); + } + + if (string.IsNullOrWhiteSpace(prjSettings.KeenUrl) || + !Uri.IsWellFormedUriString(prjSettings.KeenUrl, UriKind.Absolute)) + { + throw new KeenException( + "A properly formatted KeenUrl must be provided via Project Settings."); + } + + var serverBaseUrl = new Uri(prjSettings.KeenUrl); + _keenHttpClient = keenHttpClientProvider.GetForUrl(serverBaseUrl); + _accesKeyRelativeUrl = KeenHttpClient.GetRelativeUrl(prjSettings.ProjectId, + KeenConstants.AccessKeyResource); + + _readKey = prjSettings.ReadKey; + _masterKey = prjSettings.MasterKey; + } + + public async Task CreateAccessKey(AccessKey accesskey) + { + if (string.IsNullOrWhiteSpace(_masterKey)) + { + throw new KeenException("An API WriteKey is required to add events."); + } + + DefaultContractResolver contractResolver = new DefaultContractResolver + { + NamingStrategy = new SnakeCaseNamingStrategy() + }; + + var content = JsonConvert.SerializeObject(accesskey, new JsonSerializerSettings + { + ContractResolver = contractResolver, + Formatting = Formatting.Indented + }).ToSafeString(); + + var responseMsg = await _keenHttpClient + .PostAsync(_accesKeyRelativeUrl, _masterKey, content) + .ConfigureAwait(continueOnCapturedContext: false); + + var responseString = await responseMsg + .Content + .ReadAsStringAsync() + .ConfigureAwait(continueOnCapturedContext: false); + + JObject jsonResponse = null; + + try + { + jsonResponse = JObject.Parse(responseString); + } + catch (Exception) + { + // To avoid any flow stoppers + } + if (!responseMsg.IsSuccessStatusCode) + { + throw new KeenException("AddEvents failed with status: " + responseMsg.StatusCode); + } + + if (null == jsonResponse) + { + throw new KeenException("AddEvents failed with empty response from server."); + } + + return jsonResponse; + } + } +} diff --git a/Keen/AccessKey/IAccessKeys.cs b/Keen/AccessKey/IAccessKeys.cs new file mode 100644 index 0000000..96bc9ea --- /dev/null +++ b/Keen/AccessKey/IAccessKeys.cs @@ -0,0 +1,18 @@ +using Newtonsoft.Json.Linq; +using System.Threading.Tasks; + +namespace Keen.Core.AccessKey +{ + /// + /// Public interface for Access Key related functionalities + /// + public interface IAccessKeys + { + /// + /// Creates an Access Key + /// + /// + /// + Task CreateAccessKey(AccessKey accesskey); + } +} diff --git a/Keen/Keen.csproj b/Keen/Keen.csproj index 5744708..b6bb48e 100644 --- a/Keen/Keen.csproj +++ b/Keen/Keen.csproj @@ -52,6 +52,9 @@ Properties\SharedVersionInfo.cs + + + diff --git a/Keen/KeenClient.cs b/Keen/KeenClient.cs index b932e92..c4d7506 100644 --- a/Keen/KeenClient.cs +++ b/Keen/KeenClient.cs @@ -1,6 +1,7 @@ using Keen.Core.DataEnrichment; using Keen.Core.EventCache; using Keen.Core.Query; +using Keen.Core.AccessKey; using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; @@ -51,6 +52,12 @@ public class KeenClient public IDataset Datasets { get; set; } + /// + /// AccessKeys provides direct access to the Keen.IO Access Keys API methods. + /// The default implementation can be overridden by setting a new implementation here. + /// + public IAccessKeys AccessKeys { get; set; } + /// /// Add a static global property. This property will be added to /// every event. @@ -118,8 +125,9 @@ private KeenClient(IProjectSettings prjSettings, // implementation via their respective properties. EventCollection = new EventCollection(_prjSettings, keenHttpClientProvider); Event = new Event(_prjSettings, keenHttpClientProvider); - Queries = new Queries(_prjSettings, keenHttpClientProvider); - Datasets = new Datasets(_prjSettings, keenHttpClientProvider); + Queries = new Query.Queries(_prjSettings, keenHttpClientProvider); + AccessKeys = new AccessKeys(_prjSettings, keenHttpClientProvider); + Datasets = new Dataset.Datasets(_prjSettings, keenHttpClientProvider); } /// @@ -974,8 +982,7 @@ public IEnumerable + /// Get query results from a Cached Dataset. /// /// Name of cached dataset to query. @@ -1146,5 +1153,32 @@ public void DeleteDataset(string datasetName) throw ex.TryUnwrap(); } } + + /// + /// + public void CreateAccessKey(AccessKey.AccessKey accessKey) + { + try + { + CreateAccessKeyAsync(accessKey).Wait(); + } + catch (AggregateException ex) + { + Debug.WriteLine(ex.TryUnwrap()); + } + } + + /// + /// + private async Task CreateAccessKeyAsync(AccessKey.AccessKey accessKey) + { + if (null == accessKey) + throw new KeenException("Access Key required"); + + var createdKey = await AccessKeys.CreateAccessKey(accessKey) + .ConfigureAwait(false); + + return createdKey; + } } } diff --git a/Keen/KeenConstants.cs b/Keen/KeenConstants.cs index 0b1c6a4..bb4574b 100644 --- a/Keen/KeenConstants.cs +++ b/Keen/KeenConstants.cs @@ -9,6 +9,9 @@ public class KeenConstants private const string eventsResource = "events"; public static string EventsResource { get { return eventsResource; } protected set { ;} } + private const string accesskeyResource = "keys"; + public static string AccessKeyResource { get { return accesskeyResource; } protected set {; } } + private const string queriesResource = "queries"; public static string QueriesResource { get { return queriesResource; } protected set { ;} }