1
0
mirror of https://github.com/immich-app/immich.git synced 2024-12-27 10:58:13 +02:00

refactor(server): shared link asset access check (#2680)

This commit is contained in:
Jason Rasmussen 2023-06-07 00:34:42 -04:00 committed by GitHub
parent d1b0b64d59
commit 284edd97d6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 39 additions and 38 deletions

View File

@ -225,7 +225,7 @@ describe('AssetService', () => {
assetRepositoryMock.getById.mockResolvedValue(asset1); assetRepositoryMock.getById.mockResolvedValue(asset1);
sharedLinkRepositoryMock.get.mockResolvedValue(null); sharedLinkRepositoryMock.get.mockResolvedValue(null);
sharedLinkRepositoryMock.hasAssetAccess.mockResolvedValue(true); accessMock.hasSharedLinkAssetAccess.mockResolvedValue(true);
await expect(sut.addAssetsToSharedLink(authDto, dto)).rejects.toBeInstanceOf(BadRequestException); await expect(sut.addAssetsToSharedLink(authDto, dto)).rejects.toBeInstanceOf(BadRequestException);
@ -242,7 +242,7 @@ describe('AssetService', () => {
assetRepositoryMock.getById.mockResolvedValue(asset1); assetRepositoryMock.getById.mockResolvedValue(asset1);
sharedLinkRepositoryMock.get.mockResolvedValue(sharedLinkStub.valid); sharedLinkRepositoryMock.get.mockResolvedValue(sharedLinkStub.valid);
sharedLinkRepositoryMock.hasAssetAccess.mockResolvedValue(true); accessMock.hasSharedLinkAssetAccess.mockResolvedValue(true);
sharedLinkRepositoryMock.update.mockResolvedValue(sharedLinkStub.valid); sharedLinkRepositoryMock.update.mockResolvedValue(sharedLinkStub.valid);
await expect(sut.addAssetsToSharedLink(authDto, dto)).resolves.toEqual(sharedLinkResponseStub.valid); await expect(sut.addAssetsToSharedLink(authDto, dto)).resolves.toEqual(sharedLinkResponseStub.valid);
@ -260,7 +260,7 @@ describe('AssetService', () => {
assetRepositoryMock.getById.mockResolvedValue(asset1); assetRepositoryMock.getById.mockResolvedValue(asset1);
sharedLinkRepositoryMock.get.mockResolvedValue(sharedLinkStub.valid); sharedLinkRepositoryMock.get.mockResolvedValue(sharedLinkStub.valid);
sharedLinkRepositoryMock.hasAssetAccess.mockResolvedValue(true); accessMock.hasSharedLinkAssetAccess.mockResolvedValue(true);
sharedLinkRepositoryMock.update.mockResolvedValue(sharedLinkStub.valid); sharedLinkRepositoryMock.update.mockResolvedValue(sharedLinkStub.valid);
await expect(sut.removeAssetsFromSharedLink(authDto, dto)).resolves.toEqual(sharedLinkResponseStub.valid); await expect(sut.removeAssetsFromSharedLink(authDto, dto)).resolves.toEqual(sharedLinkResponseStub.valid);

View File

@ -564,10 +564,12 @@ export class AssetService {
} }
private async checkAssetsAccess(authUser: AuthUserDto, assetIds: string[], mustBeOwner = false) { private async checkAssetsAccess(authUser: AuthUserDto, assetIds: string[], mustBeOwner = false) {
const sharedLinkId = authUser.sharedLinkId;
for (const assetId of assetIds) { for (const assetId of assetIds) {
// Step 1: Check if asset is part of a public shared // Step 1: Check if asset is part of a public shared
if (authUser.sharedLinkId) { if (sharedLinkId) {
const canAccess = await this.shareCore.hasAssetAccess(authUser.sharedLinkId, assetId); const canAccess = await this.accessRepository.hasSharedLinkAssetAccess(sharedLinkId, assetId);
if (canAccess) { if (canAccess) {
continue; continue;
} }

View File

@ -3,4 +3,5 @@ export const IAccessRepository = 'IAccessRepository';
export interface IAccessRepository { export interface IAccessRepository {
hasPartnerAccess(userId: string, partnerId: string): Promise<boolean>; hasPartnerAccess(userId: string, partnerId: string): Promise<boolean>;
hasPartnerAssetAccess(userId: string, assetId: string): Promise<boolean>; hasPartnerAssetAccess(userId: string, assetId: string): Promise<boolean>;
hasSharedLinkAssetAccess(userId: string, assetId: string): Promise<boolean>;
} }

View File

@ -47,10 +47,6 @@ export class SharedLinkCore {
return this.repository.update({ ...link, assets: newAssets }); return this.repository.update({ ...link, assets: newAssets });
} }
async hasAssetAccess(id: string, assetId: string): Promise<boolean> {
return this.repository.hasAssetAccess(id, assetId);
}
checkDownloadAccess(user: AuthUserDto) { checkDownloadAccess(user: AuthUserDto) {
if (user.isPublicUser && !user.isAllowDownload) { if (user.isPublicUser && !user.isAllowDownload) {
throw new ForbiddenException(); throw new ForbiddenException();

View File

@ -9,5 +9,4 @@ export interface ISharedLinkRepository {
create(entity: Omit<SharedLinkEntity, 'id' | 'user'>): Promise<SharedLinkEntity>; create(entity: Omit<SharedLinkEntity, 'id' | 'user'>): Promise<SharedLinkEntity>;
update(entity: Partial<SharedLinkEntity>): Promise<SharedLinkEntity>; update(entity: Partial<SharedLinkEntity>): Promise<SharedLinkEntity>;
remove(entity: SharedLinkEntity): Promise<void>; remove(entity: SharedLinkEntity): Promise<void>;
hasAssetAccess(id: string, assetId: string): Promise<boolean>;
} }

View File

@ -4,5 +4,6 @@ export const newAccessRepositoryMock = (): jest.Mocked<IAccessRepository> => {
return { return {
hasPartnerAccess: jest.fn(), hasPartnerAccess: jest.fn(),
hasPartnerAssetAccess: jest.fn(), hasPartnerAssetAccess: jest.fn(),
hasSharedLinkAssetAccess: jest.fn(),
}; };
}; };

View File

@ -8,6 +8,5 @@ export const newSharedLinkRepositoryMock = (): jest.Mocked<ISharedLinkRepository
create: jest.fn(), create: jest.fn(),
remove: jest.fn(), remove: jest.fn(),
update: jest.fn(), update: jest.fn(),
hasAssetAccess: jest.fn(),
}; };
}; };

View File

@ -1,10 +1,13 @@
import { IAccessRepository } from '@app/domain'; import { IAccessRepository } from '@app/domain';
import { InjectRepository } from '@nestjs/typeorm'; import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm'; import { Repository } from 'typeorm';
import { PartnerEntity } from '../entities'; import { PartnerEntity, SharedLinkEntity } from '../entities';
export class AccessRepository implements IAccessRepository { export class AccessRepository implements IAccessRepository {
constructor(@InjectRepository(PartnerEntity) private partnerRepository: Repository<PartnerEntity>) {} constructor(
@InjectRepository(PartnerEntity) private partnerRepository: Repository<PartnerEntity>,
@InjectRepository(SharedLinkEntity) private sharedLinkRepository: Repository<SharedLinkEntity>,
) {}
hasPartnerAccess(userId: string, partnerId: string): Promise<boolean> { hasPartnerAccess(userId: string, partnerId: string): Promise<boolean> {
return this.partnerRepository.exist({ return this.partnerRepository.exist({
@ -35,4 +38,29 @@ export class AccessRepository implements IAccessRepository {
}, },
}); });
} }
async hasSharedLinkAssetAccess(sharedLinkId: string, assetId: string): Promise<boolean> {
return (
// album asset
(await this.sharedLinkRepository.exist({
where: {
id: sharedLinkId,
album: {
assets: {
id: assetId,
},
},
},
})) ||
// individual asset
(await this.sharedLinkRepository.exist({
where: {
id: sharedLinkId,
assets: {
id: assetId,
},
},
}))
);
}
} }

View File

@ -86,31 +86,6 @@ export class SharedLinkRepository implements ISharedLinkRepository {
await this.repository.remove(entity); await this.repository.remove(entity);
} }
async hasAssetAccess(id: string, assetId: string): Promise<boolean> {
return (
// album asset
(await this.repository.exist({
where: {
id,
album: {
assets: {
id: assetId,
},
},
},
})) ||
// individual asset
(await this.repository.exist({
where: {
id,
assets: {
id: assetId,
},
},
}))
);
}
private async save(entity: Partial<SharedLinkEntity>): Promise<SharedLinkEntity> { private async save(entity: Partial<SharedLinkEntity>): Promise<SharedLinkEntity> {
await this.repository.save(entity); await this.repository.save(entity);
return this.repository.findOneOrFail({ where: { id: entity.id } }); return this.repository.findOneOrFail({ where: { id: entity.id } });