1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-09 21:20:27 +00:00

remove emergency api service dependency from other files

This commit is contained in:
Jacob Fink
2023-10-06 12:21:25 -04:00
parent 11a5f1a8a2
commit be656b8117
7 changed files with 96 additions and 136 deletions

View File

@@ -10,6 +10,49 @@ describe("EmergencyAccessService", () => {
// emergencyAccessService = new EmergencyAccessService();
});
describe("updateEmergencyAccesses", () => {
let mockUserKey: UserKey;
beforeEach(() => {
const mockRandomBytes = new Uint8Array(64) as CsprngArray;
mockUserKey = new SymmetricCryptoKey(mockRandomBytes) as UserKey;
const mockEmergencyAccess = {
data: [
createMockEmergencyAccess("0", "EA 0", EmergencyAccessStatusType.Invited),
createMockEmergencyAccess("1", "EA 1", EmergencyAccessStatusType.Accepted),
createMockEmergencyAccess("2", "EA 2", EmergencyAccessStatusType.Confirmed),
createMockEmergencyAccess("3", "EA 3", EmergencyAccessStatusType.RecoveryInitiated),
createMockEmergencyAccess("4", "EA 4", EmergencyAccessStatusType.RecoveryApproved),
],
} as ListResponse<EmergencyAccessGranteeDetailsResponse>;
emergencyAccessApiService.getEmergencyAccessTrusted.mockResolvedValue(mockEmergencyAccess);
apiService.getUserPublicKey.mockResolvedValue({
userId: "mockUserId",
publicKey: "mockPublicKey",
} as UserKeyResponse);
cryptoService.rsaEncrypt.mockImplementation((plainValue, publicKey) => {
return Promise.resolve(
new EncString(EncryptionType.Rsa2048_OaepSha1_B64, "Encrypted: " + plainValue)
);
});
});
it("Only updates emergency accesses with allowed statuses", async () => {
await migrateFromLegacyEncryptionService.updateEmergencyAccesses(mockUserKey);
expect(emergencyAccessApiService.putEmergencyAccess).not.toHaveBeenCalledWith(
"0",
expect.any(EmergencyAccessUpdateRequest)
);
expect(emergencyAccessApiService.putEmergencyAccess).not.toHaveBeenCalledWith(
"1",
expect.any(EmergencyAccessUpdateRequest)
);
});
});
// describe("createCredential", () => {
// it("should return undefined when navigator.credentials throws", async () => {
// credentials.create.mockRejectedValue(new Error("Mocked error"));

View File

@@ -1,6 +1,8 @@
import { Injectable } from "@angular/core";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { PolicyData } from "@bitwarden/common/admin-console/models/data/policy.data";
import { Policy } from "@bitwarden/common/admin-console/models/domain/policy";
import { KdfConfig } from "@bitwarden/common/auth/models/domain/kdf-config";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
@@ -40,6 +42,14 @@ export class EmergencyAccessService {
private logService: LogService
) {}
/**
* Gets an emergency access by id.
* @param id emergency access id
*/
getEmergencyAccess(id: string): Promise<EmergencyAccessGranteeView> {
return this.emergencyAccessApiService.getEmergencyAccess(id);
}
/**
* Gets all emergency access that the user has been granted.
*/
@@ -54,6 +64,20 @@ export class EmergencyAccessService {
return (await this.emergencyAccessApiService.getEmergencyAccessGranted()).data;
}
/**
* Returns policies that apply to the grantor.
* Intended for grantee.
* @param id emergency access id
*/
async getGrantorPolicies(id: string): Promise<Policy[]> {
const response = await this.emergencyAccessApiService.getEmergencyGrantorPolicies(id);
let policies: Policy[];
if (response.data != null && response.data.length > 0) {
policies = response.data.map((policyResponse) => new Policy(new PolicyData(policyResponse)));
}
return policies;
}
/**
* Invites the email address to be an emergency contact.
* Step 1 of the 3 step setup flow.
@@ -230,7 +254,12 @@ export class EmergencyAccessService {
this.emergencyAccessApiService.postEmergencyAccessPassword(id, request);
}
async rotateEmergencyAccess(newUserKey: UserKey) {
/**
* Rotates the user key for all existing emergency access.
* Intended for grantor.
* @param newUserKey the new user key
*/
async rotate(newUserKey: UserKey): Promise<void> {
const emergencyAccess = await this.emergencyAccessApiService.getEmergencyAccessTrusted();
// Any Invited or Accepted requests won't have the key yet, so we don't need to update them
const allowedStatuses = new Set([

View File

@@ -8,9 +8,6 @@ import { OrganizationService } from "@bitwarden/common/admin-console/abstraction
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { OrganizationKeysResponse } from "@bitwarden/common/admin-console/models/response/organization-keys.response";
import { OrganizationApiService } from "@bitwarden/common/admin-console/services/organization/organization-api.service";
import { EmergencyAccessStatusType } from "@bitwarden/common/auth/enums/emergency-access-status-type";
import { EmergencyAccessUpdateRequest } from "@bitwarden/common/auth/models/request/emergency-access-update.request";
import { EmergencyAccessGranteeDetailsResponse } from "@bitwarden/common/auth/models/response/emergency-access.response";
import { EncryptionType, KdfType } from "@bitwarden/common/enums";
import { ListResponse } from "@bitwarden/common/models/response/list.response";
import { UserKeyResponse } from "@bitwarden/common/models/response/user-key.response";
@@ -35,7 +32,7 @@ import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { FolderView } from "@bitwarden/common/vault/models/view/folder.view";
import { MigrateFromLegacyEncryptionService } from "./migrate-legacy-encryption.service";
import { EmergencyAccessApiService } from "../core/services/emergency-access/emergency-access-api.service";
import { EmergencyAccessService } from "../core/services/emergency-access/emergency-access.service";
describe("migrateFromLegacyEncryptionService", () => {
let migrateFromLegacyEncryptionService: MigrateFromLegacyEncryptionService;
@@ -43,7 +40,7 @@ describe("migrateFromLegacyEncryptionService", () => {
const organizationService = mock<OrganizationService>();
const organizationApiService = mock<OrganizationApiService>();
const organizationUserService = mock<OrganizationUserService>();
const emergencyAccessApiService = mock<EmergencyAccessApiService>();
const emergencyAccessService = mock<EmergencyAccessService>();
const apiService = mock<ApiService>();
const encryptService = mock<EncryptService>();
const cryptoService = mock<CryptoService>();
@@ -62,7 +59,7 @@ describe("migrateFromLegacyEncryptionService", () => {
organizationService,
organizationApiService,
organizationUserService,
emergencyAccessApiService,
emergencyAccessService,
apiService,
cryptoService,
encryptService,
@@ -112,6 +109,9 @@ describe("migrateFromLegacyEncryptionService", () => {
const mockSends = [createMockSend("1", "Send 1"), createMockSend("2", "Send 2")];
cryptoService.getPrivateKey.mockResolvedValue(new Uint8Array(64) as CsprngArray);
cryptoService.rsaEncrypt.mockResolvedValue(
new EncString(EncryptionType.AesCbc256_HmacSha256_B64, "Encrypted")
);
folderViews = new BehaviorSubject<FolderView[]>(mockFolders);
folderService.folderViews$ = folderViews;
@@ -204,40 +204,12 @@ describe("migrateFromLegacyEncryptionService", () => {
beforeEach(() => {
const mockRandomBytes = new Uint8Array(64) as CsprngArray;
mockUserKey = new SymmetricCryptoKey(mockRandomBytes) as UserKey;
const mockEmergencyAccess = {
data: [
createMockEmergencyAccess("0", "EA 0", EmergencyAccessStatusType.Invited),
createMockEmergencyAccess("1", "EA 1", EmergencyAccessStatusType.Accepted),
createMockEmergencyAccess("2", "EA 2", EmergencyAccessStatusType.Confirmed),
createMockEmergencyAccess("3", "EA 3", EmergencyAccessStatusType.RecoveryInitiated),
createMockEmergencyAccess("4", "EA 4", EmergencyAccessStatusType.RecoveryApproved),
],
} as ListResponse<EmergencyAccessGranteeDetailsResponse>;
emergencyAccessApiService.getEmergencyAccessTrusted.mockResolvedValue(mockEmergencyAccess);
apiService.getUserPublicKey.mockResolvedValue({
userId: "mockUserId",
publicKey: "mockPublicKey",
} as UserKeyResponse);
cryptoService.rsaEncrypt.mockImplementation((plainValue, publicKey) => {
return Promise.resolve(
new EncString(EncryptionType.Rsa2048_OaepSha1_B64, "Encrypted: " + plainValue)
);
});
});
it("Only updates emergency accesses with allowed statuses", async () => {
it("Uses emergency access service to rotate", async () => {
await migrateFromLegacyEncryptionService.updateEmergencyAccesses(mockUserKey);
expect(emergencyAccessApiService.putEmergencyAccess).not.toHaveBeenCalledWith(
"0",
expect.any(EmergencyAccessUpdateRequest)
);
expect(emergencyAccessApiService.putEmergencyAccess).not.toHaveBeenCalledWith(
"1",
expect.any(EmergencyAccessUpdateRequest)
);
expect(emergencyAccessService.rotate).toHaveBeenCalled();
});
});
@@ -325,19 +297,6 @@ function createMockSend(id: string, name: string): Send {
return send;
}
function createMockEmergencyAccess(
id: string,
name: string,
status: EmergencyAccessStatusType
): EmergencyAccessGranteeDetailsResponse {
const emergencyAccess = new EmergencyAccessGranteeDetailsResponse({});
emergencyAccess.id = id;
emergencyAccess.name = name;
emergencyAccess.type = 0;
emergencyAccess.status = status;
return emergencyAccess;
}
function createOrganization(id: string, name: string, resetPasswordEnrolled: boolean) {
const org = new Organization();
org.id = id;

View File

@@ -6,8 +6,6 @@ import { OrganizationUserService } from "@bitwarden/common/abstractions/organiza
import { OrganizationUserResetPasswordEnrollmentRequest } from "@bitwarden/common/abstractions/organization-user/requests";
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { EmergencyAccessStatusType } from "@bitwarden/common/auth/enums/emergency-access-status-type";
import { EmergencyAccessUpdateRequest } from "@bitwarden/common/auth/models/request/emergency-access-update.request";
import { UpdateKeyRequest } from "@bitwarden/common/models/request/update-key.request";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
@@ -22,7 +20,7 @@ import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folde
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
import { CipherWithIdRequest } from "@bitwarden/common/vault/models/request/cipher-with-id.request";
import { FolderWithIdRequest } from "@bitwarden/common/vault/models/request/folder-with-id.request";
import { EmergencyAccessApiService } from "../core/services/emergency-access/emergency-access-api.service";
import { EmergencyAccessService } from "../core/services/emergency-access/emergency-access.service";
// TODO: PM-3797 - This service should be expanded and used for user key rotations in change-password.component.ts
@Injectable()
@@ -31,7 +29,7 @@ export class MigrateFromLegacyEncryptionService {
private organizationService: OrganizationService,
private organizationApiService: OrganizationApiServiceAbstraction,
private organizationUserService: OrganizationUserService,
private emergencyAccessApiService: EmergencyAccessApiService,
private emergencyAccessService: EmergencyAccessService,
private apiService: ApiService,
private cryptoService: CryptoService,
private encryptService: EncryptService,
@@ -104,31 +102,8 @@ export class MigrateFromLegacyEncryptionService {
* on the server.
* @param newUserKey The new user key
*/
async updateEmergencyAccesses(newUserKey: UserKey) {
const emergencyAccess = await this.emergencyAccessApiService.getEmergencyAccessTrusted();
// Any Invited or Accepted requests won't have the key yet, so we don't need to update them
const allowedStatuses = new Set([
EmergencyAccessStatusType.Confirmed,
EmergencyAccessStatusType.RecoveryInitiated,
EmergencyAccessStatusType.RecoveryApproved,
]);
const filteredAccesses = emergencyAccess.data.filter((d) => allowedStatuses.has(d.status));
for (const details of filteredAccesses) {
// Get public key of grantee
const publicKeyResponse = await this.apiService.getUserPublicKey(details.granteeId);
const publicKey = Utils.fromB64ToArray(publicKeyResponse.publicKey);
// Encrypt new user key with public key
const encryptedKey = await this.cryptoService.rsaEncrypt(newUserKey.key, publicKey);
const updateRequest = new EmergencyAccessUpdateRequest();
updateRequest.type = details.type;
updateRequest.waitTimeDays = details.waitTimeDays;
updateRequest.keyEncrypted = encryptedKey.encryptedString;
await this.emergencyAccessApiService.putEmergencyAccess(details.id, updateRequest);
}
updateEmergencyAccesses(newUserKey: UserKey) {
return this.emergencyAccessService.rotate(newUserKey);
}
/** Updates all admin recovery keys on the server with the new user key

View File

@@ -23,11 +23,7 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
import {
MasterKey,
SymmetricCryptoKey,
UserKey,
} from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
import { MasterKey, UserKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password";
import { SendWithIdRequest } from "@bitwarden/common/tools/send/models/request/send-with-id.request";
import { SendService } from "@bitwarden/common/tools/send/services/send.service.abstraction";
@@ -37,9 +33,7 @@ import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.serv
import { CipherWithIdRequest } from "@bitwarden/common/vault/models/request/cipher-with-id.request";
import { FolderWithIdRequest } from "@bitwarden/common/vault/models/request/folder-with-id.request";
import { DialogService } from "@bitwarden/components";
import { EmergencyAccessApiService } from "../core/services/emergency-access/emergency-access-api.service";
import { EmergencyAccessStatusType } from "../core/enums/emergency-access-status-type";
import { EmergencyAccessUpdateRequest } from "../core/services/emergency-access/request/emergency-access-update.request";
import { EmergencyAccessService } from "../core/services/emergency-access/emergency-access.service";
@Component({
selector: "app-change-password",
@@ -66,7 +60,7 @@ export class ChangePasswordComponent extends BaseChangePasswordComponent {
private folderService: FolderService,
private cipherService: CipherService,
private syncService: SyncService,
private emergencyAccessApiService: EmergencyAccessApiService,
private emergencyAccessService: EmergencyAccessService,
private apiService: ApiService,
private sendService: SendService,
private organizationService: OrganizationService,
@@ -269,36 +263,11 @@ export class ChangePasswordComponent extends BaseChangePasswordComponent {
await this.apiService.postAccountKey(request);
await this.updateEmergencyAccesses(newUserKey);
await this.emergencyAccessService.rotate(newUserKey);
await this.updateAllResetPasswordKeys(newUserKey, masterPasswordHash);
}
private async updateEmergencyAccesses(encKey: SymmetricCryptoKey) {
const emergencyAccess = await this.emergencyAccessApiService.getEmergencyAccessTrusted();
const allowedStatuses = [
EmergencyAccessStatusType.Confirmed,
EmergencyAccessStatusType.RecoveryInitiated,
EmergencyAccessStatusType.RecoveryApproved,
];
const filteredAccesses = emergencyAccess.data.filter((d) => allowedStatuses.includes(d.status));
for (const details of filteredAccesses) {
const publicKeyResponse = await this.apiService.getUserPublicKey(details.granteeId);
const publicKey = Utils.fromB64ToArray(publicKeyResponse.publicKey);
const encryptedKey = await this.cryptoService.rsaEncrypt(encKey.key, publicKey);
const updateRequest = new EmergencyAccessUpdateRequest();
updateRequest.type = details.type;
updateRequest.waitTimeDays = details.waitTimeDays;
updateRequest.keyEncrypted = encryptedKey.encryptedString;
await this.emergencyAccessApiService.putEmergencyAccess(details.id, updateRequest);
}
}
private async updateAllResetPasswordKeys(userKey: UserKey, masterPasswordHash: string) {
const orgs = await this.organizationService.getAll();

View File

@@ -1,10 +1,9 @@
import { Component, EventEmitter, Input, OnInit, Output } from "@angular/core";
import { EmergencyAccessType } from "@bitwarden/common/auth/enums/emergency-access-type";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { EmergencyAccessApiService } from "../../core/services/emergency-access/emergency-access-api.service";
import { EmergencyAccessType } from "../../core/enums/emergency-access-type";
import { EmergencyAccessService } from "../../core/services/emergency-access/emergency-access.service";
@Component({
@@ -31,7 +30,6 @@ export class EmergencyAccessAddEditComponent implements OnInit {
waitTime: number;
constructor(
private emergencyAccessApiService: EmergencyAccessApiService,
private emergencyAccessService: EmergencyAccessService,
private i18nService: I18nService,
private platformUtilsService: PlatformUtilsService,
@@ -54,7 +52,7 @@ export class EmergencyAccessAddEditComponent implements OnInit {
this.editMode = true;
this.title = this.i18nService.t("editEmergencyContact");
try {
const emergencyAccess = await this.emergencyAccessApiService.getEmergencyAccess(
const emergencyAccess = await this.emergencyAccessService.getEmergencyAccess(
this.emergencyAccessId
);
this.type = emergencyAccess.type;

View File

@@ -3,9 +3,6 @@ import { takeUntil } from "rxjs";
import { ChangePasswordComponent } from "@bitwarden/angular/auth/components/change-password.component";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { PolicyData } from "@bitwarden/common/admin-console/models/data/policy.data";
import { Policy } from "@bitwarden/common/admin-console/models/domain/policy";
import { PolicyResponse } from "@bitwarden/common/admin-console/models/response/policy.response";
import { KdfType } from "@bitwarden/common/enums";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
@@ -16,7 +13,6 @@ import { StateService } from "@bitwarden/common/platform/abstractions/state.serv
import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password";
import { DialogService } from "@bitwarden/components";
import { EmergencyAccessApiService } from "../../core/services/emergency-access/emergency-access-api.service";
import { EmergencyAccessService } from "../../core/services/emergency-access/emergency-access.service";
@Component({
@@ -46,7 +42,6 @@ export class EmergencyAccessTakeoverComponent
platformUtilsService: PlatformUtilsService,
policyService: PolicyService,
private emergencyAccessService: EmergencyAccessService,
private emergencyAccessApiService: EmergencyAccessApiService,
private logService: LogService,
dialogService: DialogService
) {
@@ -63,19 +58,11 @@ export class EmergencyAccessTakeoverComponent
}
async ngOnInit() {
const response = await this.emergencyAccessApiService.getEmergencyGrantorPolicies(
this.emergencyAccessId
);
if (response.data != null && response.data.length > 0) {
const policies = response.data.map(
(policyResponse: PolicyResponse) => new Policy(new PolicyData(policyResponse))
);
this.policyService
.masterPasswordPolicyOptions$(policies)
.pipe(takeUntil(this.destroy$))
.subscribe((enforcedPolicyOptions) => (this.enforcedPolicyOptions = enforcedPolicyOptions));
}
const policies = await this.emergencyAccessService.getGrantorPolicies(this.emergencyAccessId);
this.policyService
.masterPasswordPolicyOptions$(policies)
.pipe(takeUntil(this.destroy$))
.subscribe((enforcedPolicyOptions) => (this.enforcedPolicyOptions = enforcedPolicyOptions));
}
// eslint-disable-next-line rxjs-angular/prefer-takeuntil