Skip to content

Commit

Permalink
Merge branch 'main' into release/v5
Browse files Browse the repository at this point in the history
  • Loading branch information
tnotheis committed May 8, 2024
2 parents a06996b + 74203c0 commit d696876
Show file tree
Hide file tree
Showing 35 changed files with 293 additions and 113 deletions.
1 change: 1 addition & 0 deletions AdminApi.Sdk/AdminApi.Sdk.csproj
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<ProjectReference Include="..\BuildingBlocks\src\BuildingBlocks.SDK\BuildingBlocks.SDK.csproj" />
<ProjectReference Include="..\BuildingBlocks\src\Crypto\Crypto.csproj" />
<ProjectReference Include="..\BuildingBlocks\src\Tooling\Tooling.csproj" />
</ItemGroup>
</Project>
3 changes: 3 additions & 0 deletions AdminApi.Sdk/Client.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System.Text.Json;
using Backbone.AdminApi.Sdk.Authentication;
using Backbone.AdminApi.Sdk.Endpoints.ApiKeyValidation;
using Backbone.AdminApi.Sdk.Endpoints.Challenges;
using Backbone.AdminApi.Sdk.Endpoints.Clients;
using Backbone.AdminApi.Sdk.Endpoints.Identities;
using Backbone.AdminApi.Sdk.Endpoints.Logs;
Expand Down Expand Up @@ -30,6 +31,7 @@ private Client(HttpClient httpClient, string apiKey)
Metrics = new MetricsEndpoint(endpointClient);
Relationships = new RelationshipsEndpoint(endpointClient);
Tiers = new TiersEndpoint(endpointClient);
Challenges = new ChallengesEndpoint(endpointClient);
}

public ApiKeyValidationEndpoint ApiKeyValidation { get; }
Expand All @@ -39,6 +41,7 @@ private Client(HttpClient httpClient, string apiKey)
public MetricsEndpoint Metrics { get; }
public RelationshipsEndpoint Relationships { get; }
public TiersEndpoint Tiers { get; }
public ChallengesEndpoint Challenges { get; }

public static Client Create(string baseUrl, string apiKey)
{
Expand Down
13 changes: 13 additions & 0 deletions AdminApi.Sdk/Endpoints/Challenges/ChallengesEndpoint.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using Backbone.AdminApi.Sdk.Endpoints.Challenges.Types;
using Backbone.BuildingBlocks.SDK.Endpoints.Common;
using Backbone.BuildingBlocks.SDK.Endpoints.Common.Types;

namespace Backbone.AdminApi.Sdk.Endpoints.Challenges;

public class ChallengesEndpoint(EndpointClient client) : Endpoint(client)
{
public async Task<ApiResponse<Challenge>> CreateChallenge()
{
return await _client.Post<Challenge>("Challenges");
}
}
9 changes: 9 additions & 0 deletions AdminApi.Sdk/Endpoints/Challenges/Types/Challenge.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace Backbone.AdminApi.Sdk.Endpoints.Challenges.Types;

public class Challenge
{
public required string Id { get; set; }
public required DateTime ExpiresAt { get; set; }
public string? CreatedBy { get; set; }
public string? CreatedByDevice { get; set; }
}
19 changes: 19 additions & 0 deletions AdminApi.Sdk/Endpoints/Challenges/Types/SignedChallenge.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using Backbone.BuildingBlocks.SDK.Crypto;
using Backbone.Crypto;
using Newtonsoft.Json;

namespace Backbone.AdminApi.Sdk.Endpoints.Challenges.Types;

public class SignedChallenge
{
public SignedChallenge(string challenge, ConvertibleString signature)
{
Challenge = challenge;
Signature = ConvertibleString.FromUtf8(
JsonConvert.SerializeObject(new CryptoSignatureSignedChallenge { alg = CryptoHashAlgorithm.SHA512, sig = signature.BytesRepresentation }
)).Base64Representation;
}

public string Challenge { get; internal set; }
public string Signature { get; internal set; }
}
4 changes: 4 additions & 0 deletions AdminApi.Sdk/Endpoints/Identities/IdentitiesEndpoint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ public async Task<ApiResponse<IndividualQuota>> CreateIndividualQuota(string add
public async Task<ApiResponse<EmptyResponse>> DeleteIndividualQuota(string address, string quotaId)
=> await _client.Delete<EmptyResponse>($"api/{API_VERSION}/Identities/{address}/Quotas/{quotaId}");

public async Task<ApiResponse<ListIdentitiesResponse>> ListIdentities() => await _client.Request<ListIdentitiesResponse>(HttpMethod.Get, "odata/Identities")
.Authenticate()
.ExecuteOData();

public async Task<ApiResponse<GetIdentityResponse>> GetIdentity(string address) => await _client.Get<GetIdentityResponse>($"api/{API_VERSION}/Identities/{address}");

public async Task<ApiResponse<EmptyResponse>> UpdateIdentityTier(string address, UpdateIdentityTierRequest request)
Expand Down
15 changes: 15 additions & 0 deletions AdminApi.Sdk/Endpoints/Identities/Types/IdentityOverview.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using Backbone.AdminApi.Sdk.Endpoints.Tiers.Types;

namespace Backbone.AdminApi.Sdk.Endpoints.Identities.Types;

public class IdentityOverview
{
public required string Address { get; set; }
public required DateTime CreatedAt { get; set; }
public DateTime? LastLoginAt { get; set; }
public string? CreatedWithClient { get; set; }
public int? DatawalletVersion { get; set; }
public required byte IdentityVersion { get; set; }
public Tier? Tier { get; set; }
public int? NumberOfDevices { get; set; }
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
namespace Backbone.AdminApi.Sdk.Endpoints.Identities.Types.Requests;
using Backbone.AdminApi.Sdk.Endpoints.Challenges.Types;

namespace Backbone.AdminApi.Sdk.Endpoints.Identities.Types.Requests;

public class CreateIdentityRequest
{
Expand All @@ -7,4 +9,5 @@ public class CreateIdentityRequest
public required byte[] IdentityPublicKey { get; set; }
public required string DevicePassword { get; set; }
public required byte IdentityVersion { get; set; }
public required SignedChallenge SignedChallenge { get; set; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
using Backbone.BuildingBlocks.SDK.Endpoints.Common.Types;

namespace Backbone.AdminApi.Sdk.Endpoints.Identities.Types.Responses;

public class ListIdentitiesResponse : EnumerableResponseBase<IdentityOverview>;
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
using Backbone.AdminApi.Infrastructure.DTOs;
using Backbone.AdminApi.Infrastructure.Persistence.Database;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.OData.Query;
using Microsoft.AspNetCore.OData.Routing.Controllers;

namespace Backbone.AdminApi.Controllers.OData;

[Authorize("ApiKey")]
public class IdentitiesController : ODataController
{
private readonly AdminApiDbContext _adminApiDbContext;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.12.0" />
<PackageReference Include="Newtonsoft.Json.Schema" Version="3.0.15" />
<PackageReference Include="NJsonSchema.NewtonsoftJson" Version="11.0.0" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
using System.Text.Json;
using System.Web;
using Backbone.BuildingBlocks.SDK.Endpoints.Common.Types;
using Newtonsoft.Json;
using JsonSerializer = System.Text.Json.JsonSerializer;

namespace Backbone.BuildingBlocks.SDK.Endpoints.Common;

Expand All @@ -16,6 +18,12 @@ public class EndpointClient
}
""";

private const string EMPTY_VALUE = """
{
"value": {}
}
""";

private readonly IAuthenticator _authenticator;

private readonly HttpClient _httpClient;
Expand All @@ -29,46 +37,65 @@ public EndpointClient(HttpClient httpClient, IAuthenticator authenticator, JsonS
}

public async Task<ApiResponse<T>> Post<T>(string url, object? requestContent = null)
=> await Request<T>(HttpMethod.Post, url)
{
return await Request<T>(HttpMethod.Post, url)
.Authenticate()
.WithJson(requestContent)
.Execute();
}

public async Task<ApiResponse<T>> PostUnauthenticated<T>(string url, object? requestContent = null)
=> await Request<T>(HttpMethod.Post, url)
{
return await Request<T>(HttpMethod.Post, url)
.WithJson(requestContent)
.Execute();
}

public async Task<ApiResponse<T>> Get<T>(string url, object? requestContent = null, PaginationFilter? pagination = null)
=> await Request<T>(HttpMethod.Get, url)
{
return await Request<T>(HttpMethod.Get, url)
.Authenticate()
.WithPagination(pagination)
.WithJson(requestContent)
.Execute();
}

public async Task<ApiResponse<T>> GetUnauthenticated<T>(string url, object? requestContent = null, PaginationFilter? pagination = null)
=> await Request<T>(HttpMethod.Get, url)
{
return await Request<T>(HttpMethod.Get, url)
.WithPagination(pagination)
.WithJson(requestContent)
.Execute();
}

public async Task<ApiResponse<T>> Put<T>(string url, object? requestContent = null)
=> await Request<T>(HttpMethod.Put, url)
{
return await Request<T>(HttpMethod.Put, url)
.Authenticate()
.WithJson(requestContent)
.Execute();
}

public async Task<ApiResponse<T>> Patch<T>(string url, object? requestContent = null) => await Request<T>(HttpMethod.Patch, url)
.Authenticate()
.WithJson(requestContent)
.Execute();
public async Task<ApiResponse<T>> Patch<T>(string url, object? requestContent = null)
{
return await Request<T>(HttpMethod.Patch, url)
.Authenticate()
.WithJson(requestContent)
.Execute();
}

public async Task<ApiResponse<T>> Delete<T>(string url, object? requestContent = null) => await Request<T>(HttpMethod.Delete, url)
.Authenticate()
.WithJson(requestContent)
.Execute();
public async Task<ApiResponse<T>> Delete<T>(string url, object? requestContent = null)
{
return await Request<T>(HttpMethod.Delete, url)
.Authenticate()
.WithJson(requestContent)
.Execute();
}

public RequestBuilder<T> Request<T>(HttpMethod method, string url) => new(this, _jsonSerializerOptions, _authenticator, method, url);
public RequestBuilder<T> Request<T>(HttpMethod method, string url)
{
return new RequestBuilder<T>(this, _jsonSerializerOptions, _authenticator, method, url);
}

private async Task<ApiResponse<T>> Execute<T>(HttpRequestMessage request)
{
Expand All @@ -82,12 +109,29 @@ private async Task<ApiResponse<T>> Execute<T>(HttpRequestMessage request)
responseContent = new MemoryStream(Encoding.UTF8.GetBytes(EMPTY_RESULT));
}

var deserializedResponseContent = JsonSerializer.Deserialize<ApiResponse<T>>(responseContent, _jsonSerializerOptions)!;
deserializedResponseContent.Status = statusCode;
var deserializedResponseContent = JsonSerializer.Deserialize<ApiResponse<T>>(responseContent, _jsonSerializerOptions);
deserializedResponseContent!.Status = statusCode;
deserializedResponseContent.RawContent = JsonConvert.SerializeObject(deserializedResponseContent.Result);

return deserializedResponseContent;
}

private async Task<ApiResponse<T>> ExecuteOData<T>(HttpRequestMessage request)
{
var response = await _httpClient.SendAsync(request);
var responseContent = await response.Content.ReadAsStreamAsync();
var statusCode = response.StatusCode;

if (statusCode == HttpStatusCode.NoContent || responseContent.Length == 0)
{
responseContent.Close();
responseContent = new MemoryStream(Encoding.UTF8.GetBytes(EMPTY_VALUE));
}

var deserializedResponseContent = JsonSerializer.Deserialize<ODataResponse<T>>(responseContent, _jsonSerializerOptions)!;
return deserializedResponseContent.ToApiResponse(statusCode);
}

private async Task<RawApiResponse> ExecuteRaw(HttpRequestMessage request)
{
var response = await _httpClient.SendAsync(request);
Expand All @@ -104,10 +148,10 @@ private async Task<RawApiResponse> ExecuteRaw(HttpRequestMessage request)

public class RequestBuilder<T>
{
private readonly IAuthenticator _authenticator;
private readonly EndpointClient _client;
private readonly NameValueCollection _extraHeaders = [];
private readonly JsonSerializerOptions _jsonSerializerOptions;
private readonly IAuthenticator _authenticator;
private readonly HttpMethod _method;
private readonly NameValueCollection _queryParameters = [];

Expand Down Expand Up @@ -207,9 +251,20 @@ public RequestBuilder<T> AddExtraHeaders(NameValueCollection headers)
return this;
}

public async Task<ApiResponse<T>> Execute() => await _client.Execute<T>(await CreateRequestMessage());
public async Task<ApiResponse<T>> Execute()
{
return await _client.Execute<T>(await CreateRequestMessage());
}

public async Task<RawApiResponse> ExecuteRaw() => await _client.ExecuteRaw(await CreateRequestMessage());
public async Task<ApiResponse<T>> ExecuteOData()
{
return await _client.ExecuteOData<T>(await CreateRequestMessage());
}

public async Task<RawApiResponse> ExecuteRaw()
{
return await _client.ExecuteRaw(await CreateRequestMessage());
}

private async Task<HttpRequestMessage> CreateRequestMessage()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,5 @@ public class ApiResponse<TResult>
public ApiError? Error { get; set; }
public PaginationData? Pagination { get; set; }
public HttpStatusCode Status { get; set; }
public string? RawContent { get; set; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using System.Net;

namespace Backbone.BuildingBlocks.SDK.Endpoints.Common.Types;

public class ODataResponse<TResult>
{
public TResult? Value { get; set; }

public ApiResponse<TResult> ToApiResponse(HttpStatusCode status) => new() { Result = Value, Status = status };
}
3 changes: 2 additions & 1 deletion ConsumerApi.Sdk/Client.cs
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,8 @@ public async Task<Client> OnboardNewDevice(string password)
var challengeSignature = signatureHelper.CreateSignature(keyPair.PrivateKey, ConvertibleString.FromUtf8(serializedChallenge));
var signedChallenge = new SignedChallenge
{
Challenge = serializedChallenge, Signature = ConvertibleString.FromUtf8(
Challenge = serializedChallenge,
Signature = ConvertibleString.FromUtf8(
JsonConvert.SerializeObject(new CryptoSignatureSignedChallenge { alg = CryptoHashAlgorithm.SHA512, sig = challengeSignature.BytesRepresentation }
)).BytesRepresentation
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,11 @@
using Backbone.Modules.Devices.Application.DTOs;
using Backbone.Modules.Devices.Domain.Entities.Identities;

namespace Backbone.Modules.Devices.Application.Identities.Commands.ApproveDeletionProcess;

public class ApproveDeletionProcessResponse
public class ApproveDeletionProcessResponse : IdentityDeletionProcessOverviewDTO
{
public ApproveDeletionProcessResponse(IdentityDeletionProcess deletionProcess)
public ApproveDeletionProcessResponse(IdentityDeletionProcess deletionProcess) : base(deletionProcess)
{
Id = deletionProcess.Id;
Status = deletionProcess.Status;
CreatedAt = deletionProcess.CreatedAt;
ApprovedAt = deletionProcess.ApprovedAt ?? throw new Exception($"The '{nameof(IdentityDeletionProcess.ApprovedAt)}' property of the given deletion process must not be null.");
ApprovedByDevice = deletionProcess.ApprovedByDevice ?? throw new Exception($"The '{nameof(IdentityDeletionProcess.ApprovedByDevice)}' property of the given deletion process must not be null.");
}

public string Id { get; }
public DeletionProcessStatus Status { get; }
public DateTime CreatedAt { get; }
public DateTime ApprovedAt { get; }
public string ApprovedByDevice { get; }
}
Original file line number Diff line number Diff line change
@@ -1,19 +1,11 @@
using Backbone.Modules.Devices.Domain.Entities.Identities;
using Backbone.Modules.Devices.Application.DTOs;
using Backbone.Modules.Devices.Domain.Entities.Identities;

namespace Backbone.Modules.Devices.Application.Identities.Commands.CancelDeletionProcessAsOwner;

public class CancelDeletionProcessAsOwnerResponse
public class CancelDeletionProcessAsOwnerResponse : IdentityDeletionProcessOverviewDTO
{
public CancelDeletionProcessAsOwnerResponse(IdentityDeletionProcess deletionProcess)
public CancelDeletionProcessAsOwnerResponse(IdentityDeletionProcess deletionProcess) : base(deletionProcess)
{
Id = deletionProcess.Id;
Status = deletionProcess.Status;
CancelledAt = deletionProcess.CancelledAt ?? throw new Exception($"The '{nameof(IdentityDeletionProcess.CancelledAt)}' property of the given deletion process must not be null.");
CancelledByDevice = deletionProcess.CancelledByDevice ?? throw new Exception($"The '{nameof(IdentityDeletionProcess.CancelledByDevice)}' property of the given deletion process must not be null.");
}

public string Id { get; }
public DeletionProcessStatus Status { get; }
public DateTime CancelledAt { get; }
public string CancelledByDevice { get; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public async Task<CancelDeletionProcessAsOwnerResponse> Handle(CancelDeletionPro
var newTierId = identity.TierId;

_eventBus.Publish(new TierOfIdentityChangedDomainEvent(identity, oldTierId, newTierId));
_eventBus.Publish(new IdentityDeletionProcessStatusChangedDomainEvent(identity.Address, deletionProcess.Id));
_eventBus.Publish(new IdentityDeletionProcessStatusChangedDomainEvent(identity.Address, deletionProcess.Id, _userContext.GetAddress()));

return new CancelDeletionProcessAsOwnerResponse(deletionProcess);
}
Expand Down
Loading

0 comments on commit d696876

Please sign in to comment.