diff --git a/backend/LexBoxApi/Controllers/IntegrationController.cs b/backend/LexBoxApi/Controllers/IntegrationController.cs index 84819a63e..64de785a6 100644 --- a/backend/LexBoxApi/Controllers/IntegrationController.cs +++ b/backend/LexBoxApi/Controllers/IntegrationController.cs @@ -35,7 +35,7 @@ public class IntegrationController( [ProducesResponseType(StatusCodes.Status302Found)] public async Task OpenWithFlex(Guid projectId) { - if (!await permissionService.CanSyncProjectAsync(projectId)) return Unauthorized(); + if (!await permissionService.CanSyncProject(projectId)) return Unauthorized(); var project = await lexBoxDbContext.Projects.FirstOrDefaultAsync(p => p.Id == projectId); if (project is null) return NotFound(); var repoId = await hgService.GetRepositoryIdentifier(project); diff --git a/backend/LexBoxApi/GraphQL/LexQueries.cs b/backend/LexBoxApi/GraphQL/LexQueries.cs index 3a3f0b10d..5376f314d 100644 --- a/backend/LexBoxApi/GraphQL/LexQueries.cs +++ b/backend/LexBoxApi/GraphQL/LexQueries.cs @@ -229,7 +229,7 @@ public IQueryable UsersInMyOrg(LexBoxDbContext context, LoggedInContext lo } }); // Members and non-members alike can see all public projects plus their own - org.Projects = org.Projects?.Where(p => p.IsConfidential == false || permissionService.CanSyncProject(p.Id))?.ToList() ?? []; + org.Projects = org.Projects?.Where(p => p.IsConfidential == false || permissionService.IsProjectMember(p.Id, updatedUser))?.ToList() ?? []; if (!permissionService.IsOrgMember(orgId, updatedUser)) { // Non-members also cannot see membership, only org admins diff --git a/backend/LexBoxApi/Services/PermissionService.cs b/backend/LexBoxApi/Services/PermissionService.cs index 64e9bf3e2..983461be7 100644 --- a/backend/LexBoxApi/Services/PermissionService.cs +++ b/backend/LexBoxApi/Services/PermissionService.cs @@ -20,7 +20,7 @@ private async ValueTask ManagesOrgThatOwnsProject(Guid projectId, LexAuthU // Org admins can view, edit, and sync all projects, even confidential ones var managedOrgIds = user.Orgs.Where(o => o.Role == OrgRole.Admin).Select(o => o.OrgId).ToHashSet(); var projectOrgIds = await projectService.LookupProjectOrgIds(projectId); - if (projectOrgIds.Any(oId => managedOrgIds.Contains(oId))) return true; + if (projectOrgIds.Any(managedOrgIds.Contains)) return true; } return false; } @@ -31,7 +31,7 @@ private async ValueTask IsMemberOfOrgThatOwnsProject(Guid projectId) { var memberOfOrgIds = User.Orgs.Select(o => o.OrgId).ToHashSet(); var projectOrgIds = await projectService.LookupProjectOrgIds(projectId); - if (projectOrgIds.Any(oId => memberOfOrgIds.Contains(oId))) return true; + if (projectOrgIds.Any(memberOfOrgIds.Contains)) return true; } return false; } @@ -40,20 +40,21 @@ public async ValueTask CanSyncProject(string projectCode) { if (User is null) return false; if (User.Role == UserRole.admin) return true; - return await CanSyncProjectAsync(await projectService.LookupProjectId(projectCode)); + return await CanSyncProject(await projectService.LookupProjectId(projectCode)); } - public bool CanSyncProject(Guid projectId) + public bool IsProjectMember(Guid projectId, LexAuthUser? overrideUser = null) { - if (User is null) return false; - if (User.Role == UserRole.admin) return true; - if (User.Projects is null) return false; - return User.IsProjectMember(projectId); + var user = overrideUser ?? User; + if (user is null) return false; + return user.IsProjectMember(projectId); } - public async ValueTask CanSyncProjectAsync(Guid projectId) + public async ValueTask CanSyncProject(Guid projectId) { - if (CanSyncProject(projectId)) return true; + if (User is null) return false; + if (User.Role == UserRole.admin) return true; + if (User.IsProjectMember(projectId)) return true; // Org managers can sync any project owned by their org(s) return await ManagesOrgThatOwnsProject(projectId); } @@ -65,7 +66,7 @@ public async ValueTask AssertCanSyncProject(string projectCode) public async ValueTask AssertCanSyncProject(Guid projectId) { - if (!await CanSyncProjectAsync(projectId)) throw new UnauthorizedAccessException(); + if (!await CanSyncProject(projectId)) throw new UnauthorizedAccessException(); } public async ValueTask CanViewProject(Guid projectId, LexAuthUser? overrideUser = null) diff --git a/backend/LexCore/Auth/LexAuthUser.cs b/backend/LexCore/Auth/LexAuthUser.cs index 85b94925f..3e5cc040c 100644 --- a/backend/LexCore/Auth/LexAuthUser.cs +++ b/backend/LexCore/Auth/LexAuthUser.cs @@ -237,6 +237,8 @@ public ClaimsPrincipal GetPrincipal(string authenticationType) public bool IsProjectMember(Guid projectId, ProjectRole? role = null) { + if (Projects is null) return false; + if (role is not null) { return Projects.Any(p => p.ProjectId == projectId && p.Role == role); diff --git a/backend/LexCore/ServiceInterfaces/IPermissionService.cs b/backend/LexCore/ServiceInterfaces/IPermissionService.cs index aea787a1f..f575a4af3 100644 --- a/backend/LexCore/ServiceInterfaces/IPermissionService.cs +++ b/backend/LexCore/ServiceInterfaces/IPermissionService.cs @@ -6,15 +6,8 @@ namespace LexCore.ServiceInterfaces; public interface IPermissionService { ValueTask CanSyncProject(string projectCode); - /// - /// Does NOT check permissions for org managers, because that requires async DB access. - /// Use CanSyncProject(projectCode) or CanSyncProjectAsync(projectId) if org manager permissions also need to be checked. - /// - bool CanSyncProject(Guid projectId); - /// - /// Does all the checks from CanSyncProject, plus allows org managers to access the org as well. - /// - ValueTask CanSyncProjectAsync(Guid projectId); + bool IsProjectMember(Guid projectId, LexAuthUser? overrideUser = null); + ValueTask CanSyncProject(Guid projectId); ValueTask AssertCanSyncProject(string projectCode); ValueTask AssertCanSyncProject(Guid projectId); ValueTask CanViewProject(Guid projectId, LexAuthUser? overrideUser = null);