separate and implement album READ and WRITE permission
This commit is contained in:
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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[];
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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>>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user