mirror of
https://github.com/docmost/docmost.git
synced 2026-05-18 23:44:24 +08:00
Fix permission - WIP
This commit is contained in:
@@ -366,20 +366,19 @@ export class PagePermissionService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if user has writer permission on ALL restricted ancestors of a page.
|
||||||
|
* Used for permission management operations.
|
||||||
|
*/
|
||||||
async hasWritePermission(userId: string, pageId: string): Promise<boolean> {
|
async hasWritePermission(userId: string, pageId: string): Promise<boolean> {
|
||||||
const restrictedAncestor =
|
const hasRestriction =
|
||||||
await this.pagePermissionRepo.findRestrictedAncestor(pageId);
|
await this.pagePermissionRepo.hasRestrictedAncestor(pageId);
|
||||||
|
|
||||||
if (!restrictedAncestor) {
|
if (!hasRestriction) {
|
||||||
return false;
|
return false; // no restrictions, defer to space permissions
|
||||||
}
|
}
|
||||||
|
|
||||||
const permission = await this.pagePermissionRepo.getUserPagePermission(
|
return this.pagePermissionRepo.canUserEditPage(userId, pageId);
|
||||||
userId,
|
|
||||||
restrictedAncestor.pageId,
|
|
||||||
);
|
|
||||||
|
|
||||||
return permission?.role === PagePermissionRole.WRITER;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async hasPageAccess(pageId: string): Promise<boolean> {
|
async hasPageAccess(pageId: string): Promise<boolean> {
|
||||||
@@ -402,46 +401,34 @@ export class PagePermissionService {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if user can view a page.
|
* Check if user can view a page.
|
||||||
|
* User must have permission (reader or writer) on EVERY restricted ancestor.
|
||||||
* Returns true if:
|
* Returns true if:
|
||||||
* - Page has no restricted ancestor: fall back to space permission
|
* - No ancestors are restricted (defer to space permission)
|
||||||
* - Page has restricted ancestor: user has reader or writer permission on that ancestor
|
* - User has permission on all restricted ancestors
|
||||||
*/
|
*/
|
||||||
async canViewPage(userId: string, pageId: string): Promise<boolean> {
|
async canViewPage(userId: string, pageId: string): Promise<boolean> {
|
||||||
const restrictedAncestor =
|
return this.pagePermissionRepo.canUserAccessPage(userId, pageId);
|
||||||
await this.pagePermissionRepo.findRestrictedAncestor(pageId);
|
|
||||||
|
|
||||||
if (!restrictedAncestor) {
|
|
||||||
return true; // no page-level restriction, defer to space permission
|
|
||||||
}
|
|
||||||
|
|
||||||
const permission = await this.pagePermissionRepo.getUserPagePermission(
|
|
||||||
userId,
|
|
||||||
restrictedAncestor.pageId,
|
|
||||||
);
|
|
||||||
|
|
||||||
return !!permission; // has any permission (reader or writer)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if user can edit a page.
|
* Check if user can edit a page.
|
||||||
|
* User must have WRITER permission on EVERY restricted ancestor.
|
||||||
* Returns true if:
|
* Returns true if:
|
||||||
* - Page has no restricted ancestor: fall back to space permission
|
* - No ancestors are restricted (defer to space permission)
|
||||||
* - Page has restricted ancestor: user has writer permission on that ancestor
|
* - User has writer permission on all restricted ancestors
|
||||||
*/
|
*/
|
||||||
async canEditPage(userId: string, pageId: string): Promise<boolean> {
|
async canEditPage(userId: string, pageId: string): Promise<boolean> {
|
||||||
const restrictedAncestor =
|
return this.pagePermissionRepo.canUserEditPage(userId, pageId);
|
||||||
await this.pagePermissionRepo.findRestrictedAncestor(pageId);
|
}
|
||||||
|
|
||||||
if (!restrictedAncestor) {
|
/**
|
||||||
return true; // no page-level restriction, defer to space permission
|
* Filter page IDs to only those the user can access.
|
||||||
}
|
*/
|
||||||
|
async filterAccessiblePages(
|
||||||
const permission = await this.pagePermissionRepo.getUserPagePermission(
|
pageIds: string[],
|
||||||
userId,
|
userId: string,
|
||||||
restrictedAncestor.pageId,
|
): Promise<string[]> {
|
||||||
);
|
return this.pagePermissionRepo.filterAccessiblePageIds(pageIds, userId);
|
||||||
|
|
||||||
return permission?.role === PagePermissionRole.WRITER;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -267,4 +267,145 @@ export class PagePermissionRepo {
|
|||||||
.orderBy('pageHierarchy.depth', 'asc')
|
.orderBy('pageHierarchy.depth', 'asc')
|
||||||
.executeTakeFirst();
|
.executeTakeFirst();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if user can access a page by verifying they have permission on ALL restricted ancestors.
|
||||||
|
* Returns true if:
|
||||||
|
* - No ancestors are restricted, OR
|
||||||
|
* - User has permission (reader or writer) on every restricted ancestor
|
||||||
|
*/
|
||||||
|
async canUserAccessPage(userId: string, pageId: string): Promise<boolean> {
|
||||||
|
// Find any restricted ancestor where user lacks permission
|
||||||
|
const deniedAncestor = await this.db
|
||||||
|
.selectFrom('pageHierarchy')
|
||||||
|
.innerJoin('pageAccess', 'pageAccess.pageId', 'pageHierarchy.ancestorId')
|
||||||
|
.leftJoin('pagePermissions', (join) =>
|
||||||
|
join
|
||||||
|
.onRef('pagePermissions.pageAccessId', '=', 'pageAccess.id')
|
||||||
|
.on((eb) =>
|
||||||
|
eb.or([
|
||||||
|
eb('pagePermissions.userId', '=', userId),
|
||||||
|
eb(
|
||||||
|
'pagePermissions.groupId',
|
||||||
|
'in',
|
||||||
|
eb
|
||||||
|
.selectFrom('groupUsers')
|
||||||
|
.select('groupUsers.groupId')
|
||||||
|
.where('groupUsers.userId', '=', userId),
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.select('pageAccess.pageId')
|
||||||
|
.where('pageHierarchy.descendantId', '=', pageId)
|
||||||
|
.where('pagePermissions.id', 'is', null)
|
||||||
|
.executeTakeFirst();
|
||||||
|
|
||||||
|
return !deniedAncestor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if user can edit a page by verifying they have WRITER permission on ALL restricted ancestors.
|
||||||
|
* Returns true if:
|
||||||
|
* - No ancestors are restricted, OR
|
||||||
|
* - User has writer permission on every restricted ancestor
|
||||||
|
*/
|
||||||
|
async canUserEditPage(userId: string, pageId: string): Promise<boolean> {
|
||||||
|
// Find any restricted ancestor where user lacks writer permission
|
||||||
|
const deniedAncestor = await this.db
|
||||||
|
.selectFrom('pageHierarchy')
|
||||||
|
.innerJoin('pageAccess', 'pageAccess.pageId', 'pageHierarchy.ancestorId')
|
||||||
|
.leftJoin('pagePermissions', (join) =>
|
||||||
|
join
|
||||||
|
.onRef('pagePermissions.pageAccessId', '=', 'pageAccess.id')
|
||||||
|
.on('pagePermissions.role', '=', 'writer')
|
||||||
|
.on((eb) =>
|
||||||
|
eb.or([
|
||||||
|
eb('pagePermissions.userId', '=', userId),
|
||||||
|
eb(
|
||||||
|
'pagePermissions.groupId',
|
||||||
|
'in',
|
||||||
|
eb
|
||||||
|
.selectFrom('groupUsers')
|
||||||
|
.select('groupUsers.groupId')
|
||||||
|
.where('groupUsers.userId', '=', userId),
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.select('pageAccess.pageId')
|
||||||
|
.where('pageHierarchy.descendantId', '=', pageId)
|
||||||
|
.where('pagePermissions.id', 'is', null)
|
||||||
|
.executeTakeFirst();
|
||||||
|
|
||||||
|
return !deniedAncestor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter a list of page IDs to only those the user can access.
|
||||||
|
* Efficient single-query implementation for bulk filtering.
|
||||||
|
*/
|
||||||
|
async filterAccessiblePageIds(
|
||||||
|
pageIds: string[],
|
||||||
|
userId: string,
|
||||||
|
): Promise<string[]> {
|
||||||
|
if (pageIds.length === 0) return [];
|
||||||
|
|
||||||
|
// For each page, count restricted ancestors vs permitted ancestors
|
||||||
|
// A page is accessible if restrictedCount == permittedCount
|
||||||
|
const results = await this.db
|
||||||
|
.selectFrom('pages')
|
||||||
|
.select('pages.id')
|
||||||
|
.where('pages.id', 'in', pageIds)
|
||||||
|
.where(({ not, exists, selectFrom }) =>
|
||||||
|
not(
|
||||||
|
exists(
|
||||||
|
selectFrom('pageHierarchy')
|
||||||
|
.innerJoin(
|
||||||
|
'pageAccess',
|
||||||
|
'pageAccess.pageId',
|
||||||
|
'pageHierarchy.ancestorId',
|
||||||
|
)
|
||||||
|
.leftJoin('pagePermissions', (join) =>
|
||||||
|
join
|
||||||
|
.onRef('pagePermissions.pageAccessId', '=', 'pageAccess.id')
|
||||||
|
.on((eb) =>
|
||||||
|
eb.or([
|
||||||
|
eb('pagePermissions.userId', '=', userId),
|
||||||
|
eb(
|
||||||
|
'pagePermissions.groupId',
|
||||||
|
'in',
|
||||||
|
eb
|
||||||
|
.selectFrom('groupUsers')
|
||||||
|
.select('groupUsers.groupId')
|
||||||
|
.where('groupUsers.userId', '=', userId),
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.select('pageAccess.pageId')
|
||||||
|
.whereRef('pageHierarchy.descendantId', '=', 'pages.id')
|
||||||
|
.where('pagePermissions.id', 'is', null),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
return results.map((r) => r.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a page or any of its ancestors has restrictions.
|
||||||
|
* Used to determine if page-level permission checks are needed.
|
||||||
|
*/
|
||||||
|
async hasRestrictedAncestor(pageId: string): Promise<boolean> {
|
||||||
|
const result = await this.db
|
||||||
|
.selectFrom('pageHierarchy')
|
||||||
|
.innerJoin('pageAccess', 'pageAccess.pageId', 'pageHierarchy.ancestorId')
|
||||||
|
.select('pageAccess.id')
|
||||||
|
.where('pageHierarchy.descendantId', '=', pageId)
|
||||||
|
.executeTakeFirst();
|
||||||
|
|
||||||
|
return !!result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user