PM-238 Master Reprompt Enabled Appears on Item in Organization (#5858)
* add-edit components and cipher service updated for organization item saveCiphers * update cipher service spec file
This commit is contained in:
parent
0bb76e2d06
commit
8613b83d98
|
@ -20,8 +20,6 @@ import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folde
|
|||
import { PasswordRepromptService } from "@bitwarden/common/vault/abstractions/password-reprompt.service";
|
||||
import { CipherData } from "@bitwarden/common/vault/models/data/cipher.data";
|
||||
import { Cipher } from "@bitwarden/common/vault/models/domain/cipher";
|
||||
import { CipherCreateRequest } from "@bitwarden/common/vault/models/request/cipher-create.request";
|
||||
import { CipherRequest } from "@bitwarden/common/vault/models/request/cipher.request";
|
||||
|
||||
import { AddEditComponent as BaseAddEditComponent } from "../individual-vault/add-edit.component";
|
||||
|
||||
|
@ -115,19 +113,6 @@ export class AddEditComponent extends BaseAddEditComponent {
|
|||
return this.cipherService.encrypt(this.cipher, null, this.originalCipher);
|
||||
}
|
||||
|
||||
protected async saveCipher(cipher: Cipher) {
|
||||
if (!this.organization.canEditAnyCollection || cipher.organizationId == null) {
|
||||
return super.saveCipher(cipher);
|
||||
}
|
||||
if (this.editMode && !this.cloneMode) {
|
||||
const request = new CipherRequest(cipher);
|
||||
return this.apiService.putCipherAdmin(this.cipherId, request);
|
||||
} else {
|
||||
const request = new CipherCreateRequest(cipher);
|
||||
return this.apiService.postCipherAdmin(request);
|
||||
}
|
||||
}
|
||||
|
||||
protected async deleteCipher() {
|
||||
if (!this.organization.canEditAnyCollection) {
|
||||
return super.deleteCipher();
|
||||
|
|
|
@ -600,9 +600,11 @@ export class AddEditComponent implements OnInit, OnDestroy {
|
|||
}
|
||||
|
||||
protected saveCipher(cipher: Cipher) {
|
||||
const isNotClone = this.editMode && !this.cloneMode;
|
||||
const orgAdmin = this.organization?.isAdmin;
|
||||
return this.cipher.id == null
|
||||
? this.cipherService.createWithServer(cipher)
|
||||
: this.cipherService.updateWithServer(cipher);
|
||||
? this.cipherService.createWithServer(cipher, orgAdmin)
|
||||
: this.cipherService.updateWithServer(cipher, orgAdmin, isNotClone);
|
||||
}
|
||||
|
||||
protected deleteCipher() {
|
||||
|
|
|
@ -33,8 +33,8 @@ export abstract class CipherService {
|
|||
updateLastUsedDate: (id: string) => Promise<void>;
|
||||
updateLastLaunchedDate: (id: string) => Promise<void>;
|
||||
saveNeverDomain: (domain: string) => Promise<void>;
|
||||
createWithServer: (cipher: Cipher) => Promise<any>;
|
||||
updateWithServer: (cipher: Cipher) => Promise<any>;
|
||||
createWithServer: (cipher: Cipher, orgAdmin?: boolean) => Promise<any>;
|
||||
updateWithServer: (cipher: Cipher, orgAdmin?: boolean, isNotClone?: boolean) => Promise<any>;
|
||||
shareWithServer: (
|
||||
cipher: CipherView,
|
||||
organizationId: string,
|
||||
|
|
|
@ -1,48 +1,106 @@
|
|||
// eslint-disable-next-line no-restricted-imports
|
||||
import { Arg, Substitute, SubstituteOf } from "@fluffy-spoon/substitute";
|
||||
import { mock, mockReset } from "jest-mock-extended";
|
||||
|
||||
import { ApiService } from "../../abstractions/api.service";
|
||||
import { SearchService } from "../../abstractions/search.service";
|
||||
import { SettingsService } from "../../abstractions/settings.service";
|
||||
import { UriMatchType, FieldType } from "../../enums";
|
||||
import { CryptoService } from "../../platform/abstractions/crypto.service";
|
||||
import { EncryptService } from "../../platform/abstractions/encrypt.service";
|
||||
import { I18nService } from "../../platform/abstractions/i18n.service";
|
||||
import { StateService } from "../../platform/abstractions/state.service";
|
||||
import { EncArrayBuffer } from "../../platform/models/domain/enc-array-buffer";
|
||||
import { EncString } from "../../platform/models/domain/enc-string";
|
||||
import { SymmetricCryptoKey } from "../../platform/models/domain/symmetric-crypto-key";
|
||||
import { CipherFileUploadService } from "../abstractions/file-upload/cipher-file-upload.service";
|
||||
import { CipherRepromptType } from "../enums/cipher-reprompt-type";
|
||||
import { CipherType } from "../enums/cipher-type";
|
||||
import { CipherData } from "../models/data/cipher.data";
|
||||
import { Cipher } from "../models/domain/cipher";
|
||||
import { CipherCreateRequest } from "../models/request/cipher-create.request";
|
||||
import { CipherPartialRequest } from "../models/request/cipher-partial.request";
|
||||
import { CipherRequest } from "../models/request/cipher.request";
|
||||
|
||||
import { CipherService } from "./cipher.service";
|
||||
|
||||
const ENCRYPTED_TEXT = "This data has been encrypted";
|
||||
const ENCRYPTED_BYTES = Substitute.for<EncArrayBuffer>();
|
||||
const cipherData: CipherData = {
|
||||
id: "id",
|
||||
organizationId: "orgId",
|
||||
folderId: "folderId",
|
||||
edit: true,
|
||||
viewPassword: true,
|
||||
organizationUseTotp: true,
|
||||
favorite: false,
|
||||
revisionDate: "2022-01-31T12:00:00.000Z",
|
||||
type: CipherType.Login,
|
||||
name: "EncryptedString",
|
||||
notes: "EncryptedString",
|
||||
creationDate: "2022-01-01T12:00:00.000Z",
|
||||
deletedDate: null,
|
||||
reprompt: CipherRepromptType.None,
|
||||
login: {
|
||||
uris: [{ uri: "EncryptedString", match: UriMatchType.Domain }],
|
||||
username: "EncryptedString",
|
||||
password: "EncryptedString",
|
||||
passwordRevisionDate: "2022-01-31T12:00:00.000Z",
|
||||
totp: "EncryptedString",
|
||||
autofillOnPageLoad: false,
|
||||
},
|
||||
passwordHistory: [{ password: "EncryptedString", lastUsedDate: "2022-01-31T12:00:00.000Z" }],
|
||||
attachments: [
|
||||
{
|
||||
id: "a1",
|
||||
url: "url",
|
||||
size: "1100",
|
||||
sizeName: "1.1 KB",
|
||||
fileName: "file",
|
||||
key: "EncKey",
|
||||
},
|
||||
{
|
||||
id: "a2",
|
||||
url: "url",
|
||||
size: "1100",
|
||||
sizeName: "1.1 KB",
|
||||
fileName: "file",
|
||||
key: "EncKey",
|
||||
},
|
||||
],
|
||||
fields: [
|
||||
{
|
||||
name: "EncryptedString",
|
||||
value: "EncryptedString",
|
||||
type: FieldType.Text,
|
||||
linkedId: null,
|
||||
},
|
||||
{
|
||||
name: "EncryptedString",
|
||||
value: "EncryptedString",
|
||||
type: FieldType.Hidden,
|
||||
linkedId: null,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
describe("Cipher Service", () => {
|
||||
let cryptoService: SubstituteOf<CryptoService>;
|
||||
let stateService: SubstituteOf<StateService>;
|
||||
let settingsService: SubstituteOf<SettingsService>;
|
||||
let apiService: SubstituteOf<ApiService>;
|
||||
let cipherFileUploadService: SubstituteOf<CipherFileUploadService>;
|
||||
let i18nService: SubstituteOf<I18nService>;
|
||||
let searchService: SubstituteOf<SearchService>;
|
||||
let encryptService: SubstituteOf<EncryptService>;
|
||||
const cryptoService = mock<CryptoService>();
|
||||
const stateService = mock<StateService>();
|
||||
const settingsService = mock<SettingsService>();
|
||||
const apiService = mock<ApiService>();
|
||||
const cipherFileUploadService = mock<CipherFileUploadService>();
|
||||
const i18nService = mock<I18nService>();
|
||||
const searchService = mock<SearchService>();
|
||||
const encryptService = mock<EncryptService>();
|
||||
|
||||
let cipherService: CipherService;
|
||||
let cipherObj: Cipher;
|
||||
|
||||
beforeEach(() => {
|
||||
cryptoService = Substitute.for<CryptoService>();
|
||||
stateService = Substitute.for<StateService>();
|
||||
settingsService = Substitute.for<SettingsService>();
|
||||
apiService = Substitute.for<ApiService>();
|
||||
cipherFileUploadService = Substitute.for<CipherFileUploadService>();
|
||||
i18nService = Substitute.for<I18nService>();
|
||||
searchService = Substitute.for<SearchService>();
|
||||
encryptService = Substitute.for<EncryptService>();
|
||||
|
||||
cryptoService.encryptToBytes(Arg.any(), Arg.any()).resolves(ENCRYPTED_BYTES);
|
||||
cryptoService.encrypt(Arg.any(), Arg.any()).resolves(new EncString(ENCRYPTED_TEXT));
|
||||
mockReset(apiService);
|
||||
mockReset(cryptoService);
|
||||
mockReset(stateService);
|
||||
mockReset(settingsService);
|
||||
mockReset(cipherFileUploadService);
|
||||
mockReset(i18nService);
|
||||
mockReset(searchService);
|
||||
mockReset(encryptService);
|
||||
|
||||
cipherService = new CipherService(
|
||||
cryptoService,
|
||||
|
@ -54,17 +112,97 @@ describe("Cipher Service", () => {
|
|||
encryptService,
|
||||
cipherFileUploadService
|
||||
);
|
||||
});
|
||||
|
||||
it("attachments upload encrypted file contents", async () => {
|
||||
cipherObj = new Cipher(cipherData);
|
||||
});
|
||||
describe("saveAttachmentRawWithServer()", () => {
|
||||
it("should upload encrypted file contents with save attachments", async () => {
|
||||
const fileName = "filename";
|
||||
const fileData = new Uint8Array(10).buffer;
|
||||
cryptoService.getOrgKey(Arg.any()).resolves(new SymmetricCryptoKey(new Uint8Array(32)));
|
||||
cryptoService.getOrgKey.mockReturnValue(
|
||||
Promise.resolve<any>(new SymmetricCryptoKey(new Uint8Array(32)))
|
||||
);
|
||||
cryptoService.makeEncKey.mockReturnValue(
|
||||
Promise.resolve<any>(new SymmetricCryptoKey(new Uint8Array(32)))
|
||||
);
|
||||
const spy = jest.spyOn(cipherFileUploadService, "upload");
|
||||
|
||||
await cipherService.saveAttachmentRawWithServer(new Cipher(), fileName, fileData);
|
||||
|
||||
cipherFileUploadService
|
||||
.received(1)
|
||||
.upload(Arg.any(), Arg.any(), ENCRYPTED_BYTES, Arg.any(), Arg.any());
|
||||
expect(spy).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("createWithServer()", () => {
|
||||
it("should call apiService.postCipherAdmin when orgAdmin param is true", async () => {
|
||||
const spy = jest
|
||||
.spyOn(apiService, "postCipherAdmin")
|
||||
.mockImplementation(() => Promise.resolve<any>(cipherObj));
|
||||
cipherService.createWithServer(cipherObj, true);
|
||||
const expectedObj = new CipherCreateRequest(cipherObj);
|
||||
|
||||
expect(spy).toHaveBeenCalled();
|
||||
expect(spy).toHaveBeenCalledWith(expectedObj);
|
||||
});
|
||||
|
||||
it("should call apiService.postCipherCreate if collectionsIds != null", async () => {
|
||||
cipherObj.collectionIds = ["123"];
|
||||
const spy = jest
|
||||
.spyOn(apiService, "postCipherCreate")
|
||||
.mockImplementation(() => Promise.resolve<any>(cipherObj));
|
||||
cipherService.createWithServer(cipherObj);
|
||||
const expectedObj = new CipherCreateRequest(cipherObj);
|
||||
|
||||
expect(spy).toHaveBeenCalled();
|
||||
expect(spy).toHaveBeenCalledWith(expectedObj);
|
||||
});
|
||||
|
||||
it("should call apiService.postCipher when orgAdmin and collectionIds logic is false", async () => {
|
||||
const spy = jest
|
||||
.spyOn(apiService, "postCipher")
|
||||
.mockImplementation(() => Promise.resolve<any>(cipherObj));
|
||||
cipherService.createWithServer(cipherObj);
|
||||
const expectedObj = new CipherRequest(cipherObj);
|
||||
|
||||
expect(spy).toHaveBeenCalled();
|
||||
expect(spy).toHaveBeenCalledWith(expectedObj);
|
||||
});
|
||||
});
|
||||
|
||||
describe("updateWithServer()", () => {
|
||||
it("should call apiService.putCipherAdmin when orgAdmin and isNotClone params are true", async () => {
|
||||
const spy = jest
|
||||
.spyOn(apiService, "putCipherAdmin")
|
||||
.mockImplementation(() => Promise.resolve<any>(cipherObj));
|
||||
cipherService.updateWithServer(cipherObj, true, true);
|
||||
const expectedObj = new CipherRequest(cipherObj);
|
||||
|
||||
expect(spy).toHaveBeenCalled();
|
||||
expect(spy).toHaveBeenCalledWith(cipherObj.id, expectedObj);
|
||||
});
|
||||
|
||||
it("should call apiService.putCipher if cipher.edit is true", async () => {
|
||||
cipherObj.edit = true;
|
||||
const spy = jest
|
||||
.spyOn(apiService, "putCipher")
|
||||
.mockImplementation(() => Promise.resolve<any>(cipherObj));
|
||||
cipherService.updateWithServer(cipherObj);
|
||||
const expectedObj = new CipherRequest(cipherObj);
|
||||
|
||||
expect(spy).toHaveBeenCalled();
|
||||
expect(spy).toHaveBeenCalledWith(cipherObj.id, expectedObj);
|
||||
});
|
||||
|
||||
it("should call apiService.putPartialCipher when orgAdmin, isNotClone, and edit are false", async () => {
|
||||
cipherObj.edit = false;
|
||||
const spy = jest
|
||||
.spyOn(apiService, "putPartialCipher")
|
||||
.mockImplementation(() => Promise.resolve<any>(cipherObj));
|
||||
cipherService.updateWithServer(cipherObj);
|
||||
const expectedObj = new CipherPartialRequest(cipherObj);
|
||||
|
||||
expect(spy).toHaveBeenCalled();
|
||||
expect(spy).toHaveBeenCalledWith(cipherObj.id, expectedObj);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -519,9 +519,12 @@ export class CipherService implements CipherServiceAbstraction {
|
|||
await this.stateService.setNeverDomains(domains);
|
||||
}
|
||||
|
||||
async createWithServer(cipher: Cipher): Promise<any> {
|
||||
async createWithServer(cipher: Cipher, orgAdmin?: boolean): Promise<any> {
|
||||
let response: CipherResponse;
|
||||
if (cipher.collectionIds != null) {
|
||||
if (orgAdmin) {
|
||||
const request = new CipherCreateRequest(cipher);
|
||||
response = await this.apiService.postCipherAdmin(request);
|
||||
} else if (cipher.collectionIds != null) {
|
||||
const request = new CipherCreateRequest(cipher);
|
||||
response = await this.apiService.postCipherCreate(request);
|
||||
} else {
|
||||
|
@ -534,9 +537,12 @@ export class CipherService implements CipherServiceAbstraction {
|
|||
await this.upsert(data);
|
||||
}
|
||||
|
||||
async updateWithServer(cipher: Cipher): Promise<any> {
|
||||
async updateWithServer(cipher: Cipher, orgAdmin?: boolean, isNotClone?: boolean): Promise<any> {
|
||||
let response: CipherResponse;
|
||||
if (cipher.edit) {
|
||||
if (orgAdmin && isNotClone) {
|
||||
const request = new CipherRequest(cipher);
|
||||
response = await this.apiService.putCipherAdmin(cipher.id, request);
|
||||
} else if (cipher.edit) {
|
||||
const request = new CipherRequest(cipher);
|
||||
response = await this.apiService.putCipher(cipher.id, request);
|
||||
} else {
|
||||
|
|
Loading…
Reference in New Issue