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

Miscellaneous fixes. #46

Merged
merged 5 commits into from
Jan 30, 2024
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
Expand Up @@ -25,10 +25,11 @@ public record ApiKeyUpdatedEvent : DomainEvent, INotification
/// <summary>
/// Gets or sets the custom attribute modifications of the API key.
/// </summary>
public Dictionary<string, string?> CustomAttributes { get; } = [];
public Dictionary<string, string?> CustomAttributes { get; init; } = [];

/// <summary>
/// Gets a value indicating whether or not the API key is being modified.
/// </summary>
[JsonIgnore]
public bool HasChanges => DisplayName != null || Description != null || ExpiresOn != null || CustomAttributes.Count > 0;
}
10 changes: 5 additions & 5 deletions src/Logitar.Identity.Domain/Logitar.Identity.Domain.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,14 @@
<PackageReadmeFile>README.md</PackageReadmeFile>
<RepositoryUrl>https://github.com/Logitar/Identity</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<AssemblyVersion>0.11.3.0</AssemblyVersion>
<AssemblyVersion>0.11.4.0</AssemblyVersion>
<FileVersion>$(AssemblyVersion)</FileVersion>
<PackageLicenseFile>LICENSE</PackageLicenseFile>
<PackageRequireLicenseAcceptance>True</PackageRequireLicenseAcceptance>
<Version>0.11.3</Version>
<Version>0.11.4</Version>
<NeutralLanguage>en-CA</NeutralLanguage>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
<PackageReleaseNotes>Created a Contracts package.</PackageReleaseNotes>
<PackageReleaseNotes>Fixed event serialization.</PackageReleaseNotes>
<PackageTags>logitar;net;framework;identity;domain</PackageTags>
<PackageProjectUrl>https://github.com/Logitar/Identity/tree/main/src/Logitar.Identity.Domain</PackageProjectUrl>
</PropertyGroup>
Expand All @@ -45,9 +45,9 @@

<ItemGroup>
<PackageReference Include="FluentValidation" Version="11.9.0" />
<PackageReference Include="libphonenumber-csharp" Version="8.13.27" />
<PackageReference Include="libphonenumber-csharp" Version="8.13.29" />
<PackageReference Include="Logitar.EventSourcing" Version="5.0.1" />
<PackageReference Include="Logitar.Security" Version="6.1.0" />
<PackageReference Include="Logitar.Security" Version="6.1.1" />
<PackageReference Include="MediatR.Contracts" Version="2.0.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="8.0.1" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.0" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@ public record OneTimePasswordUpdatedEvent : DomainEvent, INotification
/// <summary>
/// Gets or sets the custom attribute modifications of the One-Time Password (OTP).
/// </summary>
public Dictionary<string, string?> CustomAttributes { get; } = [];
public Dictionary<string, string?> CustomAttributes { get; init; } = [];

/// <summary>
/// Gets a value indicating whether or not the One-Time Password (OTP) is being modified.
/// </summary>
[JsonIgnore]
public bool HasChanges => CustomAttributes.Count > 0;
}
3 changes: 2 additions & 1 deletion src/Logitar.Identity.Domain/Roles/Events/RoleUpdatedEvent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,11 @@ public record RoleUpdatedEvent : DomainEvent, INotification
/// <summary>
/// Gets or sets the custom attribute modifications of the role.
/// </summary>
public Dictionary<string, string?> CustomAttributes { get; } = [];
public Dictionary<string, string?> CustomAttributes { get; init; } = [];

/// <summary>
/// Gets a value indicating whether or not the role is being modified.
/// </summary>
[JsonIgnore]
public bool HasChanges => DisplayName != null || Description != null || CustomAttributes.Count > 0;
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@ public record SessionUpdatedEvent : DomainEvent, INotification
/// <summary>
/// Gets or sets the custom attribute modifications of the session.
/// </summary>
public Dictionary<string, string?> CustomAttributes { get; } = [];
public Dictionary<string, string?> CustomAttributes { get; init; } = [];

/// <summary>
/// Gets a value indicating whether or not the session is being modified.
/// </summary>
[JsonIgnore]
public bool HasChanges => CustomAttributes.Count > 0;
}
3 changes: 2 additions & 1 deletion src/Logitar.Identity.Domain/Users/Events/UserUpdatedEvent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,12 @@ public record UserUpdatedEvent : DomainEvent, INotification
/// <summary>
/// Gets or sets the custom attribute modifications of the user.
/// </summary>
public Dictionary<string, string?> CustomAttributes { get; } = [];
public Dictionary<string, string?> CustomAttributes { get; init; } = [];

/// <summary>
/// Gets a value indicating whether or not the user is being modified.
/// </summary>
[JsonIgnore]
public bool HasChanges => FirstName != null || MiddleName != null || LastName != null || FullName != null || Nickname != null
|| Birthdate != null || Gender != null || Locale != null || TimeZone != null
|| Picture != null || Profile != null || Website != null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,14 @@ public CustomAttributeService(IdentityContext context)
Context = context;
}

public virtual async Task RemoveAsync(string entityType, int entityId, CancellationToken cancellationToken)
{
CustomAttributeEntity[] entities = await Context.CustomAttributes
.Where(x => x.EntityType == entityType && x.EntityId == entityId)
.ToArrayAsync(cancellationToken);
Context.CustomAttributes.RemoveRange(entities);
}

public virtual async Task SynchronizeAsync(string entityType, int entityId, Dictionary<string, string?> customAttributes, CancellationToken cancellationToken)
{
if (customAttributes.Count == 0)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@

public interface ICustomAttributeService
{
Task RemoveAsync(string entityType, int entityId, CancellationToken cancellationToken = default);
Task SynchronizeAsync(string entityType, int entityId, Dictionary<string, string?> customAttributes, CancellationToken cancellationToken = default);
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ public virtual async Task HandleAsync(ApiKeyDeletedEvent @event, CancellationTok
Context.ApiKeys.Remove(apiKey);

await DeleteActorAsync(apiKey, cancellationToken);
await CustomAttributes.RemoveAsync(EntityType, apiKey.ApiKeyId, cancellationToken);
await Context.SaveChangesAsync(cancellationToken);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ public virtual async Task HandleAsync(OneTimePasswordDeletedEvent @event, Cancel
{
Context.OneTimePasswords.Remove(oneTimePassword);

await CustomAttributes.RemoveAsync(EntityType, oneTimePassword.OneTimePasswordId, cancellationToken);
await Context.SaveChangesAsync(cancellationToken);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ public virtual async Task HandleAsync(RoleDeletedEvent @event, CancellationToken
{
Context.Roles.Remove(role);

await CustomAttributes.RemoveAsync(EntityType, role.RoleId, cancellationToken);
await Context.SaveChangesAsync(cancellationToken);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ public virtual async Task HandleAsync(SessionDeletedEvent @event, CancellationTo
if (session != null)
{
Context.Sessions.Remove(session);

await CustomAttributes.RemoveAsync(EntityType, session.SessionId, cancellationToken);
await Context.SaveChangesAsync(cancellationToken);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ public virtual async Task HandleAsync(UserDeletedEvent @event, CancellationToken
Context.Users.Remove(user);

await DeleteActorAsync(user, cancellationToken);
await CustomAttributes.RemoveAsync(EntityType, user.UserId, cancellationToken);
await Context.SaveChangesAsync(cancellationToken);
}
}
Expand Down Expand Up @@ -90,33 +91,34 @@ public virtual async Task HandleAsync(UserEmailChangedEvent @event, Cancellation
}
}

public virtual async Task HandleAsync(UserIdentifierChangedEvent @event, CancellationToken cancellationToken)
public virtual async Task HandleAsync(UserEnabledEvent @event, CancellationToken cancellationToken)
{
UserEntity? user = await TryLoadAsync(@event.AggregateId, cancellationToken);
if (user != null)
{
user.SetCustomIdentifier(@event);
user.Enable(@event);

await Context.SaveChangesAsync(cancellationToken);
}
}
public virtual async Task HandleAsync(UserIdentifierRemovedEvent @event, CancellationToken cancellationToken)

public virtual async Task HandleAsync(UserIdentifierChangedEvent @event, CancellationToken cancellationToken)
{
UserEntity? user = await TryLoadAsync(@event.AggregateId, cancellationToken);
if (user != null)
{
user.RemoveCustomIdentifier(@event);
user.SetCustomIdentifier(@event);

await Context.SaveChangesAsync(cancellationToken);
}
}

public virtual async Task HandleAsync(UserEnabledEvent @event, CancellationToken cancellationToken)
public virtual async Task HandleAsync(UserIdentifierRemovedEvent @event, CancellationToken cancellationToken)
{
UserEntity? user = await TryLoadAsync(@event.AggregateId, cancellationToken);
if (user != null)
{
user.Enable(@event);
user.RemoveCustomIdentifier(@event);

await Context.SaveChangesAsync(cancellationToken);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,14 @@
<PackageReadmeFile>README.md</PackageReadmeFile>
<RepositoryUrl>https://github.com/Logitar/Identity</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<AssemblyVersion>0.11.2.0</AssemblyVersion>
<AssemblyVersion>0.11.3.0</AssemblyVersion>
<FileVersion>$(AssemblyVersion)</FileVersion>
<PackageLicenseFile>LICENSE</PackageLicenseFile>
<PackageRequireLicenseAcceptance>True</PackageRequireLicenseAcceptance>
<Version>0.11.2</Version>
<Version>0.11.3</Version>
<NeutralLanguage>en-CA</NeutralLanguage>
<GenerateDocumentationFile>False</GenerateDocumentationFile>
<PackageReleaseNotes>Implemented CustomAttributeService and SessionRepository.LoadActiveAsync.</PackageReleaseNotes>
<PackageReleaseNotes>Remove custom attributes when deleting an entity.</PackageReleaseNotes>
<PackageTags>logitar;net;framework;identity;entityframeworkcore;relational</PackageTags>
<PackageProjectUrl>https://github.com/Logitar/Identity/tree/main/src/Logitar.Identity.EntityFrameworkCore.Relational</PackageProjectUrl>
</PropertyGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using Bogus;

namespace Logitar.Identity.Domain.ApiKeys.Events;

[Trait(Traits.Category, Categories.Unit)]
public class ApiKeyUpdatedEventTests
{
private readonly Faker _faker = new();

[Fact(DisplayName = "It should be serializable and deserializable.")]
public void It_should_be_serializable_and_deserializable()
{
ApiKeyUpdatedEvent @event = new();
@event.CustomAttributes.Add("Owner", _faker.Person.UserName);
@event.CustomAttributes.Add("SubSystem", "Identity");

string json = JsonSerializer.Serialize(@event);
Assert.DoesNotContain("haschanges", json.ToLower());

ApiKeyUpdatedEvent? deserialized = JsonSerializer.Deserialize<ApiKeyUpdatedEvent>(json);
Assert.NotNull(deserialized);
Assert.Equal(@event.CustomAttributes, deserialized.CustomAttributes);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
<Using Include="System.IdentityModel.Tokens.Jwt" />
<Using Include="System.Reflection" />
<Using Include="System.Security.Claims" />
<Using Include="System.Text.Json" />
<Using Include="Xunit" />
</ItemGroup>

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
namespace Logitar.Identity.Domain.Passwords.Events;

[Trait(Traits.Category, Categories.Unit)]
public class OneTimePasswordUpdatedEventTests
{
[Fact(DisplayName = "It should be serializable and deserializable.")]
public void It_should_be_serializable_and_deserializable()
{
OneTimePasswordUpdatedEvent @event = new();
@event.CustomAttributes.Add("Purpose", "reset_password");
@event.CustomAttributes.Add("UserId", Guid.NewGuid().ToString());

string json = JsonSerializer.Serialize(@event);
Assert.DoesNotContain("haschanges", json.ToLower());

OneTimePasswordUpdatedEvent? deserialized = JsonSerializer.Deserialize<OneTimePasswordUpdatedEvent>(json);
Assert.NotNull(deserialized);
Assert.Equal(@event.CustomAttributes, deserialized.CustomAttributes);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
namespace Logitar.Identity.Domain.Roles.Events;

[Trait(Traits.Category, Categories.Unit)]
public class RoleUpdatedEventTests
{
[Fact(DisplayName = "It should be serializable and deserializable.")]
public void It_should_be_serializable_and_deserializable()
{
RoleUpdatedEvent @event = new();
@event.CustomAttributes.Add("manage_users", bool.FalseString);
@event.CustomAttributes.Add("configuration", bool.TrueString);

string json = JsonSerializer.Serialize(@event);
Assert.DoesNotContain("haschanges", json.ToLower());

RoleUpdatedEvent? deserialized = JsonSerializer.Deserialize<RoleUpdatedEvent>(json);
Assert.NotNull(deserialized);
Assert.Equal(@event.CustomAttributes, deserialized.CustomAttributes);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using Bogus;

namespace Logitar.Identity.Domain.Sessions.Events;

[Trait(Traits.Category, Categories.Unit)]
public class SessionUpdatedEventTests
{
private readonly Faker _faker = new();

[Fact(DisplayName = "It should be serializable and deserializable.")]
public void It_should_be_serializable_and_deserializable()
{
SessionUpdatedEvent @event = new();
@event.CustomAttributes.Add("AdditionalInformation", $@"{{""User-Agent"":""{_faker.Internet.UserAgent()}""}}");
@event.CustomAttributes.Add("IpAddress", _faker.Internet.Ip());

string json = JsonSerializer.Serialize(@event);
Assert.DoesNotContain("haschanges", json.ToLower());

SessionUpdatedEvent? deserialized = JsonSerializer.Deserialize<SessionUpdatedEvent>(json);
Assert.NotNull(deserialized);
Assert.Equal(@event.CustomAttributes, deserialized.CustomAttributes);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using Bogus;

namespace Logitar.Identity.Domain.Users.Events;

[Trait(Traits.Category, Categories.Unit)]
public class UserUpdatedEventTests
{
private readonly Faker _faker = new();

[Fact(DisplayName = "It should be serializable and deserializable.")]
public void It_should_be_serializable_and_deserializable()
{
UserUpdatedEvent @event = new();
@event.CustomAttributes.Add("HealthInsuranceNumber", _faker.Person.BuildHealthInsuranceNumber());
@event.CustomAttributes.Add("JobTitle", "Sales Manager");

string json = JsonSerializer.Serialize(@event);
Assert.DoesNotContain("haschanges", json.ToLower());

UserUpdatedEvent? deserialized = JsonSerializer.Deserialize<UserUpdatedEvent>(json);
Assert.NotNull(deserialized);
Assert.Equal(@event.CustomAttributes, deserialized.CustomAttributes);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using Logitar.EventSourcing.EntityFrameworkCore.Relational;
using Logitar.Identity.Domain.Passwords;
using Logitar.Identity.Domain.Shared;
using Logitar.Identity.Domain.Users;
using Logitar.Identity.EntityFrameworkCore.Relational;
using Logitar.Identity.EntityFrameworkCore.Relational.Entities;
using Microsoft.EntityFrameworkCore;
Expand Down Expand Up @@ -108,6 +109,36 @@ public async Task LoadAsync_it_should_load_the_One_Time_Passwords_by_identifiers
Assert.Contains(oneTimePasswords, deleted.Equals);
}

[Fact(DisplayName = "SaveAsync: it should save the deleted One-Time Password.")]
public async Task SaveAsync_it_should_save_the_deleted_One_Time_Password()
{
_oneTimePassword.SetCustomAttribute("Purpose", "reset_password");
_oneTimePassword.SetCustomAttribute("UserId", UserId.NewId().Value);
_oneTimePassword.Update();
await _oneTimePasswordRepository.SaveAsync(_oneTimePassword);

OneTimePasswordEntity? entity = await IdentityContext.OneTimePasswords.AsNoTracking()
.SingleOrDefaultAsync(x => x.AggregateId == _oneTimePassword.Id.Value);
Assert.NotNull(entity);

CustomAttributeEntity[] customAttributes = await IdentityContext.CustomAttributes.AsNoTracking()
.Where(x => x.EntityType == nameof(IdentityContext.OneTimePasswords) && x.EntityId == entity.OneTimePasswordId)
.ToArrayAsync();
Assert.Equal(_oneTimePassword.CustomAttributes.Count, customAttributes.Length);
foreach (KeyValuePair<string, string> customAttribute in _oneTimePassword.CustomAttributes)
{
Assert.Contains(customAttributes, c => c.Key == customAttribute.Key && c.Value == customAttribute.Value);
}

_oneTimePassword.Delete();
await _oneTimePasswordRepository.SaveAsync(_oneTimePassword);

customAttributes = await IdentityContext.CustomAttributes.AsNoTracking()
.Where(x => x.EntityType == nameof(IdentityContext.OneTimePasswords) && x.EntityId == entity.OneTimePasswordId)
.ToArrayAsync();
Assert.Empty(customAttributes);
}

[Fact(DisplayName = "SaveAsync: it should save the specified One-Time Password.")]
public async Task SaveAsync_it_should_save_the_specified_One_Time_Password()
{
Expand Down
Loading