From 48149e43844eeaba7976cc19934d1913e40ab23d Mon Sep 17 00:00:00 2001 From: mgabor <> Date: Fri, 12 Apr 2024 11:10:22 +0200 Subject: [PATCH] separate and implement album READ and WRITE permission --- server/src/cores/access.core.ts | 23 ++++++++++++++++++-- server/src/entities/album.entity.ts | 4 ++++ server/src/entities/index.ts | 2 ++ server/src/interfaces/access.interface.ts | 4 +++- server/src/queries/access.repository.sql | 15 +++++++------ server/src/repositories/access.repository.ts | 16 +++++++++----- server/src/services/album.service.ts | 4 ++-- 7 files changed, 51 insertions(+), 17 deletions(-) diff --git a/server/src/cores/access.core.ts b/server/src/cores/access.core.ts index 72644870d3..dfd4133ebe 100644 --- a/server/src/cores/access.core.ts +++ b/server/src/cores/access.core.ts @@ -20,6 +20,7 @@ export enum Permission { // ALBUM_CREATE = 'album.create', ALBUM_READ = 'album.read', + ALBUM_WRITE = 'album.write', ALBUM_UPDATE = 'album.update', ALBUM_DELETE = 'album.delete', ALBUM_REMOVE_ASSET = 'album.removeAsset', @@ -215,7 +216,21 @@ export class AccessCore { case Permission.ALBUM_READ: { const isOwner = await this.repository.album.checkOwnerAccess(auth.user.id, ids); - const isShared = await this.repository.album.checkSharedAlbumAccess(auth.user.id, setDifference(ids, isOwner)); + const isShared = await this.repository.album.checkSharedAlbumAccess( + auth.user.id, + setDifference(ids, isOwner), + 'read', + ); + return setUnion(isOwner, isShared); + } + + case Permission.ALBUM_WRITE: { + const isOwner = await this.repository.album.checkOwnerAccess(auth.user.id, ids); + const isShared = await this.repository.album.checkSharedAlbumAccess( + auth.user.id, + setDifference(ids, isOwner), + 'write', + ); return setUnion(isOwner, isShared); } @@ -233,7 +248,11 @@ export class AccessCore { case Permission.ALBUM_DOWNLOAD: { const isOwner = await this.repository.album.checkOwnerAccess(auth.user.id, ids); - const isShared = await this.repository.album.checkSharedAlbumAccess(auth.user.id, setDifference(ids, isOwner)); + const isShared = await this.repository.album.checkSharedAlbumAccess( + auth.user.id, + setDifference(ids, isOwner), + 'read', + ); return setUnion(isOwner, isShared); } diff --git a/server/src/entities/album.entity.ts b/server/src/entities/album.entity.ts index fcd74fe81a..35abce574f 100644 --- a/server/src/entities/album.entity.ts +++ b/server/src/entities/album.entity.ts @@ -1,3 +1,4 @@ +import { AlbumPermissionEntity } from 'src/entities/album-permission.entity'; import { AssetEntity } from 'src/entities/asset.entity'; import { SharedLinkEntity } from 'src/entities/shared-link.entity'; import { UserEntity } from 'src/entities/user.entity'; @@ -59,6 +60,9 @@ export class AlbumEntity { }) sharedUsers!: UserEntity[]; + @OneToMany(() => AlbumPermissionEntity, (permission) => permission.albums) + albumPermissions!: AlbumPermissionEntity[]; + @ManyToMany(() => AssetEntity, (asset) => asset.albums) @JoinTable() assets!: AssetEntity[]; diff --git a/server/src/entities/index.ts b/server/src/entities/index.ts index 761b476930..f03141f4fe 100644 --- a/server/src/entities/index.ts +++ b/server/src/entities/index.ts @@ -1,4 +1,5 @@ import { ActivityEntity } from 'src/entities/activity.entity'; +import { AlbumPermissionEntity } from 'src/entities/album-permission.entity'; import { AlbumEntity } from 'src/entities/album.entity'; import { APIKeyEntity } from 'src/entities/api-key.entity'; import { AssetFaceEntity } from 'src/entities/asset-face.entity'; @@ -25,6 +26,7 @@ import { UserEntity } from 'src/entities/user.entity'; export const entities = [ ActivityEntity, AlbumEntity, + AlbumPermissionEntity, APIKeyEntity, AssetEntity, AssetStackEntity, diff --git a/server/src/interfaces/access.interface.ts b/server/src/interfaces/access.interface.ts index 8b9bdcc4b5..0e147bf65e 100644 --- a/server/src/interfaces/access.interface.ts +++ b/server/src/interfaces/access.interface.ts @@ -1,5 +1,7 @@ export const IAccessRepository = 'IAccessRepository'; +export type ReadWrite = 'read' | 'write'; + export interface IAccessRepository { activity: { checkOwnerAccess(userId: string, activityIds: Set): Promise>; @@ -20,7 +22,7 @@ export interface IAccessRepository { album: { checkOwnerAccess(userId: string, albumIds: Set): Promise>; - checkSharedAlbumAccess(userId: string, albumIds: Set): Promise>; + checkSharedAlbumAccess(userId: string, albumIds: Set, readWrite: ReadWrite): Promise>; checkSharedLinkAccess(sharedLinkId: string, albumIds: Set): Promise>; }; diff --git a/server/src/queries/access.repository.sql b/server/src/queries/access.repository.sql index 0e1cab6d0b..9e59e4f891 100644 --- a/server/src/queries/access.repository.sql +++ b/server/src/queries/access.repository.sql @@ -67,21 +67,22 @@ WHERE -- AccessRepository.album.checkSharedAlbumAccess SELECT - "AlbumEntity"."id" AS "AlbumEntity_id" + "AlbumEntity"."id" AS "AlbumEntity_id", + "AlbumEntity__AlbumEntity_albumPermissions"."albumsId" AS "AlbumEntity__AlbumEntity_albumPermissions_albumsId", + "AlbumEntity__AlbumEntity_albumPermissions"."usersId" AS "AlbumEntity__AlbumEntity_albumPermissions_usersId", + "AlbumEntity__AlbumEntity_albumPermissions"."readonly" AS "AlbumEntity__AlbumEntity_albumPermissions_readonly" FROM "albums" "AlbumEntity" - LEFT JOIN "albums_shared_users_users" "AlbumEntity_AlbumEntity__AlbumEntity_sharedUsers" ON "AlbumEntity_AlbumEntity__AlbumEntity_sharedUsers"."albumsId" = "AlbumEntity"."id" - LEFT JOIN "users" "AlbumEntity__AlbumEntity_sharedUsers" ON "AlbumEntity__AlbumEntity_sharedUsers"."id" = "AlbumEntity_AlbumEntity__AlbumEntity_sharedUsers"."usersId" - AND ( - "AlbumEntity__AlbumEntity_sharedUsers"."deletedAt" IS NULL - ) + LEFT JOIN "albums_shared_users_users" "AlbumEntity__AlbumEntity_albumPermissions" ON "AlbumEntity__AlbumEntity_albumPermissions"."albumsId" = "AlbumEntity"."id" WHERE ( ( ("AlbumEntity"."id" IN ($1)) AND ( ( - ("AlbumEntity__AlbumEntity_sharedUsers"."id" = $2) + ( + "AlbumEntity__AlbumEntity_albumPermissions"."usersId" = $2 + ) ) ) ) diff --git a/server/src/repositories/access.repository.ts b/server/src/repositories/access.repository.ts index fd74eb2ec9..a27f0d4d3f 100644 --- a/server/src/repositories/access.repository.ts +++ b/server/src/repositories/access.repository.ts @@ -10,9 +10,9 @@ import { PartnerEntity } from 'src/entities/partner.entity'; import { PersonEntity } from 'src/entities/person.entity'; import { SharedLinkEntity } from 'src/entities/shared-link.entity'; import { UserTokenEntity } from 'src/entities/user-token.entity'; -import { IAccessRepository } from 'src/interfaces/access.interface'; +import { IAccessRepository, ReadWrite } from 'src/interfaces/access.interface'; import { Instrumentation } from 'src/utils/instrumentation'; -import { Brackets, In, Repository } from 'typeorm'; +import { Brackets, Equal, In, Repository } from 'typeorm'; type IActivityAccess = IAccessRepository['activity']; type IAlbumAccess = IAccessRepository['album']; @@ -118,18 +118,24 @@ class AlbumAccess implements IAlbumAccess { @GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID_SET] }) @ChunkedSet({ paramIndex: 1 }) - async checkSharedAlbumAccess(userId: string, albumIds: Set): Promise> { + async checkSharedAlbumAccess(userId: string, albumIds: Set, readWrite: ReadWrite): Promise> { if (albumIds.size === 0) { return new Set(); } + console.log(readWrite); + return this.albumRepository .find({ select: { id: true }, + relations: { albumPermissions: true }, + // -@ts-expect-error asd where: { id: In([...albumIds]), - sharedUsers: { - id: userId, + albumPermissions: { + users: Equal(userId), + // If write is needed we check for it, otherwise both are accepted + readonly: readWrite === 'write' ? false : undefined, }, }, }) diff --git a/server/src/services/album.service.ts b/server/src/services/album.service.ts index df6c6b814c..a6943193ed 100644 --- a/server/src/services/album.service.ts +++ b/server/src/services/album.service.ts @@ -164,7 +164,7 @@ export class AlbumService { async addAssets(auth: AuthDto, id: string, dto: BulkIdsDto): Promise { const album = await this.findOrFail(id, { withAssets: false }); - await this.access.requirePermission(auth, Permission.ALBUM_READ, id); + await this.access.requirePermission(auth, Permission.ALBUM_WRITE, id); const results = await addAssets( auth, @@ -187,7 +187,7 @@ export class AlbumService { async removeAssets(auth: AuthDto, id: string, dto: BulkIdsDto): Promise { const album = await this.findOrFail(id, { withAssets: false }); - await this.access.requirePermission(auth, Permission.ALBUM_READ, id); + await this.access.requirePermission(auth, Permission.ALBUM_WRITE, id); const results = await removeAssets( auth,