chore(server): Check more permissions in bulk (#5315)

Modify Access repository, to evaluate `authDevice`, `library`, `partner`,
`person`, and `timeline` permissions in bulk.
Queries have been validated to match what they currently generate for
single ids.

As an extra performance improvement, we now use a custom QueryBuilder
for the Partners queries, to avoid the eager relationships that add
unneeded `LEFT JOIN` clauses. We only filter based on the ids present in
the `partners` table, so those joins can be avoided.

Queries:

* `library` owner access:

```sql
-- Before
SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS (
  SELECT 1
  FROM "libraries" "LibraryEntity"
  WHERE
    "LibraryEntity"."id" = $1
    AND "LibraryEntity"."ownerId" = $2
    AND "LibraryEntity"."deletedAt" IS NULL
)
LIMIT 1

-- After
SELECT "LibraryEntity"."id" AS "LibraryEntity_id"
FROM "libraries" "LibraryEntity"
WHERE
  "LibraryEntity"."id" IN ($1, $2)
  AND "LibraryEntity"."ownerId" = $3
  AND "LibraryEntity"."deletedAt" IS NULL
```

* `library` 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__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"."sharedWithId" = $1
    AND "PartnerEntity"."sharedById" = $2
)
LIMIT 1

-- After
SELECT
  "partner"."sharedById" AS "partner_sharedById",
  "partner"."sharedWithId" AS "partner_sharedWithId"
FROM "partners" "partner"
WHERE
  "partner"."sharedById" IN ($1, $2)
  AND "partner"."sharedWithId" = $3
```

* `authDevice` owner access:

```sql
-- Before
SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS (
  SELECT 1
  FROM "user_token" "UserTokenEntity"
  WHERE
    "UserTokenEntity"."userId" = $1
    AND "UserTokenEntity"."id" = $2
)
LIMIT 1

-- After
SELECT "UserTokenEntity"."id" AS "UserTokenEntity_id"
FROM "user_token" "UserTokenEntity"
WHERE
  "UserTokenEntity"."userId" = $1
  AND "UserTokenEntity"."id" IN ($2, $3)
```

* `timeline` 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__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"."sharedWithId" = $1
    AND "PartnerEntity"."sharedById" = $2
)
LIMIT 1

-- After
SELECT
  "partner"."sharedById" AS "partner_sharedById",
  "partner"."sharedWithId" AS "partner_sharedWithId"
FROM "partners" "partner"
WHERE
  "partner"."sharedById" IN ($1, $2)
  AND "partner"."sharedWithId" = $3
```

* `person` owner access:

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

-- After
SELECT "PersonEntity"."id" AS "PersonEntity_id"
FROM "person" "PersonEntity"
WHERE
  "PersonEntity"."id" IN ($1, $2)
  AND "PersonEntity"."ownerId" = $3
```

* `partner` update 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__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"."sharedWithId" = $1
    AND "PartnerEntity"."sharedById" = $2
)
LIMIT 1

-- After
SELECT
  "partner"."sharedById" AS "partner_sharedById",
  "partner"."sharedWithId" AS "partner_sharedWithId"
FROM "partners" "partner"
WHERE
  "partner"."sharedById" IN ($1, $2)
  AND "partner"."sharedWithId" = $3
```
This commit is contained in:
Michael Manganiello
2023-11-26 07:50:41 -05:00
committed by GitHub
parent f97dca7707
commit c04340c63e
9 changed files with 190 additions and 151 deletions

View File

@@ -179,6 +179,48 @@ export class AccessCore {
case Permission.ALBUM_REMOVE_ASSET:
return this.repository.album.checkOwnerAccess(authUser.id, ids);
case Permission.ASSET_UPLOAD:
return this.repository.library.checkOwnerAccess(authUser.id, ids);
case Permission.ARCHIVE_READ:
return ids.has(authUser.id) ? new Set([authUser.id]) : new Set();
case Permission.AUTH_DEVICE_DELETE:
return this.repository.authDevice.checkOwnerAccess(authUser.id, ids);
case Permission.TIMELINE_READ: {
const isOwner = ids.has(authUser.id) ? new Set([authUser.id]) : new Set<string>();
const isPartner = await this.repository.timeline.checkPartnerAccess(authUser.id, setDifference(ids, isOwner));
return setUnion(isOwner, isPartner);
}
case Permission.TIMELINE_DOWNLOAD:
return ids.has(authUser.id) ? new Set([authUser.id]) : new Set();
case Permission.LIBRARY_READ: {
const isOwner = await this.repository.library.checkOwnerAccess(authUser.id, ids);
const isPartner = await this.repository.library.checkPartnerAccess(authUser.id, setDifference(ids, isOwner));
return setUnion(isOwner, isPartner);
}
case Permission.LIBRARY_UPDATE:
return this.repository.library.checkOwnerAccess(authUser.id, ids);
case Permission.LIBRARY_DELETE:
return this.repository.library.checkOwnerAccess(authUser.id, ids);
case Permission.PERSON_READ:
return this.repository.person.checkOwnerAccess(authUser.id, ids);
case Permission.PERSON_WRITE:
return this.repository.person.checkOwnerAccess(authUser.id, ids);
case Permission.PERSON_MERGE:
return this.repository.person.checkOwnerAccess(authUser.id, ids);
case Permission.PARTNER_UPDATE:
return this.repository.partner.checkUpdateAccess(authUser.id, ids);
}
const allowedIds = new Set();
@@ -240,45 +282,6 @@ export class AccessCore {
(await this.repository.asset.hasPartnerAccess(authUser.id, id))
);
case Permission.ASSET_UPLOAD:
return this.repository.library.hasOwnerAccess(authUser.id, id);
case Permission.ARCHIVE_READ:
return authUser.id === id;
case Permission.AUTH_DEVICE_DELETE:
return this.repository.authDevice.hasOwnerAccess(authUser.id, id);
case Permission.TIMELINE_READ:
return authUser.id === id || (await this.repository.timeline.hasPartnerAccess(authUser.id, id));
case Permission.TIMELINE_DOWNLOAD:
return authUser.id === id;
case Permission.LIBRARY_READ:
return (
(await this.repository.library.hasOwnerAccess(authUser.id, id)) ||
(await this.repository.library.hasPartnerAccess(authUser.id, id))
);
case Permission.LIBRARY_UPDATE:
return this.repository.library.hasOwnerAccess(authUser.id, id);
case Permission.LIBRARY_DELETE:
return this.repository.library.hasOwnerAccess(authUser.id, id);
case Permission.PERSON_READ:
return this.repository.person.hasOwnerAccess(authUser.id, id);
case Permission.PERSON_WRITE:
return this.repository.person.hasOwnerAccess(authUser.id, id);
case Permission.PERSON_MERGE:
return this.repository.person.hasOwnerAccess(authUser.id, id);
case Permission.PARTNER_UPDATE:
return this.repository.partner.hasUpdateAccess(authUser.id, id);
default:
return false;
}