Skip to content
Merged
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{62989500-31BC-44FA-97CA-84F484C6F1AA}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>OneTrueError.Api.Client.Tests</RootNamespace>
<AssemblyName>OneTrueError.Api.Client.Tests</AssemblyName>
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="DotNetCqs, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\DotNetCqs.1.0.0\lib\net45\DotNetCqs.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="FluentAssertions, Version=4.14.0.0, Culture=neutral, PublicKeyToken=33f2691a05b67b6a, processorArchitecture=MSIL">
<HintPath>..\packages\FluentAssertions.4.14.0\lib\net45\FluentAssertions.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="FluentAssertions.Core, Version=4.14.0.0, Culture=neutral, PublicKeyToken=33f2691a05b67b6a, processorArchitecture=MSIL">
<HintPath>..\packages\FluentAssertions.4.14.0\lib\net45\FluentAssertions.Core.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
<Reference Include="xunit.abstractions, Version=2.0.0.0, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL">
<HintPath>..\packages\xunit.abstractions.2.0.0\lib\net35\xunit.abstractions.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="xunit.assert, Version=2.1.0.3179, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL">
<HintPath>..\packages\xunit.assert.2.1.0\lib\dotnet\xunit.assert.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="xunit.core, Version=2.1.0.3179, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL">
<HintPath>..\packages\xunit.extensibility.core.2.1.0\lib\dotnet\xunit.core.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="xunit.execution.desktop, Version=2.1.0.3179, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL">
<HintPath>..\packages\xunit.extensibility.execution.2.1.0\lib\net45\xunit.execution.desktop.dll</HintPath>
<Private>True</Private>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="TryTheClient.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config">
<SubType>Designer</SubType>
</None>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\OneTrueError.Api.Client\OneTrueError.Api.Client.csproj">
<Project>{017F8863-3DE0-4AD2-9ED3-5ACB87BBBCD0}</Project>
<Name>OneTrueError.Api.Client</Name>
</ProjectReference>
<ProjectReference Include="..\OneTrueError.Api\OneTrueError.Api.csproj">
<Project>{fc331a95-fca4-4764-8004-0884665dd01f}</Project>
<Name>OneTrueError.Api</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("OneTrueError.Api.Client.Tests")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("OneTrueError.Api.Client.Tests")]
[assembly: AssemblyCopyright("Copyright © 2016")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]

// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]

// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("62989500-31bc-44fa-97ca-84f484c6f1aa")]

// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
37 changes: 37 additions & 0 deletions src/Server/OneTrueError.Api.Client.Tests/TryTheClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using FluentAssertions;
using OneTrueError.Api.Core.Accounts.Queries;
using Xunit;

namespace OneTrueError.Api.Client.Tests
{
#if DEBUG
public class TryTheClient
{
[Fact]
public async Task Test()
{
OneTrueClient client = new OneTrueClient();
client.Credentials = new NetworkCredential("jonas", "123456");
client.Open(new Uri("http://localhost/onetrueerror/"));
FindAccountByUserNameResult result = null;
try
{
result = await client.QueryAsync(new FindAccountByUserName("admin"));
}
catch (WebException ex)
{

}


result.Should().NotBeNull();
}
}
#endif
}
11 changes: 11 additions & 0 deletions src/Server/OneTrueError.Api.Client.Tests/packages.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="DotNetCqs" version="1.0.0" targetFramework="net452" />
<package id="FluentAssertions" version="4.14.0" targetFramework="net452" />
<package id="xunit" version="2.1.0" targetFramework="net452" />
<package id="xunit.abstractions" version="2.0.0" targetFramework="net452" />
<package id="xunit.assert" version="2.1.0" targetFramework="net452" />
<package id="xunit.core" version="2.1.0" targetFramework="net452" />
<package id="xunit.extensibility.core" version="2.1.0" targetFramework="net452" />
<package id="xunit.extensibility.execution" version="2.1.0" targetFramework="net452" />
</packages>
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using System.Reflection;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;

namespace OneTrueError.Api.Client.Json
{
/// <summary>
/// Allows us to serialize properties with private setters.
/// </summary>
internal class IncludeNonPublicMembersContractResolver : DefaultContractResolver
{
/// <summary>
/// Creates a <see cref="T:Newtonsoft.Json.Serialization.JsonProperty" /> for the given
/// <see cref="T:System.Reflection.MemberInfo" />.
/// </summary>
/// <param name="memberSerialization">The member's parent <see cref="T:Newtonsoft.Json.MemberSerialization" />.</param>
/// <param name="member">The member to create a <see cref="T:Newtonsoft.Json.Serialization.JsonProperty" /> for.</param>
/// <returns>
/// A created <see cref="T:Newtonsoft.Json.Serialization.JsonProperty" /> for the given
/// <see cref="T:System.Reflection.MemberInfo" />.
/// </returns>
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
//TODO: Maybe cache
var prop = base.CreateProperty(member, memberSerialization);

if (!prop.Writable)
{
var property = member as PropertyInfo;
if (property != null)
{
var hasPrivateSetter = property.GetSetMethod(true) != null;
prop.Writable = hasPrivateSetter;
}
}

return prop;
}
}
}
125 changes: 125 additions & 0 deletions src/Server/OneTrueError.Api.Client/OneTrueClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
using System;
using System.Net;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using DotNetCqs;
using Newtonsoft.Json;
using OneTrueError.Api.Client.Json;

namespace OneTrueError.Api.Client
{
/// <summary>
/// Client for the OneTrueError server API
/// </summary>
public class OneTrueApiClient : IQueryBus, ICommandBus, IEventBus
{
private string _apiKey;

private readonly JsonSerializerSettings _jsonSerializerSettings = new JsonSerializerSettings
{
ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor,
Formatting = Formatting.Indented
};

private string _sharedSecret;
private Uri _uri;


/// <summary>
/// Creates a new instance of <see cref="OneTrueApiClient" />.
/// </summary>
public OneTrueApiClient() {

_jsonSerializerSettings.ContractResolver = new IncludeNonPublicMembersContractResolver();
}

/// <summary>
/// Execute a command
/// </summary>
/// <typeparam name="T">type of query (from the <c>OneTrueError.Api</c> class library)</typeparam>
/// <param name="command">command to execute</param>
/// <returns>task</returns>
public async Task ExecuteAsync<T>(T command) where T : Command
{
var response = await RequestAsync("POST", "command", command);
response.Close();
}

/// <summary>
/// Publish an event
/// </summary>
/// <typeparam name="TApplicationEvent">type of event (from the <c>OneTrueError.Api</c> class library)</typeparam>
/// <param name="e">event to publish</param>
/// <returns>task</returns>
public async Task PublishAsync<TApplicationEvent>(TApplicationEvent e)
where TApplicationEvent : ApplicationEvent
{
var response = await RequestAsync("POST", "event", e);
response.Close();
}

/// <summary>
/// Make a query
/// </summary>
/// <typeparam name="TResult">Result from a query (a class from the <c>OneTrueError.Api</c> library)</typeparam>
/// <param name="query"></param>
/// <returns></returns>
public async Task<TResult> QueryAsync<TResult>(Query<TResult> query)
{
//TODO: Unwrap the cqs object to query parameters instead
//to allow caching in the server
var response = await RequestAsync("POST", "query", query);
return await DeserializeResponse<TResult>(response);
}

/// <summary>
/// Open a channel
/// </summary>
/// <param name="uri">Root URL to the OneTrueError web</param>
/// <param name="apiKey">Api key from the admin area in OneTrueError web</param>
/// <param name="sharedSecret">Shared secret from the admin area in OneTrueError web</param>
public void Open(Uri uri, string apiKey, string sharedSecret)
{
if (apiKey == null) throw new ArgumentNullException(nameof(apiKey));
if (sharedSecret == null) throw new ArgumentNullException(nameof(sharedSecret));
if (uri == null) throw new ArgumentNullException(nameof(uri));

_apiKey = apiKey;
_sharedSecret = sharedSecret;
_uri = uri;
}

private async Task<TResult> DeserializeResponse<TResult>(HttpWebResponse response)
{
var responseStream = response.GetResponseStream();
var jsonBuf = new byte[response.ContentLength];
await responseStream.ReadAsync(jsonBuf, 0, jsonBuf.Length);
var jsonStr = Encoding.UTF8.GetString(jsonBuf);
var responseObj = JsonConvert.DeserializeObject(jsonStr, typeof(TResult), _jsonSerializerSettings);
return (TResult) responseObj;
}

private async Task<HttpWebResponse> RequestAsync(string httpMethod, string cqsType, object cqsObject)
{
var request = WebRequest.CreateHttp(_uri + "api/cqs");
request.Method = httpMethod;
request.Headers.Add("X-Api-Key", _apiKey);
request.Headers.Add("X-Cqs-Name", cqsObject.GetType().Name);

var stream = await request.GetRequestStreamAsync();
var json = JsonConvert.SerializeObject(cqsObject, _jsonSerializerSettings);
var buffer = Encoding.UTF8.GetBytes(json);

var hamc = new HMACSHA256(Encoding.UTF8.GetBytes(_sharedSecret.ToLower()));
var hash = hamc.ComputeHash(buffer);
var signature = Convert.ToBase64String(hash);

await stream.WriteAsync(buffer, 0, buffer.Length);

request.Headers.Add("X-Api-Signature", signature);

return (HttpWebResponse) await request.GetResponseAsync();
}
}
}
Loading