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

ForIdentity column of Relationship Templates and Tokens are not anonymized during identity deletion #962

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
970b7b1
fix: Revert wrong test user infos introduced in #949
MH321Productions Dec 4, 2024
d3437a7
feat: Add methods for anonymizing the ForIdentity property
MH321Productions Dec 4, 2024
1224f70
feat: Add methods for querying and updating relationship templates
MH321Productions Dec 4, 2024
e5a5122
feat: Add domain error for a non-personalized relationship template
MH321Productions Dec 4, 2024
ab52423
feat: Add command, handler and validator for anonymizing personalized…
MH321Productions Dec 4, 2024
4d96e58
feat: Add anonymization command to IdentityDeleter
MH321Productions Dec 4, 2024
8483edf
test: Add unit tests for the IdentityDeleter and the relationship tem…
MH321Productions Dec 4, 2024
fd7c691
Merge branch 'main' into fix-anonymization-of-relationship-templates-…
mergify[bot] Dec 4, 2024
03811cf
Merge branch 'main' into fix-anonymization-of-relationship-templates-…
mergify[bot] Dec 5, 2024
0e6329d
Merge branch 'main' into fix-anonymization-of-relationship-templates-…
mergify[bot] Dec 5, 2024
af7ab12
chore: Make loop more readable
MH321Productions Dec 5, 2024
25049cc
feat: Add anonymization logic to Token class
MH321Productions Dec 5, 2024
a266417
feat: Add didDomainName to Token application options
MH321Productions Dec 5, 2024
14c148b
feat: Add repository methods for querying and updating tokens
MH321Productions Dec 5, 2024
e6d27a9
feat: Add command, handler and validator for anonymizing all tokens f…
MH321Productions Dec 5, 2024
c2be360
feat: Add anonymization command to token identity deleter
MH321Productions Dec 5, 2024
65ced90
feat: Add unit tests for anonymizing tokens
MH321Productions Dec 5, 2024
9733b17
chore: Add forIdentity column to bruno POST /Tokens request
MH321Productions Dec 5, 2024
2e7d527
chore: Add didDomainName to test appsettings
MH321Productions Dec 5, 2024
3c7ad15
Merge branch 'main' into fix-anonymization-of-relationship-templates-…
mergify[bot] Dec 6, 2024
5aa78b1
Merge branch 'main' into fix-anonymization-of-relationship-templates-…
mergify[bot] Dec 6, 2024
19db4d4
Merge branch 'main' into fix-anonymization-of-relationship-templates-…
mergify[bot] Dec 7, 2024
c7dbff2
Merge branch 'main' into fix-anonymization-of-relationship-templates-…
mergify[bot] Dec 9, 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
3 changes: 3 additions & 0 deletions .ci/appsettings.override.postgres.docker.json
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,9 @@
}
},
"Tokens": {
"Application": {
"DidDomainName": "localhost"
},
"Infrastructure": {
"SqlDatabase": {
"Provider": "Postgres",
Expand Down
3 changes: 3 additions & 0 deletions .ci/appsettings.override.postgres.local.json
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,9 @@
}
},
"Tokens": {
"Application": {
"DidDomainName": "localhost"
},
"Infrastructure": {
"SqlDatabase": {
"Provider": "Postgres",
Expand Down
3 changes: 3 additions & 0 deletions .ci/appsettings.override.sqlserver.docker.json
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,9 @@
}
},
"Tokens": {
"Application": {
"DidDomainName": "localhost"
},
"Infrastructure": {
"SqlDatabase": {
"Provider": "SqlServer",
Expand Down
3 changes: 3 additions & 0 deletions .ci/appsettings.override.sqlserver.local.json
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,9 @@
}
},
"Tokens": {
"Application": {
"DidDomainName": "localhost"
},
"Infrastructure": {
"SqlDatabase": {
"Provider": "SqlServer",
Expand Down
3 changes: 2 additions & 1 deletion Applications/ConsumerApi/src/http/Tokens/Create Token.bru
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ post {
body:json {
{
"content": "AAAA",
"expiresAt": "2024-12-17"
"expiresAt": "2024-12-17",
"forIdentity": "did:e:localhost:dids:8234cca0160ff05c785636"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public async Task Handle(SeedTestUsersCommand request, CancellationToken cancell
_basicTier = (await _tiersRepository.FindBasicTier(cancellationToken))!;

await CreateIdentityIfNecessary([1, 1, 1, 1, 1], "USRa", "Aaaaaaaa1!");
await CreateIdentityIfNecessary([1, 1, 1, 1, 1], "USRa", "Bbbbbbbb1!");
await CreateIdentityIfNecessary([2, 2, 2, 2, 2], "USRb", "Bbbbbbbb1!");
}

private async Task CreateIdentityIfNecessary(byte[] publicKey, string username, string password)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using Backbone.BuildingBlocks.Application.Identities;
using Backbone.Modules.Relationships.Application.Relationships.Commands.DecomposeAndAnonymizeRelationshipsOfIdentity;
using Backbone.Modules.Relationships.Application.RelationshipTemplates.Commands.AnonymizeRelationshipTemplateAllocationsAllocatedByIdentity;
using Backbone.Modules.Relationships.Application.RelationshipTemplates.Commands.AnonymizeRelationshipTemplatesForIdentity;
using Backbone.Modules.Relationships.Application.RelationshipTemplates.Commands.DeleteRelationshipTemplatesOfIdentity;
using FakeItEasy;
using MediatR;
Expand Down Expand Up @@ -28,6 +29,9 @@ public async Task Deleter_calls_correct_command()
A.CallTo(() => mockMediator.Send(
A<DeleteRelationshipTemplatesOfIdentityCommand>.That.Matches(i => i.IdentityAddress == identityAddress),
A<CancellationToken>._)).MustHaveHappenedOnceExactly();
A.CallTo(() => mockMediator.Send(
A<AnonymizeRelationshipTemplatesForIdentityCommand>.That.Matches(i => i.IdentityAddress == identityAddress),
A<CancellationToken>._)).MustHaveHappenedOnceExactly();
A.CallTo(() => mockMediator.Send(
A<AnonymizeRelationshipTemplateAllocationsAllocatedByIdentityCommand>.That.Matches(i => i.IdentityAddress == identityAddress),
A<CancellationToken>._)).MustHaveHappenedOnceExactly();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using Backbone.DevelopmentKit.Identity.ValueObjects;
using Backbone.Modules.Relationships.Application.Relationships.Commands.DecomposeAndAnonymizeRelationshipsOfIdentity;
using Backbone.Modules.Relationships.Application.RelationshipTemplates.Commands.AnonymizeRelationshipTemplateAllocationsAllocatedByIdentity;
using Backbone.Modules.Relationships.Application.RelationshipTemplates.Commands.AnonymizeRelationshipTemplatesForIdentity;
using Backbone.Modules.Relationships.Application.RelationshipTemplates.Commands.DeleteRelationshipTemplatesOfIdentity;
using MediatR;

Expand All @@ -23,6 +24,7 @@ public async Task Delete(IdentityAddress identityAddress)
await _mediator.Send(new DecomposeAndAnonymizeRelationshipsOfIdentityCommand(identityAddress));
await _deletionProcessLogger.LogDeletion(identityAddress, "Relationships");
await _mediator.Send(new DeleteRelationshipTemplatesOfIdentityCommand(identityAddress));
await _mediator.Send(new AnonymizeRelationshipTemplatesForIdentityCommand(identityAddress));
await _deletionProcessLogger.LogDeletion(identityAddress, "RelationshipTemplates");
await _mediator.Send(new AnonymizeRelationshipTemplateAllocationsAllocatedByIdentityCommand(identityAddress));
await _deletionProcessLogger.LogDeletion(identityAddress, "RelationshipTemplateAllocations");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,12 @@ public interface IRelationshipTemplatesRepository
Task<DbPaginationResult<RelationshipTemplate>> FindTemplates(IEnumerable<ListRelationshipTemplatesQueryItem> queryItems, IdentityAddress activeIdentity, PaginationFilter paginationFilter,
CancellationToken cancellationToken, bool track = false);

Task<IEnumerable<RelationshipTemplate>> FindTemplates(Expression<Func<RelationshipTemplate, bool>> filter, CancellationToken cancellationToken);

Task<RelationshipTemplate?> Find(RelationshipTemplateId id, IdentityAddress identityAddress, CancellationToken cancellationToken, bool track = false);
Task Add(RelationshipTemplate template, CancellationToken cancellationToken);
Task Update(RelationshipTemplate template);
Task Update(IEnumerable<RelationshipTemplate> templates, CancellationToken cancellationToken);
Task Delete(Expression<Func<RelationshipTemplate, bool>> filter, CancellationToken cancellationToken);
Task Delete(RelationshipTemplate template, CancellationToken cancellationToken);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using MediatR;

namespace Backbone.Modules.Relationships.Application.RelationshipTemplates.Commands.AnonymizeRelationshipTemplatesForIdentity;

public class AnonymizeRelationshipTemplatesForIdentityCommand : IRequest
{
public AnonymizeRelationshipTemplatesForIdentityCommand(string identityAddress)
{
IdentityAddress = identityAddress;
}

public string IdentityAddress { get; set; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using Backbone.DevelopmentKit.Identity.ValueObjects;
using Backbone.Modules.Relationships.Application.Infrastructure.Persistence.Repository;
using Backbone.Modules.Relationships.Domain.Aggregates.RelationshipTemplates;
using MediatR;
using Microsoft.Extensions.Options;

namespace Backbone.Modules.Relationships.Application.RelationshipTemplates.Commands.AnonymizeRelationshipTemplatesForIdentity;

public class Handler : IRequestHandler<AnonymizeRelationshipTemplatesForIdentityCommand>
{
private readonly IRelationshipTemplatesRepository _relationshipTemplatesRepository;
private readonly ApplicationOptions _applicationOptions;

public Handler(IRelationshipTemplatesRepository relationshipTemplatesRepository, IOptions<ApplicationOptions> options)
{
_relationshipTemplatesRepository = relationshipTemplatesRepository;
_applicationOptions = options.Value;
}

public async Task Handle(AnonymizeRelationshipTemplatesForIdentityCommand request, CancellationToken cancellationToken)
{
var relationshipTemplates = (await _relationshipTemplatesRepository.FindTemplates(RelationshipTemplate.IsFor(IdentityAddress.Parse(request.IdentityAddress)), cancellationToken)).ToList();

foreach (var relationshipTemplate in relationshipTemplates)
relationshipTemplate.AnonymizeForIdentity(_applicationOptions.DidDomainName);

await _relationshipTemplatesRepository.Update(relationshipTemplates, cancellationToken);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using Backbone.BuildingBlocks.Application.Extensions;
using Backbone.DevelopmentKit.Identity.ValueObjects;
using FluentValidation;

namespace Backbone.Modules.Relationships.Application.RelationshipTemplates.Commands.AnonymizeRelationshipTemplatesForIdentity;

public class Validator : AbstractValidator<AnonymizeRelationshipTemplatesForIdentityCommand>
{
public Validator()
{
RuleFor(c => c.IdentityAddress)
.ValidId<AnonymizeRelationshipTemplatesForIdentityCommand, IdentityAddress>();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
using Backbone.BuildingBlocks.Domain.Exceptions;
using Backbone.DevelopmentKit.Identity.ValueObjects;
using Backbone.UnitTestTools.Extensions;

namespace Backbone.Modules.Relationships.Domain.Aggregates.RelationshipTemplates;

public class RelationshipTemplateAnonymizeForIdentityTests : AbstractTestsBase
{
private const string DID_DOMAIN_NAME = "localhost";

[Fact]
public void Personalized_template_can_be_anonymized()
{
// Arrange
var creatorIdentityAddress = CreateRandomIdentityAddress();
var forIdentityAddress = CreateRandomIdentityAddress();
var deviceId = CreateRandomDeviceId();
byte[] content = [1, 1, 1, 1, 1, 1, 1, 1];
var relationshipTemplate = new RelationshipTemplate(creatorIdentityAddress, deviceId, null, null, content, forIdentityAddress);

// Act
relationshipTemplate.AnonymizeForIdentity(DID_DOMAIN_NAME);

// Assert
relationshipTemplate.ForIdentity.Should().Be(IdentityAddress.GetAnonymized(DID_DOMAIN_NAME));
}

[Fact]
public void Non_personalized_template_can_not_be_anonymized()
{
// Arrange
var creatorIdentityAddress = CreateRandomIdentityAddress();
var deviceId = CreateRandomDeviceId();
byte[] content = [1, 1, 1, 1, 1, 1, 1, 1];
var relationshipTemplate = new RelationshipTemplate(creatorIdentityAddress, deviceId, null, _dateTimeNow, content);

// Act
var acting = () => relationshipTemplate.AnonymizeForIdentity(DID_DOMAIN_NAME);

// Assert
acting.Should().Throw<DomainException>().WithError("error.platform.validation.relationship.relationshipTemplateNotPersonalized");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ public RelationshipTemplate(IdentityAddress createdBy, DeviceId createdByDevice,

public DateTime CreatedAt { get; set; }

public IdentityAddress? ForIdentity { get; set; }
public IdentityAddress? ForIdentity { get; private set; }
public byte[]? Password { get; set; }

public List<RelationshipTemplateAllocation> Allocations { get; set; } = [];
Expand All @@ -69,6 +69,15 @@ public void AllocateFor(IdentityAddress identity, DeviceId device)
Allocations.Add(new RelationshipTemplateAllocation(Id, identity, device));
}

public void AnonymizeForIdentity(string didDomainName)
{
EnsureIsPersonalized();

var anonymousIdentity = IdentityAddress.GetAnonymized(didDomainName);

ForIdentity = anonymousIdentity;
}

public bool IsAllocatedBy(IdentityAddress identity)
{
return Allocations.Any(x => x.AllocatedBy == identity);
Expand All @@ -87,6 +96,11 @@ public void EnsureCanBeDeletedBy(IdentityAddress identityAddress)
if (CreatedBy != identityAddress) throw new DomainActionForbiddenException();
}

public void EnsureIsPersonalized()
{
if (ForIdentity == null) throw new DomainException(DomainErrors.RelationshipTemplateNotPersonalized());
}

#region Expressions

public static Expression<Func<RelationshipTemplate, bool>> HasId(RelationshipTemplateId id)
Expand Down Expand Up @@ -117,5 +131,10 @@ public static Expression<Func<RelationshipTemplate, bool>> CanBeCollectedWithPas
a.AllocatedBy == activeIdentity); // if the template has already been allocated by the active identity, it doesn't need to pass the password again;
}

public static Expression<Func<RelationshipTemplate, bool>> IsFor(IdentityAddress identityAddress)
{
return relationshipTemplate => relationshipTemplate.ForIdentity == identityAddress;
}

#endregion
}
Original file line number Diff line number Diff line change
Expand Up @@ -82,4 +82,10 @@ public static DomainError RelationshipTemplateNotAllocated()
return new DomainError("error.platform.validation.relationship.relationshipTemplateNotAllocated",
"The relationship template has to be allocated before it can be used to establish a relationship. Send a GET request to the /RelationshipTemplates/{id} endpoint to allocate the template.");
}

public static DomainError RelationshipTemplateNotPersonalized()
{
return new DomainError("error.platform.validation.relationship.relationshipTemplateNotPersonalized",
"The relationship template has to be personalized.");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -79,12 +79,23 @@ public async Task<DbPaginationResult<RelationshipTemplate>> FindTemplates(IEnume
return templates;
}

public async Task<IEnumerable<RelationshipTemplate>> FindTemplates(Expression<Func<RelationshipTemplate, bool>> filter, CancellationToken cancellationToken)
{
return await _templates.Where(filter).ToListAsync(cancellationToken);
}

public async Task Update(RelationshipTemplate template)
{
_templates.Update(template);
await _dbContext.SaveChangesAsync();
}

public async Task Update(IEnumerable<RelationshipTemplate> templates, CancellationToken cancellationToken)
{
_templates.UpdateRange(templates);
await _dbContext.SaveChangesAsync(cancellationToken);
}

public async Task<IEnumerable<RelationshipTemplateAllocation>> FindRelationshipTemplateAllocations(Expression<Func<RelationshipTemplateAllocation, bool>> filter,
CancellationToken cancellationToken)
{
Expand Down
5 changes: 5 additions & 0 deletions Modules/Tokens/src/Tokens.Application/ApplicationOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ public class ApplicationOptions
{
[Required]
public PaginationOptions Pagination { get; set; } = new();

[Required]
[MinLength(3)]
[MaxLength(45)]
public string DidDomainName { get; set; } = null!;
}

public class PaginationOptions
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Backbone.BuildingBlocks.Application.Identities;
using Backbone.DevelopmentKit.Identity.ValueObjects;
using Backbone.Modules.Tokens.Application.Tokens.Commands.AnonymizeTokensForIdentity;
using Backbone.Modules.Tokens.Application.Tokens.Commands.DeleteTokensOfIdentity;
using MediatR;

Expand All @@ -19,6 +20,7 @@ public IdentityDeleter(IMediator mediator, IDeletionProcessLogger deletionProces
public async Task Delete(IdentityAddress identityAddress)
{
await _mediator.Send(new DeleteTokensOfIdentityCommand(identityAddress));
await _mediator.Send(new AnonymizeTokensForIdentityCommand(identityAddress));
await _deletionProcessLogger.LogDeletion(identityAddress, "Tokens");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@ public interface ITokensRepository
Task<DbPaginationResult<Token>> FindTokens(IEnumerable<ListTokensQueryItem> queryItems, IdentityAddress activeIdentity, PaginationFilter paginationFilter,
CancellationToken cancellationToken, bool track = false);

Task<IEnumerable<Token>> FindTokens(Expression<Func<Token, bool>> filter, CancellationToken cancellationToken, bool track = false);

Task<Token?> Find(TokenId tokenId, IdentityAddress? activeIdentity, CancellationToken cancellationToken, bool track = false);
Task Update(IEnumerable<Token> tokens, CancellationToken cancellationToken);
Task DeleteTokens(Expression<Func<Token, bool>> filter, CancellationToken cancellationToken);
Task DeleteToken(Token token, CancellationToken cancellationToken);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using MediatR;

namespace Backbone.Modules.Tokens.Application.Tokens.Commands.AnonymizeTokensForIdentity;

public class AnonymizeTokensForIdentityCommand : IRequest
{
public AnonymizeTokensForIdentityCommand(string identityAddress)
{
IdentityAddress = identityAddress;
}

public string IdentityAddress { get; set; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using Backbone.DevelopmentKit.Identity.ValueObjects;
using Backbone.Modules.Tokens.Application.Infrastructure.Persistence.Repository;
using Backbone.Modules.Tokens.Domain.Entities;
using MediatR;
using Microsoft.Extensions.Options;

namespace Backbone.Modules.Tokens.Application.Tokens.Commands.AnonymizeTokensForIdentity;

public class Handler : IRequestHandler<AnonymizeTokensForIdentityCommand>
{
private readonly ITokensRepository _tokensRepository;
private readonly ApplicationOptions _applicationOptions;

public Handler(ITokensRepository tokensRepository, IOptions<ApplicationOptions> applicationOptions)
{
_tokensRepository = tokensRepository;
_applicationOptions = applicationOptions.Value;
}

public async Task Handle(AnonymizeTokensForIdentityCommand request, CancellationToken cancellationToken)
{
var tokens = (await _tokensRepository.FindTokens(Token.IsFor(IdentityAddress.Parse(request.IdentityAddress)), cancellationToken)).ToList();

foreach (var token in tokens)
token.AnonymizeForIdentity(_applicationOptions.DidDomainName);

await _tokensRepository.Update(tokens, cancellationToken);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using Backbone.BuildingBlocks.Application.Extensions;
using Backbone.DevelopmentKit.Identity.ValueObjects;
using FluentValidation;

namespace Backbone.Modules.Tokens.Application.Tokens.Commands.AnonymizeTokensForIdentity;

public class Validator : AbstractValidator<AnonymizeTokensForIdentityCommand>
{
public Validator()
{
RuleFor(c => c.IdentityAddress)
.ValidId<AnonymizeTokensForIdentityCommand, IdentityAddress>();
}
}
Loading
Loading