1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-20 11:24:07 +00:00

Merge branch 'main' of https://github.com/bitwarden/clients into vault/pm-27632/sdk-cipher-ops

This commit is contained in:
Nik Gilmore
2026-01-06 15:13:34 -08:00
88 changed files with 3594 additions and 1074 deletions

View File

@@ -5,7 +5,6 @@ import { CollectionAdminView, CollectionService } from "@bitwarden/admin-console
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { StateProvider } from "@bitwarden/common/platform/state";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
@@ -35,7 +34,6 @@ export class VaultFilterService extends BaseVaultFilterService implements OnDest
stateProvider: StateProvider,
collectionService: CollectionService,
accountService: AccountService,
configService: ConfigService,
) {
super(
organizationService,
@@ -46,7 +44,6 @@ export class VaultFilterService extends BaseVaultFilterService implements OnDest
stateProvider,
collectionService,
accountService,
configService,
);
}

View File

@@ -6,7 +6,6 @@ import { firstValueFrom, map, Observable, switchMap } from "rxjs";
import {
OrganizationUserApiService,
OrganizationUserBulkConfirmRequest,
OrganizationUserBulkPublicKeyResponse,
OrganizationUserBulkResponse,
OrganizationUserService,
@@ -15,10 +14,8 @@ import { OrganizationUserStatusType } from "@bitwarden/common/admin-console/enum
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { ProviderUserBulkPublicKeyResponse } from "@bitwarden/common/admin-console/models/response/provider/provider-user-bulk-public-key.response";
import { ProviderUserBulkResponse } from "@bitwarden/common/admin-console/models/response/provider/provider-user-bulk.response";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
import { ListResponse } from "@bitwarden/common/models/response/list.response";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
import { StateProvider } from "@bitwarden/common/platform/state";
@@ -54,7 +51,6 @@ export class BulkConfirmDialogComponent extends BaseBulkConfirmComponent {
protected i18nService: I18nService,
private stateProvider: StateProvider,
private organizationUserService: OrganizationUserService,
private configService: ConfigService,
) {
super(keyService, encryptService, i18nService);
@@ -84,19 +80,9 @@ export class BulkConfirmDialogComponent extends BaseBulkConfirmComponent {
protected postConfirmRequest = async (
userIdsWithKeys: { id: string; key: string }[],
): Promise<ListResponse<OrganizationUserBulkResponse | ProviderUserBulkResponse>> => {
if (
await firstValueFrom(this.configService.getFeatureFlag$(FeatureFlag.CreateDefaultLocation))
) {
return await firstValueFrom(
this.organizationUserService.bulkConfirmUsers(this.organization, userIdsWithKeys),
);
} else {
const request = new OrganizationUserBulkConfirmRequest(userIdsWithKeys);
return await this.organizationUserApiService.postOrganizationUserBulkConfirm(
this.organization.id,
request,
);
}
return await firstValueFrom(
this.organizationUserService.bulkConfirmUsers(this.organization, userIdsWithKeys),
);
};
static open(dialogService: DialogService, config: DialogConfig<BulkConfirmDialogParams>) {

View File

@@ -12,15 +12,10 @@ import {
} from "@bitwarden/common/admin-console/enums";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { OrganizationMetadataServiceAbstraction } from "@bitwarden/common/billing/abstractions/organization-metadata.service.abstraction";
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string";
import { ListResponse } from "@bitwarden/common/models/response/list.response";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec";
import { OrganizationId, UserId } from "@bitwarden/common/types/guid";
import { OrgKey } from "@bitwarden/common/types/key";
import { newGuid } from "@bitwarden/guid";
import { KeyService } from "@bitwarden/key-management";
import { OrganizationUserView } from "../../../core/views/organization-user.view";
@@ -30,13 +25,9 @@ describe("MemberActionsService", () => {
let service: MemberActionsService;
let organizationUserApiService: MockProxy<OrganizationUserApiService>;
let organizationUserService: MockProxy<OrganizationUserService>;
let keyService: MockProxy<KeyService>;
let encryptService: MockProxy<EncryptService>;
let configService: MockProxy<ConfigService>;
let accountService: FakeAccountService;
let organizationMetadataService: MockProxy<OrganizationMetadataServiceAbstraction>;
const userId = newGuid() as UserId;
const organizationId = newGuid() as OrganizationId;
const userIdToManage = newGuid();
@@ -46,10 +37,7 @@ describe("MemberActionsService", () => {
beforeEach(() => {
organizationUserApiService = mock<OrganizationUserApiService>();
organizationUserService = mock<OrganizationUserService>();
keyService = mock<KeyService>();
encryptService = mock<EncryptService>();
configService = mock<ConfigService>();
accountService = mockAccountServiceWith(userId);
organizationMetadataService = mock<OrganizationMetadataServiceAbstraction>();
mockOrganization = {
@@ -71,10 +59,7 @@ describe("MemberActionsService", () => {
service = new MemberActionsService(
organizationUserApiService,
organizationUserService,
keyService,
encryptService,
configService,
accountService,
organizationMetadataService,
);
});
@@ -242,8 +227,7 @@ describe("MemberActionsService", () => {
describe("confirmUser", () => {
const publicKey = new Uint8Array([1, 2, 3, 4, 5]);
it("should confirm user using new flow when feature flag is enabled", async () => {
configService.getFeatureFlag$.mockReturnValue(of(true));
it("should confirm user", async () => {
organizationUserService.confirmUser.mockReturnValue(of(undefined));
const result = await service.confirmUser(mockOrgUser, publicKey, mockOrganization);
@@ -257,44 +241,7 @@ describe("MemberActionsService", () => {
expect(organizationUserApiService.postOrganizationUserConfirm).not.toHaveBeenCalled();
});
it("should confirm user using exising flow when feature flag is disabled", async () => {
configService.getFeatureFlag$.mockReturnValue(of(false));
const mockOrgKey = mock<OrgKey>();
const mockOrgKeys = { [organizationId]: mockOrgKey };
keyService.orgKeys$.mockReturnValue(of(mockOrgKeys));
const mockEncryptedKey = new EncString("encrypted-key-data");
encryptService.encapsulateKeyUnsigned.mockResolvedValue(mockEncryptedKey);
organizationUserApiService.postOrganizationUserConfirm.mockResolvedValue(undefined);
const result = await service.confirmUser(mockOrgUser, publicKey, mockOrganization);
expect(result).toEqual({ success: true });
expect(keyService.orgKeys$).toHaveBeenCalledWith(userId);
expect(encryptService.encapsulateKeyUnsigned).toHaveBeenCalledWith(mockOrgKey, publicKey);
expect(organizationUserApiService.postOrganizationUserConfirm).toHaveBeenCalledWith(
organizationId,
userIdToManage,
expect.objectContaining({
key: "encrypted-key-data",
}),
);
});
it("should handle missing organization keys", async () => {
configService.getFeatureFlag$.mockReturnValue(of(false));
keyService.orgKeys$.mockReturnValue(of({}));
const result = await service.confirmUser(mockOrgUser, publicKey, mockOrganization);
expect(result.success).toBe(false);
expect(result.error).toContain("Organization keys not found");
});
it("should handle confirm errors", async () => {
configService.getFeatureFlag$.mockReturnValue(of(true));
const errorMessage = "Confirm failed";
organizationUserService.confirmUser.mockImplementation(() => {
throw new Error(errorMessage);

View File

@@ -1,10 +1,9 @@
import { Injectable } from "@angular/core";
import { firstValueFrom, switchMap, map } from "rxjs";
import { firstValueFrom } from "rxjs";
import {
OrganizationUserApiService,
OrganizationUserBulkResponse,
OrganizationUserConfirmRequest,
OrganizationUserService,
} from "@bitwarden/admin-console/common";
import {
@@ -12,14 +11,10 @@ import {
OrganizationUserStatusType,
} from "@bitwarden/common/admin-console/enums";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { OrganizationMetadataServiceAbstraction } from "@bitwarden/common/billing/abstractions/organization-metadata.service.abstraction";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
import { ListResponse } from "@bitwarden/common/models/response/list.response";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { KeyService } from "@bitwarden/key-management";
import { UserId } from "@bitwarden/user-core";
import { OrganizationUserView } from "../../../core/views/organization-user.view";
@@ -38,15 +33,10 @@ export interface BulkActionResult {
@Injectable()
export class MemberActionsService {
private userId$ = this.accountService.activeAccount$.pipe(getUserId);
constructor(
private organizationUserApiService: OrganizationUserApiService,
private organizationUserService: OrganizationUserService,
private keyService: KeyService,
private encryptService: EncryptService,
private configService: ConfigService,
private accountService: AccountService,
private organizationMetadataService: OrganizationMetadataServiceAbstraction,
) {}
@@ -128,37 +118,9 @@ export class MemberActionsService {
organization: Organization,
): Promise<MemberActionResult> {
try {
if (
await firstValueFrom(this.configService.getFeatureFlag$(FeatureFlag.CreateDefaultLocation))
) {
await firstValueFrom(
this.organizationUserService.confirmUser(organization, user.id, publicKey),
);
} else {
const request = await firstValueFrom(
this.userId$.pipe(
switchMap((userId) => this.keyService.orgKeys$(userId)),
map((orgKeys) => {
if (orgKeys == null || orgKeys[organization.id] == null) {
throw new Error("Organization keys not found for provided User.");
}
return orgKeys[organization.id];
}),
switchMap((orgKey) => this.encryptService.encapsulateKeyUnsigned(orgKey, publicKey)),
map((encKey) => {
const req = new OrganizationUserConfirmRequest();
req.key = encKey.encryptedString;
return req;
}),
),
);
await this.organizationUserApiService.postOrganizationUserConfirm(
organization.id,
user.id,
request,
);
}
await firstValueFrom(
this.organizationUserService.confirmUser(organization, user.id, publicKey),
);
return { success: true };
} catch (error) {
return { success: false, error: (error as Error).message ?? String(error) };

View File

@@ -30,7 +30,6 @@ import { PolicyType } from "@bitwarden/common/admin-console/enums";
import { PolicyRequest } from "@bitwarden/common/admin-console/models/request/policy.request";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { getById } from "@bitwarden/common/platform/misc";
import {
@@ -115,7 +114,6 @@ export class AutoConfirmPolicyDialogComponent
formBuilder: FormBuilder,
dialogRef: DialogRef<PolicyEditDialogResult>,
toastService: ToastService,
configService: ConfigService,
keyService: KeyService,
private organizationService: OrganizationService,
private policyService: PolicyService,
@@ -131,7 +129,6 @@ export class AutoConfirmPolicyDialogComponent
formBuilder,
dialogRef,
toastService,
configService,
keyService,
);

View File

@@ -1,9 +1,8 @@
import { ChangeDetectionStrategy, Component } from "@angular/core";
import { map, Observable } from "rxjs";
import { of, Observable } from "rxjs";
import { PolicyType } from "@bitwarden/common/admin-console/enums";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { SharedModule } from "../../../../shared";
@@ -16,9 +15,8 @@ export class OrganizationDataOwnershipPolicy extends BasePolicyEditDefinition {
component = OrganizationDataOwnershipPolicyComponent;
display$(organization: Organization, configService: ConfigService): Observable<boolean> {
return configService
.getFeatureFlag$(FeatureFlag.CreateDefaultLocation)
.pipe(map((enabled) => !enabled));
// TODO Remove this entire component upon verifying that it can be deleted due to its sole reliance of the CreateDefaultLocation feature flag
return of(false);
}
}

View File

@@ -1,12 +1,9 @@
import { ChangeDetectionStrategy, Component, OnInit, TemplateRef, ViewChild } from "@angular/core";
import { lastValueFrom, Observable } from "rxjs";
import { lastValueFrom } from "rxjs";
import { PolicyType } from "@bitwarden/common/admin-console/enums";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { PolicyRequest } from "@bitwarden/common/admin-console/models/request/policy.request";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { OrgKey } from "@bitwarden/common/types/key";
import { CenterPositionStrategy, DialogService } from "@bitwarden/components";
@@ -28,10 +25,6 @@ export class vNextOrganizationDataOwnershipPolicy extends BasePolicyEditDefiniti
type = PolicyType.OrganizationDataOwnership;
component = vNextOrganizationDataOwnershipPolicyComponent;
showDescription = false;
override display$(organization: Organization, configService: ConfigService): Observable<boolean> {
return configService.getFeatureFlag$(FeatureFlag.CreateDefaultLocation);
}
}
@Component({

View File

@@ -14,8 +14,6 @@ import { PolicyType } from "@bitwarden/common/admin-console/enums";
import { PolicyResponse } from "@bitwarden/common/admin-console/models/response/policy.response";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { OrganizationId } from "@bitwarden/common/types/guid";
import {
@@ -75,7 +73,6 @@ export class PolicyEditDialogComponent implements AfterViewInit {
private formBuilder: FormBuilder,
protected dialogRef: DialogRef<PolicyEditDialogResult>,
protected toastService: ToastService,
private configService: ConfigService,
private keyService: KeyService,
) {}
@@ -132,10 +129,7 @@ export class PolicyEditDialogComponent implements AfterViewInit {
}
try {
if (
this.policyComponent instanceof vNextOrganizationDataOwnershipPolicyComponent &&
(await this.isVNextEnabled())
) {
if (this.policyComponent instanceof vNextOrganizationDataOwnershipPolicyComponent) {
await this.handleVNextSubmission(this.policyComponent);
} else {
await this.handleStandardSubmission();
@@ -154,14 +148,6 @@ export class PolicyEditDialogComponent implements AfterViewInit {
}
};
private async isVNextEnabled(): Promise<boolean> {
const isVNextFeatureEnabled = await firstValueFrom(
this.configService.getFeatureFlag$(FeatureFlag.CreateDefaultLocation),
);
return isVNextFeatureEnabled;
}
private async handleStandardSubmission(): Promise<void> {
if (!this.policyComponent) {
throw new Error("PolicyComponent not initialized.");

View File

@@ -59,9 +59,11 @@ import {
} from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { AccountApiService as AccountApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/account-api.service";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { AuthRequestAnsweringService } from "@bitwarden/common/auth/abstractions/auth-request-answering/auth-request-answering.service.abstraction";
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
import { MasterPasswordApiService } from "@bitwarden/common/auth/abstractions/master-password-api.service.abstraction";
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
import { NoopAuthRequestAnsweringService } from "@bitwarden/common/auth/services/auth-request-answering/noop-auth-request-answering.service";
import { OrganizationInviteService } from "@bitwarden/common/auth/services/organization-invite/organization-invite.service";
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
import { ClientType } from "@bitwarden/common/enums";
@@ -483,6 +485,11 @@ const safeProviders: SafeProvider[] = [
useClass: SessionTimeoutSettingsComponentService,
deps: [I18nServiceAbstraction, SessionTimeoutTypeService, PolicyService],
}),
safeProvider({
provide: AuthRequestAnsweringService,
useClass: NoopAuthRequestAnsweringService,
deps: [],
}),
];
@NgModule({

View File

@@ -64,11 +64,11 @@
<p class="tw-mt-4 tw-max-w-96 tw-text-center">
{{ "gettingStartedWithBitwardenPart1" | i18n }}
<a bitLink href="https://bitwarden.com/help/learning-center/">
<a target="_blank" bitLink href="https://bitwarden.com/help/learning-center/">
{{ "gettingStartedWithBitwardenPart2" | i18n }}
</a>
{{ "and" | i18n }}
<a bitLink href="https://bitwarden.com/help">
<a target="_blank" bitLink href="https://bitwarden.com/help">
{{ "gettingStartedWithBitwardenPart3" | i18n }}
</a>
</p>

View File

@@ -3,7 +3,7 @@
{{ title }}
</span>
@if (cipherIsArchived) {
<span bitBadge bitDialogHeaderEnd> {{ "archiveNoun" | i18n }} </span>
<span bitBadge bitDialogHeaderEnd> {{ "archived" | i18n }} </span>
}
<div bitDialogContent #dialogContent>

View File

@@ -24,8 +24,6 @@ import { PolicyType } from "@bitwarden/common/admin-console/enums";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { SingleUserState, StateProvider } from "@bitwarden/common/platform/state";
import { OrganizationId, UserId } from "@bitwarden/common/types/guid";
@@ -111,11 +109,8 @@ export class VaultFilterService implements VaultFilterServiceAbstraction {
collectionTree$: Observable<TreeNode<CollectionFilter>> = combineLatest([
this.filteredCollections$,
this.memberOrganizations$,
this.configService.getFeatureFlag$(FeatureFlag.CreateDefaultLocation),
]).pipe(
map(([collections, organizations, defaultCollectionsFlagEnabled]) =>
this.buildCollectionTree(collections, organizations, defaultCollectionsFlagEnabled),
),
map(([collections, organizations]) => this.buildCollectionTree(collections, organizations)),
);
cipherTypeTree$: Observable<TreeNode<CipherTypeFilter>> = this.buildCipherTypeTree();
@@ -133,7 +128,6 @@ export class VaultFilterService implements VaultFilterServiceAbstraction {
protected stateProvider: StateProvider,
protected collectionService: CollectionService,
protected accountService: AccountService,
protected configService: ConfigService,
) {}
async getCollectionNodeFromTree(id: string) {
@@ -241,18 +235,13 @@ export class VaultFilterService implements VaultFilterServiceAbstraction {
protected buildCollectionTree(
collections?: CollectionView[],
orgs?: Organization[],
defaultCollectionsFlagEnabled?: boolean,
): TreeNode<CollectionFilter> {
const headNode = this.getCollectionFilterHead();
if (!collections) {
return headNode;
}
const all: TreeNode<CollectionFilter>[] = [];
if (defaultCollectionsFlagEnabled) {
collections = sortDefaultCollections(collections, orgs, this.i18nService.collator);
}
collections = sortDefaultCollections(collections, orgs, this.i18nService.collator);
const groupedByOrg = this.collectionService.groupByOrganization(collections);
for (const group of groupedByOrg.values()) {

View File

@@ -47,7 +47,11 @@ export function createFilterFunction(
if (filter.type === "archive" && !CipherViewLikeUtils.isArchived(cipher)) {
return false;
}
if (filter.type !== "archive" && CipherViewLikeUtils.isArchived(cipher)) {
if (
filter.type !== "archive" &&
filter.type !== "trash" &&
CipherViewLikeUtils.isArchived(cipher)
) {
return false;
}
}

View File

@@ -3146,6 +3146,9 @@
"premiumSubscriptionEndedDesc": {
"message": "To regain access to your archive, restart your Premium subscription. If you edit details for an archived item before restarting, it'll be moved back into your vault."
},
"itemRestored": {
"message": "Item has been restored"
},
"restartPremium": {
"message": "Restart Premium"
},
@@ -11613,6 +11616,9 @@
"unArchive": {
"message": "Unarchive"
},
"archived": {
"message": "Archived"
},
"unArchiveAndSave": {
"message": "Unarchive and save"
},