mirror of
https://github.com/bitwarden/browser
synced 2026-01-28 15:23:53 +00:00
Merge branch 'main' into PM-29919-Add-dropdown-to-select-email-verification-and-emails-field-to-Send-when-creating-or-editing-a-Send
This commit is contained in:
@@ -582,8 +582,8 @@
|
||||
"archiveItem": {
|
||||
"message": "Archive item"
|
||||
},
|
||||
"archiveItemConfirmDesc": {
|
||||
"message": "Archived items are excluded from general search results and autofill suggestions. Are you sure you want to archive this item?"
|
||||
"archiveItemDialogContent": {
|
||||
"message": "Once archived, this item will be excluded from search results and autofill suggestions."
|
||||
},
|
||||
"archived": {
|
||||
"message": "Archived"
|
||||
|
||||
@@ -376,7 +376,8 @@ export class ItemMoreOptionsComponent {
|
||||
|
||||
const confirmed = await this.dialogService.openSimpleDialog({
|
||||
title: { key: "archiveItem" },
|
||||
content: { key: "archiveItemConfirmDesc" },
|
||||
content: { key: "archiveItemDialogContent" },
|
||||
acceptButtonText: { key: "archiveVerb" },
|
||||
type: "info",
|
||||
});
|
||||
|
||||
|
||||
@@ -42,9 +42,23 @@
|
||||
<app-vault-icon [cipher]="cipher"></app-vault-icon>
|
||||
</div>
|
||||
<span data-testid="item-name">{{ cipher.name }}</span>
|
||||
@if (CipherViewLikeUtils.hasAttachments(cipher)) {
|
||||
<i class="bwi bwi-paperclip bwi-sm" [appA11yTitle]="'attachments' | i18n"></i>
|
||||
}
|
||||
<div slot="default-trailing" class="tw-flex tw-gap-1.5">
|
||||
@if (cipher.organizationId) {
|
||||
<i
|
||||
appOrgIcon
|
||||
[tierType]="orgTierType(cipher)"
|
||||
[size]="'small'"
|
||||
[appA11yTitle]="orgIconTooltip(cipher)"
|
||||
></i>
|
||||
}
|
||||
@if (CipherViewLikeUtils.hasAttachments(cipher)) {
|
||||
<i
|
||||
slot="default-trailing"
|
||||
class="bwi bwi-paperclip bwi-sm"
|
||||
[appA11yTitle]="'attachments' | i18n"
|
||||
></i>
|
||||
}
|
||||
</div>
|
||||
<span slot="secondary">{{ CipherViewLikeUtils.subtitle(cipher) }}</span>
|
||||
</button>
|
||||
<bit-item-action slot="end">
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { Component, inject } from "@angular/core";
|
||||
import { toSignal } from "@angular/core/rxjs-interop";
|
||||
import { Router } from "@angular/router";
|
||||
import { combineLatest, firstValueFrom, map, Observable, startWith, switchMap } from "rxjs";
|
||||
|
||||
import { CollectionService } from "@bitwarden/admin-console/common";
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
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 { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
@@ -33,6 +35,7 @@ import {
|
||||
import {
|
||||
CanDeleteCipherDirective,
|
||||
DecryptionFailureDialogComponent,
|
||||
OrgIconDirective,
|
||||
PasswordRepromptService,
|
||||
} from "@bitwarden/vault";
|
||||
|
||||
@@ -60,6 +63,7 @@ import { ROUTES_AFTER_EDIT_DELETION } from "../components/vault-v2/add-edit/add-
|
||||
SectionComponent,
|
||||
SectionHeaderComponent,
|
||||
TypographyModule,
|
||||
OrgIconDirective,
|
||||
CardComponent,
|
||||
ButtonComponent,
|
||||
],
|
||||
@@ -79,6 +83,26 @@ export class ArchiveComponent {
|
||||
|
||||
private userId$: Observable<UserId> = this.accountService.activeAccount$.pipe(getUserId);
|
||||
|
||||
private readonly orgMap = toSignal(
|
||||
this.userId$.pipe(
|
||||
switchMap((userId) =>
|
||||
this.organizationService.organizations$(userId).pipe(
|
||||
map((orgs) => {
|
||||
const map = new Map<string, Organization>();
|
||||
for (const org of orgs) {
|
||||
map.set(org.id, org);
|
||||
}
|
||||
return map;
|
||||
}),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
private readonly collections = toSignal(
|
||||
this.userId$.pipe(switchMap((userId) => this.collectionService.decryptedCollections$(userId))),
|
||||
);
|
||||
|
||||
protected archivedCiphers$ = this.userId$.pipe(
|
||||
switchMap((userId) => this.cipherArchiveService.archivedCiphers$(userId)),
|
||||
);
|
||||
@@ -242,4 +266,22 @@ export class ArchiveComponent {
|
||||
|
||||
return this.passwordRepromptService.passwordRepromptCheck(cipher);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the organization tier type for the given cipher.
|
||||
*/
|
||||
orgTierType({ organizationId }: CipherViewLike) {
|
||||
return this.orgMap()?.get(organizationId as string)?.productTierType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the organization icon tooltip for the given cipher.
|
||||
*/
|
||||
orgIconTooltip({ collectionIds }: CipherViewLike) {
|
||||
if (collectionIds.length !== 1) {
|
||||
return this.i18nService.t("nCollections", collectionIds.length);
|
||||
}
|
||||
|
||||
return this.collections()?.find((c) => c.id === collectionIds[0])?.name;
|
||||
}
|
||||
}
|
||||
|
||||
4
apps/desktop/desktop_native/Cargo.lock
generated
4
apps/desktop/desktop_native/Cargo.lock
generated
@@ -2949,9 +2949,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sha2"
|
||||
version = "0.10.8"
|
||||
version = "0.10.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8"
|
||||
checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
|
||||
@@ -58,7 +58,7 @@ security-framework = "=3.5.1"
|
||||
security-framework-sys = "=2.15.0"
|
||||
serde = "=1.0.209"
|
||||
serde_json = "=1.0.127"
|
||||
sha2 = "=0.10.8"
|
||||
sha2 = "=0.10.9"
|
||||
ssh-encoding = "=0.2.0"
|
||||
ssh-key = { version = "=0.6.7", default-features = false }
|
||||
sysinfo = "=0.37.2"
|
||||
|
||||
@@ -4397,8 +4397,8 @@
|
||||
"archiveItem": {
|
||||
"message": "Archive item"
|
||||
},
|
||||
"archiveItemConfirmDesc": {
|
||||
"message": "Archived items are excluded from general search results and autofill suggestions. Are you sure you want to archive this item?"
|
||||
"archiveItemDialogContent": {
|
||||
"message": "Once archived, this item will be excluded from search results and autofill suggestions."
|
||||
},
|
||||
"unArchiveAndSave": {
|
||||
"message": "Unarchive and save"
|
||||
|
||||
@@ -12,7 +12,7 @@ import { VaultFilter, CollectionFilter } from "@bitwarden/vault";
|
||||
imports: [A11yTitleDirective, NavigationModule],
|
||||
})
|
||||
export class CollectionFilterComponent {
|
||||
protected readonly collection = input<TreeNode<CollectionFilter>>();
|
||||
protected readonly collection = input.required<TreeNode<CollectionFilter>>();
|
||||
protected readonly activeFilter = input<VaultFilter>();
|
||||
|
||||
protected readonly displayName = computed<string>(() => {
|
||||
|
||||
@@ -13,7 +13,7 @@ import { VaultFilter, FolderFilter } from "@bitwarden/vault";
|
||||
imports: [A11yTitleDirective, NavigationModule, IconButtonModule, I18nPipe],
|
||||
})
|
||||
export class FolderFilterComponent {
|
||||
protected readonly folder = input<TreeNode<FolderFilter>>();
|
||||
protected readonly folder = input.required<TreeNode<FolderFilter>>();
|
||||
protected readonly activeFilter = input<VaultFilter>();
|
||||
protected onEditFolder = output<FolderFilter>();
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { Component, computed, input, inject } from "@angular/core";
|
||||
|
||||
import { DisplayMode } from "@bitwarden/angular/vault/vault-filter/models/display-mode";
|
||||
@@ -20,7 +22,7 @@ export class OrganizationFilterComponent {
|
||||
private vaultFilterService: VaultFilterServiceAbstraction = inject(VaultFilterServiceAbstraction);
|
||||
|
||||
protected readonly hide = input(false);
|
||||
protected readonly organizations = input<TreeNode<OrganizationFilter>>();
|
||||
protected readonly organizations = input.required<TreeNode<OrganizationFilter>>();
|
||||
protected readonly activeFilter = input<VaultFilter>();
|
||||
protected readonly activeOrganizationDataOwnership = input<boolean>(false);
|
||||
protected readonly activeSingleOrganizationPolicy = input<boolean>(false);
|
||||
@@ -56,7 +58,6 @@ export class OrganizationFilterComponent {
|
||||
if (!organization.node.enabled) {
|
||||
this.toastService.showToast({
|
||||
variant: "error",
|
||||
title: null,
|
||||
message: this.i18nService.t("disabledOrganizationFilterError"),
|
||||
});
|
||||
return;
|
||||
|
||||
@@ -6,10 +6,13 @@
|
||||
[text]="archiveFilter.name | i18n"
|
||||
[attr.aria-pressed]="activeFilter()?.isArchived"
|
||||
[appA11yTitle]="archiveFilter.name | i18n"
|
||||
/>
|
||||
@if (!(canArchive$ | async)) {
|
||||
<app-premium-badge />
|
||||
}
|
||||
>
|
||||
@if (!(canArchive$ | async)) {
|
||||
<ng-container slot="end">
|
||||
<app-premium-badge />
|
||||
</ng-container>
|
||||
}
|
||||
</bit-nav-item>
|
||||
}
|
||||
<bit-nav-item
|
||||
[icon]="trashFilter.icon"
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { Component, viewChild, input, inject } from "@angular/core";
|
||||
import { combineLatest, firstValueFrom, map, switchMap } from "rxjs";
|
||||
@@ -23,7 +25,10 @@ export class StatusFilterComponent {
|
||||
private cipherArchiveService: CipherArchiveService = inject(CipherArchiveService);
|
||||
|
||||
protected readonly hideArchive = input(false);
|
||||
protected readonly activeFilter = input<VaultFilter>();
|
||||
protected readonly activeFilter = input.required<VaultFilter>();
|
||||
|
||||
private readonly premiumBadgeComponent = viewChild(PremiumBadgeComponent);
|
||||
|
||||
protected readonly archiveFilter: CipherTypeFilter = {
|
||||
id: "archive",
|
||||
name: "archiveNoun",
|
||||
@@ -38,7 +43,7 @@ export class StatusFilterComponent {
|
||||
};
|
||||
|
||||
protected applyFilter(filterType: CipherStatus) {
|
||||
let filter: CipherTypeFilter = null;
|
||||
let filter: CipherTypeFilter | null = null;
|
||||
if (filterType === "archive") {
|
||||
filter = this.archiveFilter;
|
||||
} else if (filterType === "trash") {
|
||||
@@ -50,8 +55,6 @@ export class StatusFilterComponent {
|
||||
}
|
||||
}
|
||||
|
||||
private readonly premiumBadgeComponent = viewChild.required(PremiumBadgeComponent);
|
||||
|
||||
private userId$ = this.accountService.activeAccount$.pipe(getUserId);
|
||||
protected canArchive$ = this.userId$.pipe(
|
||||
switchMap((userId) => this.cipherArchiveService.userCanArchive$(userId)),
|
||||
@@ -71,7 +74,7 @@ export class StatusFilterComponent {
|
||||
if (canArchive || hasArchivedCiphers) {
|
||||
this.applyFilter("archive");
|
||||
} else {
|
||||
await this.premiumBadgeComponent().promptForPremium(event);
|
||||
await this.premiumBadgeComponent()?.promptForPremium(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ export class TypeFilterComponent {
|
||||
RestrictedItemTypesService,
|
||||
);
|
||||
|
||||
protected readonly cipherTypes = input<TreeNode<CipherTypeFilter>>();
|
||||
protected readonly cipherTypes = input.required<TreeNode<CipherTypeFilter>>();
|
||||
protected readonly activeFilter = input<VaultFilter>();
|
||||
|
||||
protected applyTypeFilter(event: Event, cipherType: TreeNode<CipherTypeFilter>) {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { Component, inject, OnInit, output, computed, signal } from "@angular/core";
|
||||
import { firstValueFrom, Observable, Subject, takeUntil } from "rxjs";
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { ChangeDetectorRef, Component, NgZone, OnDestroy, OnInit, ViewChild } from "@angular/core";
|
||||
import { ActivatedRoute, Router } from "@angular/router";
|
||||
|
||||
@@ -746,7 +746,8 @@ export class VaultComponent<C extends CipherViewLike> implements OnInit, OnDestr
|
||||
|
||||
const confirmed = await this.dialogService.openSimpleDialog({
|
||||
title: { key: "archiveItem" },
|
||||
content: { key: "archiveItemConfirmDesc" },
|
||||
content: { key: "archiveItemDialogContent" },
|
||||
acceptButtonText: { key: "archiveVerb" },
|
||||
type: "info",
|
||||
});
|
||||
|
||||
|
||||
@@ -11707,8 +11707,8 @@
|
||||
"message": "Archive item",
|
||||
"description": "Verb"
|
||||
},
|
||||
"archiveItemConfirmDesc": {
|
||||
"message": "Archived items are excluded from general search results and autofill suggestions. Are you sure you want to archive this item?"
|
||||
"archiveItemDialogContent": {
|
||||
"message": "Once archived, this item will be excluded from search results and autofill suggestions."
|
||||
},
|
||||
"archiveBulkItems": {
|
||||
"message": "Archive items",
|
||||
|
||||
@@ -264,6 +264,13 @@ export abstract class OrganizationUserApiService {
|
||||
ids: string[],
|
||||
): Promise<ListResponse<OrganizationUserBulkResponse>>;
|
||||
|
||||
/**
|
||||
* Revoke the current user's access to the organization
|
||||
* if they decline an item transfer under the Organization Data Ownership policy.
|
||||
* @param organizationId - Identifier for the organization the user belongs to
|
||||
*/
|
||||
abstract revokeSelf(organizationId: string): Promise<void>;
|
||||
|
||||
/**
|
||||
* Restore an organization user's access to the organization
|
||||
* @param organizationId - Identifier for the organization the user belongs to
|
||||
|
||||
@@ -339,6 +339,16 @@ export class DefaultOrganizationUserApiService implements OrganizationUserApiSer
|
||||
return new ListResponse(r, OrganizationUserBulkResponse);
|
||||
}
|
||||
|
||||
revokeSelf(organizationId: string): Promise<void> {
|
||||
return this.apiService.send(
|
||||
"PUT",
|
||||
"/organizations/" + organizationId + "/users/revoke-self",
|
||||
null,
|
||||
true,
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
restoreOrganizationUser(organizationId: string, id: string): Promise<void> {
|
||||
return this.apiService.send(
|
||||
"PUT",
|
||||
|
||||
@@ -280,8 +280,7 @@ export abstract class KeyService {
|
||||
* encrypted private key at all.
|
||||
*
|
||||
* @param userId The user id of the user to get the data for.
|
||||
* @returns An observable stream of the decrypted private key or null.
|
||||
* @throws Error when decryption of the encrypted private key fails.
|
||||
* @returns An observable stream of the decrypted private key or null if the private key is not present or fails to decrypt
|
||||
*/
|
||||
abstract userPrivateKey$(userId: UserId): Observable<UserPrivateKey | null>;
|
||||
|
||||
|
||||
@@ -437,14 +437,13 @@ describe("keyService", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("throws an error if unwrapping encrypted private key fails", async () => {
|
||||
it("emits null if unwrapping encrypted private key fails", async () => {
|
||||
encryptService.unwrapDecapsulationKey.mockImplementationOnce(() => {
|
||||
throw new Error("Unwrapping failed");
|
||||
});
|
||||
|
||||
await expect(firstValueFrom(keyService.userPrivateKey$(mockUserId))).rejects.toThrow(
|
||||
"Unwrapping failed",
|
||||
);
|
||||
const result = await firstValueFrom(keyService.userPrivateKey$(mockUserId));
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it("returns null if user key is not set", async () => {
|
||||
|
||||
@@ -791,7 +791,10 @@ export class DefaultKeyService implements KeyServiceAbstraction {
|
||||
return this.stateProvider.getUser(userId, USER_ENCRYPTED_PRIVATE_KEY).state$;
|
||||
}
|
||||
|
||||
private userPrivateKeyHelper$(userId: UserId) {
|
||||
private userPrivateKeyHelper$(userId: UserId): Observable<{
|
||||
userKey: UserKey;
|
||||
userPrivateKey: UserPrivateKey | null;
|
||||
} | null> {
|
||||
const userKey$ = this.userKey$(userId);
|
||||
return userKey$.pipe(
|
||||
switchMap((userKey) => {
|
||||
@@ -801,18 +804,20 @@ export class DefaultKeyService implements KeyServiceAbstraction {
|
||||
|
||||
return this.stateProvider.getUser(userId, USER_ENCRYPTED_PRIVATE_KEY).state$.pipe(
|
||||
switchMap(async (encryptedPrivateKey) => {
|
||||
try {
|
||||
return await this.decryptPrivateKey(encryptedPrivateKey, userKey);
|
||||
} catch (e) {
|
||||
this.logService.error("Failed to decrypt private key for user ", userId, e);
|
||||
throw e;
|
||||
}
|
||||
return await this.decryptPrivateKey(encryptedPrivateKey, userKey);
|
||||
}),
|
||||
// Combine outerscope info with user private key
|
||||
map((userPrivateKey) => ({
|
||||
userKey,
|
||||
userPrivateKey,
|
||||
})),
|
||||
catchError((err: unknown) => {
|
||||
this.logService.error(`Failed to decrypt private key for user ${userId}`);
|
||||
return of({
|
||||
userKey,
|
||||
userPrivateKey: null,
|
||||
});
|
||||
}),
|
||||
);
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -1,17 +1,8 @@
|
||||
import { CommonModule } from "@angular/common";
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
computed,
|
||||
effect,
|
||||
inject,
|
||||
input,
|
||||
output,
|
||||
} from "@angular/core";
|
||||
import { ChangeDetectionStrategy, Component, computed, effect, input, output } from "@angular/core";
|
||||
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import { NoResults, NoSendsIcon } from "@bitwarden/assets/svg";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { SendView } from "@bitwarden/common/tools/send/models/view/send.view";
|
||||
import {
|
||||
ButtonModule,
|
||||
@@ -57,8 +48,6 @@ export class SendListComponent {
|
||||
protected readonly noResultsIcon = NoResults;
|
||||
protected readonly sendListState = SendListState;
|
||||
|
||||
private i18nService = inject(I18nService);
|
||||
|
||||
readonly sends = input.required<SendView[]>();
|
||||
readonly loading = input<boolean>(false);
|
||||
readonly disableSend = input<boolean>(false);
|
||||
@@ -70,7 +59,7 @@ export class SendListComponent {
|
||||
);
|
||||
|
||||
protected readonly noSearchResults = computed(
|
||||
() => this.showSearchBar() && (this.sends().length === 0 || this.searchText().length > 0),
|
||||
() => this.showSearchBar() && this.sends().length === 0,
|
||||
);
|
||||
|
||||
// Reusable data source instance - updated reactively when sends change
|
||||
|
||||
@@ -41,7 +41,8 @@ export class ArchiveCipherUtilitiesService {
|
||||
|
||||
const confirmed = await this.dialogService.openSimpleDialog({
|
||||
title: { key: "archiveItem" },
|
||||
content: { key: "archiveItemConfirmDesc" },
|
||||
content: { key: "archiveItemDialogContent" },
|
||||
acceptButtonText: { key: "archiveVerb" },
|
||||
type: "info",
|
||||
});
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { mock, MockProxy } from "jest-mock-extended";
|
||||
import { firstValueFrom, of, Subject } from "rxjs";
|
||||
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { CollectionService } from "@bitwarden/admin-console/common";
|
||||
import { CollectionService, OrganizationUserApiService } from "@bitwarden/admin-console/common";
|
||||
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
|
||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||
@@ -42,6 +42,7 @@ describe("DefaultVaultItemsTransferService", () => {
|
||||
let mockToastService: MockProxy<ToastService>;
|
||||
let mockEventCollectionService: MockProxy<EventCollectionService>;
|
||||
let mockConfigService: MockProxy<ConfigService>;
|
||||
let mockOrganizationUserApiService: MockProxy<OrganizationUserApiService>;
|
||||
|
||||
const userId = "user-id" as UserId;
|
||||
const organizationId = "org-id" as OrganizationId;
|
||||
@@ -77,6 +78,7 @@ describe("DefaultVaultItemsTransferService", () => {
|
||||
mockToastService = mock<ToastService>();
|
||||
mockEventCollectionService = mock<EventCollectionService>();
|
||||
mockConfigService = mock<ConfigService>();
|
||||
mockOrganizationUserApiService = mock<OrganizationUserApiService>();
|
||||
|
||||
mockI18nService.t.mockImplementation((key) => key);
|
||||
transferInProgressValues = [];
|
||||
@@ -92,6 +94,7 @@ describe("DefaultVaultItemsTransferService", () => {
|
||||
mockToastService,
|
||||
mockEventCollectionService,
|
||||
mockConfigService,
|
||||
mockOrganizationUserApiService,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -632,9 +635,15 @@ describe("DefaultVaultItemsTransferService", () => {
|
||||
mockDialogService.open
|
||||
.mockReturnValueOnce(createMockDialogRef(TransferItemsDialogResult.Declined))
|
||||
.mockReturnValueOnce(createMockDialogRef(LeaveConfirmationDialogResult.Confirmed));
|
||||
mockOrganizationUserApiService.revokeSelf.mockResolvedValue(undefined);
|
||||
|
||||
await service.enforceOrganizationDataOwnership(userId);
|
||||
|
||||
expect(mockOrganizationUserApiService.revokeSelf).toHaveBeenCalledWith(organizationId);
|
||||
expect(mockToastService.showToast).toHaveBeenCalledWith({
|
||||
variant: "success",
|
||||
message: "leftOrganization",
|
||||
});
|
||||
expect(mockCipherService.shareManyWithServer).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
} from "rxjs";
|
||||
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { CollectionService } from "@bitwarden/admin-console/common";
|
||||
import { CollectionService, OrganizationUserApiService } from "@bitwarden/admin-console/common";
|
||||
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
|
||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||
@@ -53,6 +53,7 @@ export class DefaultVaultItemsTransferService implements VaultItemsTransferServi
|
||||
private toastService: ToastService,
|
||||
private eventCollectionService: EventCollectionService,
|
||||
private configService: ConfigService,
|
||||
private organizationUserApiService: OrganizationUserApiService,
|
||||
) {}
|
||||
|
||||
private _transferInProgressSubject = new BehaviorSubject(false);
|
||||
@@ -162,7 +163,12 @@ export class DefaultVaultItemsTransferService implements VaultItemsTransferServi
|
||||
);
|
||||
|
||||
if (!userAcceptedTransfer) {
|
||||
// TODO: Revoke user from organization if they decline migration and show toast PM-29465
|
||||
await this.organizationUserApiService.revokeSelf(migrationInfo.enforcingOrganization.id);
|
||||
|
||||
this.toastService.showToast({
|
||||
variant: "success",
|
||||
message: this.i18nService.t("leftOrganization"),
|
||||
});
|
||||
|
||||
await this.eventCollectionService.collect(
|
||||
EventType.Organization_ItemOrganization_Declined,
|
||||
|
||||
Reference in New Issue
Block a user