1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-23 19:53:43 +00:00

[PM-3797 Part 1] Add Emergency Access Service (#6612)

* lazy load and move accept emergency component

* create emergency access services
- move api calls to specific api service and refactor

* remove any from emergency api service

* move emergency access logic to service

* create emergency access view

* move view ciphers logic to service

* move models to web folder

* move takeover logic to service

* remove emergency api service dependency from other files

* write tests for emergency access service

* import shared module into component

* fix imports

* Revert "fix imports"

This reverts commit d21cb02bd8.

* create emergency access module for service

* move emergency access out of core folder
- add more organization to components under settings

* change EA views to domain models

* move EA enums to folder

* resolve PR feedback
This commit is contained in:
Jake Fink
2023-11-08 16:03:10 -05:00
committed by GitHub
parent cf6ada531e
commit 929a08339f
36 changed files with 889 additions and 435 deletions

View File

@@ -8,6 +8,7 @@ import { MessagingService } from "@bitwarden/common/platform/abstractions/messag
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { SharedModule } from "../../shared";
import { EmergencyAccessModule } from "../emergency-access";
import { MigrateFromLegacyEncryptionService } from "./migrate-legacy-encryption.service";
@@ -15,7 +16,7 @@ import { MigrateFromLegacyEncryptionService } from "./migrate-legacy-encryption.
// This component is used to migrate from the old encryption scheme to the new one.
@Component({
standalone: true,
imports: [SharedModule],
imports: [SharedModule, EmergencyAccessModule],
providers: [MigrateFromLegacyEncryptionService],
templateUrl: "migrate-legacy-encryption.component.html",
})

View File

@@ -8,12 +8,7 @@ import { OrganizationUserResetPasswordEnrollmentRequest } from "@bitwarden/commo
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";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
@@ -34,6 +29,8 @@ import { Folder } from "@bitwarden/common/vault/models/domain/folder";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { FolderView } from "@bitwarden/common/vault/models/view/folder.view";
import { EmergencyAccessService } from "../emergency-access";
import { MigrateFromLegacyEncryptionService } from "./migrate-legacy-encryption.service";
describe("migrateFromLegacyEncryptionService", () => {
@@ -42,6 +39,7 @@ describe("migrateFromLegacyEncryptionService", () => {
const organizationService = mock<OrganizationService>();
const organizationApiService = mock<OrganizationApiService>();
const organizationUserService = mock<OrganizationUserService>();
const emergencyAccessService = mock<EmergencyAccessService>();
const apiService = mock<ApiService>();
const encryptService = mock<EncryptService>();
const cryptoService = mock<CryptoService>();
@@ -60,6 +58,7 @@ describe("migrateFromLegacyEncryptionService", () => {
organizationService,
organizationApiService,
organizationUserService,
emergencyAccessService,
apiService,
cryptoService,
encryptService,
@@ -109,6 +108,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;
@@ -201,40 +203,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>;
apiService.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(apiService.putEmergencyAccess).not.toHaveBeenCalledWith(
"0",
expect.any(EmergencyAccessUpdateRequest)
);
expect(apiService.putEmergencyAccess).not.toHaveBeenCalledWith(
"1",
expect.any(EmergencyAccessUpdateRequest)
);
expect(emergencyAccessService.rotate).toHaveBeenCalled();
});
});
@@ -322,19 +296,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 { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-conso
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service";
import { OrganizationUserResetPasswordEnrollmentRequest } from "@bitwarden/common/admin-console/abstractions/organization-user/requests";
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";
@@ -23,6 +21,8 @@ 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 { EmergencyAccessService } from "../emergency-access";
// TODO: PM-3797 - This service should be expanded and used for user key rotations in change-password.component.ts
@Injectable()
export class MigrateFromLegacyEncryptionService {
@@ -30,6 +30,7 @@ export class MigrateFromLegacyEncryptionService {
private organizationService: OrganizationService,
private organizationApiService: OrganizationApiServiceAbstraction,
private organizationUserService: OrganizationUserService,
private emergencyAccessService: EmergencyAccessService,
private apiService: ApiService,
private cryptoService: CryptoService,
private encryptService: EncryptService,
@@ -102,31 +103,8 @@ export class MigrateFromLegacyEncryptionService {
* on the server.
* @param newUserKey The new user key
*/
async updateEmergencyAccesses(newUserKey: UserKey) {
const emergencyAccess = await this.apiService.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.apiService.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