Skip to content

Commit fe20724

Browse files
author
gauffininteractive
committed
Almost done with API keys
1 parent cd63903 commit fe20724

File tree

98 files changed

+1761
-5019
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

98 files changed

+1761
-5019
lines changed
Lines changed: 47 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,77 @@
11
using System;
2-
using System.Collections.Specialized;
32
using System.Net;
3+
using System.Security.Cryptography;
44
using System.Text;
55
using System.Threading.Tasks;
66
using DotNetCqs;
77
using Newtonsoft.Json;
88
using OneTrueError.Api.Client.Json;
9-
using OneTrueError.Api.Client.Tests;
109

1110
namespace OneTrueError.Api.Client
1211
{
1312
/// <summary>
14-
/// Client for the OneTrueError server API
13+
/// Client for the OneTrueError server API
1514
/// </summary>
1615
public class OneTrueClient : IQueryBus, ICommandBus, IEventBus
1716
{
18-
private CookieContainer _cookies;
19-
private Encoding _basicAuthEncoding = Encoding.GetEncoding("ISO-8859-1");
17+
private readonly string _apiKey;
2018

2119
private readonly JsonSerializerSettings _jsonSerializerSettings = new JsonSerializerSettings
2220
{
2321
ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor,
2422
Formatting = Formatting.Indented
2523
};
2624

25+
private readonly string _sharedSecret;
2726
private Uri _uri;
2827

2928

30-
public OneTrueClient()
29+
/// <summary>
30+
/// Creates a new instance of <see cref="OneTrueClient" />.
31+
/// </summary>
32+
/// <param name="apiKey">Api key from the admin area in OneTrueError web</param>
33+
/// <param name="sharedSecret">Shared secret from the admin area in OneTrueError web</param>
34+
public OneTrueClient(string apiKey, string sharedSecret)
3135
{
36+
if (apiKey == null) throw new ArgumentNullException(nameof(apiKey));
37+
if (sharedSecret == null) throw new ArgumentNullException(nameof(sharedSecret));
38+
39+
_apiKey = apiKey;
40+
_sharedSecret = sharedSecret;
3241
_jsonSerializerSettings.ContractResolver = new IncludeNonPublicMembersContractResolver();
3342
}
3443

35-
public NetworkCredential Credentials { get; set; }
36-
44+
/// <summary>
45+
/// Execute a command
46+
/// </summary>
47+
/// <typeparam name="T">type of query (from the <c>OneTrueError.Api</c> class library)</typeparam>
48+
/// <param name="command">command to execute</param>
49+
/// <returns>task</returns>
3750
public async Task ExecuteAsync<T>(T command) where T : Command
3851
{
3952
var response = await RequestAsync("POST", "command", command);
4053
response.Close();
4154
}
4255

56+
/// <summary>
57+
/// Publish an event
58+
/// </summary>
59+
/// <typeparam name="TApplicationEvent">type of event (from the <c>OneTrueError.Api</c> class library)</typeparam>
60+
/// <param name="e">event to publish</param>
61+
/// <returns>task</returns>
4362
public async Task PublishAsync<TApplicationEvent>(TApplicationEvent e)
4463
where TApplicationEvent : ApplicationEvent
4564
{
4665
var response = await RequestAsync("POST", "event", e);
4766
response.Close();
4867
}
4968

69+
/// <summary>
70+
/// Make a query
71+
/// </summary>
72+
/// <typeparam name="TResult">Result from a query (a class from the <c>OneTrueError.Api</c> library)</typeparam>
73+
/// <param name="query"></param>
74+
/// <returns></returns>
5075
public async Task<TResult> QueryAsync<TResult>(Query<TResult> query)
5176
{
5277
//TODO: Unwrap the cqs object to query parameters instead
@@ -55,12 +80,15 @@ public async Task<TResult> QueryAsync<TResult>(Query<TResult> query)
5580
return await DeserializeResponse<TResult>(response);
5681
}
5782

83+
/// <summary>
84+
/// Open a channel
85+
/// </summary>
86+
/// <param name="uri">Root URL to the OneTrueError web</param>
5887
public void Open(Uri uri)
5988
{
6089
if (uri == null) throw new ArgumentNullException(nameof(uri));
6190

6291
_uri = uri;
63-
_cookies = new CookieContainer();
6492
}
6593

6694
private async Task<TResult> DeserializeResponse<TResult>(HttpWebResponse response)
@@ -70,44 +98,29 @@ private async Task<TResult> DeserializeResponse<TResult>(HttpWebResponse respons
7098
await responseStream.ReadAsync(jsonBuf, 0, jsonBuf.Length);
7199
var jsonStr = Encoding.UTF8.GetString(jsonBuf);
72100
var responseObj = JsonConvert.DeserializeObject(jsonStr, typeof(TResult), _jsonSerializerSettings);
73-
return (TResult)responseObj;
101+
return (TResult) responseObj;
74102
}
75103

76104
private async Task<HttpWebResponse> RequestAsync(string httpMethod, string cqsType, object cqsObject)
77105
{
78-
if (_cookies.Count == 0)
79-
{
80-
var sb = new StringBuilder();
81-
sb.AppendUrlEncoded("username", Credentials.UserName);
82-
sb.AppendUrlEncoded("password", Credentials.Password);
83-
var data = Encoding.UTF8.GetBytes(sb.ToString());
84-
85-
var authRequest = WebRequest.CreateHttp(_uri + "/account/login");
86-
authRequest.Method = "POST";
87-
authRequest.CookieContainer = _cookies;
88-
authRequest.ContentType = "application/x-www-form-urlencoded";
89-
authRequest.ContentLength = data.Length;
90-
var authReqStream = await authRequest.GetRequestStreamAsync();
91-
await authReqStream.WriteAsync(data, 0, data.Length);
92-
var resp = authRequest.GetResponse();
93-
94-
}
95-
string authInfo = Credentials.UserName + ":" + Credentials.Password;
96-
authInfo = Convert.ToBase64String(_basicAuthEncoding.GetBytes(authInfo));
97-
98106
var request = WebRequest.CreateHttp(_uri + "api/cqs");
99107
request.Method = httpMethod;
100-
request.Headers.Add("Authorization", "Basic " + authInfo);
108+
request.Headers.Add("X-Api-Key", _apiKey);
101109
request.Headers.Add("X-Cqs-Name", cqsObject.GetType().Name);
102-
//request.PreAuthenticate = true;
103-
request.CookieContainer = _cookies;
104110

105111
var stream = await request.GetRequestStreamAsync();
106112
var json = JsonConvert.SerializeObject(cqsObject, _jsonSerializerSettings);
107113
var buffer = Encoding.UTF8.GetBytes(json);
114+
115+
var hamc = new HMACSHA256(Encoding.UTF8.GetBytes(_sharedSecret.ToLower()));
116+
var hash = hamc.ComputeHash(buffer);
117+
var signature = Convert.ToBase64String(hash);
118+
108119
await stream.WriteAsync(buffer, 0, buffer.Length);
109120

110-
return (HttpWebResponse)await request.GetResponseAsync();
121+
request.Headers.Add("X-Api-Signature", signature);
122+
123+
return (HttpWebResponse) await request.GetResponseAsync();
111124
}
112125
}
113126
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
using System;
2+
3+
namespace OneTrueError.Api
4+
{
5+
[AttributeUsage(AttributeTargets.Class)]
6+
public class AuthorizeAttribute : Attribute
7+
{
8+
public AuthorizeAttribute(params string[] roles)
9+
{
10+
if (roles == null) throw new ArgumentNullException("roles");
11+
Roles = roles;
12+
}
13+
14+
public string[] Roles { get; set; }
15+
}
16+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
using System;
2+
using DotNetCqs;
3+
4+
namespace OneTrueError.Api.Core.ApiKeys.Commands
5+
{
6+
/// <summary>
7+
/// Create a new api key
8+
/// </summary>
9+
[Authorize("SysAdmin")]
10+
public class CreateApiKey : Command
11+
{
12+
/// <summary>
13+
/// Creates a new instance of <see cref="CreateApiKey"/>.
14+
/// </summary>
15+
/// <param name="applicationName"><see cref="ApplicationName"/></param>
16+
/// <param name="apiKey"><see cref="ApiKey"/></param>
17+
/// <param name="sharedSecret"><see cref="SharedSecret"/></param>
18+
/// <param name="applicationIds"><see cref="ApplicationIds"/></param>
19+
public CreateApiKey(string applicationName, string apiKey, string sharedSecret, int[] applicationIds)
20+
{
21+
if (applicationName == null) throw new ArgumentNullException("applicationName");
22+
if (apiKey == null) throw new ArgumentNullException("apiKey");
23+
if (sharedSecret == null) throw new ArgumentNullException("sharedSecret");
24+
if (applicationIds == null) throw new ArgumentNullException("applicationIds");
25+
26+
ApplicationName = applicationName;
27+
ApiKey = apiKey;
28+
SharedSecret = sharedSecret;
29+
ApplicationIds = applicationIds;
30+
}
31+
32+
33+
/// <summary>
34+
/// Must always be the one that creates the key (will be assigned by the CommandBus per convention)
35+
/// </summary>
36+
public int AccountId { get; set; }
37+
38+
/// <summary>
39+
/// Generated api key
40+
/// </summary>
41+
public string ApiKey { get; set; }
42+
43+
/// <summary>
44+
/// applications that this key may modify. Empty = allow for all applications.
45+
/// </summary>
46+
public int[] ApplicationIds { get; set; }
47+
48+
/// <summary>
49+
/// Application that uses this api key
50+
/// </summary>
51+
public string ApplicationName { get; set; }
52+
53+
/// <summary>
54+
/// Used to sign all requests.
55+
/// </summary>
56+
public string SharedSecret { get; set; }
57+
}
58+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
using System;
2+
using DotNetCqs;
3+
4+
namespace OneTrueError.Api.Core.ApiKeys.Commands
5+
{
6+
/// <summary>
7+
/// Delete an API key.
8+
/// </summary>
9+
public class DeleteApiKey : Command
10+
{
11+
/// <summary>
12+
/// Serialization constructor
13+
/// </summary>
14+
protected DeleteApiKey()
15+
{
16+
}
17+
18+
/// <summary>
19+
/// Creates a new instance of <see cref="DeleteApiKey" />.
20+
/// </summary>
21+
/// <param name="id">PK</param>
22+
public DeleteApiKey(int id)
23+
{
24+
Id = id;
25+
}
26+
27+
/// <summary>
28+
/// Creates a new instance of <see cref="DeleteApiKey" />.
29+
/// </summary>
30+
/// <param name="apiKey">The generated ApiKey</param>
31+
public DeleteApiKey(string apiKey)
32+
{
33+
if (apiKey == null) throw new ArgumentNullException("apiKey");
34+
Guid guid;
35+
if (!Guid.TryParse(apiKey, out guid))
36+
throw new ArgumentException("Not a valid api key: " + apiKey, "apiKey");
37+
38+
ApiKey = apiKey;
39+
}
40+
41+
/// <summary>
42+
/// generated api key (if specified)
43+
/// </summary>
44+
public string ApiKey { get; private set; }
45+
46+
/// <summary>
47+
/// PK (if specified)
48+
/// </summary>
49+
public int Id { get; private set; }
50+
}
51+
}
52+
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
using System;
2+
using DotNetCqs;
3+
4+
namespace OneTrueError.Api.Core.ApiKeys.Queries
5+
{
6+
/// <summary>
7+
/// Get information about an API key
8+
/// </summary>
9+
public class GetApiKey : Query<GetApiKeyResult>
10+
{
11+
/// <summary>
12+
/// Serialization constructor
13+
/// </summary>
14+
protected GetApiKey()
15+
{
16+
}
17+
18+
/// <summary>
19+
/// Creates a new instance of <see cref="GetApiKey" />.
20+
/// </summary>
21+
/// <param name="id">PK</param>
22+
public GetApiKey(int id)
23+
{
24+
Id = id;
25+
}
26+
27+
/// <summary>
28+
/// Creates a new instance of <see cref="GetApiKey" />.
29+
/// </summary>
30+
/// <param name="apiKey">The generated ApiKey</param>
31+
public GetApiKey(string apiKey)
32+
{
33+
if (apiKey == null) throw new ArgumentNullException("apiKey");
34+
Guid guid;
35+
if (!Guid.TryParse(apiKey, out guid))
36+
throw new ArgumentException("Not a valid api key: " + apiKey, "apiKey");
37+
38+
ApiKey = apiKey;
39+
}
40+
41+
/// <summary>
42+
/// generated api key (if specified)
43+
/// </summary>
44+
public string ApiKey { get; private set; }
45+
46+
/// <summary>
47+
/// PK (if specified)
48+
/// </summary>
49+
public int Id { get; private set; }
50+
}
51+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
using System;
2+
3+
namespace OneTrueError.Api.Core.ApiKeys.Queries
4+
{
5+
/// <summary>
6+
/// Result for <see cref="GetApiKey" />.
7+
/// </summary>
8+
public class GetApiKeyResult
9+
{
10+
/// <summary>
11+
/// Application ids that we've been granted to work with
12+
/// </summary>
13+
public GetApiKeyResultApplication[] AllowedApplications { get; set; }
14+
15+
/// <summary>
16+
/// Application that will be using this key
17+
/// </summary>
18+
public string ApplicationName { get; set; }
19+
20+
21+
/// <summary>
22+
/// When this key was generated
23+
/// </summary>
24+
public DateTime CreatedAtUtc { get; set; }
25+
26+
/// <summary>
27+
/// AccountId that generated this key
28+
/// </summary>
29+
public int CreatedById { get; set; }
30+
31+
/// <summary>
32+
/// Api key
33+
/// </summary>
34+
public string GeneratedKey { get; set; }
35+
36+
37+
/// <summary>
38+
/// PK
39+
/// </summary>
40+
public int Id { get; set; }
41+
42+
43+
/// <summary>
44+
/// Used when generating signatures.
45+
/// </summary>
46+
public string SharedSecret { get; set; }
47+
}
48+
}

0 commit comments

Comments
 (0)