238 lines
8.9 KiB
C#
238 lines
8.9 KiB
C#
using System.Security.Claims;
|
|
using Microsoft.AspNetCore.Authorization;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using Tiku.Api.Security;
|
|
using Tiku.Application.Security;
|
|
|
|
namespace Tiku.IntegrationTests.Api;
|
|
|
|
public sealed class RbacAuthorizationTests
|
|
{
|
|
[Fact]
|
|
public async Task TenantPolicy_requires_current_membership_and_permission()
|
|
{
|
|
var tenantId = Guid.NewGuid();
|
|
var userId = Guid.NewGuid();
|
|
var snapshot = Snapshot(
|
|
userId,
|
|
tenantId,
|
|
[BackendPermissions.TenantRoleManage]);
|
|
await using var provider = Services(snapshot);
|
|
var authorization = provider.GetRequiredService<IAuthorizationService>();
|
|
|
|
var allowed = await authorization.AuthorizeAsync(
|
|
Principal(userId, "tenant", tenantId),
|
|
null,
|
|
BackendPermissions.TenantRoleManage);
|
|
var wrongTenant = await authorization.AuthorizeAsync(
|
|
Principal(userId, "tenant", Guid.NewGuid()),
|
|
null,
|
|
BackendPermissions.TenantRoleManage);
|
|
|
|
Assert.True(allowed.Succeeded);
|
|
Assert.False(wrongTenant.Succeeded);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task PlatformPolicy_RejectsTenantRealmEvenWhenUserHasPlatformPermission()
|
|
{
|
|
var tenantId = Guid.NewGuid();
|
|
var userId = Guid.NewGuid();
|
|
var snapshot = Snapshot(
|
|
userId,
|
|
tenantId,
|
|
platformPermissions: [BackendPermissions.PlatformRoleManage]);
|
|
await using var provider = Services(snapshot);
|
|
var authorization = provider.GetRequiredService<IAuthorizationService>();
|
|
|
|
var tenantRealm = await authorization.AuthorizeAsync(
|
|
Principal(userId, "tenant", tenantId),
|
|
null,
|
|
BackendPermissions.PlatformRoleManage);
|
|
var platformRealm = await authorization.AuthorizeAsync(
|
|
Principal(userId, "platform", null),
|
|
null,
|
|
BackendPermissions.PlatformRoleManage);
|
|
|
|
Assert.False(tenantRealm.Succeeded);
|
|
Assert.True(platformRealm.Succeeded);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task PermissionPolicy_DoesNotUseJwtRoleClaims()
|
|
{
|
|
var tenantId = Guid.NewGuid();
|
|
var userId = Guid.NewGuid();
|
|
var snapshot = Snapshot(userId, tenantId);
|
|
await using var provider = Services(snapshot);
|
|
var authorization = provider.GetRequiredService<IAuthorizationService>();
|
|
var principal = Principal(userId, "tenant", tenantId);
|
|
((ClaimsIdentity)principal.Identity!).AddClaim(new Claim(ClaimTypes.Role, "TenantOwner"));
|
|
|
|
var result = await authorization.AuthorizeAsync(
|
|
principal,
|
|
null,
|
|
BackendPermissions.TenantRoleManage);
|
|
|
|
Assert.False(result.Succeeded);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task AllScopePolicy_RejectsRestrictedOrSelfDataScope()
|
|
{
|
|
var tenantId = Guid.NewGuid();
|
|
var userId = Guid.NewGuid();
|
|
var principal = Principal(userId, "tenant", tenantId);
|
|
var selfSnapshot = Snapshot(
|
|
userId,
|
|
tenantId,
|
|
[BackendPermissions.TenantContentManage]);
|
|
await using var selfProvider = Services(selfSnapshot);
|
|
var denied = await selfProvider.GetRequiredService<IAuthorizationService>().AuthorizeAsync(
|
|
principal,
|
|
null,
|
|
TikuPolicies.TenantContentManageAllScope);
|
|
|
|
var allScope = new CurrentDataScope(DataScopeMode.All, new HashSet<Guid>(), new HashSet<Guid>(), true);
|
|
var allSnapshot = Snapshot(
|
|
userId,
|
|
tenantId,
|
|
[BackendPermissions.TenantContentManage],
|
|
dataScope: allScope);
|
|
await using var allProvider = Services(allSnapshot);
|
|
var allowed = await allProvider.GetRequiredService<IAuthorizationService>().AuthorizeAsync(
|
|
principal,
|
|
null,
|
|
TikuPolicies.TenantContentManageAllScope);
|
|
|
|
Assert.False(denied.Succeeded);
|
|
Assert.True(allowed.Succeeded);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ResourceRequirement_UsesOwnerRegionClassAndTenantBoundary()
|
|
{
|
|
var tenantId = Guid.NewGuid();
|
|
var userId = Guid.NewGuid();
|
|
var regionId = Guid.NewGuid();
|
|
var classId = Guid.NewGuid();
|
|
var scope = new CurrentDataScope(
|
|
DataScopeMode.Restricted,
|
|
new HashSet<Guid> { regionId },
|
|
new HashSet<Guid> { classId },
|
|
true);
|
|
await using var provider = Services(Snapshot(userId, tenantId, dataScope: scope));
|
|
var authorization = provider.GetRequiredService<IAuthorizationService>();
|
|
var principal = Principal(userId, "tenant", tenantId);
|
|
var requirement = new TenantResourceAccessRequirement();
|
|
|
|
var own = await authorization.AuthorizeAsync(
|
|
principal,
|
|
new TenantResourceAuthorizationResource(tenantId, userId),
|
|
requirement);
|
|
var region = await authorization.AuthorizeAsync(
|
|
principal,
|
|
new TenantResourceAuthorizationResource(tenantId, RegionId: regionId),
|
|
requirement);
|
|
var @class = await authorization.AuthorizeAsync(
|
|
principal,
|
|
new TenantResourceAuthorizationResource(tenantId, ClassId: classId),
|
|
requirement);
|
|
var outside = await authorization.AuthorizeAsync(
|
|
principal,
|
|
new TenantResourceAuthorizationResource(tenantId, RegionId: Guid.NewGuid()),
|
|
requirement);
|
|
var otherTenant = await authorization.AuthorizeAsync(
|
|
principal,
|
|
new TenantResourceAuthorizationResource(Guid.NewGuid(), userId),
|
|
requirement);
|
|
|
|
Assert.True(own.Succeeded);
|
|
Assert.True(region.Succeeded);
|
|
Assert.True(@class.Succeeded);
|
|
Assert.False(outside.Succeeded);
|
|
Assert.False(otherTenant.Succeeded);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task BackofficeBootstrapPolicies_RequireCurrentRealmAndEffectiveAccess()
|
|
{
|
|
var tenantId = Guid.NewGuid();
|
|
var userId = Guid.NewGuid();
|
|
var tenantSnapshot = Snapshot(userId, tenantId);
|
|
await using var tenantProvider = Services(tenantSnapshot);
|
|
var tenantAuthorization = tenantProvider.GetRequiredService<IAuthorizationService>();
|
|
var tenantAllowed = await tenantAuthorization.AuthorizeAsync(
|
|
Principal(userId, "tenant", tenantId),
|
|
null,
|
|
TikuPolicies.TenantBackofficeBootstrap);
|
|
|
|
var platformSnapshot = Snapshot(
|
|
userId,
|
|
null,
|
|
platformPermissions: [BackendPermissions.PlatformDashboardView]);
|
|
await using var platformProvider = Services(platformSnapshot);
|
|
var platformAuthorization = platformProvider.GetRequiredService<IAuthorizationService>();
|
|
var platformAllowed = await platformAuthorization.AuthorizeAsync(
|
|
Principal(userId, "platform", null),
|
|
null,
|
|
TikuPolicies.PlatformBackofficeBootstrap);
|
|
var tenantRealmDenied = await platformAuthorization.AuthorizeAsync(
|
|
Principal(userId, "tenant", tenantId),
|
|
null,
|
|
TikuPolicies.PlatformBackofficeBootstrap);
|
|
|
|
Assert.True(tenantAllowed.Succeeded);
|
|
Assert.True(platformAllowed.Succeeded);
|
|
Assert.False(tenantRealmDenied.Succeeded);
|
|
}
|
|
|
|
private static ServiceProvider Services(CurrentAccessSnapshot snapshot)
|
|
{
|
|
var services = new ServiceCollection();
|
|
services.AddLogging();
|
|
services.AddSingleton<ICurrentAccessContext>(new StubCurrentAccessContext(snapshot));
|
|
services.AddTikuRbacAuthorization();
|
|
return services.BuildServiceProvider();
|
|
}
|
|
|
|
private static ClaimsPrincipal Principal(
|
|
Guid userId,
|
|
string realm,
|
|
Guid? tenantId)
|
|
{
|
|
var claims = new List<Claim>
|
|
{
|
|
new(TikuClaimTypes.UserId, userId.ToString()),
|
|
new(TikuClaimTypes.Realm, realm)
|
|
};
|
|
if (tenantId.HasValue) claims.Add(new Claim(TikuClaimTypes.TenantId, tenantId.Value.ToString()));
|
|
|
|
return new ClaimsPrincipal(new ClaimsIdentity(claims, "test"));
|
|
}
|
|
|
|
private static CurrentAccessSnapshot Snapshot(
|
|
Guid userId,
|
|
Guid? tenantId,
|
|
IEnumerable<string>? tenantPermissions = null,
|
|
IEnumerable<string>? platformPermissions = null,
|
|
CurrentDataScope? dataScope = null)
|
|
{
|
|
return new CurrentAccessSnapshot(
|
|
userId,
|
|
tenantId,
|
|
true,
|
|
tenantId.HasValue,
|
|
(tenantPermissions ?? []).ToHashSet(StringComparer.Ordinal),
|
|
(platformPermissions ?? []).ToHashSet(StringComparer.Ordinal),
|
|
dataScope ?? CurrentDataScope.Self);
|
|
}
|
|
|
|
private sealed class StubCurrentAccessContext(CurrentAccessSnapshot snapshot) : ICurrentAccessContext
|
|
{
|
|
public Task<CurrentAccessSnapshot> GetAsync(CancellationToken cancellationToken = default)
|
|
{
|
|
return Task.FromResult(snapshot);
|
|
}
|
|
}
|
|
} |