Skip to content

Commit 1d90cc5

Browse files
committed
v9.0.5 / Fixed tenant registration / UX identity roles.
1 parent 9249937 commit 1d90cc5

23 files changed

Lines changed: 720 additions & 207 deletions

File tree

Shuttle.Access.Application/RegisterTenantParticipant.cs

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,28 @@ public async Task HandleAsync(RegisterTenant message, CancellationToken cancella
1313
ArgumentNullException.ThrowIfNull(idKeyRepository);
1414

1515
var key = Tenant.Key(message.Name);
16+
var id = await idKeyRepository.FindAsync(key, cancellationToken);
1617

17-
if (await idKeyRepository.ContainsAsync(key, cancellationToken))
18+
if (!id.HasValue)
1819
{
19-
return;
20+
await idKeyRepository.AddAsync(message.Id, key, cancellationToken);
2021
}
21-
22-
await idKeyRepository.AddAsync(message.Id, key, cancellationToken);
23-
24-
var stream = (await eventStore.GetAsync(message.Id, cancellationToken)).MustBeEmpty();
22+
else
23+
{
24+
if (!id.Value.Equals(message.Id))
25+
{
26+
throw new ApplicationException($"There is already a tenant key '{key}' which is associated with id '{id.Value}'.");
27+
}
28+
}
29+
30+
var stream = await eventStore.GetAsync(message.Id, cancellationToken);
2531
var aggregate = stream.Get<Tenant>();
2632

33+
if (!string.IsNullOrWhiteSpace(aggregate.Name))
34+
{
35+
return;
36+
}
37+
2738
stream.Add(aggregate.Register(message.Name, (int)message.Status, message.LogoSvg, message.LogoUrl));
2839

2940
await eventStore.SaveAsync(stream, builder => builder.Audit(message), cancellationToken);

Shuttle.Access.Application/Shuttle.Access.Application.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232

3333
<PropertyGroup>
3434
<Description>Application concern implementations such as Shuttle.Mediator participants.</Description>
35-
<Version>9.0.4</Version>
35+
<Version>9.0.5</Version>
3636
<Authors>Eben Roux</Authors>
3737
<Company>Shuttle</Company>
3838
<Copyright>Copyright (c) 2025, Eben Roux</Copyright>

Shuttle.Access.AspNetCore/Shuttle.Access.AspNetCore.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333

3434
<PropertyGroup>
3535
<Description>Authorization middleware for web API endpoints using Shuttle.Access.</Description>
36-
<Version>9.0.4</Version>
36+
<Version>9.0.5</Version>
3737
<Authors>Eben Roux</Authors>
3838
<Company>Shuttle</Company>
3939
<Copyright>Copyright (c) 2025, Eben Roux</Copyright>

Shuttle.Access.Messages/Shuttle.Access.Messages.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727

2828
<PropertyGroup>
2929
<Description>Messages for use in Shuttle.Access implementations.</Description>
30-
<Version>9.0.4</Version>
30+
<Version>9.0.5</Version>
3131
<Authors>Eben Roux</Authors>
3232
<Company>Shuttle</Company>
3333
<Copyright>Copyright (c) 2025, Eben Roux</Copyright>

Shuttle.Access.Messages/v1/RegisterTenant.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,5 @@ public class RegisterTenant : AuditMessage
88
public string LogoUrl { get; set; } = string.Empty;
99
public int Status { get; set; } = 1;
1010
public string AdministratorIdentityName { get; set; } = string.Empty;
11+
public Guid AccessAdministratorRoleId { get; set; }
1112
}

Shuttle.Access.RestClient/Shuttle.Access.RestClient.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333

3434
<PropertyGroup>
3535
<Description>A client used to interact with the restful Shuttle.Access web API.</Description>
36-
<Version>9.0.4</Version>
36+
<Version>9.0.5</Version>
3737
<Authors>Eben Roux</Authors>
3838
<Company>Shuttle</Company>
3939
<Copyright>Copyright (c) 2025, Eben Roux</Copyright>

Shuttle.Access.Server/v1/MessageHandlers/RegisterRoleHandler.cs

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,11 @@
11
using Shuttle.Access.Messages.v1;
2-
using Shuttle.Contract;
3-
using Shuttle.Mediator;
42
using Shuttle.Hopper;
5-
using Shuttle.Recall;
3+
using Shuttle.Mediator;
64
using Shuttle.Recall.SqlServer.Storage;
7-
using RoleRegistered = Shuttle.Access.Messages.v2.RoleRegistered;
85

96
namespace Shuttle.Access.Server.v1.MessageHandlers;
107

11-
public class RegisterRoleHandler(IBus bus, IMediator mediator, IPermissionQuery permissionQuery)
8+
public class RegisterRoleHandler(IBus bus, IMediator mediator, IPermissionQuery permissionQuery, IIdKeyRepository idKeyRepository)
129
: IMessageHandler<RegisterRole>
1310
{
1411
public async Task HandleAsync(RegisterRole message, CancellationToken cancellationToken = default)
@@ -17,6 +14,7 @@ public async Task HandleAsync(RegisterRole message, CancellationToken cancellati
1714
ArgumentNullException.ThrowIfNull(bus);
1815
ArgumentNullException.ThrowIfNull(mediator);
1916
ArgumentNullException.ThrowIfNull(permissionQuery);
17+
ArgumentNullException.ThrowIfNull(idKeyRepository);
2018

2119
var permissionIds = new List<Guid>();
2220

@@ -42,6 +40,15 @@ public async Task HandleAsync(RegisterRole message, CancellationToken cancellati
4240
}
4341
}
4442

43+
var key = Role.Key(message.Name, message.TenantId);
44+
45+
if (await idKeyRepository.ContainsAsync(key, cancellationToken))
46+
{
47+
return;
48+
}
49+
50+
await idKeyRepository.AddAsync(message.Id, key, cancellationToken);
51+
4552
var registerRole = new Application.RegisterRole(message.Id, message.TenantId, message.Name, message.AuditTenantId, message.AuditIdentityName);
4653

4754
foreach (var permissionId in permissionIds)

Shuttle.Access.Server/v1/MessageHandlers/RegisterTenantHandler.cs

Lines changed: 32 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,50 @@
1-
using System.Transactions;
2-
using Shuttle.Access.Messages.v1;
3-
using Shuttle.Contract;
1+
using Shuttle.Access.Messages.v1;
42
using Shuttle.Mediator;
53
using Shuttle.Hopper;
64

75
namespace Shuttle.Access.Server.v1.MessageHandlers;
86

9-
public class RegisterTenantHandler(ITenantQuery tenantQuery, IRoleQuery roleQuery, IIdentityQuery identityQuery, IMediator mediator) : IMessageHandler<RegisterTenant>
7+
public class RegisterTenantHandler(ITenantQuery tenantQuery, IRoleQuery roleQuery, IPermissionQuery permissionQuery, IIdentityQuery identityQuery, IMediator mediator, IBus bus) : IMessageHandler<RegisterTenant>
108
{
11-
private readonly IIdentityQuery _identityQuery = Guard.AgainstNull(identityQuery);
12-
private readonly IMediator _mediator = Guard.AgainstNull(mediator);
13-
private readonly IRoleQuery _roleQuery = Guard.AgainstNull(roleQuery);
14-
private readonly ITenantQuery _tenantQuery = Guard.AgainstNull(tenantQuery);
15-
169
public async Task HandleAsync(RegisterTenant message, CancellationToken cancellationToken = default)
1710
{
18-
Guard.AgainstNull(message);
11+
ArgumentNullException.ThrowIfNull(tenantQuery);
12+
ArgumentNullException.ThrowIfNull(roleQuery);
13+
ArgumentNullException.ThrowIfNull(permissionQuery);
14+
ArgumentNullException.ThrowIfNull(identityQuery);
15+
ArgumentNullException.ThrowIfNull(mediator);
16+
ArgumentNullException.ThrowIfNull(bus);
17+
ArgumentNullException.ThrowIfNull(message);
1918

20-
var identity = (await _identityQuery.SearchAsync(new Query.Identity.Specification().WithName(message.AdministratorIdentityName), cancellationToken)).FirstOrDefault();
19+
var identity = (await identityQuery.SearchAsync(new Query.Identity.Specification().WithName(message.AdministratorIdentityName), cancellationToken)).FirstOrDefault();
2120

2221
if (identity == null)
2322
{
24-
return;
23+
throw new ApplicationException($"Could not find the administrator identity with name '{message.AdministratorIdentityName}'.");
2524
}
2625

27-
using (new TransactionScope(TransactionScopeOption.Suppress, TransactionScopeAsyncFlowOption.Enabled))
26+
var accessAdministratorPermission = (await permissionQuery.SearchAsync(new Query.Permission.Specification().AddName(AccessPermissions.Administrator), cancellationToken)).FirstOrDefault();
27+
28+
if (accessAdministratorPermission == null)
2829
{
29-
await mediator.SendAsync(message, cancellationToken);
30+
throw new ApplicationException($"Could not find the Access administrator permission '{AccessPermissions.Administrator}'.");
3031
}
3132

33+
var registerTenant = new Application.RegisterTenant(message.Id, message.Name, (TenantStatus)message.Status, message.AuditTenantId, message.AuditIdentityName)
34+
{
35+
LogoUrl = message.LogoUrl,
36+
LogoSvg = message.LogoSvg
37+
};
38+
39+
await mediator.SendAsync(registerTenant, cancellationToken);
40+
3241
Query.Tenant? tenant;
3342

3443
var timeout = DateTime.UtcNow.AddSeconds(15);
44+
3545
do
3646
{
37-
tenant = (await _tenantQuery.SearchAsync(new Query.Tenant.Specification().AddId(message.Id), cancellationToken)).FirstOrDefault();
47+
tenant = (await tenantQuery.SearchAsync(new Query.Tenant.Specification().AddId(message.Id), cancellationToken)).FirstOrDefault();
3848

3949
if (tenant == null)
4050
{
@@ -49,19 +59,18 @@ public async Task HandleAsync(RegisterTenant message, CancellationToken cancella
4959

5060
var auditInformation = new AuditInformation(message.Id, "system");
5161

52-
var registerRoleMessage = new Application.RegisterRole(Guid.NewGuid(), message.Id, "Access Administrator", message.AuditTenantId, message.AuditIdentityName);
62+
var registerRoleMessage = new Application.RegisterRole(message.AccessAdministratorRoleId, message.Id, "Access Administrator", message.AuditTenantId, message.AuditIdentityName);
5363

54-
using (new TransactionScope(TransactionScopeOption.Suppress, TransactionScopeAsyncFlowOption.Enabled))
55-
{
56-
await _mediator.SendAsync(registerRoleMessage, cancellationToken);
57-
}
64+
registerRoleMessage.AddPermissionId(accessAdministratorPermission.Id);
65+
66+
await mediator.SendAsync(registerRoleMessage, cancellationToken);
5867

5968
Query.Role? role;
6069

6170
timeout = DateTime.UtcNow.AddSeconds(15);
6271
do
6372
{
64-
role = (await _roleQuery.SearchAsync(new Query.Role.Specification().AddName("Access Administrator").WithTenantId(message.Id), cancellationToken)).FirstOrDefault();
73+
role = (await roleQuery.SearchAsync(new Query.Role.Specification().AddId(message.AccessAdministratorRoleId), cancellationToken)).FirstOrDefault();
6574

6675
if (role == null)
6776
{
@@ -83,7 +92,7 @@ public async Task HandleAsync(RegisterTenant message, CancellationToken cancella
8392
AuditTenantId = auditInformation.AuditTenantId
8493
};
8594

86-
await _mediator.SendAsync(registerAdministratorMessage, cancellationToken);
95+
await bus.SendAsync(registerAdministratorMessage, builder => builder.ToSelf(), cancellationToken);
8796

8897
var setIdentityRoleMessage = new SetIdentityRoleStatus
8998
{
@@ -94,6 +103,6 @@ public async Task HandleAsync(RegisterTenant message, CancellationToken cancella
94103
AuditTenantId = auditInformation.AuditTenantId
95104
};
96105

97-
await _mediator.SendAsync(setIdentityRoleMessage, cancellationToken);
106+
await bus.SendAsync(setIdentityRoleMessage, builder => builder.ToSelf(), cancellationToken);
98107
}
99108
}

Shuttle.Access.SqlServer/IdentityQuery.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ public async Task<IEnumerable<Guid>> TenantIdsAsync(Query.Identity.Specification
8181
Id = item.RoleId,
8282
Name = item.Role.Name,
8383
TenantId = item.Role.TenantId,
84+
TenantName = item.Role.Tenant.Name,
8485
Permissions = specification.ShouldIncludePermissions
8586
? item.Role.RolePermissions.Select(rp => new Query.Permission
8687
{
@@ -104,7 +105,9 @@ public async Task<IEnumerable<Guid>> TenantIdsAsync(Query.Identity.Specification
104105
: queryable;
105106

106107
queryable = identitySpecification is { ShouldIncludeRoles: true, ShouldIncludePermissions: false }
107-
? queryable.Include(item => item.IdentityRoles).ThenInclude(item => item.Role)
108+
? queryable.Include(item => item.IdentityRoles)
109+
.ThenInclude(item => item.Role)
110+
.ThenInclude(item => item.Tenant)
108111
: queryable;
109112

110113
queryable = identitySpecification.ShouldIncludePermissions

0 commit comments

Comments
 (0)