separate and implement album READ and WRITE permission

This commit is contained in:
mgabor
2024-04-12 11:10:22 +02:00
parent 156cfb5820
commit 48149e4384
7 changed files with 51 additions and 17 deletions
+21 -2
View File
@@ -20,6 +20,7 @@ export enum Permission {
// ALBUM_CREATE = 'album.create', // ALBUM_CREATE = 'album.create',
ALBUM_READ = 'album.read', ALBUM_READ = 'album.read',
ALBUM_WRITE = 'album.write',
ALBUM_UPDATE = 'album.update', ALBUM_UPDATE = 'album.update',
ALBUM_DELETE = 'album.delete', ALBUM_DELETE = 'album.delete',
ALBUM_REMOVE_ASSET = 'album.removeAsset', ALBUM_REMOVE_ASSET = 'album.removeAsset',
@@ -215,7 +216,21 @@ export class AccessCore {
case Permission.ALBUM_READ: { case Permission.ALBUM_READ: {
const isOwner = await this.repository.album.checkOwnerAccess(auth.user.id, ids); 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); return setUnion(isOwner, isShared);
} }
@@ -233,7 +248,11 @@ export class AccessCore {
case Permission.ALBUM_DOWNLOAD: { case Permission.ALBUM_DOWNLOAD: {
const isOwner = await this.repository.album.checkOwnerAccess(auth.user.id, ids); 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); return setUnion(isOwner, isShared);
} }
+4
View File
@@ -1,3 +1,4 @@
import { AlbumPermissionEntity } from 'src/entities/album-permission.entity';
import { AssetEntity } from 'src/entities/asset.entity'; import { AssetEntity } from 'src/entities/asset.entity';
import { SharedLinkEntity } from 'src/entities/shared-link.entity'; import { SharedLinkEntity } from 'src/entities/shared-link.entity';
import { UserEntity } from 'src/entities/user.entity'; import { UserEntity } from 'src/entities/user.entity';
@@ -59,6 +60,9 @@ export class AlbumEntity {
}) })
sharedUsers!: UserEntity[]; sharedUsers!: UserEntity[];
@OneToMany(() => AlbumPermissionEntity, (permission) => permission.albums)
albumPermissions!: AlbumPermissionEntity[];
@ManyToMany(() => AssetEntity, (asset) => asset.albums) @ManyToMany(() => AssetEntity, (asset) => asset.albums)
@JoinTable() @JoinTable()
assets!: AssetEntity[]; assets!: AssetEntity[];
+2
View File
@@ -1,4 +1,5 @@
import { ActivityEntity } from 'src/entities/activity.entity'; import { ActivityEntity } from 'src/entities/activity.entity';
import { AlbumPermissionEntity } from 'src/entities/album-permission.entity';
import { AlbumEntity } from 'src/entities/album.entity'; import { AlbumEntity } from 'src/entities/album.entity';
import { APIKeyEntity } from 'src/entities/api-key.entity'; import { APIKeyEntity } from 'src/entities/api-key.entity';
import { AssetFaceEntity } from 'src/entities/asset-face.entity'; import { AssetFaceEntity } from 'src/entities/asset-face.entity';
@@ -25,6 +26,7 @@ import { UserEntity } from 'src/entities/user.entity';
export const entities = [ export const entities = [
ActivityEntity, ActivityEntity,
AlbumEntity, AlbumEntity,
AlbumPermissionEntity,
APIKeyEntity, APIKeyEntity,
AssetEntity, AssetEntity,
AssetStackEntity, AssetStackEntity,
+3 -1
View File
@@ -1,5 +1,7 @@
export const IAccessRepository = 'IAccessRepository'; export const IAccessRepository = 'IAccessRepository';
export type ReadWrite = 'read' | 'write';
export interface IAccessRepository { export interface IAccessRepository {
activity: { activity: {
checkOwnerAccess(userId: string, activityIds: Set<string>): Promise<Set<string>>; checkOwnerAccess(userId: string, activityIds: Set<string>): Promise<Set<string>>;
@@ -20,7 +22,7 @@ export interface IAccessRepository {
album: { album: {
checkOwnerAccess(userId: string, albumIds: Set<string>): Promise<Set<string>>; checkOwnerAccess(userId: string, albumIds: Set<string>): Promise<Set<string>>;
checkSharedAlbumAccess(userId: string, albumIds: Set<string>): Promise<Set<string>>; checkSharedAlbumAccess(userId: string, albumIds: Set<string>, readWrite: ReadWrite): Promise<Set<string>>;
checkSharedLinkAccess(sharedLinkId: string, albumIds: Set<string>): Promise<Set<string>>; checkSharedLinkAccess(sharedLinkId: string, albumIds: Set<string>): Promise<Set<string>>;
}; };
+8 -7
View File
@@ -67,21 +67,22 @@ WHERE
-- AccessRepository.album.checkSharedAlbumAccess -- AccessRepository.album.checkSharedAlbumAccess
SELECT 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 FROM
"albums" "AlbumEntity" "albums" "AlbumEntity"
LEFT JOIN "albums_shared_users_users" "AlbumEntity_AlbumEntity__AlbumEntity_sharedUsers" ON "AlbumEntity_AlbumEntity__AlbumEntity_sharedUsers"."albumsId" = "AlbumEntity"."id" LEFT JOIN "albums_shared_users_users" "AlbumEntity__AlbumEntity_albumPermissions" ON "AlbumEntity__AlbumEntity_albumPermissions"."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
)
WHERE WHERE
( (
( (
("AlbumEntity"."id" IN ($1)) ("AlbumEntity"."id" IN ($1))
AND ( AND (
( (
("AlbumEntity__AlbumEntity_sharedUsers"."id" = $2) (
"AlbumEntity__AlbumEntity_albumPermissions"."usersId" = $2
)
) )
) )
) )
+11 -5
View File
@@ -10,9 +10,9 @@ import { PartnerEntity } from 'src/entities/partner.entity';
import { PersonEntity } from 'src/entities/person.entity'; import { PersonEntity } from 'src/entities/person.entity';
import { SharedLinkEntity } from 'src/entities/shared-link.entity'; import { SharedLinkEntity } from 'src/entities/shared-link.entity';
import { UserTokenEntity } from 'src/entities/user-token.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 { Instrumentation } from 'src/utils/instrumentation';
import { Brackets, In, Repository } from 'typeorm'; import { Brackets, Equal, In, Repository } from 'typeorm';
type IActivityAccess = IAccessRepository['activity']; type IActivityAccess = IAccessRepository['activity'];
type IAlbumAccess = IAccessRepository['album']; type IAlbumAccess = IAccessRepository['album'];
@@ -118,18 +118,24 @@ class AlbumAccess implements IAlbumAccess {
@GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID_SET] }) @GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID_SET] })
@ChunkedSet({ paramIndex: 1 }) @ChunkedSet({ paramIndex: 1 })
async checkSharedAlbumAccess(userId: string, albumIds: Set<string>): Promise<Set<string>> { async checkSharedAlbumAccess(userId: string, albumIds: Set<string>, readWrite: ReadWrite): Promise<Set<string>> {
if (albumIds.size === 0) { if (albumIds.size === 0) {
return new Set(); return new Set();
} }
console.log(readWrite);
return this.albumRepository return this.albumRepository
.find({ .find({
select: { id: true }, select: { id: true },
relations: { albumPermissions: true },
// -@ts-expect-error asd
where: { where: {
id: In([...albumIds]), id: In([...albumIds]),
sharedUsers: { albumPermissions: {
id: userId, users: Equal(userId),
// If write is needed we check for it, otherwise both are accepted
readonly: readWrite === 'write' ? false : undefined,
}, },
}, },
}) })
+2 -2
View File
@@ -164,7 +164,7 @@ export class AlbumService {
async addAssets(auth: AuthDto, id: string, dto: BulkIdsDto): Promise<BulkIdResponseDto[]> { async addAssets(auth: AuthDto, id: string, dto: BulkIdsDto): Promise<BulkIdResponseDto[]> {
const album = await this.findOrFail(id, { withAssets: false }); 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( const results = await addAssets(
auth, auth,
@@ -187,7 +187,7 @@ export class AlbumService {
async removeAssets(auth: AuthDto, id: string, dto: BulkIdsDto): Promise<BulkIdResponseDto[]> { async removeAssets(auth: AuthDto, id: string, dto: BulkIdsDto): Promise<BulkIdResponseDto[]> {
const album = await this.findOrFail(id, { withAssets: false }); 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( const results = await removeAssets(
auth, auth,