reset permission stuff

This commit is contained in:
Nate Kelley 2025-09-20 14:26:21 -06:00
parent 273008adc6
commit 3cdd0bfa57
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
5 changed files with 42 additions and 50 deletions

View File

@ -79,7 +79,7 @@ export const checkIfAssetIsEditable = async ({
assetType: AssetType; assetType: AssetType;
organizationId: string; organizationId: string;
workspaceSharing: WorkspaceSharing; workspaceSharing: WorkspaceSharing;
requiredRole?: AssetPermissionRole | AssetPermissionRole[]; requiredRole?: AssetPermissionRole;
}) => { }) => {
const assetPermissionResult = await checkPermission({ const assetPermissionResult = await checkPermission({
userId: user.id, userId: user.id,

View File

@ -33,11 +33,9 @@ export function getPermissionCacheKey(
userId: string, userId: string,
assetId: string, assetId: string,
assetType: AssetType, assetType: AssetType,
requiredRole: AssetPermissionRole | AssetPermissionRole[] requiredRole: AssetPermissionRole
): CacheKey { ): CacheKey {
// Normalize and sort roles for consistent cache keys return `${userId}:${assetId}:${assetType}:${requiredRole}`;
const roles = Array.isArray(requiredRole) ? [...requiredRole].sort() : [requiredRole];
return `${userId}:${assetId}:${assetType}:${roles.join(',')}`;
} }
/** /**
@ -47,7 +45,7 @@ export function getCachedPermission(
userId: string, userId: string,
assetId: string, assetId: string,
assetType: AssetType, assetType: AssetType,
requiredRole: AssetPermissionRole | AssetPermissionRole[] requiredRole: AssetPermissionRole
): AssetPermissionResult | undefined { ): AssetPermissionResult | undefined {
const key = getPermissionCacheKey(userId, assetId, assetType, requiredRole); const key = getPermissionCacheKey(userId, assetId, assetType, requiredRole);
const cached = permissionCache.get(key); const cached = permissionCache.get(key);
@ -68,7 +66,7 @@ export function setCachedPermission(
userId: string, userId: string,
assetId: string, assetId: string,
assetType: AssetType, assetType: AssetType,
requiredRole: AssetPermissionRole | AssetPermissionRole[], requiredRole: AssetPermissionRole,
result: AssetPermissionResult result: AssetPermissionResult
): void { ): void {
const key = getPermissionCacheKey(userId, assetId, assetType, requiredRole); const key = getPermissionCacheKey(userId, assetId, assetType, requiredRole);
@ -198,13 +196,18 @@ export function invalidateUser(userId: string) {
* Invalidate all cached entries for a user-asset combination * Invalidate all cached entries for a user-asset combination
*/ */
export function invalidateUserAsset(userId: string, assetId: string, assetType: AssetType) { export function invalidateUserAsset(userId: string, assetId: string, assetType: AssetType) {
// Invalidate all permission cache entries for this user-asset combination // Invalidate all permission levels for this user-asset combination
// Since we now support arrays, we need to invalidate all possible combinations const permissionRoles: AssetPermissionRole[] = [
// The simplest approach is to invalidate all entries containing the user-asset-type pattern 'owner',
for (const key of Array.from(permissionCache.keys())) { 'full_access',
if (key.startsWith(`${userId}:${assetId}:${assetType}:`)) { 'can_edit',
permissionCache.delete(key); 'can_filter',
} 'can_view',
];
for (const role of permissionRoles) {
const key = getPermissionCacheKey(userId, assetId, assetType, role);
permissionCache.delete(key);
} }
// Invalidate cascading cache // Invalidate cascading cache

View File

@ -205,7 +205,7 @@ describe('Asset Permission Checks', () => {
'user123', 'user123',
'asset123', 'asset123',
'dashboard_file', 'dashboard_file',
['can_view'], 'can_view',
{ hasAccess: false } { hasAccess: false }
); );
}); });

View File

@ -6,7 +6,7 @@ import {
import type { User } from '@buster/database/queries'; import type { User } from '@buster/database/queries';
import type { AssetType } from '@buster/database/schema-types'; import type { AssetType } from '@buster/database/schema-types';
import type { AssetPermissionRole, OrganizationMembership, WorkspaceSharing } from '../types'; import type { AssetPermissionRole, OrganizationMembership, WorkspaceSharing } from '../types';
import { getHighestPermission, isPermissionSufficientForAny } from '../types/asset-permissions'; import { getHighestPermission, isPermissionSufficient } from '../types/asset-permissions';
import { getCachedPermission, setCachedPermission } from './cache'; import { getCachedPermission, setCachedPermission } from './cache';
import { checkCascadingPermissions } from './cascading-permissions'; import { checkCascadingPermissions } from './cascading-permissions';
@ -14,7 +14,7 @@ export interface AssetPermissionCheck {
userId: string; userId: string;
assetId: string; assetId: string;
assetType: AssetType; assetType: AssetType;
requiredRole: AssetPermissionRole | AssetPermissionRole[]; requiredRole: AssetPermissionRole;
organizationId?: string; organizationId?: string;
workspaceSharing?: WorkspaceSharing; workspaceSharing?: WorkspaceSharing;
} }
@ -31,12 +31,8 @@ export interface AssetPermissionResult {
export async function checkPermission(check: AssetPermissionCheck): Promise<AssetPermissionResult> { export async function checkPermission(check: AssetPermissionCheck): Promise<AssetPermissionResult> {
const { userId, assetId, assetType, requiredRole, organizationId, workspaceSharing } = check; const { userId, assetId, assetType, requiredRole, organizationId, workspaceSharing } = check;
// Normalize requiredRole to an array for consistent handling // Check cache first
const requiredRoles = Array.isArray(requiredRole) ? requiredRole : [requiredRole]; const cached = getCachedPermission(userId, assetId, assetType, requiredRole);
// Check cache first (using serialized array as key)
const cached = getCachedPermission(userId, assetId, assetType, requiredRoles);
if (cached !== undefined) { if (cached !== undefined) {
return cached; return cached;
} }
@ -60,8 +56,8 @@ export async function checkPermission(check: AssetPermissionCheck): Promise<Asse
const dbResult = await checkDbAssetPermission(dbParams); const dbResult = await checkDbAssetPermission(dbParams);
if (dbResult.hasAccess && dbResult.role) { if (dbResult.hasAccess && dbResult.role) {
// Check if the role is sufficient for any of the required roles // Check if the role is sufficient
if (isPermissionSufficientForAny(dbResult.role, requiredRoles)) { if (isPermissionSufficient(dbResult.role, requiredRole)) {
const result: AssetPermissionResult = { const result: AssetPermissionResult = {
hasAccess: true, hasAccess: true,
effectiveRole: dbResult.role, effectiveRole: dbResult.role,
@ -69,7 +65,7 @@ export async function checkPermission(check: AssetPermissionCheck): Promise<Asse
if (dbResult.accessPath !== undefined) { if (dbResult.accessPath !== undefined) {
result.accessPath = dbResult.accessPath; result.accessPath = dbResult.accessPath;
} }
setCachedPermission(userId, assetId, assetType, requiredRoles, result); setCachedPermission(userId, assetId, assetType, requiredRole, result);
return result; return result;
} }
} }
@ -80,21 +76,20 @@ export async function checkPermission(check: AssetPermissionCheck): Promise<Asse
if (isOrgMember) { if (isOrgMember) {
const workspaceRole = mapWorkspaceSharingToRole(workspaceSharing); const workspaceRole = mapWorkspaceSharingToRole(workspaceSharing);
if (workspaceRole && isPermissionSufficientForAny(workspaceRole, requiredRoles)) { if (workspaceRole && isPermissionSufficient(workspaceRole, requiredRole)) {
const result = { const result = {
hasAccess: true, hasAccess: true,
effectiveRole: workspaceRole, effectiveRole: workspaceRole,
accessPath: 'workspace_sharing' as const, accessPath: 'workspace_sharing' as const,
}; };
setCachedPermission(userId, assetId, assetType, requiredRoles, result); setCachedPermission(userId, assetId, assetType, requiredRole, result);
return result; return result;
} }
} }
} }
// Check cascading permissions for specific asset types // Check cascading permissions for specific asset types
// Only check if any of the required roles is 'can_view' (cascading only provides view access) if (requiredRole === 'can_view') {
if (requiredRoles.includes('can_view')) {
// Create a user object for cascading permissions check // Create a user object for cascading permissions check
const user: Pick<User, 'id'> = { id: userId }; const user: Pick<User, 'id'> = { id: userId };
const hasCascadingAccess = await checkCascadingPermissions(assetId, assetType, user as User); const hasCascadingAccess = await checkCascadingPermissions(assetId, assetType, user as User);
@ -104,13 +99,13 @@ export async function checkPermission(check: AssetPermissionCheck): Promise<Asse
effectiveRole: 'can_view' as AssetPermissionRole, effectiveRole: 'can_view' as AssetPermissionRole,
accessPath: 'cascading' as const, accessPath: 'cascading' as const,
}; };
setCachedPermission(userId, assetId, assetType, requiredRoles, result); setCachedPermission(userId, assetId, assetType, requiredRole, result);
return result; return result;
} }
} }
const result = { hasAccess: false }; const result = { hasAccess: false };
setCachedPermission(userId, assetId, assetType, requiredRoles, result); setCachedPermission(userId, assetId, assetType, requiredRole, result);
return result; return result;
} }
@ -154,24 +149,18 @@ export function computeEffectivePermission(
* Map workspace sharing level to permission role * Map workspace sharing level to permission role
*/ */
function mapWorkspaceSharingToRole(workspaceSharing: WorkspaceSharing): AssetPermissionRole | null { function mapWorkspaceSharingToRole(workspaceSharing: WorkspaceSharing): AssetPermissionRole | null {
if (workspaceSharing === 'can_view') { switch (workspaceSharing) {
return 'can_view'; case 'can_view':
return 'can_view';
case 'can_edit':
return 'can_edit';
case 'full_access':
return 'full_access';
case 'none':
return null;
default:
return null;
} }
if (workspaceSharing === 'can_edit') {
return 'can_edit';
}
if (workspaceSharing === 'full_access') {
return 'full_access';
}
if (workspaceSharing === 'none') {
return null;
}
const _exhaustiveCheck: never = workspaceSharing;
return null;
} }
/** /**

View File

@ -204,7 +204,7 @@ export async function hasAssetPermission(params: {
userId: string; userId: string;
assetId: string; assetId: string;
assetType: AssetType; assetType: AssetType;
requiredRole: AssetPermissionRole | AssetPermissionRole[]; requiredRole: AssetPermissionRole;
organizationId?: string; organizationId?: string;
workspaceSharing?: WorkspaceSharing; workspaceSharing?: WorkspaceSharing;
}): Promise<boolean> { }): Promise<boolean> {