1
0
mirror of https://github.com/bitwarden/browser synced 2026-03-01 02:51:24 +00:00

Merge branch 'main' of https://github.com/bitwarden/clients into innovation/user-achievements/event-stream-prototype

This commit is contained in:
Daniel James Smith
2025-03-20 12:37:33 +01:00
491 changed files with 13870 additions and 15942 deletions

View File

@@ -38,7 +38,6 @@ import {
ProviderUserUserDetailsResponse,
} from "../admin-console/models/response/provider/provider-user.response";
import { SelectionReadOnlyResponse } from "../admin-console/models/response/selection-read-only.response";
import { AuthRequest } from "../auth/models/request/auth.request";
import { DeviceVerificationRequest } from "../auth/models/request/device-verification.request";
import { DisableTwoFactorAuthenticatorRequest } from "../auth/models/request/disable-two-factor-authenticator.request";
import { EmailTokenRequest } from "../auth/models/request/email-token.request";
@@ -47,19 +46,12 @@ import { PasswordTokenRequest } from "../auth/models/request/identity-token/pass
import { SsoTokenRequest } from "../auth/models/request/identity-token/sso-token.request";
import { UserApiTokenRequest } from "../auth/models/request/identity-token/user-api-token.request";
import { WebAuthnLoginTokenRequest } from "../auth/models/request/identity-token/webauthn-login-token.request";
import { KeyConnectorUserKeyRequest } from "../auth/models/request/key-connector-user-key.request";
import { PasswordHintRequest } from "../auth/models/request/password-hint.request";
import { PasswordRequest } from "../auth/models/request/password.request";
import { PasswordlessAuthRequest } from "../auth/models/request/passwordless-auth.request";
import { SecretVerificationRequest } from "../auth/models/request/secret-verification.request";
import { SetKeyConnectorKeyRequest } from "../auth/models/request/set-key-connector-key.request";
import { SetPasswordRequest } from "../auth/models/request/set-password.request";
import { TwoFactorEmailRequest } from "../auth/models/request/two-factor-email.request";
import { TwoFactorProviderRequest } from "../auth/models/request/two-factor-provider.request";
import { TwoFactorRecoveryRequest } from "../auth/models/request/two-factor-recovery.request";
import { UpdateProfileRequest } from "../auth/models/request/update-profile.request";
import { UpdateTdeOffboardingPasswordRequest } from "../auth/models/request/update-tde-offboarding-password.request";
import { UpdateTempPasswordRequest } from "../auth/models/request/update-temp-password.request";
import { UpdateTwoFactorAuthenticatorRequest } from "../auth/models/request/update-two-factor-authenticator.request";
import { UpdateTwoFactorDuoRequest } from "../auth/models/request/update-two-factor-duo.request";
import { UpdateTwoFactorEmailRequest } from "../auth/models/request/update-two-factor-email.request";
@@ -96,6 +88,8 @@ import { PaymentResponse } from "../billing/models/response/payment.response";
import { PlanResponse } from "../billing/models/response/plan.response";
import { SubscriptionResponse } from "../billing/models/response/subscription.response";
import { TaxInfoResponse } from "../billing/models/response/tax-info.response";
import { KeyConnectorUserKeyRequest } from "../key-management/key-connector/models/key-connector-user-key.request";
import { SetKeyConnectorKeyRequest } from "../key-management/key-connector/models/set-key-connector-key.request";
import { DeleteRecoverRequest } from "../models/request/delete-recover.request";
import { EventRequest } from "../models/request/event.request";
import { KdfRequest } from "../models/request/kdf.request";
@@ -169,8 +163,6 @@ export abstract class ApiService {
postPrelogin: (request: PreloginRequest) => Promise<PreloginResponse>;
postEmailToken: (request: EmailTokenRequest) => Promise<any>;
postEmail: (request: EmailRequest) => Promise<any>;
postPassword: (request: PasswordRequest) => Promise<any>;
setPassword: (request: SetPasswordRequest) => Promise<any>;
postSetKeyConnectorKey: (request: SetKeyConnectorKeyRequest) => Promise<any>;
postSecurityStamp: (request: SecretVerificationRequest) => Promise<any>;
getAccountRevisionDate: () => Promise<number>;
@@ -189,13 +181,8 @@ export abstract class ApiService {
postAccountKdf: (request: KdfRequest) => Promise<any>;
postUserApiKey: (id: string, request: SecretVerificationRequest) => Promise<ApiKeyResponse>;
postUserRotateApiKey: (id: string, request: SecretVerificationRequest) => Promise<ApiKeyResponse>;
putUpdateTempPassword: (request: UpdateTempPasswordRequest) => Promise<any>;
putUpdateTdeOffboardingPassword: (request: UpdateTdeOffboardingPasswordRequest) => Promise<any>;
postConvertToKeyConnector: () => Promise<void>;
//passwordless
postAuthRequest: (request: AuthRequest) => Promise<AuthRequestResponse>;
postAdminAuthRequest: (request: AuthRequest) => Promise<AuthRequestResponse>;
getAuthResponse: (id: string, accessCode: string) => Promise<AuthRequestResponse>;
getAuthRequest: (id: string) => Promise<AuthRequestResponse>;
putAuthRequest: (id: string, request: PasswordlessAuthRequest) => Promise<AuthRequestResponse>;
getAuthRequests: () => Promise<ListResponse<AuthRequestResponse>>;
@@ -356,7 +343,6 @@ export abstract class ApiService {
organizationId: string,
request: TwoFactorProviderRequest,
) => Promise<TwoFactorProviderResponse>;
postTwoFactorRecover: (request: TwoFactorRecoveryRequest) => Promise<any>;
postTwoFactorEmailSetup: (request: TwoFactorEmailRequest) => Promise<any>;
postTwoFactorEmail: (request: TwoFactorEmailRequest) => Promise<any>;
getDeviceVerificationSettings: () => Promise<DeviceVerificationResponse>;

View File

@@ -0,0 +1,28 @@
import { PasswordRequest } from "../models/request/password.request";
import { SetPasswordRequest } from "../models/request/set-password.request";
import { UpdateTdeOffboardingPasswordRequest } from "../models/request/update-tde-offboarding-password.request";
import { UpdateTempPasswordRequest } from "../models/request/update-temp-password.request";
export abstract class MasterPasswordApiService {
/**
* POSTs a SetPasswordRequest to "/accounts/set-password"
*/
abstract setPassword: (request: SetPasswordRequest) => Promise<any>;
/**
* POSTs a PasswordRequest to "/accounts/password"
*/
abstract postPassword: (request: PasswordRequest) => Promise<any>;
/**
* PUTs an UpdateTempPasswordRequest to "/accounts/update-temp-password"
*/
abstract putUpdateTempPassword: (request: UpdateTempPasswordRequest) => Promise<any>;
/**
* PUTs an UpdateTdeOffboardingPasswordRequest to "/accounts/update-tde-offboarding-password"
*/
abstract putUpdateTdeOffboardingPassword: (
request: UpdateTdeOffboardingPasswordRequest,
) => Promise<any>;
}

View File

@@ -1,8 +0,0 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { SecretVerificationRequest } from "./secret-verification.request";
export class TwoFactorRecoveryRequest extends SecretVerificationRequest {
recoveryCode: string;
email: string;
}

View File

@@ -0,0 +1,17 @@
import { Jsonify } from "type-fest";
import { AuthRequest } from "@bitwarden/common/auth/models/request/auth.request";
import { AuthRequestResponse } from "@bitwarden/common/auth/models/response/auth-request.response";
import { View } from "@bitwarden/common/models/view/view";
export class LoginViaAuthRequestView implements View {
authRequest: AuthRequest | undefined = undefined;
authRequestResponse: AuthRequestResponse | undefined = undefined;
fingerprintPhrase: string | undefined = undefined;
privateKey: string | undefined = undefined;
publicKey: string | undefined = undefined;
static fromJSON(obj: Partial<Jsonify<LoginViaAuthRequestView>>): LoginViaAuthRequestView {
return Object.assign(new LoginViaAuthRequestView(), obj);
}
}

View File

@@ -0,0 +1,85 @@
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { MasterPasswordApiService as MasterPasswordApiServiceAbstraction } from "../../abstractions/master-password-api.service.abstraction";
import { PasswordRequest } from "../../models/request/password.request";
import { SetPasswordRequest } from "../../models/request/set-password.request";
import { UpdateTdeOffboardingPasswordRequest } from "../../models/request/update-tde-offboarding-password.request";
import { UpdateTempPasswordRequest } from "../../models/request/update-temp-password.request";
export class MasterPasswordApiService implements MasterPasswordApiServiceAbstraction {
constructor(
private apiService: ApiService,
private logService: LogService,
) {}
async setPassword(request: SetPasswordRequest): Promise<any> {
try {
const response = await this.apiService.send(
"POST",
"/accounts/set-password",
request,
true,
false,
);
return response;
} catch (e: unknown) {
this.logService.error(e);
throw e;
}
}
async postPassword(request: PasswordRequest): Promise<any> {
try {
const response = await this.apiService.send(
"POST",
"/accounts/password",
request,
true,
false,
);
return response;
} catch (e: unknown) {
this.logService.error(e);
throw e;
}
}
async putUpdateTempPassword(request: UpdateTempPasswordRequest): Promise<any> {
try {
const response = await this.apiService.send(
"PUT",
"/accounts/update-temp-password",
request,
true,
false,
);
return response;
} catch (e: unknown) {
this.logService.error(e);
throw e;
}
}
async putUpdateTdeOffboardingPassword(
request: UpdateTdeOffboardingPasswordRequest,
): Promise<any> {
try {
const response = await this.apiService.send(
"PUT",
"/accounts/update-tde-offboarding-password",
request,
true,
false,
);
return response;
} catch (e: unknown) {
this.logService.error(e);
throw e;
}
}
}

View File

@@ -0,0 +1,130 @@
import { mock, MockProxy } from "jest-mock-extended";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { KdfType } from "@bitwarden/key-management";
import { PasswordRequest } from "../../models/request/password.request";
import { SetPasswordRequest } from "../../models/request/set-password.request";
import { UpdateTdeOffboardingPasswordRequest } from "../../models/request/update-tde-offboarding-password.request";
import { UpdateTempPasswordRequest } from "../../models/request/update-temp-password.request";
import { MasterPasswordApiService } from "./master-password-api.service.implementation";
describe("MasterPasswordApiService", () => {
let apiService: MockProxy<ApiService>;
let logService: MockProxy<LogService>;
let sut: MasterPasswordApiService;
beforeEach(() => {
apiService = mock<ApiService>();
logService = mock<LogService>();
sut = new MasterPasswordApiService(apiService, logService);
});
it("should instantiate", () => {
expect(sut).not.toBeFalsy();
});
describe("setPassword", () => {
it("should call apiService.send with the correct parameters", async () => {
// Arrange
const request = new SetPasswordRequest(
"masterPasswordHash",
"key",
"masterPasswordHint",
"orgIdentifier",
{
publicKey: "publicKey",
encryptedPrivateKey: "encryptedPrivateKey",
},
KdfType.PBKDF2_SHA256,
600_000,
);
// Act
await sut.setPassword(request);
// Assert
expect(apiService.send).toHaveBeenCalledWith(
"POST",
"/accounts/set-password",
request,
true,
false,
);
});
});
describe("postPassword", () => {
it("should call apiService.send with the correct parameters", async () => {
// Arrange
const request = {
newMasterPasswordHash: "newMasterPasswordHash",
masterPasswordHint: "masterPasswordHint",
key: "key",
masterPasswordHash: "masterPasswordHash",
} as PasswordRequest;
// Act
await sut.postPassword(request);
// Assert
expect(apiService.send).toHaveBeenCalledWith(
"POST",
"/accounts/password",
request,
true,
false,
);
});
});
describe("putUpdateTempPassword", () => {
it("should call apiService.send with the correct parameters", async () => {
// Arrange
const request = {
masterPasswordHint: "masterPasswordHint",
newMasterPasswordHash: "newMasterPasswordHash",
key: "key",
} as UpdateTempPasswordRequest;
// Act
await sut.putUpdateTempPassword(request);
// Assert
expect(apiService.send).toHaveBeenCalledWith(
"PUT",
"/accounts/update-temp-password",
request,
true,
false,
);
});
});
describe("putUpdateTdeOffboardingPassword", () => {
it("should call apiService.send with the correct parameters", async () => {
// Arrange
const request = {
masterPasswordHint: "masterPasswordHint",
newMasterPasswordHash: "newMasterPasswordHash",
key: "key",
} as UpdateTdeOffboardingPasswordRequest;
// Act
await sut.putUpdateTdeOffboardingPassword(request);
// Assert
expect(apiService.send).toHaveBeenCalledWith(
"PUT",
"/accounts/update-tde-offboarding-password",
request,
true,
false,
);
});
});
});

View File

@@ -16,13 +16,13 @@ import {
} from "@bitwarden/key-management";
import { FakeAccountService, mockAccountServiceWith } from "../../../../spec";
import { InternalMasterPasswordServiceAbstraction } from "../../../key-management/master-password/abstractions/master-password.service.abstraction";
import { VaultTimeoutSettingsService } from "../../../key-management/vault-timeout";
import { I18nService } from "../../../platform/abstractions/i18n.service";
import { HashPurpose } from "../../../platform/enums";
import { Utils } from "../../../platform/misc/utils";
import { UserId } from "../../../types/guid";
import { MasterKey } from "../../../types/key";
import { InternalMasterPasswordServiceAbstraction } from "../../abstractions/master-password.service.abstraction";
import { UserVerificationApiServiceAbstraction } from "../../abstractions/user-verification/user-verification-api.service.abstraction";
import { VerificationType } from "../../enums/verification-type";
import { MasterPasswordPolicyResponse } from "../../models/response/master-password-policy.response";

View File

@@ -13,11 +13,11 @@ import {
// FIXME: remove `src` and fix import
// eslint-disable-next-line no-restricted-imports
import { PinServiceAbstraction } from "../../../../../auth/src/common/abstractions/pin.service.abstraction";
import { InternalMasterPasswordServiceAbstraction } from "../../../key-management/master-password/abstractions/master-password.service.abstraction";
import { I18nService } from "../../../platform/abstractions/i18n.service";
import { HashPurpose } from "../../../platform/enums";
import { UserId } from "../../../types/guid";
import { AccountService } from "../../abstractions/account.service";
import { InternalMasterPasswordServiceAbstraction } from "../../abstractions/master-password.service.abstraction";
import { UserVerificationApiServiceAbstraction } from "../../abstractions/user-verification/user-verification-api.service.abstraction";
import { UserVerificationService as UserVerificationServiceAbstraction } from "../../abstractions/user-verification/user-verification.service.abstraction";
import { VerificationType } from "../../enums/verification-type";

View File

@@ -1,6 +1,8 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { TaxInfoResponse } from "@bitwarden/common/billing/models/response/tax-info.response";
import { OrganizationCreateRequest } from "../../admin-console/models/request/organization-create.request";
import { ProviderOrganizationOrganizationDetailsResponse } from "../../admin-console/models/response/provider/provider-organization.response";
import { SubscriptionCancellationRequest } from "../../billing/models/request/subscription-cancellation.request";
@@ -50,6 +52,8 @@ export abstract class BillingApiServiceAbstraction {
getProviderSubscription: (providerId: string) => Promise<ProviderSubscriptionResponse>;
getProviderTaxInformation: (providerId: string) => Promise<TaxInfoResponse>;
updateOrganizationPaymentMethod: (
organizationId: string,
request: UpdatePaymentMethodRequest,
@@ -66,6 +70,11 @@ export abstract class BillingApiServiceAbstraction {
request: UpdateClientOrganizationRequest,
) => Promise<any>;
updateProviderPaymentMethod: (
providerId: string,
request: UpdatePaymentMethodRequest,
) => Promise<void>;
updateProviderTaxInformation: (
providerId: string,
request: ExpandedTaxInfoUpdateRequest,
@@ -76,6 +85,11 @@ export abstract class BillingApiServiceAbstraction {
request: VerifyBankAccountRequest,
) => Promise<void>;
verifyProviderBankAccount: (
providerId: string,
request: VerifyBankAccountRequest,
) => Promise<void>;
restartSubscription: (
organizationId: string,
request: OrganizationCreateRequest,

View File

@@ -1,3 +1,5 @@
import { PaymentSourceResponse } from "@bitwarden/common/billing/models/response/payment-source.response";
import { ProviderType } from "../../../admin-console/enums";
import { BaseResponse } from "../../../models/response/base.response";
import { PlanType, ProductTierType } from "../../enums";
@@ -16,6 +18,7 @@ export class ProviderSubscriptionResponse extends BaseResponse {
cancelAt?: string;
suspension?: SubscriptionSuspensionResponse;
providerType: ProviderType;
paymentSource?: PaymentSourceResponse;
constructor(response: any) {
super(response);
@@ -38,6 +41,10 @@ export class ProviderSubscriptionResponse extends BaseResponse {
this.suspension = new SubscriptionSuspensionResponse(suspension);
}
this.providerType = this.getResponseProperty("providerType");
const paymentSource = this.getResponseProperty("paymentSource");
if (paymentSource != null) {
this.paymentSource = new PaymentSourceResponse(paymentSource);
}
}
}

View File

@@ -1,6 +1,8 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { TaxInfoResponse } from "@bitwarden/common/billing/models/response/tax-info.response";
import { ApiService } from "../../abstractions/api.service";
import { OrganizationCreateRequest } from "../../admin-console/models/request/organization-create.request";
import { ProviderOrganizationOrganizationDetailsResponse } from "../../admin-console/models/response/provider/provider-organization.response";
@@ -143,6 +145,17 @@ export class BillingApiService implements BillingApiServiceAbstraction {
return new ProviderSubscriptionResponse(response);
}
async getProviderTaxInformation(providerId: string): Promise<TaxInfoResponse> {
const response = await this.apiService.send(
"GET",
"/providers/" + providerId + "/billing/tax-information",
null,
true,
true,
);
return new TaxInfoResponse(response);
}
async updateOrganizationPaymentMethod(
organizationId: string,
request: UpdatePaymentMethodRequest,
@@ -183,6 +196,19 @@ export class BillingApiService implements BillingApiServiceAbstraction {
);
}
async updateProviderPaymentMethod(
providerId: string,
request: UpdatePaymentMethodRequest,
): Promise<void> {
return await this.apiService.send(
"PUT",
"/providers/" + providerId + "/billing/payment-method",
request,
true,
false,
);
}
async updateProviderTaxInformation(providerId: string, request: ExpandedTaxInfoUpdateRequest) {
return await this.apiService.send(
"PUT",
@@ -206,6 +232,19 @@ export class BillingApiService implements BillingApiServiceAbstraction {
);
}
async verifyProviderBankAccount(
providerId: string,
request: VerifyBankAccountRequest,
): Promise<void> {
return await this.apiService.send(
"POST",
"/providers/" + providerId + "/billing/payment-method/verify-bank-account",
request,
true,
false,
);
}
async restartSubscription(
organizationId: string,
request: OrganizationCreateRequest,

View File

@@ -26,23 +26,30 @@ export enum FeatureFlag {
CriticalApps = "pm-14466-risk-insights-critical-application",
EnableRiskInsightsNotifications = "enable-risk-insights-notifications",
DesktopSendUIRefresh = "desktop-send-ui-refresh",
ExportAttachments = "export-attachments",
/* Vault */
PM8851_BrowserOnboardingNudge = "pm-8851-browser-onboarding-nudge",
PM9111ExtensionPersistAddEditForm = "pm-9111-extension-persist-add-edit-form",
NewDeviceVerificationTemporaryDismiss = "new-device-temporary-dismiss",
NewDeviceVerificationPermanentDismiss = "new-device-permanent-dismiss",
VaultBulkManagementAction = "vault-bulk-management-action",
SecurityTasks = "security-tasks",
/* Auth */
PM9112_DeviceApprovalPersistence = "pm-9112-device-approval-persistence",
PM4154_BulkEncryptionService = "PM-4154-bulk-encryption-service",
VaultBulkManagementAction = "vault-bulk-management-action",
UnauthenticatedExtensionUIRefresh = "unauth-ui-refresh",
CipherKeyEncryption = "cipher-key-encryption",
TrialPaymentOptional = "PM-8163-trial-payment",
SecurityTasks = "security-tasks",
NewDeviceVerificationTemporaryDismiss = "new-device-temporary-dismiss",
NewDeviceVerificationPermanentDismiss = "new-device-permanent-dismiss",
MacOsNativeCredentialSync = "macos-native-credential-sync",
PM9111ExtensionPersistAddEditForm = "pm-9111-extension-persist-add-edit-form",
PrivateKeyRegeneration = "pm-12241-private-key-regeneration",
ResellerManagedOrgAlert = "PM-15814-alert-owners-of-reseller-managed-orgs",
AccountDeprovisioningBanner = "pm-17120-account-deprovisioning-admin-console-banner",
PM15179_AddExistingOrgsFromProviderPortal = "pm-15179-add-existing-orgs-from-provider-portal",
RecoveryCodeLogin = "pm-17128-recovery-code-login",
PM12276_BreadcrumbEventLogs = "pm-12276-breadcrumbing-for-business-features",
PM18794_ProviderPaymentMethod = "pm-18794-provider-payment-method",
}
export type AllowedFeatureFlagTypes = boolean | number | string;
@@ -79,23 +86,30 @@ export const DefaultFeatureFlagValue = {
[FeatureFlag.CriticalApps]: FALSE,
[FeatureFlag.EnableRiskInsightsNotifications]: FALSE,
[FeatureFlag.DesktopSendUIRefresh]: FALSE,
[FeatureFlag.ExportAttachments]: FALSE,
/* Vault */
[FeatureFlag.PM8851_BrowserOnboardingNudge]: FALSE,
[FeatureFlag.PM9111ExtensionPersistAddEditForm]: FALSE,
[FeatureFlag.NewDeviceVerificationTemporaryDismiss]: FALSE,
[FeatureFlag.NewDeviceVerificationPermanentDismiss]: FALSE,
[FeatureFlag.VaultBulkManagementAction]: FALSE,
[FeatureFlag.SecurityTasks]: FALSE,
/* Auth */
[FeatureFlag.PM9112_DeviceApprovalPersistence]: FALSE,
[FeatureFlag.PM4154_BulkEncryptionService]: FALSE,
[FeatureFlag.VaultBulkManagementAction]: FALSE,
[FeatureFlag.UnauthenticatedExtensionUIRefresh]: FALSE,
[FeatureFlag.CipherKeyEncryption]: FALSE,
[FeatureFlag.TrialPaymentOptional]: FALSE,
[FeatureFlag.SecurityTasks]: FALSE,
[FeatureFlag.NewDeviceVerificationTemporaryDismiss]: FALSE,
[FeatureFlag.NewDeviceVerificationPermanentDismiss]: FALSE,
[FeatureFlag.MacOsNativeCredentialSync]: FALSE,
[FeatureFlag.PM9111ExtensionPersistAddEditForm]: FALSE,
[FeatureFlag.PrivateKeyRegeneration]: FALSE,
[FeatureFlag.ResellerManagedOrgAlert]: FALSE,
[FeatureFlag.AccountDeprovisioningBanner]: FALSE,
[FeatureFlag.PM15179_AddExistingOrgsFromProviderPortal]: FALSE,
[FeatureFlag.RecoveryCodeLogin]: FALSE,
[FeatureFlag.PM12276_BreadcrumbEventLogs]: FALSE,
[FeatureFlag.PM18794_ProviderPaymentMethod]: FALSE,
} satisfies Record<FeatureFlag, AllowedFeatureFlagTypes>;
export type DefaultFeatureFlagValueType = typeof DefaultFeatureFlagValue;

View File

@@ -2,11 +2,10 @@
// @ts-strict-ignore
import { Observable } from "rxjs";
import { EncString } from "../../platform/models/domain/enc-string";
import { UserId } from "../../types/guid";
import { DeviceKey, UserKey } from "../../types/key";
import { DeviceResponse } from "./devices/responses/device.response";
import { DeviceResponse } from "../../../auth/abstractions/devices/responses/device.response";
import { EncString } from "../../../platform/models/domain/enc-string";
import { UserId } from "../../../types/guid";
import { DeviceKey, UserKey } from "../../../types/key";
export abstract class DeviceTrustServiceAbstraction {
/**

View File

@@ -5,30 +5,30 @@ import { firstValueFrom, map, Observable, Subject } from "rxjs";
import { UserDecryptionOptionsServiceAbstraction } from "@bitwarden/auth/common";
import { KeyService } from "@bitwarden/key-management";
import { EncryptService } from "../../key-management/crypto/abstractions/encrypt.service";
import { AppIdService } from "../../platform/abstractions/app-id.service";
import { ConfigService } from "../../platform/abstractions/config/config.service";
import { CryptoFunctionService } from "../../platform/abstractions/crypto-function.service";
import { I18nService } from "../../platform/abstractions/i18n.service";
import { KeyGenerationService } from "../../platform/abstractions/key-generation.service";
import { LogService } from "../../platform/abstractions/log.service";
import { PlatformUtilsService } from "../../platform/abstractions/platform-utils.service";
import { AbstractStorageService } from "../../platform/abstractions/storage.service";
import { StorageLocation } from "../../platform/enums";
import { EncString } from "../../platform/models/domain/enc-string";
import { StorageOptions } from "../../platform/models/domain/storage-options";
import { SymmetricCryptoKey } from "../../platform/models/domain/symmetric-crypto-key";
import { DEVICE_TRUST_DISK_LOCAL, StateProvider, UserKeyDefinition } from "../../platform/state";
import { UserId } from "../../types/guid";
import { UserKey, DeviceKey } from "../../types/key";
import { DeviceTrustServiceAbstraction } from "../abstractions/device-trust.service.abstraction";
import { DeviceResponse } from "../abstractions/devices/responses/device.response";
import { DevicesApiServiceAbstraction } from "../abstractions/devices-api.service.abstraction";
import { SecretVerificationRequest } from "../models/request/secret-verification.request";
import { DeviceResponse } from "../../../auth/abstractions/devices/responses/device.response";
import { DevicesApiServiceAbstraction } from "../../../auth/abstractions/devices-api.service.abstraction";
import { SecretVerificationRequest } from "../../../auth/models/request/secret-verification.request";
import {
DeviceKeysUpdateRequest,
UpdateDevicesTrustRequest,
} from "../models/request/update-devices-trust.request";
} from "../../../auth/models/request/update-devices-trust.request";
import { AppIdService } from "../../../platform/abstractions/app-id.service";
import { ConfigService } from "../../../platform/abstractions/config/config.service";
import { CryptoFunctionService } from "../../../platform/abstractions/crypto-function.service";
import { I18nService } from "../../../platform/abstractions/i18n.service";
import { KeyGenerationService } from "../../../platform/abstractions/key-generation.service";
import { LogService } from "../../../platform/abstractions/log.service";
import { PlatformUtilsService } from "../../../platform/abstractions/platform-utils.service";
import { AbstractStorageService } from "../../../platform/abstractions/storage.service";
import { StorageLocation } from "../../../platform/enums";
import { EncString } from "../../../platform/models/domain/enc-string";
import { StorageOptions } from "../../../platform/models/domain/storage-options";
import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key";
import { DEVICE_TRUST_DISK_LOCAL, StateProvider, UserKeyDefinition } from "../../../platform/state";
import { UserId } from "../../../types/guid";
import { UserKey, DeviceKey } from "../../../types/key";
import { EncryptService } from "../../crypto/abstractions/encrypt.service";
import { DeviceTrustServiceAbstraction } from "../abstractions/device-trust.service.abstraction";
/** Uses disk storage so that the device key can persist after log out and tab removal. */
export const DEVICE_KEY = new UserKeyDefinition<DeviceKey | null>(

View File

@@ -3,38 +3,38 @@
import { matches, mock } from "jest-mock-extended";
import { BehaviorSubject, firstValueFrom, of } from "rxjs";
import { UserDecryptionOptionsServiceAbstraction } from "@bitwarden/auth/common";
import {
UserDecryptionOptionsServiceAbstraction,
UserDecryptionOptions,
} from "@bitwarden/auth/common";
import { KeyService } from "@bitwarden/key-management";
// FIXME: remove `src` and fix import
// eslint-disable-next-line no-restricted-imports
import { UserDecryptionOptions } from "../../../../auth/src/common/models/domain/user-decryption-options";
import { FakeAccountService, mockAccountServiceWith } from "../../../spec/fake-account-service";
import { FakeActiveUserState } from "../../../spec/fake-state";
import { FakeStateProvider } from "../../../spec/fake-state-provider";
import { DeviceType } from "../../enums";
import { EncryptService } from "../../key-management/crypto/abstractions/encrypt.service";
import { AppIdService } from "../../platform/abstractions/app-id.service";
import { ConfigService } from "../../platform/abstractions/config/config.service";
import { CryptoFunctionService } from "../../platform/abstractions/crypto-function.service";
import { I18nService } from "../../platform/abstractions/i18n.service";
import { KeyGenerationService } from "../../platform/abstractions/key-generation.service";
import { LogService } from "../../platform/abstractions/log.service";
import { PlatformUtilsService } from "../../platform/abstractions/platform-utils.service";
import { AbstractStorageService } from "../../platform/abstractions/storage.service";
import { StorageLocation } from "../../platform/enums";
import { EncryptionType } from "../../platform/enums/encryption-type.enum";
import { Utils } from "../../platform/misc/utils";
import { EncString } from "../../platform/models/domain/enc-string";
import { StorageOptions } from "../../platform/models/domain/storage-options";
import { SymmetricCryptoKey } from "../../platform/models/domain/symmetric-crypto-key";
import { CsprngArray } from "../../types/csprng";
import { UserId } from "../../types/guid";
import { DeviceKey, UserKey } from "../../types/key";
import { DeviceResponse } from "../abstractions/devices/responses/device.response";
import { DevicesApiServiceAbstraction } from "../abstractions/devices-api.service.abstraction";
import { UpdateDevicesTrustRequest } from "../models/request/update-devices-trust.request";
import { ProtectedDeviceResponse } from "../models/response/protected-device.response";
import { FakeAccountService, mockAccountServiceWith } from "../../../../spec/fake-account-service";
import { FakeActiveUserState } from "../../../../spec/fake-state";
import { FakeStateProvider } from "../../../../spec/fake-state-provider";
import { DeviceResponse } from "../../../auth/abstractions/devices/responses/device.response";
import { DevicesApiServiceAbstraction } from "../../../auth/abstractions/devices-api.service.abstraction";
import { UpdateDevicesTrustRequest } from "../../../auth/models/request/update-devices-trust.request";
import { ProtectedDeviceResponse } from "../../../auth/models/response/protected-device.response";
import { DeviceType } from "../../../enums";
import { AppIdService } from "../../../platform/abstractions/app-id.service";
import { ConfigService } from "../../../platform/abstractions/config/config.service";
import { CryptoFunctionService } from "../../../platform/abstractions/crypto-function.service";
import { I18nService } from "../../../platform/abstractions/i18n.service";
import { KeyGenerationService } from "../../../platform/abstractions/key-generation.service";
import { LogService } from "../../../platform/abstractions/log.service";
import { PlatformUtilsService } from "../../../platform/abstractions/platform-utils.service";
import { AbstractStorageService } from "../../../platform/abstractions/storage.service";
import { StorageLocation } from "../../../platform/enums";
import { EncryptionType } from "../../../platform/enums/encryption-type.enum";
import { Utils } from "../../../platform/misc/utils";
import { EncString } from "../../../platform/models/domain/enc-string";
import { StorageOptions } from "../../../platform/models/domain/storage-options";
import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key";
import { CsprngArray } from "../../../types/csprng";
import { UserId } from "../../../types/guid";
import { DeviceKey, UserKey } from "../../../types/key";
import { EncryptService } from "../../crypto/abstractions/encrypt.service";
import {
SHOULD_TRUST_DEVICE,

View File

@@ -1,8 +1,8 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { Organization } from "../../admin-console/models/domain/organization";
import { UserId } from "../../types/guid";
import { IdentityTokenResponse } from "../models/response/identity-token.response";
import { Organization } from "../../../admin-console/models/domain/organization";
import { IdentityTokenResponse } from "../../../auth/models/response/identity-token.response";
import { UserId } from "../../../types/guid";
export abstract class KeyConnectorService {
setMasterKeyFromUrl: (url: string, userId: UserId) => Promise<void>;

View File

@@ -4,27 +4,27 @@ import { of } from "rxjs";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { KeyService } from "@bitwarden/key-management";
import { FakeAccountService, FakeStateProvider, mockAccountServiceWith } from "../../../spec";
import { ApiService } from "../../abstractions/api.service";
import { OrganizationData } from "../../admin-console/models/data/organization.data";
import { Organization } from "../../admin-console/models/domain/organization";
import { ProfileOrganizationResponse } from "../../admin-console/models/response/profile-organization.response";
import { LogService } from "../../platform/abstractions/log.service";
import { Utils } from "../../platform/misc/utils";
import { SymmetricCryptoKey } from "../../platform/models/domain/symmetric-crypto-key";
import { KeyGenerationService } from "../../platform/services/key-generation.service";
import { OrganizationId, UserId } from "../../types/guid";
import { MasterKey } from "../../types/key";
import { KeyConnectorUserKeyRequest } from "../models/request/key-connector-user-key.request";
import { KeyConnectorUserKeyResponse } from "../models/response/key-connector-user-key.response";
import { FakeAccountService, FakeStateProvider, mockAccountServiceWith } from "../../../../spec";
import { ApiService } from "../../../abstractions/api.service";
import { OrganizationData } from "../../../admin-console/models/data/organization.data";
import { Organization } from "../../../admin-console/models/domain/organization";
import { ProfileOrganizationResponse } from "../../../admin-console/models/response/profile-organization.response";
import { KeyConnectorUserKeyResponse } from "../../../auth/models/response/key-connector-user-key.response";
import { TokenService } from "../../../auth/services/token.service";
import { LogService } from "../../../platform/abstractions/log.service";
import { Utils } from "../../../platform/misc/utils";
import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key";
import { KeyGenerationService } from "../../../platform/services/key-generation.service";
import { OrganizationId, UserId } from "../../../types/guid";
import { MasterKey } from "../../../types/key";
import { FakeMasterPasswordService } from "../../master-password/services/fake-master-password.service";
import { KeyConnectorUserKeyRequest } from "../models/key-connector-user-key.request";
import {
USES_KEY_CONNECTOR,
CONVERT_ACCOUNT_TO_KEY_CONNECTOR,
KeyConnectorService,
} from "./key-connector.service";
import { FakeMasterPasswordService } from "./master-password/fake-master-password.service";
import { TokenService } from "./token.service";
describe("KeyConnectorService", () => {
let keyConnectorService: KeyConnectorService;

View File

@@ -3,8 +3,6 @@
import { firstValueFrom } from "rxjs";
import { LogoutReason } from "@bitwarden/auth/common";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import {
Argon2KdfConfig,
KdfConfig,
@@ -13,28 +11,30 @@ import {
KdfType,
} from "@bitwarden/key-management";
import { ApiService } from "../../abstractions/api.service";
import { OrganizationUserType } from "../../admin-console/enums";
import { Organization } from "../../admin-console/models/domain/organization";
import { KeysRequest } from "../../models/request/keys.request";
import { KeyGenerationService } from "../../platform/abstractions/key-generation.service";
import { LogService } from "../../platform/abstractions/log.service";
import { Utils } from "../../platform/misc/utils";
import { SymmetricCryptoKey } from "../../platform/models/domain/symmetric-crypto-key";
import { ApiService } from "../../../abstractions/api.service";
import { OrganizationService } from "../../../admin-console/abstractions/organization/organization.service.abstraction";
import { OrganizationUserType } from "../../../admin-console/enums";
import { Organization } from "../../../admin-console/models/domain/organization";
import { AccountService } from "../../../auth/abstractions/account.service";
import { TokenService } from "../../../auth/abstractions/token.service";
import { IdentityTokenResponse } from "../../../auth/models/response/identity-token.response";
import { KeysRequest } from "../../../models/request/keys.request";
import { KeyGenerationService } from "../../../platform/abstractions/key-generation.service";
import { LogService } from "../../../platform/abstractions/log.service";
import { Utils } from "../../../platform/misc/utils";
import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key";
import {
ActiveUserState,
KEY_CONNECTOR_DISK,
StateProvider,
UserKeyDefinition,
} from "../../platform/state";
import { UserId } from "../../types/guid";
import { MasterKey } from "../../types/key";
} from "../../../platform/state";
import { UserId } from "../../../types/guid";
import { MasterKey } from "../../../types/key";
import { InternalMasterPasswordServiceAbstraction } from "../../master-password/abstractions/master-password.service.abstraction";
import { KeyConnectorService as KeyConnectorServiceAbstraction } from "../abstractions/key-connector.service";
import { InternalMasterPasswordServiceAbstraction } from "../abstractions/master-password.service.abstraction";
import { TokenService } from "../abstractions/token.service";
import { KeyConnectorUserKeyRequest } from "../models/request/key-connector-user-key.request";
import { SetKeyConnectorKeyRequest } from "../models/request/set-key-connector-key.request";
import { IdentityTokenResponse } from "../models/response/identity-token.response";
import { KeyConnectorUserKeyRequest } from "../models/key-connector-user-key.request";
import { SetKeyConnectorKeyRequest } from "../models/set-key-connector-key.request";
export const USES_KEY_CONNECTOR = new UserKeyDefinition<boolean | null>(
KEY_CONNECTOR_DISK,

View File

@@ -1,9 +1,9 @@
import { Observable } from "rxjs";
import { EncString } from "../../platform/models/domain/enc-string";
import { UserId } from "../../types/guid";
import { MasterKey, UserKey } from "../../types/key";
import { ForceSetPasswordReason } from "../models/domain/force-set-password-reason";
import { ForceSetPasswordReason } from "../../../auth/models/domain/force-set-password-reason";
import { EncString } from "../../../platform/models/domain/enc-string";
import { UserId } from "../../../types/guid";
import { MasterKey, UserKey } from "../../../types/key";
export abstract class MasterPasswordServiceAbstraction {
/**

View File

@@ -3,11 +3,11 @@
import { mock } from "jest-mock-extended";
import { ReplaySubject, Observable } from "rxjs";
import { ForceSetPasswordReason } from "../../../auth/models/domain/force-set-password-reason";
import { EncString } from "../../../platform/models/domain/enc-string";
import { UserId } from "../../../types/guid";
import { MasterKey, UserKey } from "../../../types/key";
import { InternalMasterPasswordServiceAbstraction } from "../../abstractions/master-password.service.abstraction";
import { ForceSetPasswordReason } from "../../models/domain/force-set-password-reason";
import { InternalMasterPasswordServiceAbstraction } from "../abstractions/master-password.service.abstraction";
export class FakeMasterPasswordService implements InternalMasterPasswordServiceAbstraction {
mock = mock<InternalMasterPasswordServiceAbstraction>();

View File

@@ -2,7 +2,7 @@
// @ts-strict-ignore
import { firstValueFrom, map, Observable } from "rxjs";
import { EncryptService } from "../../../key-management/crypto/abstractions/encrypt.service";
import { ForceSetPasswordReason } from "../../../auth/models/domain/force-set-password-reason";
import { KeyGenerationService } from "../../../platform/abstractions/key-generation.service";
import { LogService } from "../../../platform/abstractions/log.service";
import { StateService } from "../../../platform/abstractions/state.service";
@@ -17,8 +17,8 @@ import {
} from "../../../platform/state";
import { UserId } from "../../../types/guid";
import { MasterKey, UserKey } from "../../../types/key";
import { InternalMasterPasswordServiceAbstraction } from "../../abstractions/master-password.service.abstraction";
import { ForceSetPasswordReason } from "../../models/domain/force-set-password-reason";
import { EncryptService } from "../../crypto/abstractions/encrypt.service";
import { InternalMasterPasswordServiceAbstraction } from "../abstractions/master-password.service.abstraction";
/** Memory since master key shouldn't be available on lock */
const MASTER_KEY = new UserKeyDefinition<MasterKey>(MASTER_PASSWORD_MEMORY, "masterKey", {

View File

@@ -12,7 +12,6 @@ import { SearchService } from "../../../abstractions/search.service";
import { AccountInfo } from "../../../auth/abstractions/account.service";
import { AuthService } from "../../../auth/abstractions/auth.service";
import { AuthenticationStatus } from "../../../auth/enums/authentication-status";
import { FakeMasterPasswordService } from "../../../auth/services/master-password/fake-master-password.service";
import { LogService } from "../../../platform/abstractions/log.service";
import { MessagingService } from "../../../platform/abstractions/messaging.service";
import { PlatformUtilsService } from "../../../platform/abstractions/platform-utils.service";
@@ -23,6 +22,7 @@ import { StateEventRunnerService } from "../../../platform/state";
import { UserId } from "../../../types/guid";
import { CipherService } from "../../../vault/abstractions/cipher.service";
import { FolderService } from "../../../vault/abstractions/folder/folder.service.abstraction";
import { FakeMasterPasswordService } from "../../master-password/services/fake-master-password.service";
import { VaultTimeoutAction } from "../enums/vault-timeout-action.enum";
import { VaultTimeout, VaultTimeoutStringType } from "../types/vault-timeout.type";

View File

@@ -9,7 +9,6 @@ import { BiometricsService } from "@bitwarden/key-management";
import { SearchService } from "../../../abstractions/search.service";
import { AccountService } from "../../../auth/abstractions/account.service";
import { AuthService } from "../../../auth/abstractions/auth.service";
import { InternalMasterPasswordServiceAbstraction } from "../../../auth/abstractions/master-password.service.abstraction";
import { AuthenticationStatus } from "../../../auth/enums/authentication-status";
import { LogService } from "../../../platform/abstractions/log.service";
import { MessagingService } from "../../../platform/abstractions/messaging.service";
@@ -20,6 +19,7 @@ import { StateEventRunnerService } from "../../../platform/state";
import { UserId } from "../../../types/guid";
import { CipherService } from "../../../vault/abstractions/cipher.service";
import { FolderService } from "../../../vault/abstractions/folder/folder.service.abstraction";
import { InternalMasterPasswordServiceAbstraction } from "../../master-password/abstractions/master-password.service.abstraction";
import { VaultTimeoutSettingsService } from "../abstractions/vault-timeout-settings.service";
import { VaultTimeoutService as VaultTimeoutServiceAbstraction } from "../abstractions/vault-timeout.service";
import { VaultTimeoutAction } from "../enums/vault-timeout-action.enum";

View File

@@ -131,6 +131,10 @@ export const THEMING_DISK = new StateDefinition("theming", "disk", { web: "disk-
export const TRANSLATION_DISK = new StateDefinition("translation", "disk", { web: "disk-local" });
export const ANIMATION_DISK = new StateDefinition("animation", "disk");
export const TASK_SCHEDULER_DISK = new StateDefinition("taskScheduler", "disk");
export const EXTENSION_INITIAL_INSTALL_DISK = new StateDefinition(
"extensionInitialInstall",
"disk",
);
// Design System

View File

@@ -27,13 +27,13 @@ import { PolicyResponse } from "../../admin-console/models/response/policy.respo
import { AccountService } from "../../auth/abstractions/account.service";
import { AuthService } from "../../auth/abstractions/auth.service";
import { AvatarService } from "../../auth/abstractions/avatar.service";
import { KeyConnectorService } from "../../auth/abstractions/key-connector.service";
import { InternalMasterPasswordServiceAbstraction } from "../../auth/abstractions/master-password.service.abstraction";
import { TokenService } from "../../auth/abstractions/token.service";
import { AuthenticationStatus } from "../../auth/enums/authentication-status";
import { ForceSetPasswordReason } from "../../auth/models/domain/force-set-password-reason";
import { DomainSettingsService } from "../../autofill/services/domain-settings.service";
import { BillingAccountProfileStateService } from "../../billing/abstractions";
import { KeyConnectorService } from "../../key-management/key-connector/abstractions/key-connector.service";
import { InternalMasterPasswordServiceAbstraction } from "../../key-management/master-password/abstractions/master-password.service.abstraction";
import { DomainsResponse } from "../../models/response/domains.response";
import { ProfileResponse } from "../../models/response/profile.response";
import { SendData } from "../../tools/send/models/data/send.data";

View File

@@ -12,6 +12,7 @@ import { LogoutReason } from "@bitwarden/auth/common";
import { ApiService as ApiServiceAbstraction } from "../abstractions/api.service";
import { OrganizationConnectionType } from "../admin-console/enums";
import { CollectionBulkDeleteRequest } from "../admin-console/models/request/collection-bulk-delete.request";
import { OrganizationSponsorshipCreateRequest } from "../admin-console/models/request/organization/organization-sponsorship-create.request";
import { OrganizationSponsorshipRedeemRequest } from "../admin-console/models/request/organization/organization-sponsorship-redeem.request";
import { OrganizationConnectionRequest } from "../admin-console/models/request/organization-connection.request";
@@ -43,7 +44,6 @@ import {
} from "../admin-console/models/response/provider/provider-user.response";
import { SelectionReadOnlyResponse } from "../admin-console/models/response/selection-read-only.response";
import { TokenService } from "../auth/abstractions/token.service";
import { AuthRequest } from "../auth/models/request/auth.request";
import { DeviceVerificationRequest } from "../auth/models/request/device-verification.request";
import { DisableTwoFactorAuthenticatorRequest } from "../auth/models/request/disable-two-factor-authenticator.request";
import { EmailTokenRequest } from "../auth/models/request/email-token.request";
@@ -54,19 +54,12 @@ import { SsoTokenRequest } from "../auth/models/request/identity-token/sso-token
import { TokenTwoFactorRequest } from "../auth/models/request/identity-token/token-two-factor.request";
import { UserApiTokenRequest } from "../auth/models/request/identity-token/user-api-token.request";
import { WebAuthnLoginTokenRequest } from "../auth/models/request/identity-token/webauthn-login-token.request";
import { KeyConnectorUserKeyRequest } from "../auth/models/request/key-connector-user-key.request";
import { PasswordHintRequest } from "../auth/models/request/password-hint.request";
import { PasswordRequest } from "../auth/models/request/password.request";
import { PasswordlessAuthRequest } from "../auth/models/request/passwordless-auth.request";
import { SecretVerificationRequest } from "../auth/models/request/secret-verification.request";
import { SetKeyConnectorKeyRequest } from "../auth/models/request/set-key-connector-key.request";
import { SetPasswordRequest } from "../auth/models/request/set-password.request";
import { TwoFactorEmailRequest } from "../auth/models/request/two-factor-email.request";
import { TwoFactorProviderRequest } from "../auth/models/request/two-factor-provider.request";
import { TwoFactorRecoveryRequest } from "../auth/models/request/two-factor-recovery.request";
import { UpdateProfileRequest } from "../auth/models/request/update-profile.request";
import { UpdateTdeOffboardingPasswordRequest } from "../auth/models/request/update-tde-offboarding-password.request";
import { UpdateTempPasswordRequest } from "../auth/models/request/update-temp-password.request";
import { UpdateTwoFactorAuthenticatorRequest } from "../auth/models/request/update-two-factor-authenticator.request";
import { UpdateTwoFactorDuoRequest } from "../auth/models/request/update-two-factor-duo.request";
import { UpdateTwoFactorEmailRequest } from "../auth/models/request/update-two-factor-email.request";
@@ -104,9 +97,10 @@ import { PlanResponse } from "../billing/models/response/plan.response";
import { SubscriptionResponse } from "../billing/models/response/subscription.response";
import { TaxInfoResponse } from "../billing/models/response/tax-info.response";
import { DeviceType } from "../enums";
import { KeyConnectorUserKeyRequest } from "../key-management/key-connector/models/key-connector-user-key.request";
import { SetKeyConnectorKeyRequest } from "../key-management/key-connector/models/set-key-connector-key.request";
import { VaultTimeoutSettingsService } from "../key-management/vault-timeout";
import { VaultTimeoutAction } from "../key-management/vault-timeout/enums/vault-timeout-action.enum";
import { CollectionBulkDeleteRequest } from "../models/request/collection-bulk-delete.request";
import { DeleteRecoverRequest } from "../models/request/delete-recover.request";
import { EventRequest } from "../models/request/event.request";
import { KdfRequest } from "../models/request/kdf.request";
@@ -279,22 +273,6 @@ export class ApiService implements ApiServiceAbstraction {
}
// TODO: PM-3519: Create and move to AuthRequest Api service
// TODO: PM-9724: Remove legacy auth request methods when we remove legacy LoginViaAuthRequestV1Components
async postAuthRequest(request: AuthRequest): Promise<AuthRequestResponse> {
const r = await this.send("POST", "/auth-requests/", request, false, true);
return new AuthRequestResponse(r);
}
async postAdminAuthRequest(request: AuthRequest): Promise<AuthRequestResponse> {
const r = await this.send("POST", "/auth-requests/admin-request", request, true, true);
return new AuthRequestResponse(r);
}
async getAuthResponse(id: string, accessCode: string): Promise<AuthRequestResponse> {
const path = `/auth-requests/${id}/response?code=${accessCode}`;
const r = await this.send("GET", path, null, false, true);
return new AuthRequestResponse(r);
}
async getAuthRequest(id: string): Promise<AuthRequestResponse> {
const path = `/auth-requests/${id}`;
const r = await this.send("GET", path, null, true, true);
@@ -374,14 +352,6 @@ export class ApiService implements ApiServiceAbstraction {
return this.send("POST", "/accounts/email", request, true, false);
}
postPassword(request: PasswordRequest): Promise<any> {
return this.send("POST", "/accounts/password", request, true, false);
}
setPassword(request: SetPasswordRequest): Promise<any> {
return this.send("POST", "/accounts/set-password", request, true, false);
}
postSetKeyConnectorKey(request: SetKeyConnectorKeyRequest): Promise<any> {
return this.send("POST", "/accounts/set-key-connector-key", request, true, false);
}
@@ -479,14 +449,6 @@ export class ApiService implements ApiServiceAbstraction {
return new ApiKeyResponse(r);
}
putUpdateTempPassword(request: UpdateTempPasswordRequest): Promise<any> {
return this.send("PUT", "/accounts/update-temp-password", request, true, false);
}
putUpdateTdeOffboardingPassword(request: UpdateTdeOffboardingPasswordRequest): Promise<void> {
return this.send("PUT", "/accounts/update-tde-offboarding-password", request, true, false);
}
postConvertToKeyConnector(): Promise<void> {
return this.send("POST", "/accounts/convert-to-key-connector", null, true, false);
}
@@ -1101,10 +1063,6 @@ export class ApiService implements ApiServiceAbstraction {
return new TwoFactorProviderResponse(r);
}
postTwoFactorRecover(request: TwoFactorRecoveryRequest): Promise<any> {
return this.send("POST", "/two-factor/recover", request, false, false);
}
postTwoFactorEmailSetup(request: TwoFactorEmailRequest): Promise<any> {
return this.send("POST", "/two-factor/send-email", request, true, false);
}

View File

@@ -89,7 +89,7 @@ describe("buildCipherIcon", () => {
expect(iconDetails).toEqual({
icon: "bwi-globe",
image: undefined,
image: null,
fallbackImage: "",
imageEnabled: false,
});
@@ -102,7 +102,7 @@ describe("buildCipherIcon", () => {
expect(iconDetails).toEqual({
icon: "bwi-globe",
image: undefined,
image: null,
fallbackImage: "",
imageEnabled: true,
});

View File

@@ -2,9 +2,23 @@ import { Utils } from "../../platform/misc/utils";
import { CipherType } from "../enums/cipher-type";
import { CipherView } from "../models/view/cipher.view";
export function buildCipherIcon(iconsServerUrl: string, cipher: CipherView, showFavicon: boolean) {
let icon;
let image;
export interface CipherIconDetails {
imageEnabled: boolean;
image: string | null;
/**
* @deprecated Fallback to `icon` instead which will default to "bwi-globe" if no other icon is applicable.
*/
fallbackImage: string;
icon: string;
}
export function buildCipherIcon(
iconsServerUrl: string | null,
cipher: CipherView,
showFavicon: boolean,
): CipherIconDetails {
let icon: string = "bwi-globe";
let image: string | null = null;
let fallbackImage = "";
const cardIcons: Record<string, string> = {
Visa: "card-visa",
@@ -18,6 +32,10 @@ export function buildCipherIcon(iconsServerUrl: string, cipher: CipherView, show
RuPay: "card-ru-pay",
};
if (iconsServerUrl == null) {
showFavicon = false;
}
switch (cipher.type) {
case CipherType.Login:
icon = "bwi-globe";
@@ -53,9 +71,7 @@ export function buildCipherIcon(iconsServerUrl: string, cipher: CipherView, show
try {
image = `${iconsServerUrl}/${Utils.getHostname(hostnameUri)}/icon.png`;
fallbackImage = "images/bwi-globe.png";
// FIXME: Remove when updating file. Eslint update
// eslint-disable-next-line @typescript-eslint/no-unused-vars
} catch (e) {
} catch {
// Ignore error since the fallback icon will be shown if image is null.
}
}

View File

@@ -0,0 +1,21 @@
import { Jsonify } from "type-fest";
import { BaseResponse } from "../../../models/response/base.response";
export class CipherPermissionsApi extends BaseResponse {
delete: boolean = false;
restore: boolean = false;
constructor(data: any = null) {
super(data);
if (data == null) {
return;
}
this.delete = this.getResponseProperty("Delete");
this.restore = this.getResponseProperty("Restore");
}
static fromJSON(obj: Jsonify<CipherPermissionsApi>) {
return Object.assign(new CipherPermissionsApi(), obj);
}
}

View File

@@ -4,6 +4,7 @@ import { Jsonify } from "type-fest";
import { CipherRepromptType } from "../../enums/cipher-reprompt-type";
import { CipherType } from "../../enums/cipher-type";
import { CipherPermissionsApi } from "../api/cipher-permissions.api";
import { CipherResponse } from "../response/cipher.response";
import { AttachmentData } from "./attachment.data";
@@ -21,6 +22,7 @@ export class CipherData {
folderId: string;
edit: boolean;
viewPassword: boolean;
permissions: CipherPermissionsApi;
organizationUseTotp: boolean;
favorite: boolean;
revisionDate: string;
@@ -51,6 +53,7 @@ export class CipherData {
this.folderId = response.folderId;
this.edit = response.edit;
this.viewPassword = response.viewPassword;
this.permissions = response.permissions;
this.organizationUseTotp = response.organizationUseTotp;
this.favorite = response.favorite;
this.revisionDate = response.revisionDate;
@@ -95,6 +98,8 @@ export class CipherData {
}
static fromJSON(obj: Jsonify<CipherData>) {
return Object.assign(new CipherData(), obj);
const result = Object.assign(new CipherData(), obj);
result.permissions = CipherPermissionsApi.fromJSON(obj.permissions);
return result;
}
}

View File

@@ -26,6 +26,7 @@ import { SecureNote } from "../../models/domain/secure-note";
import { CardView } from "../../models/view/card.view";
import { IdentityView } from "../../models/view/identity.view";
import { LoginView } from "../../models/view/login.view";
import { CipherPermissionsApi } from "../api/cipher-permissions.api";
describe("Cipher DTO", () => {
it("Convert from empty CipherData", () => {
@@ -54,6 +55,7 @@ describe("Cipher DTO", () => {
fields: null,
passwordHistory: null,
key: null,
permissions: undefined,
});
});
@@ -75,6 +77,7 @@ describe("Cipher DTO", () => {
notes: "EncryptedString",
creationDate: "2022-01-01T12:00:00.000Z",
deletedDate: null,
permissions: new CipherPermissionsApi(),
reprompt: CipherRepromptType.None,
key: "EncryptedString",
login: {
@@ -149,6 +152,7 @@ describe("Cipher DTO", () => {
localData: null,
creationDate: new Date("2022-01-01T12:00:00.000Z"),
deletedDate: null,
permissions: new CipherPermissionsApi(),
reprompt: 0,
key: { encryptedString: "EncryptedString", encryptionType: 0 },
login: {
@@ -228,6 +232,7 @@ describe("Cipher DTO", () => {
cipher.deletedDate = null;
cipher.reprompt = CipherRepromptType.None;
cipher.key = mockEnc("EncKey");
cipher.permissions = new CipherPermissionsApi();
const loginView = new LoginView();
loginView.username = "username";
@@ -270,6 +275,7 @@ describe("Cipher DTO", () => {
deletedDate: null,
reprompt: 0,
localData: undefined,
permissions: new CipherPermissionsApi(),
});
});
});
@@ -297,6 +303,7 @@ describe("Cipher DTO", () => {
secureNote: {
type: SecureNoteType.Generic,
},
permissions: new CipherPermissionsApi(),
};
});
@@ -326,6 +333,7 @@ describe("Cipher DTO", () => {
fields: null,
passwordHistory: null,
key: { encryptedString: "EncKey", encryptionType: 0 },
permissions: new CipherPermissionsApi(),
});
});
@@ -353,6 +361,7 @@ describe("Cipher DTO", () => {
cipher.secureNote = new SecureNote();
cipher.secureNote.type = SecureNoteType.Generic;
cipher.key = mockEnc("EncKey");
cipher.permissions = new CipherPermissionsApi();
const keyService = mock<KeyService>();
const encryptService = mock<EncryptService>();
@@ -387,6 +396,7 @@ describe("Cipher DTO", () => {
deletedDate: null,
reprompt: 0,
localData: undefined,
permissions: new CipherPermissionsApi(),
});
});
});
@@ -409,6 +419,7 @@ describe("Cipher DTO", () => {
notes: "EncryptedString",
creationDate: "2022-01-01T12:00:00.000Z",
deletedDate: null,
permissions: new CipherPermissionsApi(),
reprompt: CipherRepromptType.None,
card: {
cardholderName: "EncryptedString",
@@ -455,6 +466,7 @@ describe("Cipher DTO", () => {
fields: null,
passwordHistory: null,
key: { encryptedString: "EncKey", encryptionType: 0 },
permissions: new CipherPermissionsApi(),
});
});
@@ -480,6 +492,7 @@ describe("Cipher DTO", () => {
cipher.deletedDate = null;
cipher.reprompt = CipherRepromptType.None;
cipher.key = mockEnc("EncKey");
cipher.permissions = new CipherPermissionsApi();
const cardView = new CardView();
cardView.cardholderName = "cardholderName";
@@ -522,6 +535,7 @@ describe("Cipher DTO", () => {
deletedDate: null,
reprompt: 0,
localData: undefined,
permissions: new CipherPermissionsApi(),
});
});
});
@@ -544,6 +558,7 @@ describe("Cipher DTO", () => {
notes: "EncryptedString",
creationDate: "2022-01-01T12:00:00.000Z",
deletedDate: null,
permissions: new CipherPermissionsApi(),
reprompt: CipherRepromptType.None,
key: "EncKey",
identity: {
@@ -614,6 +629,7 @@ describe("Cipher DTO", () => {
fields: null,
passwordHistory: null,
key: { encryptedString: "EncKey", encryptionType: 0 },
permissions: new CipherPermissionsApi(),
});
});
@@ -639,6 +655,7 @@ describe("Cipher DTO", () => {
cipher.deletedDate = null;
cipher.reprompt = CipherRepromptType.None;
cipher.key = mockEnc("EncKey");
cipher.permissions = new CipherPermissionsApi();
const identityView = new IdentityView();
identityView.firstName = "firstName";
@@ -681,6 +698,7 @@ describe("Cipher DTO", () => {
deletedDate: null,
reprompt: 0,
localData: undefined,
permissions: new CipherPermissionsApi(),
});
});
});

View File

@@ -10,6 +10,7 @@ import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-cr
import { InitializerKey } from "../../../platform/services/cryptography/initializer-key";
import { CipherRepromptType } from "../../enums/cipher-reprompt-type";
import { CipherType } from "../../enums/cipher-type";
import { CipherPermissionsApi } from "../api/cipher-permissions.api";
import { CipherData } from "../data/cipher.data";
import { LocalData } from "../data/local.data";
import { AttachmentView } from "../view/attachment.view";
@@ -39,6 +40,7 @@ export class Cipher extends Domain implements Decryptable<CipherView> {
organizationUseTotp: boolean;
edit: boolean;
viewPassword: boolean;
permissions: CipherPermissionsApi;
revisionDate: Date;
localData: LocalData;
login: Login;
@@ -84,6 +86,7 @@ export class Cipher extends Domain implements Decryptable<CipherView> {
} else {
this.viewPassword = true; // Default for already synced Ciphers without viewPassword
}
this.permissions = obj.permissions;
this.revisionDate = obj.revisionDate != null ? new Date(obj.revisionDate) : null;
this.collectionIds = obj.collectionIds;
this.localData = localData;
@@ -244,6 +247,7 @@ export class Cipher extends Domain implements Decryptable<CipherView> {
c.deletedDate = this.deletedDate != null ? this.deletedDate.toISOString() : null;
c.reprompt = this.reprompt;
c.key = this.key?.encryptedString;
c.permissions = this.permissions;
this.buildDataModel(this, c, {
name: null,

View File

@@ -3,6 +3,7 @@
import { BaseResponse } from "../../../models/response/base.response";
import { CipherRepromptType } from "../../enums/cipher-reprompt-type";
import { CardApi } from "../api/card.api";
import { CipherPermissionsApi } from "../api/cipher-permissions.api";
import { FieldApi } from "../api/field.api";
import { IdentityApi } from "../api/identity.api";
import { LoginApi } from "../api/login.api";
@@ -28,6 +29,7 @@ export class CipherResponse extends BaseResponse {
favorite: boolean;
edit: boolean;
viewPassword: boolean;
permissions: CipherPermissionsApi;
organizationUseTotp: boolean;
revisionDate: string;
attachments: AttachmentResponse[];
@@ -53,6 +55,7 @@ export class CipherResponse extends BaseResponse {
} else {
this.viewPassword = this.getResponseProperty("ViewPassword");
}
this.permissions = new CipherPermissionsApi(this.getResponseProperty("Permissions"));
this.organizationUseTotp = this.getResponseProperty("OrganizationUseTotp");
this.revisionDate = this.getResponseProperty("RevisionDate");
this.collectionIds = this.getResponseProperty("CollectionIds");

View File

@@ -6,6 +6,7 @@ import { InitializerKey } from "../../../platform/services/cryptography/initiali
import { DeepJsonify } from "../../../types/deep-jsonify";
import { CipherType, LinkedIdType } from "../../enums";
import { CipherRepromptType } from "../../enums/cipher-reprompt-type";
import { CipherPermissionsApi } from "../api/cipher-permissions.api";
import { LocalData } from "../data/local.data";
import { Cipher } from "../domain/cipher";
@@ -29,6 +30,7 @@ export class CipherView implements View, InitializerMetadata {
type: CipherType = null;
favorite = false;
organizationUseTotp = false;
permissions: CipherPermissionsApi = new CipherPermissionsApi();
edit = false;
viewPassword = true;
localData: LocalData;
@@ -63,6 +65,7 @@ export class CipherView implements View, InitializerMetadata {
this.organizationUseTotp = c.organizationUseTotp;
this.edit = c.edit;
this.viewPassword = c.viewPassword;
this.permissions = c.permissions;
this.type = c.type;
this.localData = c.localData;
this.collectionIds = c.collectionIds;

View File

@@ -8,6 +8,8 @@ import { Utils } from "@bitwarden/common/platform/misc/utils";
import { CollectionId, UserId } from "@bitwarden/common/types/guid";
import { FakeAccountService, mockAccountServiceWith } from "../../../spec";
import { ConfigService } from "../../platform/abstractions/config/config.service";
import { CipherPermissionsApi } from "../models/api/cipher-permissions.api";
import { CipherView } from "../models/view/cipher.view";
import {
@@ -20,6 +22,7 @@ describe("CipherAuthorizationService", () => {
const mockCollectionService = mock<CollectionService>();
const mockOrganizationService = mock<OrganizationService>();
const mockConfigService = mock<ConfigService>();
const mockUserId = Utils.newGuid() as UserId;
let mockAccountService: FakeAccountService;
@@ -28,10 +31,12 @@ describe("CipherAuthorizationService", () => {
organizationId: string | null,
collectionIds: string[],
edit: boolean = true,
permissions: CipherPermissionsApi = new CipherPermissionsApi(),
) => ({
organizationId,
collectionIds,
edit,
permissions,
});
const createMockCollection = (id: string, manage: boolean) => ({
@@ -63,7 +68,78 @@ describe("CipherAuthorizationService", () => {
mockCollectionService,
mockOrganizationService,
mockAccountService,
mockConfigService,
);
mockConfigService.getFeatureFlag$.mockReturnValue(of(false));
});
describe("canRestoreCipher$", () => {
it("should return true if isAdminConsoleAction and cipher is unassigned", (done) => {
const cipher = createMockCipher("org1", []) as CipherView;
const organization = createMockOrganization({ canEditUnassignedCiphers: true });
mockOrganizationService.organizations$.mockReturnValue(
of([organization]) as Observable<Organization[]>,
);
cipherAuthorizationService.canRestoreCipher$(cipher, true).subscribe((result) => {
expect(result).toBe(true);
done();
});
});
it("should return true if isAdminConsleAction and user can edit all ciphers in the org", (done) => {
const cipher = createMockCipher("org1", ["col1"]) as CipherView;
const organization = createMockOrganization({ canEditAllCiphers: true });
mockOrganizationService.organizations$.mockReturnValue(
of([organization]) as Observable<Organization[]>,
);
cipherAuthorizationService.canRestoreCipher$(cipher, true).subscribe((result) => {
expect(result).toBe(true);
expect(mockOrganizationService.organizations$).toHaveBeenCalledWith(mockUserId);
done();
});
});
it("should return false if isAdminConsoleAction is true but user does not have permission to edit unassigned ciphers", (done) => {
const cipher = createMockCipher("org1", []) as CipherView;
const organization = createMockOrganization({ canEditUnassignedCiphers: false });
mockOrganizationService.organizations$.mockReturnValue(of([organization] as Organization[]));
cipherAuthorizationService.canRestoreCipher$(cipher, true).subscribe((result) => {
expect(result).toBe(false);
done();
});
});
it("should return false if cipher.permission.restore is false and is not an admin action", (done) => {
const cipher = createMockCipher("org1", [], true, {
restore: false,
} as CipherPermissionsApi) as CipherView;
const organization = createMockOrganization();
mockOrganizationService.organizations$.mockReturnValue(of([organization] as Organization[]));
cipherAuthorizationService.canRestoreCipher$(cipher, false).subscribe((result) => {
expect(result).toBe(false);
expect(mockCollectionService.decryptedCollectionViews$).not.toHaveBeenCalled();
done();
});
});
it("should return true if cipher.permission.restore is true and is not an admin action", (done) => {
const cipher = createMockCipher("org1", [], true, {
restore: true,
} as CipherPermissionsApi) as CipherView;
const organization = createMockOrganization();
mockOrganizationService.organizations$.mockReturnValue(of([organization] as Organization[]));
cipherAuthorizationService.canRestoreCipher$(cipher, false).subscribe((result) => {
expect(result).toBe(true);
expect(mockCollectionService.decryptedCollectionViews$).not.toHaveBeenCalled();
done();
});
});
});
describe("canDeleteCipher$", () => {
@@ -213,6 +289,34 @@ describe("CipherAuthorizationService", () => {
done();
});
});
it("should return true if feature flag enabled and cipher.permissions.delete is true", (done) => {
const cipher = createMockCipher("org1", [], true, {
delete: true,
} as CipherPermissionsApi) as CipherView;
const organization = createMockOrganization();
mockOrganizationService.organizations$.mockReturnValue(of([organization] as Organization[]));
mockConfigService.getFeatureFlag$.mockReturnValue(of(true));
cipherAuthorizationService.canDeleteCipher$(cipher, [], false).subscribe((result) => {
expect(result).toBe(true);
expect(mockCollectionService.decryptedCollectionViews$).not.toHaveBeenCalled();
done();
});
});
it("should return false if feature flag enabled and cipher.permissions.delete is false", (done) => {
const cipher = createMockCipher("org1", []) as CipherView;
const organization = createMockOrganization();
mockOrganizationService.organizations$.mockReturnValue(of([organization] as Organization[]));
mockConfigService.getFeatureFlag$.mockReturnValue(of(true));
cipherAuthorizationService.canDeleteCipher$(cipher, [], false).subscribe((result) => {
expect(result).toBe(false);
expect(mockCollectionService.decryptedCollectionViews$).not.toHaveBeenCalled();
done();
});
});
});
describe("canCloneCipher$", () => {

View File

@@ -1,12 +1,13 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { map, Observable, of, shareReplay, switchMap } from "rxjs";
import { combineLatest, map, Observable, of, shareReplay, switchMap } from "rxjs";
import { CollectionService } from "@bitwarden/admin-console/common";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { CollectionId } from "@bitwarden/common/types/guid";
import { getUserId } from "../../auth/services/account.service";
import { FeatureFlag } from "../../enums/feature-flag.enum";
import { Cipher } from "../models/domain/cipher";
import { CipherView } from "../models/view/cipher.view";
@@ -28,12 +29,25 @@ export abstract class CipherAuthorizationService {
*
* @returns {Observable<boolean>} - An observable that emits a boolean value indicating if the user can delete the cipher.
*/
canDeleteCipher$: (
abstract canDeleteCipher$: (
cipher: CipherLike,
allowedCollections?: CollectionId[],
isAdminConsoleAction?: boolean,
) => Observable<boolean>;
/**
* Determines if the user can restore the specified cipher.
*
* @param {CipherLike} cipher - The cipher object to evaluate for restore permissions.
* @param {boolean} isAdminConsoleAction - Optional. A flag indicating if the action is being performed from the admin console.
*
* @returns {Observable<boolean>} - An observable that emits a boolean value indicating if the user can restore the cipher.
*/
abstract canRestoreCipher$: (
cipher: CipherLike,
isAdminConsoleAction?: boolean,
) => Observable<boolean>;
/**
* Determines if the user can clone the specified cipher.
*
@@ -42,7 +56,10 @@ export abstract class CipherAuthorizationService {
*
* @returns {Observable<boolean>} - An observable that emits a boolean value indicating if the user can clone the cipher.
*/
canCloneCipher$: (cipher: CipherLike, isAdminConsoleAction?: boolean) => Observable<boolean>;
abstract canCloneCipher$: (
cipher: CipherLike,
isAdminConsoleAction?: boolean,
) => Observable<boolean>;
}
/**
@@ -53,13 +70,16 @@ export class DefaultCipherAuthorizationService implements CipherAuthorizationSer
private collectionService: CollectionService,
private organizationService: OrganizationService,
private accountService: AccountService,
private configService: ConfigService,
) {}
private organization$ = (cipher: CipherLike) =>
this.accountService.activeAccount$.pipe(
switchMap((account) => this.organizationService.organizations$(account?.id)),
getUserId,
switchMap((userId) => this.organizationService.organizations$(userId)),
map((orgs) => orgs.find((org) => org.id === cipher.organizationId)),
);
/**
*
* {@link CipherAuthorizationService.canDeleteCipher$}
@@ -69,12 +89,11 @@ export class DefaultCipherAuthorizationService implements CipherAuthorizationSer
allowedCollections?: CollectionId[],
isAdminConsoleAction?: boolean,
): Observable<boolean> {
if (cipher.organizationId == null) {
return of(true);
}
return this.organization$(cipher).pipe(
switchMap((organization) => {
return combineLatest([
this.organization$(cipher),
this.configService.getFeatureFlag$(FeatureFlag.LimitItemDeletion),
]).pipe(
switchMap(([organization, featureFlagEnabled]) => {
if (isAdminConsoleAction) {
// If the user is an admin, they can delete an unassigned cipher
if (!cipher.collectionIds || cipher.collectionIds.length === 0) {
@@ -86,6 +105,14 @@ export class DefaultCipherAuthorizationService implements CipherAuthorizationSer
}
}
if (featureFlagEnabled) {
return of(cipher.permissions.delete);
}
if (cipher.organizationId == null) {
return of(true);
}
return this.collectionService
.decryptedCollectionViews$(cipher.collectionIds as CollectionId[])
.pipe(
@@ -93,7 +120,7 @@ export class DefaultCipherAuthorizationService implements CipherAuthorizationSer
const shouldFilter = allowedCollections?.some(Boolean);
const collections = shouldFilter
? allCollections.filter((c) => allowedCollections.includes(c.id as CollectionId))
? allCollections.filter((c) => allowedCollections?.includes(c.id as CollectionId))
: allCollections;
return collections.some((collection) => collection.manage);
@@ -103,6 +130,29 @@ export class DefaultCipherAuthorizationService implements CipherAuthorizationSer
);
}
/**
*
* {@link CipherAuthorizationService.canRestoreCipher$}
*/
canRestoreCipher$(cipher: CipherLike, isAdminConsoleAction?: boolean): Observable<boolean> {
return this.organization$(cipher).pipe(
map((organization) => {
if (isAdminConsoleAction) {
// If the user is an admin, they can restore an unassigned cipher
if (!cipher.collectionIds || cipher.collectionIds.length === 0) {
return organization?.canEditUnassignedCiphers === true;
}
if (organization?.canEditAllCiphers) {
return true;
}
}
return cipher.permissions.restore;
}),
);
}
/**
* {@link CipherAuthorizationService.canCloneCipher$}
*/
@@ -116,6 +166,7 @@ export class DefaultCipherAuthorizationService implements CipherAuthorizationSer
// Admins and custom users can always clone when in the Admin Console
if (
isAdminConsoleAction &&
organization &&
(organization.isAdmin || organization.permissions?.editAnyCollection)
) {
return of(true);

View File

@@ -27,6 +27,7 @@ import { CipherFileUploadService } from "../abstractions/file-upload/cipher-file
import { FieldType } from "../enums";
import { CipherRepromptType } from "../enums/cipher-reprompt-type";
import { CipherType } from "../enums/cipher-type";
import { CipherPermissionsApi } from "../models/api/cipher-permissions.api";
import { CipherData } from "../models/data/cipher.data";
import { Cipher } from "../models/domain/cipher";
import { CipherCreateRequest } from "../models/request/cipher-create.request";
@@ -57,6 +58,7 @@ const cipherData: CipherData = {
notes: "EncryptedString",
creationDate: "2022-01-01T12:00:00.000Z",
deletedDate: null,
permissions: new CipherPermissionsApi(),
key: "EncKey",
reprompt: CipherRepromptType.None,
login: {