From 5075d0865e79161577cd4a26a2b065cdb0aaa2f5 Mon Sep 17 00:00:00 2001 From: Jason Ng Date: Tue, 21 May 2024 12:32:27 -0400 Subject: [PATCH] [AC-2447] Allow the UI to save and close dialog when user removes final Can Manage Collection of an item (#9136) * update saveCollectionsWithServer to accept a new value if user can no longer manage cipher after requested update --- libs/common/src/abstractions/api.service.ts | 6 +++++- libs/common/src/services/api.service.ts | 13 ++++++++++--- .../models/response/optional-cipher.response.ts | 14 ++++++++++++++ libs/common/src/vault/services/cipher.service.ts | 9 +++++++-- 4 files changed, 36 insertions(+), 6 deletions(-) create mode 100644 libs/common/src/vault/models/response/optional-cipher.response.ts diff --git a/libs/common/src/abstractions/api.service.ts b/libs/common/src/abstractions/api.service.ts index c1a0e1f9cd..73e4f74e63 100644 --- a/libs/common/src/abstractions/api.service.ts +++ b/libs/common/src/abstractions/api.service.ts @@ -123,6 +123,7 @@ import { CollectionDetailsResponse, CollectionResponse, } from "../vault/models/response/collection.response"; +import { OptionalCipherResponse } from "../vault/models/response/optional-cipher.response"; import { SyncResponse } from "../vault/models/response/sync.response"; /** @@ -218,7 +219,10 @@ export abstract class ApiService { putMoveCiphers: (request: CipherBulkMoveRequest) => Promise; putShareCipher: (id: string, request: CipherShareRequest) => Promise; putShareCiphers: (request: CipherBulkShareRequest) => Promise; - putCipherCollections: (id: string, request: CipherCollectionsRequest) => Promise; + putCipherCollections: ( + id: string, + request: CipherCollectionsRequest, + ) => Promise; putCipherCollectionsAdmin: (id: string, request: CipherCollectionsRequest) => Promise; postPurgeCiphers: (request: SecretVerificationRequest, organizationId?: string) => Promise; putDeleteCipher: (id: string) => Promise; diff --git a/libs/common/src/services/api.service.ts b/libs/common/src/services/api.service.ts index 4620a2ccde..8d7a53ec0e 100644 --- a/libs/common/src/services/api.service.ts +++ b/libs/common/src/services/api.service.ts @@ -138,6 +138,7 @@ import { CollectionDetailsResponse, CollectionResponse, } from "../vault/models/response/collection.response"; +import { OptionalCipherResponse } from "../vault/models/response/optional-cipher.response"; import { SyncResponse } from "../vault/models/response/sync.response"; /** @@ -566,9 +567,15 @@ export class ApiService implements ApiServiceAbstraction { async putCipherCollections( id: string, request: CipherCollectionsRequest, - ): Promise { - const response = await this.send("PUT", "/ciphers/" + id + "/collections", request, true, true); - return new CipherResponse(response); + ): Promise { + const response = await this.send( + "PUT", + "/ciphers/" + id + "/collections_v2", + request, + true, + true, + ); + return new OptionalCipherResponse(response); } putCipherCollectionsAdmin(id: string, request: CipherCollectionsRequest): Promise { diff --git a/libs/common/src/vault/models/response/optional-cipher.response.ts b/libs/common/src/vault/models/response/optional-cipher.response.ts new file mode 100644 index 0000000000..08181407b2 --- /dev/null +++ b/libs/common/src/vault/models/response/optional-cipher.response.ts @@ -0,0 +1,14 @@ +import { BaseResponse } from "../../../models/response/base.response"; + +import { CipherResponse } from "./cipher.response"; + +export class OptionalCipherResponse extends BaseResponse { + unavailable: boolean; + cipher?: CipherResponse; + + constructor(response: any) { + super(response); + this.unavailable = this.getResponseProperty("Unavailable"); + this.cipher = new CipherResponse(this.getResponseProperty("Cipher")); + } +} diff --git a/libs/common/src/vault/services/cipher.service.ts b/libs/common/src/vault/services/cipher.service.ts index 0e6ddf40ca..c03b440ff5 100644 --- a/libs/common/src/vault/services/cipher.service.ts +++ b/libs/common/src/vault/services/cipher.service.ts @@ -776,9 +776,14 @@ export class CipherService implements CipherServiceAbstraction { async saveCollectionsWithServer(cipher: Cipher): Promise { const request = new CipherCollectionsRequest(cipher.collectionIds); const response = await this.apiService.putCipherCollections(cipher.id, request); - const data = new CipherData(response); + // The response will now check for an unavailable value. This value determines whether + // the user still has Can Manage access to the item after updating. + if (response.unavailable) { + await this.delete(cipher.id); + return; + } + const data = new CipherData(response.cipher); const updated = await this.upsert(data); - // Collection updates don't change local data return new Cipher(updated[cipher.id as CipherId], cipher.localData); }