1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-11 22:03:36 +00:00

[PM-27287] Items in My Items should show in Inactive 2FA report (#17434)

This commit is contained in:
Vijay Oommen
2025-11-20 12:52:23 -06:00
committed by GitHub
parent 9afba33f58
commit 43897df9ed
5 changed files with 56 additions and 53 deletions

View File

@@ -1,5 +1,3 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { Directive, OnDestroy } from "@angular/core"; import { Directive, OnDestroy } from "@angular/core";
import { import {
BehaviorSubject, BehaviorSubject,
@@ -36,7 +34,7 @@ import {
import { AdminConsoleCipherFormConfigService } from "../../../vault/org-vault/services/admin-console-cipher-form-config.service"; import { AdminConsoleCipherFormConfigService } from "../../../vault/org-vault/services/admin-console-cipher-form-config.service";
@Directive() @Directive()
export class CipherReportComponent implements OnDestroy { export abstract class CipherReportComponent implements OnDestroy {
isAdminConsoleActive = false; isAdminConsoleActive = false;
loading = false; loading = false;
@@ -44,16 +42,16 @@ export class CipherReportComponent implements OnDestroy {
ciphers: CipherView[] = []; ciphers: CipherView[] = [];
allCiphers: CipherView[] = []; allCiphers: CipherView[] = [];
dataSource = new TableDataSource<CipherView>(); dataSource = new TableDataSource<CipherView>();
organization: Organization; organization: Organization | undefined = undefined;
organizations: Organization[]; organizations: Organization[] = [];
organizations$: Observable<Organization[]>; organizations$: Observable<Organization[]>;
filterStatus: any = [0]; filterStatus: any = [0];
showFilterToggle: boolean = false; showFilterToggle: boolean = false;
vaultMsg: string = "vault"; vaultMsg: string = "vault";
currentFilterStatus: number | string; currentFilterStatus: number | string = 0;
protected filterOrgStatus$ = new BehaviorSubject<number | string>(0); protected filterOrgStatus$ = new BehaviorSubject<number | string>(0);
private destroyed$: Subject<void> = new Subject(); protected destroyed$: Subject<void> = new Subject();
private vaultItemDialogRef?: DialogRef<VaultItemDialogResult> | undefined; private vaultItemDialogRef?: DialogRef<VaultItemDialogResult> | undefined;
constructor( constructor(
@@ -107,7 +105,7 @@ export class CipherReportComponent implements OnDestroy {
if (filterId === 0) { if (filterId === 0) {
cipherCount = this.allCiphers.length; cipherCount = this.allCiphers.length;
} else if (filterId === 1) { } else if (filterId === 1) {
cipherCount = this.allCiphers.filter((c) => c.organizationId === null).length; cipherCount = this.allCiphers.filter((c) => !c.organizationId).length;
} else { } else {
this.organizations.filter((org: Organization) => { this.organizations.filter((org: Organization) => {
if (org.id === filterId) { if (org.id === filterId) {
@@ -121,9 +119,9 @@ export class CipherReportComponent implements OnDestroy {
} }
async filterOrgToggle(status: any) { async filterOrgToggle(status: any) {
let filter = null; let filter = (c: CipherView) => true;
if (typeof status === "number" && status === 1) { if (typeof status === "number" && status === 1) {
filter = (c: CipherView) => c.organizationId == null; filter = (c: CipherView) => !c.organizationId;
} else if (typeof status === "string") { } else if (typeof status === "string") {
const orgId = status as OrganizationId; const orgId = status as OrganizationId;
filter = (c: CipherView) => c.organizationId === orgId; filter = (c: CipherView) => c.organizationId === orgId;
@@ -185,7 +183,7 @@ export class CipherReportComponent implements OnDestroy {
cipher: CipherView, cipher: CipherView,
activeCollectionId?: CollectionId, activeCollectionId?: CollectionId,
) { ) {
const disableForm = cipher ? !cipher.edit && !this.organization.canEditAllCiphers : false; const disableForm = cipher ? !cipher.edit && !this.organization?.canEditAllCiphers : false;
this.vaultItemDialogRef = VaultItemDialogComponent.open(this.dialogService, { this.vaultItemDialogRef = VaultItemDialogComponent.open(this.dialogService, {
mode, mode,
@@ -230,10 +228,11 @@ export class CipherReportComponent implements OnDestroy {
let updatedCipher = await this.cipherService.get(cipher.id, activeUserId); let updatedCipher = await this.cipherService.get(cipher.id, activeUserId);
if (this.isAdminConsoleActive) { if (this.isAdminConsoleActive) {
updatedCipher = await this.adminConsoleCipherFormConfigService.getCipher( updatedCipher =
cipher.id as CipherId, (await this.adminConsoleCipherFormConfigService.getCipher(
this.organization, cipher.id as CipherId,
); this.organization!,
)) ?? updatedCipher;
} }
// convert cipher to cipher view model // convert cipher to cipher view model

View File

@@ -90,6 +90,7 @@ describe("ExposedPasswordsReportComponent", () => {
}); });
beforeEach(() => { beforeEach(() => {
jest.clearAllMocks();
fixture = TestBed.createComponent(ExposedPasswordsReportComponent); fixture = TestBed.createComponent(ExposedPasswordsReportComponent);
component = fixture.componentInstance; component = fixture.componentInstance;
fixture.detectChanges(); fixture.detectChanges();

View File

@@ -1,3 +1,4 @@
import { CUSTOM_ELEMENTS_SCHEMA } from "@angular/core";
import { ComponentFixture, TestBed } from "@angular/core/testing"; import { ComponentFixture, TestBed } from "@angular/core/testing";
import { MockProxy, mock } from "jest-mock-extended"; import { MockProxy, mock } from "jest-mock-extended";
import { of } from "rxjs"; import { of } from "rxjs";
@@ -29,14 +30,13 @@ describe("InactiveTwoFactorReportComponent", () => {
const userId = Utils.newGuid() as UserId; const userId = Utils.newGuid() as UserId;
const accountService: FakeAccountService = mockAccountServiceWith(userId); const accountService: FakeAccountService = mockAccountServiceWith(userId);
beforeEach(() => { beforeEach(async () => {
let cipherFormConfigServiceMock: MockProxy<CipherFormConfigService>; let cipherFormConfigServiceMock: MockProxy<CipherFormConfigService>;
organizationService = mock<OrganizationService>(); organizationService = mock<OrganizationService>();
organizationService.organizations$.mockReturnValue(of([])); organizationService.organizations$.mockReturnValue(of([]));
syncServiceMock = mock<SyncService>(); syncServiceMock = mock<SyncService>();
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
// eslint-disable-next-line @typescript-eslint/no-floating-promises await TestBed.configureTestingModule({
TestBed.configureTestingModule({
declarations: [InactiveTwoFactorReportComponent, I18nPipe], declarations: [InactiveTwoFactorReportComponent, I18nPipe],
providers: [ providers: [
{ {
@@ -80,9 +80,7 @@ describe("InactiveTwoFactorReportComponent", () => {
useValue: adminConsoleCipherFormConfigServiceMock, useValue: adminConsoleCipherFormConfigServiceMock,
}, },
], ],
schemas: [], schemas: [CUSTOM_ELEMENTS_SCHEMA],
// FIXME(PM-18598): Replace unknownElements and unknownProperties with actual imports
errorOnUnknownElements: false,
}).compileComponents(); }).compileComponents();
}); });

View File

@@ -1,6 +1,4 @@
// FIXME: Update this file to be type safe and remove this and next line import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit } from "@angular/core";
// @ts-strict-ignore
import { Component, OnInit } from "@angular/core";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
@@ -19,9 +17,8 @@ import { AdminConsoleCipherFormConfigService } from "../../../vault/org-vault/se
import { CipherReportComponent } from "./cipher-report.component"; import { CipherReportComponent } from "./cipher-report.component";
// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection
@Component({ @Component({
changeDetection: ChangeDetectionStrategy.OnPush,
selector: "app-inactive-two-factor-report", selector: "app-inactive-two-factor-report",
templateUrl: "inactive-two-factor-report.component.html", templateUrl: "inactive-two-factor-report.component.html",
standalone: false, standalone: false,
@@ -42,6 +39,7 @@ export class InactiveTwoFactorReportComponent extends CipherReportComponent impl
syncService: SyncService, syncService: SyncService,
cipherFormConfigService: CipherFormConfigService, cipherFormConfigService: CipherFormConfigService,
adminConsoleCipherFormConfigService: AdminConsoleCipherFormConfigService, adminConsoleCipherFormConfigService: AdminConsoleCipherFormConfigService,
protected changeDetectorRef: ChangeDetectorRef,
) { ) {
super( super(
cipherService, cipherService,
@@ -86,6 +84,7 @@ export class InactiveTwoFactorReportComponent extends CipherReportComponent impl
this.filterCiphersByOrg(inactive2faCiphers); this.filterCiphersByOrg(inactive2faCiphers);
this.cipherDocs = docs; this.cipherDocs = docs;
this.changeDetectorRef.markForCheck();
} }
} }
@@ -157,6 +156,7 @@ export class InactiveTwoFactorReportComponent extends CipherReportComponent impl
} }
this.services.set(serviceData.domain, serviceData.documentation); this.services.set(serviceData.domain, serviceData.documentation);
} }
this.changeDetectorRef.markForCheck();
} }
/** /**

View File

@@ -1,16 +1,12 @@
// FIXME: Update this file to be type safe and remove this and next line import { ChangeDetectorRef, Component, OnInit, ChangeDetectionStrategy } from "@angular/core";
// @ts-strict-ignore
import { Component, OnInit } from "@angular/core";
import { ActivatedRoute } from "@angular/router"; import { ActivatedRoute } from "@angular/router";
import { firstValueFrom, map } from "rxjs"; import { firstValueFrom, map, takeUntil } from "rxjs";
import { import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
getOrganizationById,
OrganizationService,
} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { getById } from "@bitwarden/common/platform/misc";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
import { Cipher } from "@bitwarden/common/vault/models/domain/cipher"; import { Cipher } from "@bitwarden/common/vault/models/domain/cipher";
@@ -23,9 +19,8 @@ import { RoutedVaultFilterService } from "../../../../vault/individual-vault/vau
import { AdminConsoleCipherFormConfigService } from "../../../../vault/org-vault/services/admin-console-cipher-form-config.service"; import { AdminConsoleCipherFormConfigService } from "../../../../vault/org-vault/services/admin-console-cipher-form-config.service";
import { InactiveTwoFactorReportComponent as BaseInactiveTwoFactorReportComponent } from "../inactive-two-factor-report.component"; import { InactiveTwoFactorReportComponent as BaseInactiveTwoFactorReportComponent } from "../inactive-two-factor-report.component";
// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection
@Component({ @Component({
changeDetection: ChangeDetectionStrategy.OnPush,
selector: "app-inactive-two-factor-report", selector: "app-inactive-two-factor-report",
templateUrl: "../inactive-two-factor-report.component.html", templateUrl: "../inactive-two-factor-report.component.html",
providers: [ providers: [
@@ -44,7 +39,7 @@ export class InactiveTwoFactorReportComponent
implements OnInit implements OnInit
{ {
// Contains a list of ciphers, the user running the report, can manage // Contains a list of ciphers, the user running the report, can manage
private manageableCiphers: Cipher[]; private manageableCiphers: Cipher[] = [];
constructor( constructor(
cipherService: CipherService, cipherService: CipherService,
@@ -58,6 +53,7 @@ export class InactiveTwoFactorReportComponent
syncService: SyncService, syncService: SyncService,
cipherFormConfigService: CipherFormConfigService, cipherFormConfigService: CipherFormConfigService,
adminConsoleCipherFormConfigService: AdminConsoleCipherFormConfigService, adminConsoleCipherFormConfigService: AdminConsoleCipherFormConfigService,
protected changeDetectorRef: ChangeDetectorRef,
) { ) {
super( super(
cipherService, cipherService,
@@ -70,28 +66,37 @@ export class InactiveTwoFactorReportComponent
syncService, syncService,
cipherFormConfigService, cipherFormConfigService,
adminConsoleCipherFormConfigService, adminConsoleCipherFormConfigService,
changeDetectorRef,
); );
} }
async ngOnInit() { async ngOnInit() {
this.isAdminConsoleActive = true; this.isAdminConsoleActive = true;
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
this.route.parent.parent.params.subscribe(async (params) => { this.route.parent?.parent?.params
const userId = await firstValueFrom( ?.pipe(takeUntil(this.destroyed$))
this.accountService.activeAccount$.pipe(map((a) => a?.id)), // eslint-disable-next-line rxjs/no-async-subscribe
); .subscribe(async (params) => {
this.organization = await firstValueFrom( const userId = await firstValueFrom(
this.organizationService this.accountService.activeAccount$.pipe(map((a) => a?.id)),
.organizations$(userId) );
.pipe(getOrganizationById(params.organizationId)),
); if (userId) {
this.manageableCiphers = await this.cipherService.getAll(userId); this.organization = await firstValueFrom(
await super.ngOnInit(); this.organizationService.organizations$(userId).pipe(getById(params.organizationId)),
}); );
this.manageableCiphers = await this.cipherService.getAll(userId);
await super.ngOnInit();
}
this.changeDetectorRef.markForCheck();
});
} }
getAllCiphers(): Promise<CipherView[]> { async getAllCiphers(): Promise<CipherView[]> {
return this.cipherService.getAllFromApiForOrganization(this.organization.id); if (this.organization) {
return await this.cipherService.getAllFromApiForOrganization(this.organization.id, true);
}
return [];
} }
protected canManageCipher(c: CipherView): boolean { protected canManageCipher(c: CipherView): boolean {