mirror of
https://github.com/docmost/docmost.git
synced 2026-05-18 23:44:24 +08:00
Share permissions
This commit is contained in:
@@ -26,6 +26,7 @@ import {
|
|||||||
UpdateShareDto,
|
UpdateShareDto,
|
||||||
} from './dto/share.dto';
|
} from './dto/share.dto';
|
||||||
import { PageRepo } from '@docmost/db/repos/page/page.repo';
|
import { PageRepo } from '@docmost/db/repos/page/page.repo';
|
||||||
|
import { PagePermissionRepo } from '@docmost/db/repos/page/page-permission.repo';
|
||||||
import { PageAccessService } from '../page-access/page-access.service';
|
import { PageAccessService } from '../page-access/page-access.service';
|
||||||
import { JwtAuthGuard } from '../../common/guards/jwt-auth.guard';
|
import { JwtAuthGuard } from '../../common/guards/jwt-auth.guard';
|
||||||
import { Public } from '../../common/decorators/public.decorator';
|
import { Public } from '../../common/decorators/public.decorator';
|
||||||
@@ -42,6 +43,7 @@ export class ShareController {
|
|||||||
private readonly spaceAbility: SpaceAbilityFactory,
|
private readonly spaceAbility: SpaceAbilityFactory,
|
||||||
private readonly shareRepo: ShareRepo,
|
private readonly shareRepo: ShareRepo,
|
||||||
private readonly pageRepo: PageRepo,
|
private readonly pageRepo: PageRepo,
|
||||||
|
private readonly pagePermissionRepo: PagePermissionRepo,
|
||||||
private readonly pageAccessService: PageAccessService,
|
private readonly pageAccessService: PageAccessService,
|
||||||
private readonly environmentService: EnvironmentService,
|
private readonly environmentService: EnvironmentService,
|
||||||
) {}
|
) {}
|
||||||
@@ -126,8 +128,17 @@ export class ShareController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// User must be able to edit the page to create a share
|
// User must be able to edit the page to create a share
|
||||||
|
//TODO: i dont think this is neccessary if we prevent restricted pages from getting shared
|
||||||
|
// rather, use space level permission and workspace/space level sharing restriction
|
||||||
await this.pageAccessService.validateCanEdit(page, user);
|
await this.pageAccessService.validateCanEdit(page, user);
|
||||||
|
|
||||||
|
// Prevent sharing restricted pages
|
||||||
|
const isRestricted =
|
||||||
|
await this.pagePermissionRepo.hasRestrictedAncestor(page.id);
|
||||||
|
if (isRestricted) {
|
||||||
|
throw new BadRequestException('Cannot share a restricted page');
|
||||||
|
}
|
||||||
|
|
||||||
return this.shareService.createShare({
|
return this.shareService.createShare({
|
||||||
page,
|
page,
|
||||||
authUserId: user.id,
|
authUserId: user.id,
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import {
|
|||||||
} from '../../common/helpers/prosemirror/utils';
|
} from '../../common/helpers/prosemirror/utils';
|
||||||
import { Node } from '@tiptap/pm/model';
|
import { Node } from '@tiptap/pm/model';
|
||||||
import { ShareRepo } from '@docmost/db/repos/share/share.repo';
|
import { ShareRepo } from '@docmost/db/repos/share/share.repo';
|
||||||
|
import { PagePermissionRepo } from '@docmost/db/repos/page/page-permission.repo';
|
||||||
import { updateAttachmentAttr } from './share.util';
|
import { updateAttachmentAttr } from './share.util';
|
||||||
import { Page } from '@docmost/db/types/entity.types';
|
import { Page } from '@docmost/db/types/entity.types';
|
||||||
import { validate as isValidUUID } from 'uuid';
|
import { validate as isValidUUID } from 'uuid';
|
||||||
@@ -31,6 +32,7 @@ export class ShareService {
|
|||||||
constructor(
|
constructor(
|
||||||
private readonly shareRepo: ShareRepo,
|
private readonly shareRepo: ShareRepo,
|
||||||
private readonly pageRepo: PageRepo,
|
private readonly pageRepo: PageRepo,
|
||||||
|
private readonly pagePermissionRepo: PagePermissionRepo,
|
||||||
@InjectKysely() private readonly db: KyselyDB,
|
@InjectKysely() private readonly db: KyselyDB,
|
||||||
private readonly tokenService: TokenService,
|
private readonly tokenService: TokenService,
|
||||||
) {}
|
) {}
|
||||||
@@ -46,7 +48,13 @@ export class ShareService {
|
|||||||
includeContent: false,
|
includeContent: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
return { share, pageTree: pageList };
|
// Filter out restricted pages and their descendants
|
||||||
|
const restrictedIds =
|
||||||
|
await this.pagePermissionRepo.getRestrictedSubtreeIds(share.pageId);
|
||||||
|
const restrictedSet = new Set(restrictedIds);
|
||||||
|
const filteredPages = pageList.filter((page) => !restrictedSet.has(page.id));
|
||||||
|
|
||||||
|
return { share, pageTree: filteredPages };
|
||||||
} else {
|
} else {
|
||||||
return { share, pageTree: [] };
|
return { share, pageTree: [] };
|
||||||
}
|
}
|
||||||
@@ -112,6 +120,13 @@ export class ShareService {
|
|||||||
throw new NotFoundException('Shared page not found');
|
throw new NotFoundException('Shared page not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Block access to restricted pages
|
||||||
|
const isRestricted =
|
||||||
|
await this.pagePermissionRepo.hasRestrictedAncestor(page.id);
|
||||||
|
if (isRestricted) {
|
||||||
|
throw new NotFoundException('Shared page not found');
|
||||||
|
}
|
||||||
|
|
||||||
page.content = await this.updatePublicAttachments(page);
|
page.content = await this.updatePublicAttachments(page);
|
||||||
|
|
||||||
return { page, share };
|
return { page, share };
|
||||||
|
|||||||
@@ -624,4 +624,31 @@ export class PagePermissionRepo {
|
|||||||
|
|
||||||
return results.map((r) => r.parentPageId);
|
return results.map((r) => r.parentPageId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all page IDs within a subtree that are restricted OR are descendants of restricted pages.
|
||||||
|
* Used to filter pages from public shares - if a page is restricted, it and all its
|
||||||
|
* children should be hidden.
|
||||||
|
*/
|
||||||
|
async getRestrictedSubtreeIds(rootPageId: string): Promise<string[]> {
|
||||||
|
const results = await this.db
|
||||||
|
.selectFrom('pageHierarchy as subtree')
|
||||||
|
.where('subtree.ancestorId', '=', rootPageId)
|
||||||
|
.innerJoin(
|
||||||
|
(eb) =>
|
||||||
|
eb
|
||||||
|
.selectFrom('pageHierarchy as inner')
|
||||||
|
.innerJoin('pageAccess', 'pageAccess.pageId', 'inner.ancestorId')
|
||||||
|
.select('inner.descendantId as restrictedDescendant')
|
||||||
|
.distinct()
|
||||||
|
.as('restricted'),
|
||||||
|
(join) =>
|
||||||
|
join.onRef('restricted.restrictedDescendant', '=', 'subtree.descendantId'),
|
||||||
|
)
|
||||||
|
.select('subtree.descendantId')
|
||||||
|
.distinct()
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
return results.map((r) => r.descendantId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user