Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create Access Key #129

Open
wants to merge 17 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions Keen.NET.Test/AccessKeyMock.cs
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// 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.
/// </summary>
class AccessKeysMock : IAccessKeys
{
private readonly IProjectSettings _settings;
private readonly Func<AccessKey, IProjectSettings, JObject> _createAccessKey;

public AccessKeysMock(IProjectSettings projSettings,
Func<AccessKey, IProjectSettings, JObject> createAccessKey = null)
{
_settings = projSettings;
_createAccessKey = createAccessKey ?? ((p, k) => new JObject());
}

public Task<JObject> CreateAccessKey(AccessKey accesskey)
{
return Task.Run(() => _createAccessKey(accesskey, _settings));
}
}
}
91 changes: 91 additions & 0 deletions Keen.NET.Test/AccessKeyTests.cs
Original file line number Diff line number Diff line change
@@ -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<AccessKey, IProjectSettings, JObject>((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<string> permissions = new HashSet<string>() { "queries" };
List<QueryFilter> qFilters = new List<QueryFilter>() { new QueryFilter("customer.id", QueryFilter.FilterOperator.Equals(), "asdf12345z") };
CachedQueries cachedQueries = new CachedQueries();
cachedQueries.Allowed = new HashSet<string>() { "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<AccessKey, IProjectSettings, JObject>((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<string> permissions = new HashSet<string>() { "queries" };
List<QueryFilter> qFilters = new List<QueryFilter>() { 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<string>() { "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 }));
}

}
}
2 changes: 2 additions & 0 deletions Keen.NET.Test/Keen.NET.Test.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@
<Compile Include="..\SharedVersionInfo.cs">
<Link>Properties\SharedVersionInfo.cs</Link>
</Compile>
<Compile Include="AccessKeyMock.cs" />
<Compile Include="AccessKeyTests.cs" />
<Compile Include="AddOnsTest.cs" />
<Compile Include="DatasetTests_Integration.cs" />
<Compile Include="DatasetTests.cs" />
Expand Down
2 changes: 2 additions & 0 deletions Keen.NET.Test/KeenClientTest.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
83 changes: 83 additions & 0 deletions Keen/AccessKey/AccessKey.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
using Keen.Core.Query;
using System;
using System.Collections.Generic;

namespace Keen.Core.AccessKey
{
/// <summary>
/// Model for AccessKey object
/// </summary>
public class AccessKey
{
public string Name { get; set; }
public bool IsActive { get; set; }
public ISet<string> Permitted { get; set; }
public string Key { get; set; }
public Options Options { get; set; }
}

/// <summary>
/// When SavedQueries are permitted, the Access Key will have access to run saved queries.
/// </summary>
public class SavedQueries
{
public ISet<string> Blocked { get; set; }
public IEnumerable<QueryFilter> Filters { get; set; }
public ISet<string> Allowed { get; set; }
}

/// <summary>
/// When Queries are permitted, the Access Key will have the ability to do ad-hoc queries.
/// </summary>
public class Queries
{
public IEnumerable<QueryFilter> Filters { get; set; }
}

/// <summary>
/// When Writes are permitted, the Access Key will have the ability to stream data to Keen.
/// </summary>
public class Writes
{
public dynamic Autofill { get; set; } // "customer": { "id": "93iskds39kd93id", "name": "Ada Corp." }
}

/// <summary>
/// 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.
/// </summary>
public class Datasets
{
public IEnumerable<string> Operations { get; set; }
public IDictionary<string, AllowedDatasetIndexes> Allowed { get; set; }
public ISet<string> Blocked { get; set; }
}

/// <summary>
/// Optionals limiting of allowed datasets in the access key by index
/// </summary>
public class AllowedDatasetIndexes
{
public Tuple<string, string> IndexBy { get; set;}
}

/// <summary>
/// When CachedQueries are permitted, the Access Key will have access to retrieve results from cached queries.
/// </summary>
public class CachedQueries
{
public ISet<string> Blocked { get; set; }
public ISet<string> Allowed { get; set; }
}

/// <summary>
/// An object containing more details about the key’s permitted and restricted functionality.
/// </summary>
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; }
}
}
100 changes: 100 additions & 0 deletions Keen/AccessKey/AccessKeys.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Serialization;
using System;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't forget to organize and remove unnecessary usings.

using System.Threading.Tasks;

namespace Keen.Core.AccessKey
{
/// <summary>
/// AccessKeys implements the IAccessKeys interface which represents the Keen.IO Access Key API methods.
/// </summary>
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<JObject> 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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was this because you were seeing a 404 error or something similar and it's actually OK in this case to swallow the exception? Then will we throw below? I know we do this elsewhere, and we should really keep the inner exception around for context, but that's another issue. At least if that's the case, let's put a comment here explaining the empty catch(){} block.

{
// 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;
}
}
}
18 changes: 18 additions & 0 deletions Keen/AccessKey/IAccessKeys.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using Newtonsoft.Json.Linq;
using System.Threading.Tasks;

namespace Keen.Core.AccessKey
{
/// <summary>
/// Public interface for Access Key related functionalities
/// </summary>
public interface IAccessKeys
{
/// <summary>
/// Creates an Access Key
/// </summary>
/// <param name="accesskey"></param>
/// <returns></returns>
Task<JObject> CreateAccessKey(AccessKey accesskey);
Copy link
Contributor

@masojus masojus Nov 3, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We prefer Task-returning methods to use the ...Async(...) naming guideline.

}
}
3 changes: 3 additions & 0 deletions Keen/Keen.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@
<Compile Include="..\SharedVersionInfo.cs">
<Link>Properties\SharedVersionInfo.cs</Link>
</Compile>
<Compile Include="AccessKey\AccessKey.cs" />
<Compile Include="AccessKey\AccessKeys.cs" />
<Compile Include="AccessKey\IAccessKeys.cs" />
<Compile Include="Dataset\DatasetDefinition.cs" />
<Compile Include="Dataset\DatasetDefinitionCollection.cs" />
<Compile Include="Dataset\Datasets.cs" />
Expand Down
Loading