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, tenantPermissions: [BackendPermissions.TenantRoleManage]); await using var provider = Services(snapshot); var authorization = provider.GetRequiredService(); 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(); 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(); 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, tenantPermissions: [BackendPermissions.TenantContentManage]); await using var selfProvider = Services(selfSnapshot); var denied = await selfProvider.GetRequiredService().AuthorizeAsync( principal, null, TikuPolicies.TenantContentManageAllScope); var allScope = new CurrentDataScope(DataScopeMode.All, new HashSet(), new HashSet(), true); var allSnapshot = Snapshot( userId, tenantId, tenantPermissions: [BackendPermissions.TenantContentManage], dataScope: allScope); await using var allProvider = Services(allSnapshot); var allowed = await allProvider.GetRequiredService().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 { regionId }, new HashSet { classId }, true); await using var provider = Services(Snapshot(userId, tenantId, dataScope: scope)); var authorization = provider.GetRequiredService(); var principal = Principal(userId, "tenant", tenantId); var requirement = new TenantResourceAccessRequirement(); var own = await authorization.AuthorizeAsync( principal, new TenantResourceAuthorizationResource(tenantId, OwnerUserId: 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(), OwnerUserId: 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(); 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(); 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(new StubCurrentAccessContext(snapshot)); services.AddTikuRbacAuthorization(); return services.BuildServiceProvider(); } private static ClaimsPrincipal Principal( Guid userId, string realm, Guid? tenantId) { var claims = new List { 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? tenantPermissions = null, IEnumerable? 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 GetAsync(CancellationToken cancellationToken = default) => Task.FromResult(snapshot); } }