mirror of https://github.com/buster-so/buster.git
686 lines
21 KiB
TypeScript
686 lines
21 KiB
TypeScript
import { and, count, eq, getDb, inArray, isNull } from '@buster/database/connection';
|
|
import {
|
|
datasetPermissions,
|
|
datasets,
|
|
datasetsToPermissionGroups,
|
|
permissionGroups,
|
|
permissionGroupsToIdentities,
|
|
teamsToUsers,
|
|
usersToOrganizations,
|
|
} from '@buster/database/schema';
|
|
import { z } from 'zod';
|
|
import { type AccessControlOptions, AccessControlsError } from './types';
|
|
|
|
// Schema for the permissioned dataset result
|
|
const PermissionedDatasetSchema = z.object({
|
|
id: z.string().uuid(),
|
|
name: z.string(),
|
|
ymlFile: z.string().nullable(),
|
|
createdAt: z
|
|
.string()
|
|
.or(z.date())
|
|
.transform((val) => (typeof val === 'string' ? new Date(val) : val)),
|
|
updatedAt: z
|
|
.string()
|
|
.or(z.date())
|
|
.transform((val) => (typeof val === 'string' ? new Date(val) : val)),
|
|
deletedAt: z
|
|
.string()
|
|
.or(z.date())
|
|
.transform((val) => (typeof val === 'string' ? new Date(val) : val))
|
|
.nullable(),
|
|
dataSourceId: z.string().uuid(),
|
|
});
|
|
|
|
export type PermissionedDataset = z.infer<typeof PermissionedDatasetSchema>;
|
|
|
|
// Input validation schemas
|
|
const GetPermissionedDatasetsSchema = z.object({
|
|
userId: z.string().uuid(),
|
|
page: z.number().int().min(0),
|
|
pageSize: z.number().int().min(1).max(1000),
|
|
});
|
|
|
|
const HasDatasetAccessSchema = z.object({
|
|
userId: z.string().uuid(),
|
|
datasetId: z.string().uuid(),
|
|
});
|
|
|
|
const HasAllDatasetsAccessSchema = z.object({
|
|
userId: z.string().uuid(),
|
|
datasetIds: z.array(z.string().uuid()),
|
|
});
|
|
|
|
// --- Helper Functions for Different Permission Paths ---
|
|
|
|
// Path 1: Direct User -> Dataset
|
|
async function fetchDirectUserDatasetIds(userId: string): Promise<string[]> {
|
|
const db = getDb();
|
|
|
|
const results = await db
|
|
.select({ datasetId: datasetPermissions.datasetId })
|
|
.from(datasetPermissions)
|
|
.where(
|
|
and(
|
|
eq(datasetPermissions.permissionId, userId),
|
|
eq(datasetPermissions.permissionType, 'user'),
|
|
isNull(datasetPermissions.deletedAt)
|
|
)
|
|
);
|
|
|
|
return results.map((r: { datasetId: string }) => r.datasetId);
|
|
}
|
|
|
|
// Path 3: User -> Team -> Dataset (Direct team assignment)
|
|
async function fetchTeamDirectDatasetIds(userId: string): Promise<string[]> {
|
|
const db = getDb();
|
|
|
|
const results = await db
|
|
.selectDistinct({ datasetId: datasetPermissions.datasetId })
|
|
.from(datasetPermissions)
|
|
.innerJoin(
|
|
teamsToUsers,
|
|
and(
|
|
eq(datasetPermissions.permissionId, teamsToUsers.teamId),
|
|
eq(datasetPermissions.permissionType, 'team'),
|
|
eq(teamsToUsers.userId, userId),
|
|
isNull(teamsToUsers.deletedAt)
|
|
)
|
|
)
|
|
.where(isNull(datasetPermissions.deletedAt));
|
|
|
|
return results.map((r: { datasetId: string }) => r.datasetId);
|
|
}
|
|
|
|
// Path 2: User -> Group -> Dataset
|
|
async function fetchUserGroupDatasetIds(userId: string): Promise<string[]> {
|
|
const db = getDb();
|
|
|
|
const results = await db
|
|
.selectDistinct({ datasetId: datasetsToPermissionGroups.datasetId })
|
|
.from(datasetsToPermissionGroups)
|
|
.innerJoin(
|
|
permissionGroups,
|
|
and(
|
|
eq(datasetsToPermissionGroups.permissionGroupId, permissionGroups.id),
|
|
isNull(permissionGroups.deletedAt)
|
|
)
|
|
)
|
|
.innerJoin(
|
|
permissionGroupsToIdentities,
|
|
and(
|
|
eq(permissionGroups.id, permissionGroupsToIdentities.permissionGroupId),
|
|
eq(permissionGroupsToIdentities.identityId, userId),
|
|
eq(permissionGroupsToIdentities.identityType, 'user'),
|
|
isNull(permissionGroupsToIdentities.deletedAt)
|
|
)
|
|
)
|
|
.where(isNull(datasetsToPermissionGroups.deletedAt));
|
|
|
|
return results.map((r: { datasetId: string }) => r.datasetId);
|
|
}
|
|
|
|
// Path 4: User -> Team -> Group -> Dataset
|
|
async function fetchTeamGroupDatasetIds(userId: string): Promise<string[]> {
|
|
const db = getDb();
|
|
|
|
const results = await db
|
|
.selectDistinct({ datasetId: datasetsToPermissionGroups.datasetId })
|
|
.from(datasetsToPermissionGroups)
|
|
.innerJoin(
|
|
permissionGroups,
|
|
and(
|
|
eq(datasetsToPermissionGroups.permissionGroupId, permissionGroups.id),
|
|
isNull(permissionGroups.deletedAt)
|
|
)
|
|
)
|
|
.innerJoin(
|
|
permissionGroupsToIdentities,
|
|
and(
|
|
eq(permissionGroups.id, permissionGroupsToIdentities.permissionGroupId),
|
|
eq(permissionGroupsToIdentities.identityType, 'team'),
|
|
isNull(permissionGroupsToIdentities.deletedAt)
|
|
)
|
|
)
|
|
.innerJoin(
|
|
teamsToUsers,
|
|
and(
|
|
eq(permissionGroupsToIdentities.identityId, teamsToUsers.teamId),
|
|
eq(teamsToUsers.userId, userId),
|
|
isNull(teamsToUsers.deletedAt)
|
|
)
|
|
)
|
|
.where(isNull(datasetsToPermissionGroups.deletedAt));
|
|
|
|
return results.map((r: { datasetId: string }) => r.datasetId);
|
|
}
|
|
|
|
// Path 5: User -> Organization -> Default Permission Group -> Dataset
|
|
async function fetchOrgDefaultDatasetIds(userId: string): Promise<string[]> {
|
|
const db = getDb();
|
|
|
|
// Get all the user's organizations
|
|
const userOrgs = await db
|
|
.select({ organizationId: usersToOrganizations.organizationId })
|
|
.from(usersToOrganizations)
|
|
.where(and(eq(usersToOrganizations.userId, userId), isNull(usersToOrganizations.deletedAt)));
|
|
|
|
if (userOrgs.length === 0) {
|
|
return []; // User not in any organization
|
|
}
|
|
|
|
// Get datasets from all default permission groups across all user's organizations
|
|
const allDatasetIds: string[] = [];
|
|
|
|
for (const userOrg of userOrgs) {
|
|
const { organizationId } = userOrg;
|
|
const defaultGroupName = `default:${organizationId}`;
|
|
|
|
// Find the default permission group for this organization
|
|
const defaultGroup = await db
|
|
.select({ id: permissionGroups.id })
|
|
.from(permissionGroups)
|
|
.where(
|
|
and(
|
|
eq(permissionGroups.name, defaultGroupName),
|
|
eq(permissionGroups.organizationId, organizationId),
|
|
isNull(permissionGroups.deletedAt)
|
|
)
|
|
)
|
|
.limit(1);
|
|
|
|
const [firstGroup] = defaultGroup;
|
|
if (!firstGroup) {
|
|
continue; // No default permission group exists for this org
|
|
}
|
|
|
|
const defaultGroupId = firstGroup.id;
|
|
|
|
// Get all datasets in the default permission group
|
|
const results = await db
|
|
.select({ datasetId: datasetsToPermissionGroups.datasetId })
|
|
.from(datasetsToPermissionGroups)
|
|
.where(
|
|
and(
|
|
eq(datasetsToPermissionGroups.permissionGroupId, defaultGroupId),
|
|
isNull(datasetsToPermissionGroups.deletedAt)
|
|
)
|
|
);
|
|
|
|
allDatasetIds.push(...results.map((r: { datasetId: string }) => r.datasetId));
|
|
}
|
|
|
|
// Return unique dataset IDs
|
|
return [...new Set(allDatasetIds)];
|
|
}
|
|
|
|
// --- Main Functions ---
|
|
|
|
export async function getPermissionedDatasets(
|
|
userId: string,
|
|
page: number,
|
|
pageSize: number
|
|
): Promise<PermissionedDataset[]> {
|
|
// Validate inputs
|
|
const input = GetPermissionedDatasetsSchema.parse({ userId, page, pageSize });
|
|
|
|
const db = getDb();
|
|
|
|
// Fetch all user's organizations and roles
|
|
const userOrgs = await db
|
|
.select({
|
|
organizationId: usersToOrganizations.organizationId,
|
|
role: usersToOrganizations.role,
|
|
})
|
|
.from(usersToOrganizations)
|
|
.where(
|
|
and(eq(usersToOrganizations.userId, input.userId), isNull(usersToOrganizations.deletedAt))
|
|
);
|
|
|
|
if (userOrgs.length === 0) {
|
|
// User not in any organization
|
|
return [];
|
|
}
|
|
|
|
// --- Admin/Querier Path ---
|
|
// Check if user has admin/querier role in any organization
|
|
const adminOrgs = userOrgs.filter((org) =>
|
|
['workspace_admin', 'data_admin', 'querier'].includes(org.role)
|
|
);
|
|
|
|
if (adminOrgs.length > 0) {
|
|
// Get datasets from ALL organizations where user has admin/querier access
|
|
const organizationIds = adminOrgs.map((org) => org.organizationId);
|
|
|
|
const results = await db
|
|
.select({
|
|
id: datasets.id,
|
|
name: datasets.name,
|
|
ymlFile: datasets.ymlFile,
|
|
createdAt: datasets.createdAt,
|
|
updatedAt: datasets.updatedAt,
|
|
deletedAt: datasets.deletedAt,
|
|
dataSourceId: datasets.dataSourceId,
|
|
})
|
|
.from(datasets)
|
|
.where(and(inArray(datasets.organizationId, organizationIds), isNull(datasets.deletedAt)))
|
|
.orderBy(datasets.name)
|
|
.limit(input.pageSize)
|
|
.offset(input.page * input.pageSize);
|
|
|
|
return results.map((r) => PermissionedDatasetSchema.parse(r));
|
|
}
|
|
|
|
// --- Non-Admin Path ---
|
|
// Fetch all potential dataset IDs concurrently
|
|
const [directUserIds, teamDirectIds, userGroupIds, teamGroupIds, orgDefaultIds] =
|
|
await Promise.all([
|
|
fetchDirectUserDatasetIds(input.userId),
|
|
fetchTeamDirectDatasetIds(input.userId),
|
|
fetchUserGroupDatasetIds(input.userId),
|
|
fetchTeamGroupDatasetIds(input.userId),
|
|
fetchOrgDefaultDatasetIds(input.userId),
|
|
]);
|
|
|
|
// Combine and deduplicate IDs
|
|
const allAccessibleIds = new Set([
|
|
...directUserIds,
|
|
...teamDirectIds,
|
|
...userGroupIds,
|
|
...teamGroupIds,
|
|
...orgDefaultIds,
|
|
]);
|
|
|
|
if (allAccessibleIds.size === 0) {
|
|
return []; // No datasets accessible
|
|
}
|
|
|
|
// Get all organization IDs for the user
|
|
const organizationIds = userOrgs.map((org) => org.organizationId);
|
|
|
|
// Fetch the actual dataset info for the combined IDs with pagination
|
|
// IMPORTANT: Filter by organization to prevent cross-org data access
|
|
const results = await db
|
|
.select({
|
|
id: datasets.id,
|
|
name: datasets.name,
|
|
ymlFile: datasets.ymlFile,
|
|
createdAt: datasets.createdAt,
|
|
updatedAt: datasets.updatedAt,
|
|
deletedAt: datasets.deletedAt,
|
|
dataSourceId: datasets.dataSourceId,
|
|
})
|
|
.from(datasets)
|
|
.where(
|
|
and(
|
|
inArray(datasets.id, Array.from(allAccessibleIds)),
|
|
inArray(datasets.organizationId, organizationIds),
|
|
isNull(datasets.deletedAt)
|
|
)
|
|
)
|
|
.orderBy(datasets.name)
|
|
.limit(input.pageSize)
|
|
.offset(input.page * input.pageSize);
|
|
|
|
return results.map((r) => PermissionedDatasetSchema.parse(r));
|
|
}
|
|
|
|
export async function hasDatasetAccess(userId: string, datasetId: string): Promise<boolean> {
|
|
// Validate inputs
|
|
const input = HasDatasetAccessSchema.parse({ userId, datasetId });
|
|
|
|
const db = getDb();
|
|
|
|
// --- Check if Dataset exists and get Organization ID and deleted status ---
|
|
const datasetInfo = await db
|
|
.select({
|
|
organizationId: datasets.organizationId,
|
|
deletedAt: datasets.deletedAt,
|
|
})
|
|
.from(datasets)
|
|
.where(eq(datasets.id, input.datasetId))
|
|
.limit(1);
|
|
|
|
const dataset = datasetInfo[0];
|
|
|
|
if (!dataset) {
|
|
return false; // Dataset doesn't exist
|
|
}
|
|
|
|
const { organizationId, deletedAt } = dataset;
|
|
|
|
// --- Universal Check: If dataset is deleted, NO ONE has access ---
|
|
if (deletedAt !== null) {
|
|
return false;
|
|
}
|
|
|
|
// Check Admin/Querier Access
|
|
const adminAccess = await db
|
|
.select({ role: usersToOrganizations.role })
|
|
.from(usersToOrganizations)
|
|
.where(
|
|
and(
|
|
eq(usersToOrganizations.userId, input.userId),
|
|
eq(usersToOrganizations.organizationId, organizationId),
|
|
isNull(usersToOrganizations.deletedAt)
|
|
)
|
|
)
|
|
.limit(1);
|
|
|
|
const access = adminAccess[0];
|
|
|
|
if (adminAccess.length > 0 && access) {
|
|
const { role } = access;
|
|
if (['workspace_admin', 'data_admin', 'querier'].includes(role)) {
|
|
// Admins/Queriers have access to non-deleted datasets in their org
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// --- Check Non-Admin Access Paths Concurrently ---
|
|
const permissionChecks = await Promise.all([
|
|
// Path 1: Direct User -> Dataset
|
|
checkDirectUserPermission(input.userId, input.datasetId),
|
|
// Path 3: User -> Team -> Dataset
|
|
checkTeamDirectPermission(input.userId, input.datasetId),
|
|
// Path 2: User -> Group -> Dataset
|
|
checkUserGroupPermission(input.userId, input.datasetId),
|
|
// Path 4: User -> Team -> Group -> Dataset
|
|
checkTeamGroupPermission(input.userId, input.datasetId),
|
|
// Path 5: User -> Organization -> Default Permission Group -> Dataset
|
|
checkOrgDefaultPermission(input.userId, input.datasetId),
|
|
]);
|
|
|
|
// Return true if any permission check succeeds
|
|
return permissionChecks.some((hasAccess) => hasAccess);
|
|
}
|
|
|
|
export async function hasAllDatasetsAccess(userId: string, datasetIds: string[]): Promise<boolean> {
|
|
// Validate inputs
|
|
const input = HasAllDatasetsAccessSchema.parse({ userId, datasetIds });
|
|
|
|
if (input.datasetIds.length === 0) {
|
|
return false; // No datasets means no access granted
|
|
}
|
|
|
|
const db = getDb();
|
|
|
|
// --- Step 1: Verify all datasets exist, are not deleted, and get their org IDs ---
|
|
const datasetInfos = await db
|
|
.select({
|
|
id: datasets.id,
|
|
organizationId: datasets.organizationId,
|
|
deletedAt: datasets.deletedAt,
|
|
})
|
|
.from(datasets)
|
|
.where(inArray(datasets.id, input.datasetIds));
|
|
|
|
// Check if we found info for all requested datasets
|
|
if (datasetInfos.length !== input.datasetIds.length) {
|
|
return false; // At least one dataset doesn't exist
|
|
}
|
|
|
|
// Check for deleted datasets and collect unique organization IDs
|
|
const organizationIds = new Set<string>();
|
|
for (const { organizationId, deletedAt } of datasetInfos) {
|
|
if (deletedAt !== null) {
|
|
return false; // Access denied if any dataset is deleted
|
|
}
|
|
organizationIds.add(organizationId);
|
|
}
|
|
|
|
// --- Step 2: Check Admin/Querier access across all relevant organizations ---
|
|
const adminRoles = await db
|
|
.select({
|
|
organizationId: usersToOrganizations.organizationId,
|
|
role: usersToOrganizations.role,
|
|
})
|
|
.from(usersToOrganizations)
|
|
.where(
|
|
and(
|
|
eq(usersToOrganizations.userId, input.userId),
|
|
inArray(usersToOrganizations.organizationId, Array.from(organizationIds)),
|
|
isNull(usersToOrganizations.deletedAt)
|
|
)
|
|
);
|
|
|
|
const adminOrgIdsWithAccess = new Set(
|
|
adminRoles
|
|
.filter(({ role }: { role: string }) =>
|
|
['workspace_admin', 'data_admin', 'querier'].includes(role)
|
|
)
|
|
.map(({ organizationId }: { organizationId: string }) => organizationId)
|
|
);
|
|
|
|
// Check if all required organization IDs are covered by the user's admin/querier roles
|
|
const allOrgsHaveAdminAccess = Array.from(organizationIds).every((orgId) =>
|
|
adminOrgIdsWithAccess.has(orgId)
|
|
);
|
|
|
|
if (allOrgsHaveAdminAccess) {
|
|
return true; // User is admin/querier in all necessary orgs
|
|
}
|
|
|
|
// --- Step 3: Check specific permissions for each dataset ---
|
|
for (const datasetId of input.datasetIds) {
|
|
const datasetOrgId = datasetInfos.find(
|
|
(info: { id: string; organizationId: string; deletedAt: string | null }) =>
|
|
info.id === datasetId
|
|
)?.organizationId;
|
|
|
|
if (!datasetOrgId) {
|
|
throw new AccessControlsError('Dataset info missing after validation');
|
|
}
|
|
|
|
if (adminOrgIdsWithAccess.has(datasetOrgId)) {
|
|
// User has admin/querier access in this dataset's org
|
|
continue;
|
|
}
|
|
|
|
// Check specific permissions for this dataset
|
|
const hasSpecificAccess = await checkSpecificDatasetPermissions(input.userId, datasetId);
|
|
if (!hasSpecificAccess) {
|
|
return false; // If access fails for any dataset, the whole check fails
|
|
}
|
|
}
|
|
|
|
return true; // User has access to all datasets
|
|
}
|
|
|
|
// --- Helper Functions for Individual Permission Checks ---
|
|
|
|
async function checkDirectUserPermission(userId: string, datasetId: string): Promise<boolean> {
|
|
const db = getDb();
|
|
|
|
const result = await db
|
|
.select({ count: count() })
|
|
.from(datasetPermissions)
|
|
.where(
|
|
and(
|
|
eq(datasetPermissions.permissionId, userId),
|
|
eq(datasetPermissions.permissionType, 'user'),
|
|
eq(datasetPermissions.datasetId, datasetId),
|
|
isNull(datasetPermissions.deletedAt)
|
|
)
|
|
);
|
|
|
|
const permissionCount: number = result[0]?.count ?? 0;
|
|
|
|
return permissionCount > 0;
|
|
}
|
|
|
|
async function checkTeamDirectPermission(userId: string, datasetId: string): Promise<boolean> {
|
|
const db = getDb();
|
|
|
|
const result = await db
|
|
.select({ count: count() })
|
|
.from(datasetPermissions)
|
|
.innerJoin(
|
|
teamsToUsers,
|
|
and(
|
|
eq(datasetPermissions.permissionId, teamsToUsers.teamId),
|
|
eq(datasetPermissions.permissionType, 'team'),
|
|
eq(teamsToUsers.userId, userId),
|
|
isNull(teamsToUsers.deletedAt)
|
|
)
|
|
)
|
|
.where(and(eq(datasetPermissions.datasetId, datasetId), isNull(datasetPermissions.deletedAt)));
|
|
|
|
const permissionCount: number = result[0]?.count ?? 0;
|
|
|
|
return permissionCount > 0;
|
|
}
|
|
|
|
async function checkUserGroupPermission(userId: string, datasetId: string): Promise<boolean> {
|
|
const db = getDb();
|
|
|
|
const result = await db
|
|
.select({ count: count() })
|
|
.from(datasetsToPermissionGroups)
|
|
.innerJoin(
|
|
permissionGroups,
|
|
and(
|
|
eq(datasetsToPermissionGroups.permissionGroupId, permissionGroups.id),
|
|
isNull(permissionGroups.deletedAt)
|
|
)
|
|
)
|
|
.innerJoin(
|
|
permissionGroupsToIdentities,
|
|
and(
|
|
eq(permissionGroups.id, permissionGroupsToIdentities.permissionGroupId),
|
|
eq(permissionGroupsToIdentities.identityId, userId),
|
|
eq(permissionGroupsToIdentities.identityType, 'user'),
|
|
isNull(permissionGroupsToIdentities.deletedAt)
|
|
)
|
|
)
|
|
.where(
|
|
and(
|
|
eq(datasetsToPermissionGroups.datasetId, datasetId),
|
|
isNull(datasetsToPermissionGroups.deletedAt)
|
|
)
|
|
);
|
|
|
|
const permissionCount: number = result[0]?.count ?? 0;
|
|
|
|
return permissionCount > 0;
|
|
}
|
|
|
|
async function checkTeamGroupPermission(userId: string, datasetId: string): Promise<boolean> {
|
|
const db = getDb();
|
|
|
|
const result = await db
|
|
.select({ count: count() })
|
|
.from(datasetsToPermissionGroups)
|
|
.innerJoin(
|
|
permissionGroups,
|
|
and(
|
|
eq(datasetsToPermissionGroups.permissionGroupId, permissionGroups.id),
|
|
isNull(permissionGroups.deletedAt)
|
|
)
|
|
)
|
|
.innerJoin(
|
|
permissionGroupsToIdentities,
|
|
and(
|
|
eq(permissionGroups.id, permissionGroupsToIdentities.permissionGroupId),
|
|
eq(permissionGroupsToIdentities.identityType, 'team'),
|
|
isNull(permissionGroupsToIdentities.deletedAt)
|
|
)
|
|
)
|
|
.innerJoin(
|
|
teamsToUsers,
|
|
and(
|
|
eq(permissionGroupsToIdentities.identityId, teamsToUsers.teamId),
|
|
eq(teamsToUsers.userId, userId),
|
|
isNull(teamsToUsers.deletedAt)
|
|
)
|
|
)
|
|
.where(
|
|
and(
|
|
eq(datasetsToPermissionGroups.datasetId, datasetId),
|
|
isNull(datasetsToPermissionGroups.deletedAt)
|
|
)
|
|
);
|
|
|
|
const permissionCount: number = result[0]?.count ?? 0;
|
|
|
|
return permissionCount > 0;
|
|
}
|
|
|
|
async function checkOrgDefaultPermission(userId: string, datasetId: string): Promise<boolean> {
|
|
const db = getDb();
|
|
|
|
// Get all the user's organizations
|
|
const userOrgs = await db
|
|
.select({ organizationId: usersToOrganizations.organizationId })
|
|
.from(usersToOrganizations)
|
|
.where(and(eq(usersToOrganizations.userId, userId), isNull(usersToOrganizations.deletedAt)));
|
|
|
|
if (userOrgs.length === 0) {
|
|
return false; // User not in any organization
|
|
}
|
|
|
|
// Check if the dataset is in any organization's default permission group
|
|
for (const userOrg of userOrgs) {
|
|
const { organizationId } = userOrg;
|
|
const defaultGroupName = `default:${organizationId}`;
|
|
|
|
const result = await db
|
|
.select({ count: count() })
|
|
.from(datasetsToPermissionGroups)
|
|
.innerJoin(
|
|
permissionGroups,
|
|
and(
|
|
eq(datasetsToPermissionGroups.permissionGroupId, permissionGroups.id),
|
|
eq(permissionGroups.name, defaultGroupName),
|
|
eq(permissionGroups.organizationId, organizationId),
|
|
isNull(permissionGroups.deletedAt)
|
|
)
|
|
)
|
|
.where(
|
|
and(
|
|
eq(datasetsToPermissionGroups.datasetId, datasetId),
|
|
isNull(datasetsToPermissionGroups.deletedAt)
|
|
)
|
|
);
|
|
|
|
const permissionCount: number = result[0]?.count ?? 0;
|
|
|
|
if (permissionCount > 0) {
|
|
return true; // Found access in this org's default group
|
|
}
|
|
}
|
|
|
|
return false; // No access found in any org's default group
|
|
}
|
|
|
|
async function checkSpecificDatasetPermissions(
|
|
userId: string,
|
|
datasetId: string
|
|
): Promise<boolean> {
|
|
// Check all permission paths concurrently
|
|
const permissionChecks = await Promise.all([
|
|
checkDirectUserPermission(userId, datasetId),
|
|
checkTeamDirectPermission(userId, datasetId),
|
|
checkUserGroupPermission(userId, datasetId),
|
|
checkTeamGroupPermission(userId, datasetId),
|
|
checkOrgDefaultPermission(userId, datasetId),
|
|
]);
|
|
|
|
// Return true if any permission check succeeds
|
|
return permissionChecks.some((hasAccess) => hasAccess);
|
|
}
|
|
|
|
// Legacy function names for compatibility
|
|
export function checkPermission(options: AccessControlOptions): Promise<boolean> {
|
|
return hasDatasetAccess(options.userId, options.resourceId || '');
|
|
}
|
|
|
|
export function hasRole(_userId: string, _role: string): Promise<boolean> {
|
|
// Placeholder for role-based checks - implement as needed
|
|
throw new AccessControlsError('hasRole not implemented yet');
|
|
}
|
|
|
|
export function validateAccess(options: AccessControlOptions): Promise<boolean> {
|
|
return checkPermission(options);
|
|
}
|