fix: scope organization access by membership
This commit is contained in:
@@ -24,7 +24,7 @@ internal sealed class AccessScopeService(
|
||||
|
||||
public static bool CanAccessWorkspace(ClaimsPrincipal user, Guid workspaceId)
|
||||
{
|
||||
return IsManager(user) || user.GetWorkspaceScopeIds().Contains(workspaceId);
|
||||
return user.GetWorkspaceScopeIds().Contains(workspaceId);
|
||||
}
|
||||
|
||||
public static bool CanManageWorkspace(ClaimsPrincipal user, Guid workspaceId)
|
||||
@@ -34,24 +34,25 @@ internal sealed class AccessScopeService(
|
||||
|
||||
public static bool CanAccessClient(ClaimsPrincipal user, Guid workspaceId, Guid clientId)
|
||||
{
|
||||
return IsManager(user)
|
||||
|| (CanAccessWorkspace(user, workspaceId) && user.GetClientScopeIds().Contains(clientId));
|
||||
return CanAccessWorkspace(user, workspaceId) &&
|
||||
(IsManager(user) || user.GetClientScopeIds().Contains(clientId));
|
||||
}
|
||||
|
||||
public static bool CanAccessCampaign(ClaimsPrincipal user, Guid workspaceId, Guid clientId, Guid campaignId)
|
||||
{
|
||||
return IsManager(user)
|
||||
|| (CanAccessClient(user, workspaceId, clientId) && user.GetCampaignScopeIds().Contains(campaignId));
|
||||
return CanAccessClient(user, workspaceId, clientId) &&
|
||||
(IsManager(user) || user.GetCampaignScopeIds().Contains(campaignId));
|
||||
}
|
||||
|
||||
public static bool CanContributeToCampaign(ClaimsPrincipal user, Guid workspaceId, Guid clientId, Guid campaignId)
|
||||
{
|
||||
return IsManager(user) || (IsProvider(user) && CanAccessCampaign(user, workspaceId, clientId, campaignId));
|
||||
return IsManager(user) && CanAccessCampaign(user, workspaceId, clientId, campaignId)
|
||||
|| IsProvider(user) && CanAccessCampaign(user, workspaceId, clientId, campaignId);
|
||||
}
|
||||
|
||||
public static bool CanReviewContent(ClaimsPrincipal user, Guid workspaceId, Guid clientId, Guid campaignId)
|
||||
{
|
||||
return IsManager(user)
|
||||
return IsManager(user) && CanAccessCampaign(user, workspaceId, clientId, campaignId)
|
||||
|| IsProvider(user) && CanAccessCampaign(user, workspaceId, clientId, campaignId)
|
||||
|| IsClient(user) && CanAccessClient(user, workspaceId, clientId);
|
||||
}
|
||||
@@ -68,7 +69,7 @@ internal sealed class AccessScopeService(
|
||||
Guid workspaceId,
|
||||
CancellationToken ct)
|
||||
{
|
||||
return CanAccessWorkspace(user, workspaceId)
|
||||
return user.GetWorkspaceScopeIds().Contains(workspaceId)
|
||||
|| await organizationAccessService.HasInheritedWorkspacePermissionAsync(
|
||||
user,
|
||||
workspaceId,
|
||||
@@ -81,7 +82,7 @@ internal sealed class AccessScopeService(
|
||||
Guid workspaceId,
|
||||
CancellationToken ct)
|
||||
{
|
||||
return IsManager(user)
|
||||
return CanManageWorkspace(user, workspaceId)
|
||||
|| await organizationAccessService.HasInheritedWorkspacePermissionAsync(
|
||||
user,
|
||||
workspaceId,
|
||||
@@ -94,8 +95,7 @@ internal sealed class AccessScopeService(
|
||||
Guid organizationId,
|
||||
CancellationToken ct)
|
||||
{
|
||||
return IsManager(user)
|
||||
|| await organizationAccessService.HasOrganizationPermissionAsync(
|
||||
return await organizationAccessService.HasOrganizationPermissionAsync(
|
||||
user,
|
||||
organizationId,
|
||||
OrganizationPermissions.CreateWorkspaces,
|
||||
@@ -108,8 +108,7 @@ internal sealed class AccessScopeService(
|
||||
Guid clientId,
|
||||
CancellationToken ct)
|
||||
{
|
||||
if (IsManager(user) ||
|
||||
await organizationAccessService.HasInheritedWorkspacePermissionAsync(
|
||||
if (await organizationAccessService.HasInheritedWorkspacePermissionAsync(
|
||||
user,
|
||||
workspaceId,
|
||||
OrganizationPermissions.AccessOwnedWorkspaces,
|
||||
@@ -128,8 +127,7 @@ internal sealed class AccessScopeService(
|
||||
Guid campaignId,
|
||||
CancellationToken ct)
|
||||
{
|
||||
if (IsManager(user) ||
|
||||
await organizationAccessService.HasInheritedWorkspacePermissionAsync(
|
||||
if (await organizationAccessService.HasInheritedWorkspacePermissionAsync(
|
||||
user,
|
||||
workspaceId,
|
||||
OrganizationPermissions.AccessOwnedWorkspaces,
|
||||
@@ -149,7 +147,7 @@ internal sealed class AccessScopeService(
|
||||
Guid campaignId,
|
||||
CancellationToken ct)
|
||||
{
|
||||
return IsManager(user)
|
||||
return IsManager(user) && await CanAccessCampaignAsync(user, workspaceId, clientId, campaignId, ct)
|
||||
|| await organizationAccessService.HasInheritedWorkspacePermissionAsync(
|
||||
user,
|
||||
workspaceId,
|
||||
@@ -165,7 +163,7 @@ internal sealed class AccessScopeService(
|
||||
Guid campaignId,
|
||||
CancellationToken ct)
|
||||
{
|
||||
return IsManager(user)
|
||||
return IsManager(user) && await CanAccessCampaignAsync(user, workspaceId, clientId, campaignId, ct)
|
||||
|| await organizationAccessService.HasInheritedWorkspacePermissionAsync(
|
||||
user,
|
||||
workspaceId,
|
||||
|
||||
@@ -34,24 +34,21 @@ internal class GetCampaignsHandler(
|
||||
{
|
||||
IQueryable<Campaign> query = dbContext.Campaigns.AsQueryable();
|
||||
|
||||
if (!AccessScopeService.IsManager(User))
|
||||
{
|
||||
IReadOnlyCollection<Guid> workspaceScopeIds = await accessScopeService.GetAccessibleWorkspaceIdsAsync(User, ct);
|
||||
IReadOnlyCollection<Guid> clientScopeIds = User.GetClientScopeIds();
|
||||
IReadOnlyCollection<Guid> campaignScopeIds = User.GetCampaignScopeIds();
|
||||
|
||||
query = query.Where(campaign => workspaceScopeIds.Contains(campaign.WorkspaceId));
|
||||
|
||||
if (clientScopeIds.Count > 0)
|
||||
if (!AccessScopeService.IsManager(User) && clientScopeIds.Count > 0)
|
||||
{
|
||||
query = query.Where(campaign => clientScopeIds.Contains(campaign.ClientId));
|
||||
}
|
||||
|
||||
if (campaignScopeIds.Count > 0)
|
||||
if (!AccessScopeService.IsManager(User) && campaignScopeIds.Count > 0)
|
||||
{
|
||||
query = query.Where(campaign => campaignScopeIds.Contains(campaign.Id));
|
||||
}
|
||||
}
|
||||
|
||||
if (request.ClientId.HasValue)
|
||||
{
|
||||
|
||||
@@ -23,11 +23,8 @@ internal class GetChannelsHandler(
|
||||
{
|
||||
IQueryable<Channel> query = dbContext.Channels.AsQueryable();
|
||||
|
||||
if (!AccessScopeService.IsManager(User))
|
||||
{
|
||||
IReadOnlyCollection<Guid> workspaceScopeIds = await accessScopeService.GetAccessibleWorkspaceIdsAsync(User, ct);
|
||||
query = query.Where(channel => workspaceScopeIds.Contains(channel.WorkspaceId));
|
||||
}
|
||||
|
||||
if (request.WorkspaceId.HasValue)
|
||||
{
|
||||
|
||||
@@ -33,20 +33,16 @@ internal class GetClientsHandler(
|
||||
{
|
||||
IQueryable<Client> query = dbContext.Clients.AsQueryable();
|
||||
|
||||
if (!AccessScopeService.IsManager(User))
|
||||
{
|
||||
IReadOnlyCollection<Guid> workspaceScopeIds = await accessScopeService.GetAccessibleWorkspaceIdsAsync(User, ct);
|
||||
IReadOnlyCollection<Guid> clientScopeIds = User.GetClientScopeIds();
|
||||
|
||||
query = query.Where(client => workspaceScopeIds.Contains(client.WorkspaceId));
|
||||
|
||||
if (clientScopeIds.Count > 0)
|
||||
if (!AccessScopeService.IsManager(User) && clientScopeIds.Count > 0)
|
||||
{
|
||||
query = query.Where(client => clientScopeIds.Contains(client.Id));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (request.WorkspaceId.HasValue)
|
||||
{
|
||||
query = query.Where(client => client.WorkspaceId == request.WorkspaceId.Value);
|
||||
|
||||
@@ -37,24 +37,21 @@ internal class GetContentItemsHandler(
|
||||
{
|
||||
IQueryable<ContentItem> query = dbContext.ContentItems.AsQueryable();
|
||||
|
||||
if (!AccessScopeService.IsManager(User))
|
||||
{
|
||||
IReadOnlyCollection<Guid> workspaceScopeIds = await accessScopeService.GetAccessibleWorkspaceIdsAsync(User, ct);
|
||||
IReadOnlyCollection<Guid> clientScopeIds = User.GetClientScopeIds();
|
||||
IReadOnlyCollection<Guid> campaignScopeIds = User.GetCampaignScopeIds();
|
||||
|
||||
query = query.Where(item => workspaceScopeIds.Contains(item.WorkspaceId));
|
||||
|
||||
if (clientScopeIds.Count > 0)
|
||||
if (!AccessScopeService.IsManager(User) && clientScopeIds.Count > 0)
|
||||
{
|
||||
query = query.Where(item => clientScopeIds.Contains(item.ClientId));
|
||||
}
|
||||
|
||||
if (campaignScopeIds.Count > 0)
|
||||
if (!AccessScopeService.IsManager(User) && campaignScopeIds.Count > 0)
|
||||
{
|
||||
query = query.Where(item => campaignScopeIds.Contains(item.CampaignId));
|
||||
}
|
||||
}
|
||||
|
||||
if (request.WorkspaceId.HasValue)
|
||||
{
|
||||
|
||||
@@ -56,13 +56,10 @@ internal class GetNotificationsHandler(
|
||||
IQueryable<NotificationEvent> query = dbContext.NotificationEvents.AsQueryable();
|
||||
Guid currentUserId = User.GetUserId();
|
||||
|
||||
if (!AccessScopeService.IsManager(User))
|
||||
{
|
||||
IReadOnlyCollection<Guid> workspaceScopeIds = await accessScopeService.GetAccessibleWorkspaceIdsAsync(User, ct);
|
||||
query = query.Where(notificationEvent =>
|
||||
workspaceScopeIds.Contains(notificationEvent.WorkspaceId) ||
|
||||
notificationEvent.RecipientUserId == currentUserId);
|
||||
}
|
||||
|
||||
query = query.Where(notificationEvent =>
|
||||
notificationEvent.RecipientUserId == null ||
|
||||
|
||||
@@ -2,29 +2,16 @@ using System.Security.Claims;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Socialize.Api.Data;
|
||||
using Socialize.Api.Infrastructure.Security;
|
||||
using Socialize.Api.Modules.Identity.Contracts;
|
||||
|
||||
namespace Socialize.Api.Modules.Organizations.Services;
|
||||
|
||||
internal sealed class OrganizationAccessService(
|
||||
AppDbContext dbContext)
|
||||
{
|
||||
public static bool IsGlobalManager(ClaimsPrincipal user)
|
||||
{
|
||||
return user.IsInRole(KnownRoles.Administrator) || user.IsInRole(KnownRoles.Manager);
|
||||
}
|
||||
|
||||
public async Task<IReadOnlyCollection<Guid>> GetAccessibleOrganizationIdsAsync(
|
||||
ClaimsPrincipal user,
|
||||
CancellationToken ct)
|
||||
{
|
||||
if (IsGlobalManager(user))
|
||||
{
|
||||
return await dbContext.Organizations
|
||||
.Select(organization => organization.Id)
|
||||
.ToArrayAsync(ct);
|
||||
}
|
||||
|
||||
Guid userId = user.GetUserId();
|
||||
|
||||
Guid[] ownedOrganizationIds = await dbContext.Organizations
|
||||
@@ -47,13 +34,6 @@ internal sealed class OrganizationAccessService(
|
||||
ClaimsPrincipal user,
|
||||
CancellationToken ct)
|
||||
{
|
||||
if (IsGlobalManager(user))
|
||||
{
|
||||
return await dbContext.Workspaces
|
||||
.Select(workspace => workspace.Id)
|
||||
.ToArrayAsync(ct);
|
||||
}
|
||||
|
||||
Guid[] directWorkspaceIds = user.GetWorkspaceScopeIds().ToArray();
|
||||
Guid[] organizationWorkspaceIds = await GetInheritedWorkspaceIdsAsync(user, OrganizationPermissions.AccessOwnedWorkspaces, ct);
|
||||
|
||||
@@ -68,11 +48,6 @@ internal sealed class OrganizationAccessService(
|
||||
Guid organizationId,
|
||||
CancellationToken ct)
|
||||
{
|
||||
if (IsGlobalManager(user))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
Guid userId = user.GetUserId();
|
||||
|
||||
return await dbContext.Organizations.AnyAsync(
|
||||
@@ -89,11 +64,6 @@ internal sealed class OrganizationAccessService(
|
||||
string permission,
|
||||
CancellationToken ct)
|
||||
{
|
||||
if (IsGlobalManager(user))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
Guid userId = user.GetUserId();
|
||||
|
||||
bool owner = await dbContext.Organizations.AnyAsync(
|
||||
@@ -117,11 +87,6 @@ internal sealed class OrganizationAccessService(
|
||||
Guid organizationId,
|
||||
CancellationToken ct)
|
||||
{
|
||||
if (IsGlobalManager(user))
|
||||
{
|
||||
return OrganizationPermissionRules.GetPermissionsForRole(OrganizationRoles.Owner);
|
||||
}
|
||||
|
||||
Guid userId = user.GetUserId();
|
||||
|
||||
bool owner = await dbContext.Organizations.AnyAsync(
|
||||
@@ -150,11 +115,6 @@ internal sealed class OrganizationAccessService(
|
||||
string permission,
|
||||
CancellationToken ct)
|
||||
{
|
||||
if (IsGlobalManager(user))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
Guid? organizationId = await dbContext.Workspaces
|
||||
.Where(workspace => workspace.Id == workspaceId)
|
||||
.Select(workspace => (Guid?)workspace.OrganizationId)
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
using System.Security.Claims;
|
||||
using Socialize.Api.Infrastructure.Security;
|
||||
using Socialize.Api.Modules.Identity.Contracts;
|
||||
|
||||
namespace Socialize.Tests.Security;
|
||||
|
||||
public class AccessScopeServiceTests
|
||||
{
|
||||
[Fact]
|
||||
public void Manager_role_does_not_grant_workspace_access_without_workspace_scope()
|
||||
{
|
||||
Guid workspaceId = Guid.NewGuid();
|
||||
ClaimsPrincipal user = CreateUser(KnownRoles.Manager);
|
||||
|
||||
Assert.False(AccessScopeService.CanAccessWorkspace(user, workspaceId));
|
||||
Assert.False(AccessScopeService.CanManageWorkspace(user, workspaceId));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Administrator_role_does_not_grant_workspace_access_without_workspace_scope()
|
||||
{
|
||||
Guid workspaceId = Guid.NewGuid();
|
||||
ClaimsPrincipal user = CreateUser(KnownRoles.Administrator);
|
||||
|
||||
Assert.False(AccessScopeService.CanAccessWorkspace(user, workspaceId));
|
||||
Assert.False(AccessScopeService.CanManageWorkspace(user, workspaceId));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Manager_can_manage_only_workspaces_in_scope()
|
||||
{
|
||||
Guid workspaceId = Guid.NewGuid();
|
||||
ClaimsPrincipal user = CreateUser(KnownRoles.Manager, new Claim(KnownClaims.WorkspaceScope, workspaceId.ToString()));
|
||||
|
||||
Assert.True(AccessScopeService.CanAccessWorkspace(user, workspaceId));
|
||||
Assert.True(AccessScopeService.CanManageWorkspace(user, workspaceId));
|
||||
}
|
||||
|
||||
private static ClaimsPrincipal CreateUser(string role, params Claim[] claims)
|
||||
{
|
||||
Claim[] baseClaims =
|
||||
[
|
||||
new(ClaimTypes.NameIdentifier, Guid.NewGuid().ToString()),
|
||||
new(ClaimTypes.Role, role),
|
||||
];
|
||||
|
||||
return new ClaimsPrincipal(new ClaimsIdentity(baseClaims.Concat(claims), "Test"));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user