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

Consumer Api: Deletion of Files, Tokens and Relationship Templates #934

Merged
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
d848d08
feat: Add file deletion command, handler, repository and controller
MH321Productions Oct 30, 2024
0e0b128
feat: Add file deletion command to Consumer Api SDK
MH321Productions Oct 30, 2024
d9f87e5
feat: Add file deletion command to bruno
MH321Productions Oct 30, 2024
0b62ae6
feat: Add command, handler and repository method for delete token end…
MH321Productions Oct 31, 2024
68e0891
feat: Add delete token endpoint to bruno and consumer api sdk
MH321Productions Oct 31, 2024
6fec1d3
feat: Migrate DB to make relationship template nullable in relationship
MH321Productions Nov 6, 2024
ae49df2
feat: Make Relationship Template nullable in Relationship
MH321Productions Nov 6, 2024
2a3c59c
feat: Add controller, command, handler, validator, sdk method and bru…
MH321Productions Nov 6, 2024
1736a46
chore: Add user b to bruno variables and update passwords
MH321Productions Nov 6, 2024
1221a52
chore: Add/update bruno files for multiple relationship endpoints
MH321Productions Nov 6, 2024
b8fc0fe
Merge branch 'main' into deletion-of-files-tokens-and-relationship-te…
MH321Productions Nov 6, 2024
9d4e9f1
chore: Make relationship template nullable in sdk types
MH321Productions Nov 6, 2024
8e0275c
test: Add integration test for relationship template deletion
MH321Productions Nov 6, 2024
3b1c30c
Merge branch 'main' into deletion-of-files-tokens-and-relationship-te…
mergify[bot] Nov 6, 2024
5ac2960
Merge branch 'main' into deletion-of-files-tokens-and-relationship-te…
mergify[bot] Nov 7, 2024
41725cd
Merge branch 'main' into deletion-of-files-tokens-and-relationship-te…
mergify[bot] Nov 8, 2024
2d26c7c
chore: Add tests for deletion, rewrite existing ones, refactor backin…
MH321Productions Nov 8, 2024
a95fdec
chore: Switch order for file deletion
MH321Productions Nov 8, 2024
0a17d1f
Merge branch 'main' into deletion-of-files-tokens-and-relationship-te…
mergify[bot] Nov 9, 2024
14a06a1
chore: Move owner checks to domain classes and rewrite domain exceptions
MH321Productions Nov 11, 2024
ba4bf2c
chore: Add unit tests for deletion check functions
MH321Productions Nov 11, 2024
6b68bef
chore: Change function and variable names to comply with convention
MH321Productions Nov 11, 2024
a03f31a
chore: Simplify used test functions
MH321Productions Nov 11, 2024
8294768
chore: Simplify test function
MH321Productions Nov 11, 2024
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,8 @@
using Backbone.ConsumerApi.Sdk.Endpoints.Files.Types;

namespace Backbone.ConsumerApi.Tests.Integration.Contexts;

public class FilesContext
{
public readonly Dictionary<string, FileMetadata> Files = [];
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
using Backbone.ConsumerApi.Sdk.Endpoints.Relationships.Types;
using Backbone.ConsumerApi.Sdk.Endpoints.RelationshipTemplates.Types.Responses;

namespace Backbone.ConsumerApi.Tests.Integration.Contexts;

public class RelationshipsContext
{
public readonly Dictionary<string, Relationship> Relationships = new();
public readonly Dictionary<string, CreateRelationshipTemplateResponse> CreateRelationshipTemplateResponses = new();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
@Integration
Feature: DELETE /Files/{id}

User deletes a File

Scenario: Deleting a File actually removes it
Given Identity i
And File f created by i
When i sends a DELETE request to the /Files/f.Id endpoint
And i sends a GET request to the /Files/f.Id endpoint
Then the response status code is 404 (Not Found)
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,16 @@ Feature: DELETE /RelationshipTemplates/{id}
User deletes a Relationship Template

Scenario: Deleting a template used in a relationship doesn't delete the relationship
Given Identities a and b
And a Relationship Template t created by a
And an active Relationship r between a and b with template t
When a sends a DELETE request to the /RelationshipTemplates/t endpoint
Then the relationship r still exists
And the relationship r does not have a relationship template
Given Identities i1 and i2
And a Relationship Template t created by i1
And an active Relationship r between i1 and i2 with template t
When i1 sends a DELETE request to the /RelationshipTemplates/t.Id endpoint
Then the Relationship r still exists
And the Relationship r does not have a relationship template

Scenario: Deleting a template actually removes it
Given Identity i
And a Relationship Template t created by i
When i sends a DELETE request to the /RelationshipTemplates/t.Id endpoint
And i sends a GET request to the /RelationshipTemplates/t.Id endpoint with password "-"
Then the response status code is 404 (Not Found)
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
@Integration
Feature: DELETE /Tokens/{id}

User deletes a Token

Scenario: Deleting a Token actually removes it
Given Identity i
And Token t created by i with password "-" and forIdentity -
When i sends a DELETE request to the /Tokens/t.Id endpoint
And i sends a GET request to the /Tokens/t.Id endpoint with password "-"
Then the response status code is 404 (Not Found)
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using Backbone.ConsumerApi.Sdk;
using Backbone.ConsumerApi.Sdk.Endpoints.Challenges.Types;
using Backbone.ConsumerApi.Sdk.Endpoints.Devices.Types;
using Backbone.ConsumerApi.Sdk.Endpoints.Files.Types;
using Backbone.ConsumerApi.Sdk.Endpoints.Files.Types.Requests;
using Backbone.ConsumerApi.Sdk.Endpoints.Messages.Types;
using Backbone.ConsumerApi.Sdk.Endpoints.Messages.Types.Requests;
using Backbone.ConsumerApi.Sdk.Endpoints.Relationships.Types;
Expand Down Expand Up @@ -36,22 +38,10 @@ public static async Task<Relationship> CreatePendingRelationshipBetween(Client c
var relationshipTemplateResponse = await client1.RelationshipTemplates.CreateTemplate(createRelationshipTemplateRequest);
relationshipTemplateResponse.Should().BeASuccess();

var createRelationshipRequest = new CreateRelationshipRequest
{
RelationshipTemplateId = relationshipTemplateResponse.Result!.Id,
Content = TestData.SOME_BYTES
};

var createRelationshipResponse = await client2.Relationships.CreateRelationship(createRelationshipRequest);
createRelationshipResponse.Should().BeASuccess();

var getRelationshipResponse = await client2.Relationships.GetRelationship(createRelationshipResponse.Result!.Id);
getRelationshipResponse.Should().BeASuccess();

return getRelationshipResponse.Result!;
return await CreatePendingRelationshipUsingTemplate(client2, relationshipTemplateResponse.Result!.Id);
}

public static async Task<Relationship> CreatePendingRelationshipBetween(Client client2, string templateId)
public static async Task<Relationship> CreatePendingRelationshipUsingTemplate(Client client2, string templateId)
{
var createRelationshipRequest = new CreateRelationshipRequest
{
Expand Down Expand Up @@ -88,7 +78,7 @@ public static async Task<Relationship> EstablishRelationshipBetween(Client clien

public static async Task<Relationship> EstablishRelationshipBetween(Client client1, Client client2, string templateId)
{
var pendingRelationship = await CreatePendingRelationshipBetween(client2, templateId);
var pendingRelationship = await CreatePendingRelationshipUsingTemplate(client2, templateId);

var acceptRelationshipRequest = new AcceptRelationshipRequest
{
Expand Down Expand Up @@ -148,6 +138,29 @@ public static async Task<Relationship> CreateTerminatedRelationshipWithReactivat
return getRelationshipResponse.Result!;
}

public static async Task<FileMetadata> CreateFile(Client client)
{
var createFileRequest = new CreateFileRequest
{
Content = new MemoryStream("content"u8.ToArray()),
Owner = client.IdentityData!.Address,
OwnerSignature = TestData.SOME_BASE64_STRING,
CipherHash = TestData.SOME_BASE64_STRING,
ExpiresAt = DateTime.UtcNow.AddDays(1),
EncryptedProperties = TestData.SOME_BASE64_STRING
};

var createFileResponse = await client.Files.UploadFile(createFileRequest);

createFileResponse.Should().BeASuccess();

var getFileMetadataResponse = await client.Files.GetFileMetadata(createFileResponse.Result!.Id);

getFileMetadataResponse.Should().BeASuccess();

return getFileMetadataResponse.Result!;
}

public static async Task<Message> SendMessage(Client sender, params Client[] recipients)
{
var sendMessageRequest = new SendMessageRequest
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,30 @@ namespace Backbone.ConsumerApi.Tests.Integration.StepDefinitions;
internal class FilesStepDefinitions
{
private readonly ResponseContext _responseContext;
private readonly FilesContext _filesContext;
private readonly ClientPool _clientPool;

public FilesStepDefinitions(ResponseContext responseContext, ClientPool clientPool)
public FilesStepDefinitions(ResponseContext responseContext, FilesContext filesContext, ClientPool clientPool)
{
_responseContext = responseContext;
_filesContext = filesContext;
_clientPool = clientPool;
}

#region Given

[Given($"File {RegexFor.SINGLE_THING} created by {RegexFor.SINGLE_THING}")]
public async Task GivenFileCreatedByI(string fileName, string identityName)
{
var client = _clientPool.FirstForIdentityName(identityName);

_filesContext.Files[fileName] = await Utils.CreateFile(client);
}

#endregion

#region When

[When($"{RegexFor.SINGLE_THING} sends a POST request to the /Files endpoint")]
public async Task WhenIdentitySendsAPostRequestToTheFilesEndpoint(string identityName)
{
Expand All @@ -33,4 +49,24 @@ public async Task WhenIdentitySendsAPostRequestToTheFilesEndpoint(string identit

_responseContext.WhenResponse = await identity.Files.UploadFile(createFileRequest);
}

[When($"{RegexFor.SINGLE_THING} sends a DELETE request to the /Files/{RegexFor.SINGLE_THING}.Id endpoint")]
public async Task WhenISendsADeleteRequestToTheFilesIdEndpoint(string identityName, string fileName)
{
var identity = _clientPool.FirstForIdentityName(identityName);
var file = _filesContext.Files[fileName];

_responseContext.WhenResponse = await identity.Files.DeleteFile(file.Id);
}

[When($"{RegexFor.SINGLE_THING} sends a GET request to the /Files/{RegexFor.SINGLE_THING}.Id endpoint")]
public async Task WhenISendsAGetRequestToTheFilesIdEndpoint(string identityName, string fileName)
{
var identity = _clientPool.FirstForIdentityName(identityName);
var file = _filesContext.Files[fileName];

_responseContext.WhenResponse = await identity.Files.GetFileMetadata(file.Id);
}

#endregion
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,14 @@ public RelationshipTemplatesStepDefinitions(ResponseContext responseContext, Rel

#region Given

[Given($"a Relationship Template {RegexFor.SINGLE_THING} created by {RegexFor.SINGLE_THING}")]
public async Task GivenARelationshipTemplateCreatedByIdentity(string templateName, string identityName)
{
var client = _clientPool.FirstForIdentityName(identityName);
_relationshipTemplatesContext.CreateRelationshipTemplatesResponses[templateName] =
(await client.RelationshipTemplates.CreateTemplate(new CreateRelationshipTemplateRequest { Content = TestData.SOME_BYTES })).Result!;
}

[Given($@"Relationship Template {RegexFor.SINGLE_THING} created by {RegexFor.SINGLE_THING} with password ""([^""]*)"" and forIdentity {RegexFor.OPTIONAL_SINGLE_THING}")]
public async Task GivenRelationshipTemplateCreatedByTokenOwnerWithPasswordAndForIdentity(string relationshipTemplateName, string identityName, string passwordString, string forIdentityName)
{
Expand Down Expand Up @@ -126,13 +134,13 @@ public async Task WhenISendsAGETRequestToTheRelationshipTemplatesEndpointWithThe
_responseContext.WhenResponse = _listRelationshipTemplatesResponse = await client.RelationshipTemplates.ListTemplates(queryItems);
}

[When($"{RegexFor.SINGLE_THING} sends a DELETE request to the /RelationshipTemplates/{RegexFor.SINGLE_THING} endpoint")]
[When($"{RegexFor.SINGLE_THING} sends a DELETE request to the /RelationshipTemplates/{RegexFor.SINGLE_THING}.Id endpoint")]
public async Task WhenISendsADeleteRequestToTheRelationshipTemplatesEndpoint(string identityName, string templateName)
{
var client = _clientPool.FirstForIdentityName(identityName);
var relationshipTemplateId = _relationshipTemplatesContext.CreateRelationshipTemplatesResponses[templateName].Id;

await client.RelationshipTemplates.DeleteTemplate(relationshipTemplateId);
_responseContext.WhenResponse = await client.RelationshipTemplates.DeleteTemplate(relationshipTemplateId);
}

#endregion
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
using Backbone.BuildingBlocks.SDK.Endpoints.Common.Types;
using Backbone.ConsumerApi.Sdk.Endpoints.Relationships.Types.Requests;
using Backbone.ConsumerApi.Sdk.Endpoints.Relationships.Types.Responses;
using Backbone.ConsumerApi.Sdk.Endpoints.RelationshipTemplates.Types.Requests;
using Backbone.ConsumerApi.Tests.Integration.Contexts;
using Backbone.ConsumerApi.Tests.Integration.Extensions;
using Backbone.ConsumerApi.Tests.Integration.Helpers;
Expand Down Expand Up @@ -33,14 +32,6 @@ public RelationshipsStepDefinitions(RelationshipsContext relationshipsContext, R

#region Given

[Given($"a Relationship Template {RegexFor.SINGLE_THING} created by {RegexFor.SINGLE_THING}")]
public async Task GivenARelationshipTemplateCreatedByIdentity(string templateName, string identityName)
{
var client = _clientPool.FirstForIdentityName(identityName);
_relationshipTemplatesContext.CreateRelationshipTemplatesResponses[templateName] =
(await client.RelationshipTemplates.CreateTemplate(new CreateRelationshipTemplateRequest { Content = TestData.SOME_BYTES })).Result!;
}

[Given($"a pending Relationship {RegexFor.SINGLE_THING} between {RegexFor.SINGLE_THING} and {RegexFor.SINGLE_THING} created by {RegexFor.SINGLE_THING}")]
public async Task GivenAPendingRelationshipBetween(string relationshipName, string participant1Name, string participant2Name, string creatorName)
{
Expand Down Expand Up @@ -239,7 +230,7 @@ public void ThenThereIsNoCode()
_canEstablishRelationshipResponse!.Result!.Code.Should().BeNull();
}

[Then($"the relationship {RegexFor.SINGLE_THING} still exists")]
[Then($"the Relationship {RegexFor.SINGLE_THING} still exists")]
public async Task ThenTheRelationshipStillExists(string relationshipName)
{
var relationship = _relationshipsContext.Relationships[relationshipName];
Expand All @@ -251,7 +242,7 @@ public async Task ThenTheRelationshipStillExists(string relationshipName)
_relationshipsContext.Relationships[relationshipName] = getRelationshipResponse.Result!;
}

[Then($"the relationship {RegexFor.SINGLE_THING} does not have a relationship template")]
[Then($"the Relationship {RegexFor.SINGLE_THING} does not have a relationship template")]
public void ThenTheRelationshipDoesNotHaveARelationshipTemplate(string relationshipName)
{
var relationship = _relationshipsContext.Relationships[relationshipName];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,15 @@ public async Task WhenISendsAGETRequestToTheTokensEndpointWithTheFollowingPayloa
_responseContext.WhenResponse = _listTokensResponse = await client.Tokens.ListTokens(queryItems);
}

[When($"{RegexFor.SINGLE_THING} sends a DELETE request to the /Tokens/{RegexFor.SINGLE_THING}.Id endpoint")]
public async Task WhenISendsADeleteRequestToTheTokensIdEndpoint(string identityName, string tokenName)
{
var client = _clientPool.FirstForIdentityName(identityName);
var tokenId = _tokensContext.CreateTokenResponses[tokenName].Id;

_responseContext.WhenResponse = await client.Tokens.DeleteToken(tokenId);
}

#endregion

#region Then
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ public static IServiceCollection CreateServices()
services.AddScoped<ClientPool>(sp => new ClientPool(sp.GetRequiredService<HttpClientFactory>(), sp.GetRequiredService<IOptions<HttpConfiguration>>()));

services.AddScoped<ChallengesContext>();
services.AddScoped<FilesContext>();
services.AddScoped<IdentitiesContext>();
services.AddScoped<MessagesContext>();
services.AddScoped<RelationshipsContext>();
Expand Down
7 changes: 7 additions & 0 deletions Backbone.sln
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tags.Domain", "Modules\Tags
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tags.Infrastructure", "Modules\Tags\src\Tags.Infrastructure\Tags.Infrastructure.csproj", "{98C16B16-7ECE-4E23-8D6C-2CA372EC310C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Files.Domain.Tests", "Modules\Files\test\Files.Domain.Tests\Files.Domain.Tests.csproj", "{30402564-3CAA-4CB1-A0D5-1BF157BB5B65}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -836,6 +838,10 @@ Global
{98C16B16-7ECE-4E23-8D6C-2CA372EC310C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{98C16B16-7ECE-4E23-8D6C-2CA372EC310C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{98C16B16-7ECE-4E23-8D6C-2CA372EC310C}.Release|Any CPU.Build.0 = Release|Any CPU
{30402564-3CAA-4CB1-A0D5-1BF157BB5B65}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{30402564-3CAA-4CB1-A0D5-1BF157BB5B65}.Debug|Any CPU.Build.0 = Debug|Any CPU
{30402564-3CAA-4CB1-A0D5-1BF157BB5B65}.Release|Any CPU.ActiveCfg = Release|Any CPU
{30402564-3CAA-4CB1-A0D5-1BF157BB5B65}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -1011,6 +1017,7 @@ Global
{2BE30D30-CCFF-453E-AEAC-9CB3EF677058} = {2A9D40E4-AB8F-49B5-878B-A8C7763EE609}
{3A107CCD-A7E9-448B-82DF-0FD87D1ABA9E} = {2A9D40E4-AB8F-49B5-878B-A8C7763EE609}
{98C16B16-7ECE-4E23-8D6C-2CA372EC310C} = {2A9D40E4-AB8F-49B5-878B-A8C7763EE609}
{30402564-3CAA-4CB1-A0D5-1BF157BB5B65} = {2D0BC8E9-ED6B-49D9-937C-1616ED40FB3E}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {1F3BD2C6-7CB3-450F-A21A-23EA520D5B7A}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,8 @@ private static HttpStatusCode GetStatusCodeForDomainException(DomainException ex
{
if (exception.Code == GenericDomainErrors.NotFound().Code)
return HttpStatusCode.NotFound;
if (exception.Code == GenericDomainErrors.Forbidden().Code)
return HttpStatusCode.Forbidden;

return HttpStatusCode.BadRequest;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,10 @@ public static DomainError NewAndOldParametersMatch(string nameOfParameter)
return new DomainError("error.platform.validation.newAndOldMatch",
$"The new {nameOfParameter} and the old {nameOfParameter} cannot be the same.");
}

public static DomainError Forbidden()
{
return new DomainError("error.platform.forbidden",
"You are not allowed to perform this action due to insufficient privileges.");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using Backbone.BuildingBlocks.Domain.Errors;

namespace Backbone.BuildingBlocks.Domain.Exceptions;

public class DomainActionForbiddenException : DomainException
{
public DomainActionForbiddenException() : base(GenericDomainErrors.Forbidden())
{
}

public DomainActionForbiddenException(Exception innerException) : base(GenericDomainErrors.Forbidden(), innerException)
{
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public async Task Handle(DeleteFileCommand request, CancellationToken cancellati
{
var file = await _filesRepository.Find(FileId.Parse(request.Id), cancellationToken, fillContent: false) ?? throw new NotFoundException(nameof(File));

if (file.CreatedBy != _userContext.GetAddress()) throw new ActionForbiddenException();
file.EnsureCanBeDeletedBy(_userContext.GetAddress());

await _filesRepository.Delete(file, cancellationToken);
}
Expand Down
6 changes: 6 additions & 0 deletions Modules/Files/src/Files.Domain/Entities/File.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Linq.Expressions;
using Backbone.BuildingBlocks.Domain;
using Backbone.BuildingBlocks.Domain.Exceptions;
using Backbone.DevelopmentKit.Identity.ValueObjects;
using Backbone.Modules.Files.Domain.DomainEvents.Out;
using Backbone.Tooling;
Expand Down Expand Up @@ -84,6 +85,11 @@ public void LoadContent(byte[] content)

public byte[] EncryptedProperties { get; set; }

public void EnsureCanBeDeletedBy(IdentityAddress identityAddress)
{
if (CreatedBy != identityAddress) throw new DomainActionForbiddenException();
}

public static Expression<Func<File, bool>> IsExpired =>
file => file.ExpiresAt <= SystemTime.UtcNow;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@ public async Task Delete(File file, CancellationToken cancellationToken)
_blobStorage.Remove(_blobOptions.RootFolder, file.Id);
_files.Remove(file);

await _blobStorage.SaveAsync();
await _dbContext.SaveChangesAsync(cancellationToken);
await _blobStorage.SaveAsync();
}

public async Task DeleteFilesOfIdentity(Expression<Func<File, bool>> filter, CancellationToken cancellationToken)
Expand Down
Loading