workspace permissions

This commit is contained in:
dal 2025-08-26 15:58:16 -06:00
parent 73d68fa27f
commit beb332f01a
No known key found for this signature in database
GPG Key ID: 16F4B0E1E9F61122
3 changed files with 51 additions and 22 deletions

View File

@ -1,4 +1,4 @@
import { type AssetPermissionCheck, checkPermission } from '@buster/access-controls';
import { hasAssetPermission } from '@buster/access-controls';
import { executeMetricQuery, getCachedMetricData, setCachedMetricData } from '@buster/data-source';
import type { Credentials } from '@buster/data-source';
import type { User } from '@buster/database';
@ -49,18 +49,38 @@ export async function getMetricDataHandler(
const { organizationId } = userOrg;
// Retrieve metric definition from database with data source info
const metric = await getMetricWithDataSource({ metricId, versionNumber });
if (!metric) {
throw new HTTPException(404, {
message: 'Metric not found',
});
}
// Verify metric belongs to user's organization
if (metric.organizationId !== organizationId) {
throw new HTTPException(403, {
message: 'You do not have permission to view this metric',
});
}
// Check if user has permission to view this metric file
const permissionCheck: AssetPermissionCheck = {
// This follows the same pattern as report-files.ts - hasAssetPermission handles all the logic including:
// - Direct permissions
// - Workspace sharing permissions
// - Cascading permissions (dashboard, chat, collection)
// - Admin permissions
const hasAccess = await hasAssetPermission({
userId: user.id,
assetId: metricId,
assetType: 'metric_file',
requiredRole: 'can_view',
organizationId,
};
workspaceSharing: metric.workspaceSharing ?? 'none',
});
const permissionResult = await checkPermission(permissionCheck);
if (!permissionResult.hasAccess) {
if (!hasAccess) {
throw new HTTPException(403, {
message: 'You do not have permission to view this metric',
});
@ -100,22 +120,6 @@ export async function getMetricDataHandler(
// Ensure limit is within bounds
const queryLimit = Math.min(Math.max(limit, 1), 5000);
// Retrieve metric definition from database with data source info
const metric = await getMetricWithDataSource({ metricId, versionNumber });
if (!metric) {
throw new HTTPException(404, {
message: 'Metric not found',
});
}
// Verify metric belongs to user's organization
if (metric.organizationId !== organizationId) {
throw new HTTPException(403, {
message: 'You do not have permission to view this metric',
});
}
// Extract SQL query from metric content
const sql = extractSqlFromMetricContent(metric.content);

View File

@ -12,10 +12,14 @@ export {
listPermissions,
// From checks.ts
checkPermission,
computeEffectivePermission,
type AssetPermissionCheck,
type AssetPermissionResult,
// From cascading-permissions.ts
checkCascadingPermissions,
checkMetricDashboardAccess,
checkMetricChatAccess,
checkMetricCollectionAccess,
} from './assets';
// Export dataset permissions

View File

@ -43,6 +43,13 @@ export const MetricWithDataSourceSchema = z.object({
secretId: z.string(),
dataSourceType: z.string(),
versionNumber: z.number().optional(),
workspaceSharing: z.enum(['none', 'can_view', 'can_edit', 'full_access']).nullable(),
workspaceSharingEnabledBy: z.string().nullable(),
workspaceSharingEnabledAt: z.string().nullable(),
publiclyAccessible: z.boolean(),
publiclyEnabledBy: z.string().nullable(),
publicExpiryDate: z.string().nullable(),
publicPassword: z.string().nullable(),
});
export type MetricWithDataSource = z.infer<typeof MetricWithDataSourceSchema>;
@ -68,6 +75,13 @@ export async function getMetricWithDataSource(
versionHistory: metricFiles.versionHistory,
secretId: dataSources.secretId,
dataSourceType: dataSources.type,
workspaceSharing: metricFiles.workspaceSharing,
workspaceSharingEnabledBy: metricFiles.workspaceSharingEnabledBy,
workspaceSharingEnabledAt: metricFiles.workspaceSharingEnabledAt,
publiclyAccessible: metricFiles.publiclyAccessible,
publiclyEnabledBy: metricFiles.publiclyEnabledBy,
publicExpiryDate: metricFiles.publicExpiryDate,
publicPassword: metricFiles.publicPassword,
})
.from(metricFiles)
.innerJoin(dataSources, eq(metricFiles.dataSourceId, dataSources.id))
@ -134,6 +148,13 @@ export async function getMetricWithDataSource(
versionHistory,
secretId: result.secretId,
dataSourceType: result.dataSourceType,
workspaceSharing: result.workspaceSharing,
workspaceSharingEnabledBy: result.workspaceSharingEnabledBy,
workspaceSharingEnabledAt: result.workspaceSharingEnabledAt,
publiclyAccessible: result.publiclyAccessible,
publiclyEnabledBy: result.publiclyEnabledBy,
publicExpiryDate: result.publicExpiryDate,
publicPassword: result.publicPassword,
...(versionNumber !== undefined && { versionNumber }),
};