chore(server): Check asset permissions in bulk (#5329)

Modify Access repository, to evaluate `asset` permissions in bulk.
Queries have been validated to match what they currently generate for single ids.

Queries:

* `asset` album access:

```sql
-- Before
SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS (
  SELECT 1
  FROM "albums" "AlbumEntity"
    LEFT JOIN "albums_assets_assets" "AlbumEntity_AlbumEntity__AlbumEntity_assets"
      ON "AlbumEntity_AlbumEntity__AlbumEntity_assets"."albumsId"="AlbumEntity"."id"
    LEFT JOIN "assets" "AlbumEntity__AlbumEntity_assets"
      ON "AlbumEntity__AlbumEntity_assets"."id"="AlbumEntity_AlbumEntity__AlbumEntity_assets"."assetsId"
      AND "AlbumEntity__AlbumEntity_assets"."deletedAt" IS NULL
    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
  WHERE
    (
      ("AlbumEntity"."ownerId" = $1 AND "AlbumEntity__AlbumEntity_assets"."id" = $2)
      OR ("AlbumEntity__AlbumEntity_sharedUsers"."id" = $3 AND "AlbumEntity__AlbumEntity_assets"."id" = $4)
      OR ("AlbumEntity"."ownerId" = $5 AND "AlbumEntity__AlbumEntity_assets"."livePhotoVideoId" = $6)
      OR ("AlbumEntity__AlbumEntity_sharedUsers"."id" = $7 AND "AlbumEntity__AlbumEntity_assets"."livePhotoVideoId" = $8)
    )
    AND "AlbumEntity"."deletedAt" IS NULL
)
LIMIT 1

-- After
SELECT
  "asset"."id" AS "assetId",
  "asset"."livePhotoVideoId" AS "livePhotoVideoId"
FROM "albums" "album"
  INNER JOIN "albums_assets_assets" "album_asset"
    ON "album_asset"."albumsId"="album"."id"
  INNER JOIN "assets" "asset"
    ON "asset"."id"="album_asset"."assetsId"
    AND "asset"."deletedAt" IS NULL
  LEFT JOIN "albums_shared_users_users" "album_sharedUsers"
    ON "album_sharedUsers"."albumsId"="album"."id"
  LEFT JOIN "users" "sharedUsers"
    ON "sharedUsers"."id"="album_sharedUsers"."usersId"
    AND "sharedUsers"."deletedAt" IS NULL
WHERE
  (
    "album"."ownerId" = $1
    OR "sharedUsers"."id" = $2
  )
  AND (
    "asset"."id" IN ($3, $4)
    OR "asset"."livePhotoVideoId" IN ($5, $6)
  )
  AND "album"."deletedAt" IS NULL
```

* `asset` owner access:

```sql
-- Before
SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS (
  SELECT 1
  FROM "assets" "AssetEntity"
  WHERE
    "AssetEntity"."id" = $1
    AND "AssetEntity"."ownerId" = $2
)
LIMIT 1

-- After
SELECT
  "AssetEntity"."id" AS "AssetEntity_id"
FROM "assets" "AssetEntity"
WHERE
  "AssetEntity"."id" IN ($1, $2)
  AND "AssetEntity"."ownerId" = $3
```

* `asset` partner access:

```sql
-- Before
SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS (
  SELECT 1
  FROM "partners" "PartnerEntity"
    LEFT JOIN "users" "PartnerEntity__PartnerEntity_sharedWith"
      ON "PartnerEntity__PartnerEntity_sharedWith"."id"="PartnerEntity"."sharedWithId"
      AND "PartnerEntity__PartnerEntity_sharedWith"."deletedAt" IS NULL
    LEFT JOIN "users" "PartnerEntity__PartnerEntity_sharedBy"
      ON "PartnerEntity__PartnerEntity_sharedBy"."id"="PartnerEntity"."sharedById"
      AND "PartnerEntity__PartnerEntity_sharedBy"."deletedAt" IS NULL
    LEFT JOIN "assets" "0aabe9f4a62b794e2c24a074297e534f51a4ac6c"
      ON "0aabe9f4a62b794e2c24a074297e534f51a4ac6c"."ownerId"="PartnerEntity__PartnerEntity_sharedBy"."id"
      AND "0aabe9f4a62b794e2c24a074297e534f51a4ac6c"."deletedAt" IS NULL
    LEFT JOIN "users" "PartnerEntity__sharedBy"
      ON "PartnerEntity__sharedBy"."id"="PartnerEntity"."sharedById"
      AND "PartnerEntity__sharedBy"."deletedAt" IS NULL
    LEFT JOIN "users" "PartnerEntity__sharedWith"
      ON "PartnerEntity__sharedWith"."id"="PartnerEntity"."sharedWithId"
      AND "PartnerEntity__sharedWith"."deletedAt" IS NULL
  WHERE
    "PartnerEntity__PartnerEntity_sharedWith"."id" = $1
    AND "0aabe9f4a62b794e2c24a074297e534f51a4ac6c"."id" = $2
)
LIMIT 1

-- After
SELECT
  "asset"."id" AS "assetId"
FROM "partners" "partner"
  INNER JOIN "users" "sharedBy"
    ON "sharedBy"."id"="partner"."sharedById"
    AND "sharedBy"."deletedAt" IS NULL
  INNER JOIN "assets" "asset"
    ON "asset"."ownerId"="sharedBy"."id"
    AND "asset"."deletedAt" IS NULL
WHERE
  "partner"."sharedWithId" = $1
  AND "asset"."id" IN ($2, $3)
```

* `asset` shared link access:

```sql
-- Before
SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS (
  SELECT 1
  FROM "shared_links" "SharedLinkEntity"
    LEFT JOIN "albums" "SharedLinkEntity__SharedLinkEntity_album"
      ON "SharedLinkEntity__SharedLinkEntity_album"."id"="SharedLinkEntity"."albumId"
      AND "SharedLinkEntity__SharedLinkEntity_album"."deletedAt" IS NULL
    LEFT JOIN "albums_assets_assets" "760f12c00d97bdcec1ce224d1e3bf449859942b6"
      ON "760f12c00d97bdcec1ce224d1e3bf449859942b6"."albumsId"="SharedLinkEntity__SharedLinkEntity_album"."id"
    LEFT JOIN "assets" "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"
      ON "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."id"="760f12c00d97bdcec1ce224d1e3bf449859942b6"."assetsId"
      AND "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."deletedAt" IS NULL
    LEFT JOIN "shared_link__asset" "SharedLinkEntity__SharedLinkEntity_assets_SharedLinkEntity"
      ON "SharedLinkEntity__SharedLinkEntity_assets_SharedLinkEntity"."sharedLinksId"="SharedLinkEntity"."id"
    LEFT JOIN "assets" "SharedLinkEntity__SharedLinkEntity_assets"
      ON "SharedLinkEntity__SharedLinkEntity_assets"."id"="SharedLinkEntity__SharedLinkEntity_assets_SharedLinkEntity"."assetsId"
      AND "SharedLinkEntity__SharedLinkEntity_assets"."deletedAt" IS NULL
  WHERE (
    ("SharedLinkEntity"."id" = $1 AND "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."id" = $2)
    OR ("SharedLinkEntity"."id" = $3 AND "SharedLinkEntity__SharedLinkEntity_assets"."id" = $4)
    OR ("SharedLinkEntity"."id" = $5 AND "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."livePhotoVideoId" = $6)
    OR ("SharedLinkEntity"."id" = $7 AND "SharedLinkEntity__SharedLinkEntity_assets"."livePhotoVideoId" = $8)
  )
)
LIMIT 1

-- After
SELECT
  "assets"."id" AS "assetId",
  "assets"."livePhotoVideoId" AS "assetLivePhotoVideoId",
  "albumAssets"."id" AS "albumAssetId",
  "albumAssets"."livePhotoVideoId" AS "albumAssetLivePhotoVideoId"
FROM "shared_links" "sharedLink"
  LEFT JOIN "albums" "album"
    ON "album"."id"="sharedLink"."albumId"
    AND "album"."deletedAt" IS NULL
  LEFT JOIN "shared_link__asset" "assets_sharedLink"
    ON "assets_sharedLink"."sharedLinksId"="sharedLink"."id"
  LEFT JOIN "assets" "assets"
    ON "assets"."id"="assets_sharedLink"."assetsId"
    AND "assets"."deletedAt" IS NULL
  LEFT JOIN "albums_assets_assets" "album_albumAssets"
    ON "album_albumAssets"."albumsId"="album"."id"
  LEFT JOIN "assets" "albumAssets"
    ON "albumAssets"."id"="album_albumAssets"."assetsId"
    AND "albumAssets"."deletedAt" IS NULL
WHERE
  "sharedLink"."id" = $1
  AND (
    "assets"."id" IN ($2, $3)
    OR "albumAssets"."id" IN ($4, $5)
    OR "assets"."livePhotoVideoId" IN ($6, $7)
    OR "albumAssets"."livePhotoVideoId" IN ($8, $9)
  )
```
This commit is contained in:
Michael Manganiello
2023-12-01 21:56:41 -05:00
committed by GitHub
parent 6673f1eb24
commit 5aa658de59
11 changed files with 287 additions and 275 deletions

View File

@@ -1,6 +1,6 @@
import { IAccessRepository } from '@app/domain';
import { InjectRepository } from '@nestjs/typeorm';
import { In, Repository } from 'typeorm';
import { Brackets, In, Repository } from 'typeorm';
import {
ActivityEntity,
AlbumEntity,
@@ -112,107 +112,120 @@ export class AccessRepository implements IAccessRepository {
};
asset = {
hasAlbumAccess: (userId: string, assetId: string): Promise<boolean> => {
return this.albumRepository.exist({
where: [
{
checkAlbumAccess: async (userId: string, assetIds: Set<string>): Promise<Set<string>> => {
if (assetIds.size === 0) {
return new Set();
}
return this.albumRepository
.createQueryBuilder('album')
.innerJoin('album.assets', 'asset')
.leftJoin('album.sharedUsers', 'sharedUsers')
.select('asset.id', 'assetId')
.addSelect('asset.livePhotoVideoId', 'livePhotoVideoId')
.where(
new Brackets((qb) => {
qb.where('album.ownerId = :userId', { userId }).orWhere('sharedUsers.id = :userId', { userId });
}),
)
.andWhere(
new Brackets((qb) => {
qb.where('asset.id IN (:...assetIds)', { assetIds: [...assetIds] })
// still part of a live photo is in an album
.orWhere('asset.livePhotoVideoId IN (:...assetIds)', { assetIds: [...assetIds] });
}),
)
.getRawMany()
.then((rows) => {
const allowedIds = new Set<string>();
for (const row of rows) {
if (row.assetId && assetIds.has(row.assetId)) {
allowedIds.add(row.assetId);
}
if (row.livePhotoVideoId && assetIds.has(row.livePhotoVideoId)) {
allowedIds.add(row.livePhotoVideoId);
}
}
return allowedIds;
});
},
checkOwnerAccess: async (userId: string, assetIds: Set<string>): Promise<Set<string>> => {
if (assetIds.size === 0) {
return new Set();
}
return this.assetRepository
.find({
select: { id: true },
where: {
id: In([...assetIds]),
ownerId: userId,
assets: {
id: assetId,
},
},
{
sharedUsers: {
id: userId,
},
assets: {
id: assetId,
},
},
// still part of a live photo is in an album
{
ownerId: userId,
assets: {
livePhotoVideoId: assetId,
},
},
{
sharedUsers: {
id: userId,
},
assets: {
livePhotoVideoId: assetId,
},
},
],
});
withDeleted: true,
})
.then((assets) => new Set(assets.map((asset) => asset.id)));
},
hasOwnerAccess: (userId: string, assetId: string): Promise<boolean> => {
return this.assetRepository.exist({
where: {
id: assetId,
ownerId: userId,
},
withDeleted: true,
});
checkPartnerAccess: async (userId: string, assetIds: Set<string>): Promise<Set<string>> => {
if (assetIds.size === 0) {
return new Set();
}
return this.partnerRepository
.createQueryBuilder('partner')
.innerJoin('partner.sharedBy', 'sharedBy')
.innerJoin('sharedBy.assets', 'asset')
.select('asset.id', 'assetId')
.where('partner.sharedWithId = :userId', { userId })
.andWhere('asset.id IN (:...assetIds)', { assetIds: [...assetIds] })
.getRawMany()
.then((rows) => new Set(rows.map((row) => row.assetId)));
},
hasPartnerAccess: (userId: string, assetId: string): Promise<boolean> => {
return this.partnerRepository.exist({
where: {
sharedWith: {
id: userId,
},
sharedBy: {
assets: {
id: assetId,
},
},
},
relations: {
sharedWith: true,
sharedBy: {
assets: true,
},
},
});
},
checkSharedLinkAccess: async (sharedLinkId: string, assetIds: Set<string>): Promise<Set<string>> => {
if (assetIds.size === 0) {
return new Set();
}
hasSharedLinkAccess: async (sharedLinkId: string, assetId: string): Promise<boolean> => {
return this.sharedLinkRepository.exist({
where: [
{
id: sharedLinkId,
album: {
assets: {
id: assetId,
},
},
},
{
id: sharedLinkId,
assets: {
id: assetId,
},
},
// still part of a live photo is in a shared link
{
id: sharedLinkId,
album: {
assets: {
livePhotoVideoId: assetId,
},
},
},
{
id: sharedLinkId,
assets: {
livePhotoVideoId: assetId,
},
},
],
});
return this.sharedLinkRepository
.createQueryBuilder('sharedLink')
.leftJoin('sharedLink.album', 'album')
.leftJoin('sharedLink.assets', 'assets')
.leftJoin('album.assets', 'albumAssets')
.select('assets.id', 'assetId')
.addSelect('albumAssets.id', 'albumAssetId')
.addSelect('assets.livePhotoVideoId', 'assetLivePhotoVideoId')
.addSelect('albumAssets.livePhotoVideoId', 'albumAssetLivePhotoVideoId')
.where('sharedLink.id = :sharedLinkId', { sharedLinkId })
.andWhere(
new Brackets((qb) => {
qb.where('assets.id IN (:...assetIds)', { assetIds: [...assetIds] })
.orWhere('albumAssets.id IN (:...assetIds)', { assetIds: [...assetIds] })
// still part of a live photo is in a shared link
.orWhere('assets.livePhotoVideoId IN (:...assetIds)', { assetIds: [...assetIds] })
.orWhere('albumAssets.livePhotoVideoId IN (:...assetIds)', { assetIds: [...assetIds] });
}),
)
.getRawMany()
.then((rows) => {
const allowedIds = new Set<string>();
for (const row of rows) {
if (row.assetId && assetIds.has(row.assetId)) {
allowedIds.add(row.assetId);
}
if (row.assetLivePhotoVideoId && assetIds.has(row.assetLivePhotoVideoId)) {
allowedIds.add(row.assetLivePhotoVideoId);
}
if (row.albumAssetId && assetIds.has(row.albumAssetId)) {
allowedIds.add(row.albumAssetId);
}
if (row.albumAssetLivePhotoVideoId && assetIds.has(row.albumAssetLivePhotoVideoId)) {
allowedIds.add(row.albumAssetLivePhotoVideoId);
}
}
return allowedIds;
});
},
};