This commit is contained in:
Wells Bunker 2025-09-24 13:42:45 -06:00
parent df07d9fdba
commit 68043841b3
No known key found for this signature in database
GPG Key ID: DB16D6F2679B78FC
7 changed files with 213 additions and 32 deletions

View File

@ -175,6 +175,10 @@ describe('getDashboardHandler', () => {
requiredRole: 'can_view',
organizationId: mockDashboard.organizationId,
workspaceSharing: 'none',
publiclyAccessible: false,
publicExpiryDate: undefined,
publicPassword: undefined,
userSuppliedPassword: undefined,
});
});

View File

@ -67,6 +67,10 @@ describe('checkAssetPublicAccess', () => {
requiredRole: 'can_view',
organizationId: mockOrganizationId,
workspaceSharing: mockWorkspaceSharing,
publiclyAccessible: false,
publicExpiryDate: undefined,
publicPassword: undefined,
userSuppliedPassword: undefined,
});
});
@ -88,6 +92,10 @@ describe('checkAssetPublicAccess', () => {
requiredRole: 'can_edit',
organizationId: mockOrganizationId,
workspaceSharing: mockWorkspaceSharing,
publiclyAccessible: false,
publicExpiryDate: undefined,
publicPassword: undefined,
userSuppliedPassword: undefined,
});
});
@ -378,6 +386,10 @@ describe('checkAssetPublicAccess', () => {
requiredRole: 'can_view',
organizationId: mockOrganizationId,
workspaceSharing: mockWorkspaceSharing,
publiclyAccessible: true,
publicExpiryDate: undefined,
publicPassword: undefined,
userSuppliedPassword: undefined,
});
});
});
@ -472,6 +484,10 @@ describe('checkAssetPublicAccess', () => {
requiredRole: 'can_edit',
organizationId: 'org-123',
workspaceSharing: 'can_view',
publiclyAccessible: false,
publicExpiryDate: undefined,
publicPassword: undefined,
userSuppliedPassword: undefined,
});
});
});

View File

@ -286,8 +286,9 @@ describe('metric-helpers', () => {
const metricFile = createMockMetricFile({ publiclyAccessible: true });
mockGetMetricFileById.mockResolvedValue(metricFile);
mockCheckPermission.mockResolvedValue({
hasAccess: false,
effectiveRole: undefined,
hasAccess: true,
effectiveRole: 'can_view',
accessPath: 'public',
});
const options: MetricAccessOptions = { publicAccessPreviouslyVerified: false };
@ -315,8 +316,8 @@ describe('metric-helpers', () => {
const metricFile = createMockMetricFile({ publiclyAccessible: false });
mockGetMetricFileById.mockResolvedValue(metricFile);
mockCheckPermission.mockResolvedValue({
hasAccess: false,
effectiveRole: undefined,
hasAccess: true,
effectiveRole: 'can_view',
});
const options: MetricAccessOptions = { publicAccessPreviouslyVerified: true };
@ -344,7 +345,7 @@ describe('metric-helpers', () => {
const options: MetricAccessOptions = { publicAccessPreviouslyVerified: false };
await expect(fetchAndProcessMetricData('metric-123', mockUser, options)).rejects.toThrow(
new HTTPException(403, { message: 'Public access to this metric has expired' })
new HTTPException(403, { message: "You don't have permission to view this metric" })
);
});
@ -362,7 +363,7 @@ describe('metric-helpers', () => {
const options: MetricAccessOptions = { publicAccessPreviouslyVerified: false };
await expect(fetchAndProcessMetricData('metric-123', mockUser, options)).rejects.toThrow(
new HTTPException(418, { message: 'Password required for public access' })
new HTTPException(403, { message: "You don't have permission to view this metric" })
);
});
@ -383,7 +384,7 @@ describe('metric-helpers', () => {
};
await expect(fetchAndProcessMetricData('metric-123', mockUser, options)).rejects.toThrow(
new HTTPException(403, { message: 'Incorrect password for public access' })
new HTTPException(403, { message: "You don't have permission to view this metric" })
);
});
@ -394,8 +395,9 @@ describe('metric-helpers', () => {
});
mockGetMetricFileById.mockResolvedValue(metricFile);
mockCheckPermission.mockResolvedValue({
hasAccess: false,
effectiveRole: undefined,
hasAccess: true,
effectiveRole: 'can_view',
accessPath: 'public',
});
const options: MetricAccessOptions = {

View File

@ -318,7 +318,8 @@ export async function checkChatCollectionAccess(chatId: string, user: User): Pro
export async function checkCascadingPermissions(
assetId: string,
assetType: AssetType,
user: User
user: User,
userSuppliedPassword?: string
): Promise<boolean> {
// Check cache first
const cached = getCachedCascadingPermission(user.id, assetId, assetType);
@ -332,7 +333,11 @@ export async function checkCascadingPermissions(
switch (assetType) {
case 'metric_file': {
// Check access through dashboards, chats, collections, and reports
const dashboardAccess = await checkMetricDashboardAccess(assetId, user);
const dashboardAccess = await checkMetricDashboardAccess(
assetId,
user,
userSuppliedPassword
);
if (dashboardAccess) {
hasAccess = true;
break;
@ -350,7 +355,7 @@ export async function checkCascadingPermissions(
break;
}
const reportAccess = await checkMetricReportAccess(assetId, user);
const reportAccess = await checkMetricReportAccess(assetId, user, userSuppliedPassword);
if (reportAccess) {
hasAccess = true;
break;

View File

@ -137,7 +137,12 @@ export async function checkPermission(check: AssetPermissionCheck): Promise<Asse
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);
const hasCascadingAccess = await checkCascadingPermissions(
assetId,
assetType,
user as User,
userSuppliedPassword
);
if (hasCascadingAccess) {
const result = {
hasAccess: true,

View File

@ -47,9 +47,30 @@ describe('checkDashboardsContainingMetric', () => {
it('should return dashboards with organizationId and workspaceSharing', async () => {
const mockDashboards = [
{ id: 'dash1', organizationId: 'org1', workspaceSharing: 'can_view' },
{ id: 'dash2', organizationId: 'org2', workspaceSharing: 'none' },
{ id: 'dash3', organizationId: 'org1', workspaceSharing: null },
{
id: 'dash1',
organizationId: 'org1',
workspaceSharing: 'can_view',
publiclyAccessible: false,
publicExpiryDate: null,
publicPassword: null,
},
{
id: 'dash2',
organizationId: 'org2',
workspaceSharing: 'none',
publiclyAccessible: true,
publicExpiryDate: '2024-12-31T23:59:59Z',
publicPassword: 'secret456',
},
{
id: 'dash3',
organizationId: 'org1',
workspaceSharing: null,
publiclyAccessible: false,
publicExpiryDate: null,
publicPassword: null,
},
];
mockQueryChain.where.mockResolvedValue(mockDashboards);
@ -61,6 +82,9 @@ describe('checkDashboardsContainingMetric', () => {
id: expect.anything(),
organizationId: expect.anything(),
workspaceSharing: expect.anything(),
publiclyAccessible: expect.anything(),
publicExpiryDate: expect.anything(),
publicPassword: expect.anything(),
});
expect(mockQueryChain.from).toHaveBeenCalled();
expect(mockQueryChain.innerJoin).toHaveBeenCalled();
@ -76,21 +100,67 @@ describe('checkDashboardsContainingMetric', () => {
});
it('should handle null workspace sharing values', async () => {
const mockDashboards = [{ id: 'dash1', organizationId: 'org1', workspaceSharing: null }];
const mockDashboards = [
{
id: 'dash1',
organizationId: 'org1',
workspaceSharing: null,
publiclyAccessible: false,
publicExpiryDate: null,
publicPassword: null,
},
];
mockQueryChain.where.mockResolvedValue(mockDashboards);
const result = await checkDashboardsContainingMetric('metric123');
expect(result).toEqual([{ id: 'dash1', organizationId: 'org1', workspaceSharing: null }]);
expect(result).toEqual([
{
id: 'dash1',
organizationId: 'org1',
workspaceSharing: null,
publiclyAccessible: false,
publicExpiryDate: null,
publicPassword: null,
},
]);
});
it('should handle all workspace sharing levels', async () => {
const mockDashboards = [
{ id: 'dash1', organizationId: 'org1', workspaceSharing: 'none' },
{ id: 'dash2', organizationId: 'org1', workspaceSharing: 'can_view' },
{ id: 'dash3', organizationId: 'org1', workspaceSharing: 'can_edit' },
{ id: 'dash4', organizationId: 'org1', workspaceSharing: 'full_access' },
{
id: 'dash1',
organizationId: 'org1',
workspaceSharing: 'none',
publiclyAccessible: false,
publicExpiryDate: null,
publicPassword: null,
},
{
id: 'dash2',
organizationId: 'org1',
workspaceSharing: 'can_view',
publiclyAccessible: false,
publicExpiryDate: null,
publicPassword: null,
},
{
id: 'dash3',
organizationId: 'org1',
workspaceSharing: 'can_edit',
publiclyAccessible: false,
publicExpiryDate: null,
publicPassword: null,
},
{
id: 'dash4',
organizationId: 'org1',
workspaceSharing: 'full_access',
publiclyAccessible: false,
publicExpiryDate: null,
publicPassword: null,
},
];
mockQueryChain.where.mockResolvedValue(mockDashboards);

View File

@ -47,9 +47,30 @@ describe('checkReportsContainingMetric', () => {
it('should return reports with organizationId and workspaceSharing', async () => {
const mockReports = [
{ id: 'report1', organizationId: 'org1', workspaceSharing: 'can_view' },
{ id: 'report2', organizationId: 'org2', workspaceSharing: 'full_access' },
{ id: 'report3', organizationId: 'org1', workspaceSharing: null },
{
id: 'report1',
organizationId: 'org1',
workspaceSharing: 'can_view',
publiclyAccessible: false,
publicExpiryDate: null,
publicPassword: null,
},
{
id: 'report2',
organizationId: 'org2',
workspaceSharing: 'full_access',
publiclyAccessible: true,
publicExpiryDate: '2024-12-31T23:59:59Z',
publicPassword: 'secret123',
},
{
id: 'report3',
organizationId: 'org1',
workspaceSharing: null,
publiclyAccessible: false,
publicExpiryDate: null,
publicPassword: null,
},
];
mockQueryChain.where.mockResolvedValue(mockReports);
@ -61,6 +82,9 @@ describe('checkReportsContainingMetric', () => {
id: expect.anything(),
organizationId: expect.anything(),
workspaceSharing: expect.anything(),
publiclyAccessible: expect.anything(),
publicExpiryDate: expect.anything(),
publicPassword: expect.anything(),
});
expect(mockQueryChain.from).toHaveBeenCalled();
expect(mockQueryChain.innerJoin).toHaveBeenCalled();
@ -76,21 +100,67 @@ describe('checkReportsContainingMetric', () => {
});
it('should handle null workspace sharing values', async () => {
const mockReports = [{ id: 'report1', organizationId: 'org1', workspaceSharing: null }];
const mockReports = [
{
id: 'report1',
organizationId: 'org1',
workspaceSharing: null,
publiclyAccessible: false,
publicExpiryDate: null,
publicPassword: null,
},
];
mockQueryChain.where.mockResolvedValue(mockReports);
const result = await checkReportsContainingMetric('metric123');
expect(result).toEqual([{ id: 'report1', organizationId: 'org1', workspaceSharing: null }]);
expect(result).toEqual([
{
id: 'report1',
organizationId: 'org1',
workspaceSharing: null,
publiclyAccessible: false,
publicExpiryDate: null,
publicPassword: null,
},
]);
});
it('should handle all workspace sharing levels', async () => {
const mockReports = [
{ id: 'report1', organizationId: 'org1', workspaceSharing: 'none' },
{ id: 'report2', organizationId: 'org1', workspaceSharing: 'can_view' },
{ id: 'report3', organizationId: 'org1', workspaceSharing: 'can_edit' },
{ id: 'report4', organizationId: 'org1', workspaceSharing: 'full_access' },
{
id: 'report1',
organizationId: 'org1',
workspaceSharing: 'none',
publiclyAccessible: false,
publicExpiryDate: null,
publicPassword: null,
},
{
id: 'report2',
organizationId: 'org1',
workspaceSharing: 'can_view',
publiclyAccessible: false,
publicExpiryDate: null,
publicPassword: null,
},
{
id: 'report3',
organizationId: 'org1',
workspaceSharing: 'can_edit',
publiclyAccessible: false,
publicExpiryDate: null,
publicPassword: null,
},
{
id: 'report4',
organizationId: 'org1',
workspaceSharing: 'full_access',
publiclyAccessible: false,
publicExpiryDate: null,
publicPassword: null,
},
];
mockQueryChain.where.mockResolvedValue(mockReports);
@ -103,7 +173,16 @@ describe('checkReportsContainingMetric', () => {
it('should filter out deleted reports and deleted relationships', async () => {
// This test validates that the query conditions are set up correctly
// In a real implementation, deleted items would be filtered by the database
const activeReports = [{ id: 'report1', organizationId: 'org1', workspaceSharing: 'can_view' }];
const activeReports = [
{
id: 'report1',
organizationId: 'org1',
workspaceSharing: 'can_view',
publiclyAccessible: false,
publicExpiryDate: null,
publicPassword: null,
},
];
mockQueryChain.where.mockResolvedValue(activeReports);