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;
organizationId: string;
workspaceSharing: WorkspaceSharing;
requiredRole?: AssetPermissionRole | AssetPermissionRole[];
requiredRole?: AssetPermissionRole;
}) => {
const assetPermissionResult = await checkPermission({
userId: user.id,

View File

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

View File

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

View File

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