[PM-33928] Fix: Can view MyItems Passwords in Org Vault Health Reports (#19874)

* fix(bug): Admin/Owner viewing and editing ciphers from within health reports

* Change check for unassigned ciphers to include default collection check

* Remove unnecessary typecasting
This commit is contained in:
Leslie Tilton 2026-04-03 12:45:48 -05:00 committed by GitHub
parent 3751227752
commit 4c3cf00a5a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 105 additions and 33 deletions

View File

@ -2,6 +2,7 @@ import { Component, OnInit } from "@angular/core";
import { ActivatedRoute } from "@angular/router";
import { firstValueFrom, takeUntil, tap } from "rxjs";
import { CollectionService } from "@bitwarden/admin-console/common";
import { AuditService } from "@bitwarden/common/abstractions/audit.service";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
@ -10,8 +11,8 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic
import { getById } from "@bitwarden/common/platform/misc";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
import { Cipher } from "@bitwarden/common/vault/models/domain/cipher";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { CipherViewLikeUtils } from "@bitwarden/common/vault/utils/cipher-view-like-utils";
import { BerryComponent, ChipFilterComponent, DialogService } from "@bitwarden/components";
import {
PasswordRepromptService,
@ -54,7 +55,8 @@ export class ExposedPasswordsReportComponent
extends BaseExposedPasswordsReportComponent
implements OnInit
{
private manageableCiphers: Cipher[] = [];
private manageableCipherIds = new Set<string>();
private sharedCollectionIds = new Set<string>();
constructor(
cipherService: CipherService,
@ -68,6 +70,7 @@ export class ExposedPasswordsReportComponent
syncService: SyncService,
cipherFormService: CipherFormConfigService,
adminConsoleCipherFormConfigService: AdminConsoleCipherFormConfigService,
private collectionService: CollectionService,
) {
super(
cipherService,
@ -92,7 +95,17 @@ export class ExposedPasswordsReportComponent
this.organization = await firstValueFrom(
this.organizationService.organizations$(userId).pipe(getById(params.organizationId)),
);
this.manageableCiphers = await this.cipherService.getAll(userId);
const manageableCiphers = await this.cipherService.getAll(userId);
this.manageableCipherIds = new Set(manageableCiphers.map((c) => c.id));
const collections = await firstValueFrom(
this.collectionService.decryptedCollections$(userId),
);
this.sharedCollectionIds = new Set(
collections
.filter((c) => !c.isDefaultCollection && c.organizationId === this.organization?.id)
.map((c) => c.id as string),
);
await super.ngOnInit();
}),
takeUntil(this.destroyed$),
)
@ -107,12 +120,15 @@ export class ExposedPasswordsReportComponent
}
canManageCipher(c: CipherView): boolean {
if (c.collectionIds.length === 0) {
return true;
if (
CipherViewLikeUtils.isUnassigned(c) ||
!c.collectionIds?.some((id) => this.sharedCollectionIds.has(id))
) {
return false;
}
if (this.organization?.allowAdminAccessToAllCollectionItems) {
return true;
}
return this.manageableCiphers.some((x) => x.id === c.id);
return this.manageableCipherIds.has(c.id);
}
}

View File

@ -4,6 +4,7 @@ import { ChangeDetectorRef, Component, OnInit, ChangeDetectionStrategy } from "@
import { ActivatedRoute } from "@angular/router";
import { firstValueFrom, takeUntil, tap } 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 { getUserId } from "@bitwarden/common/auth/services/account.service";
@ -12,8 +13,8 @@ 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 { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
import { Cipher } from "@bitwarden/common/vault/models/domain/cipher";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { CipherViewLikeUtils } from "@bitwarden/common/vault/utils/cipher-view-like-utils";
import {
BerryComponent,
ChipActionComponent,
@ -61,8 +62,8 @@ export class InactiveTwoFactorReportComponent
extends BaseInactiveTwoFactorReportComponent
implements OnInit
{
// Contains a list of ciphers, the user running the report, can manage
private manageableCiphers: Cipher[] = [];
private manageableCipherIds = new Set<string>();
private sharedCollectionIds = new Set<string>();
constructor(
cipherService: CipherService,
@ -77,6 +78,7 @@ export class InactiveTwoFactorReportComponent
cipherFormConfigService: CipherFormConfigService,
adminConsoleCipherFormConfigService: AdminConsoleCipherFormConfigService,
protected changeDetectorRef: ChangeDetectorRef,
private collectionService: CollectionService,
) {
super(
cipherService,
@ -103,7 +105,16 @@ export class InactiveTwoFactorReportComponent
this.organization = await firstValueFrom(
this.organizationService.organizations$(userId).pipe(getById(params.organizationId)),
);
this.manageableCiphers = await this.cipherService.getAll(userId);
const manageableCiphers = await this.cipherService.getAll(userId);
this.manageableCipherIds = new Set(manageableCiphers.map((c) => c.id));
const collections = await firstValueFrom(
this.collectionService.decryptedCollections$(userId),
);
this.sharedCollectionIds = new Set(
collections
.filter((c) => !c.isDefaultCollection && c.organizationId === this.organization?.id)
.map((c) => c.id as string),
);
await super.ngOnInit();
this.changeDetectorRef.markForCheck();
}),
@ -120,12 +131,15 @@ export class InactiveTwoFactorReportComponent
}
protected canManageCipher(c: CipherView): boolean {
if (c.collectionIds.length === 0) {
return true;
if (
CipherViewLikeUtils.isUnassigned(c) ||
!c.collectionIds?.some((id) => this.sharedCollectionIds.has(id))
) {
return false;
}
if (this.organization?.allowAdminAccessToAllCollectionItems) {
return true;
}
return this.manageableCiphers.some((x) => x.id === c.id);
return this.manageableCipherIds.has(c.id);
}
}

View File

@ -2,6 +2,7 @@ import { Component, OnInit } from "@angular/core";
import { ActivatedRoute } from "@angular/router";
import { firstValueFrom, takeUntil, tap } 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 { getUserId } from "@bitwarden/common/auth/services/account.service";
@ -9,8 +10,8 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic
import { getById } from "@bitwarden/common/platform/misc";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
import { Cipher } from "@bitwarden/common/vault/models/domain/cipher";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { CipherViewLikeUtils } from "@bitwarden/common/vault/utils/cipher-view-like-utils";
import { BerryComponent, ChipFilterComponent, DialogService } from "@bitwarden/components";
import {
CipherFormConfigService,
@ -53,7 +54,8 @@ export class ReusedPasswordsReportComponent
extends BaseReusedPasswordsReportComponent
implements OnInit
{
manageableCiphers: Cipher[] = [];
private manageableCipherIds = new Set<string>();
private sharedCollectionIds = new Set<string>();
constructor(
cipherService: CipherService,
@ -66,6 +68,7 @@ export class ReusedPasswordsReportComponent
syncService: SyncService,
cipherFormConfigService: CipherFormConfigService,
adminConsoleCipherFormConfigService: AdminConsoleCipherFormConfigService,
private collectionService: CollectionService,
) {
super(
cipherService,
@ -90,7 +93,16 @@ export class ReusedPasswordsReportComponent
this.organization = await firstValueFrom(
this.organizationService.organizations$(userId).pipe(getById(params.organizationId)),
);
this.manageableCiphers = await this.cipherService.getAll(userId);
const manageableCiphers = await this.cipherService.getAll(userId);
this.manageableCipherIds = new Set(manageableCiphers.map((c) => c.id));
const collections = await firstValueFrom(
this.collectionService.decryptedCollections$(userId),
);
this.sharedCollectionIds = new Set(
collections
.filter((c) => !c.isDefaultCollection && c.organizationId === this.organization?.id)
.map((c) => c.id as string),
);
await super.ngOnInit();
}),
takeUntil(this.destroyed$),
@ -106,12 +118,15 @@ export class ReusedPasswordsReportComponent
}
canManageCipher(c: CipherView): boolean {
if (c.collectionIds.length === 0) {
return true;
if (
CipherViewLikeUtils.isUnassigned(c) ||
!c.collectionIds?.some((id) => this.sharedCollectionIds.has(id))
) {
return false;
}
if (this.organization?.allowAdminAccessToAllCollectionItems) {
return true;
}
return this.manageableCiphers.some((x) => x.id === c.id);
return this.manageableCipherIds.has(c.id);
}
}

View File

@ -10,8 +10,8 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic
import { getById } from "@bitwarden/common/platform/misc";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
import { Cipher } from "@bitwarden/common/vault/models/domain/cipher";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { CipherViewLikeUtils } from "@bitwarden/common/vault/utils/cipher-view-like-utils";
import { BerryComponent, ChipFilterComponent, DialogService } from "@bitwarden/components";
import {
CipherFormConfigService,
@ -54,8 +54,8 @@ export class UnsecuredWebsitesReportComponent
extends BaseUnsecuredWebsitesReportComponent
implements OnInit
{
// Contains a list of ciphers, the user running the report, can manage
private manageableCiphers: Cipher[] = [];
private manageableCipherIds = new Set<string>();
private sharedCollectionIds = new Set<string>();
constructor(
cipherService: CipherService,
@ -93,7 +93,16 @@ export class UnsecuredWebsitesReportComponent
this.organization = await firstValueFrom(
this.organizationService.organizations$(userId).pipe(getById(params.organizationId)),
);
this.manageableCiphers = await this.cipherService.getAll(userId);
const manageableCiphers = await this.cipherService.getAll(userId);
this.manageableCipherIds = new Set(manageableCiphers.map((c) => c.id));
const collections = await firstValueFrom(
this.collectionService.decryptedCollections$(userId),
);
this.sharedCollectionIds = new Set(
collections
.filter((c) => !c.isDefaultCollection && c.organizationId === this.organization?.id)
.map((c) => c.id as string),
);
await super.ngOnInit();
}),
takeUntil(this.destroyed$),
@ -109,12 +118,15 @@ export class UnsecuredWebsitesReportComponent
}
protected canManageCipher(c: CipherView): boolean {
if (c.collectionIds.length === 0) {
return true;
if (
CipherViewLikeUtils.isUnassigned(c) ||
!c.collectionIds?.some((id) => this.sharedCollectionIds.has(id))
) {
return false;
}
if (this.organization?.allowAdminAccessToAllCollectionItems) {
return true;
}
return this.manageableCiphers.some((x) => x.id === c.id);
return this.manageableCipherIds.has(c.id);
}
}

View File

@ -2,6 +2,7 @@ import { Component, OnInit } from "@angular/core";
import { ActivatedRoute } from "@angular/router";
import { firstValueFrom, takeUntil, tap } 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 { getUserId } from "@bitwarden/common/auth/services/account.service";
@ -10,8 +11,8 @@ import { getById } from "@bitwarden/common/platform/misc";
import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
import { Cipher } from "@bitwarden/common/vault/models/domain/cipher";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { CipherViewLikeUtils } from "@bitwarden/common/vault/utils/cipher-view-like-utils";
import { BerryComponent, ChipFilterComponent, DialogService } from "@bitwarden/components";
import {
CipherFormConfigService,
@ -54,7 +55,8 @@ export class WeakPasswordsReportComponent
extends BaseWeakPasswordsReportComponent
implements OnInit
{
private manageableCiphers: Cipher[] = [];
private manageableCipherIds = new Set<string>();
private sharedCollectionIds = new Set<string>();
constructor(
cipherService: CipherService,
@ -68,6 +70,7 @@ export class WeakPasswordsReportComponent
cipherFormConfigService: CipherFormConfigService,
protected accountService: AccountService,
adminConsoleCipherFormConfigService: AdminConsoleCipherFormConfigService,
private collectionService: CollectionService,
) {
super(
cipherService,
@ -92,7 +95,16 @@ export class WeakPasswordsReportComponent
this.organization = await firstValueFrom(
this.organizationService.organizations$(userId).pipe(getById(params.organizationId)),
);
this.manageableCiphers = await this.cipherService.getAll(userId);
const manageableCiphers = await this.cipherService.getAll(userId);
this.manageableCipherIds = new Set(manageableCiphers.map((c) => c.id));
const collections = await firstValueFrom(
this.collectionService.decryptedCollections$(userId),
);
this.sharedCollectionIds = new Set(
collections
.filter((c) => !c.isDefaultCollection && c.organizationId === this.organization?.id)
.map((c) => c.id as string),
);
await super.ngOnInit();
}),
takeUntil(this.destroyed$),
@ -108,12 +120,15 @@ export class WeakPasswordsReportComponent
}
canManageCipher(c: CipherView): boolean {
if (c.collectionIds.length === 0) {
return true;
if (
CipherViewLikeUtils.isUnassigned(c) ||
!c.collectionIds?.some((id) => this.sharedCollectionIds.has(id))
) {
return false;
}
if (this.organization?.allowAdminAccessToAllCollectionItems) {
return true;
}
return this.manageableCiphers.some((x) => x.id === c.id);
return this.manageableCipherIds.has(c.id);
}
}

View File

@ -34,7 +34,7 @@ export class UnsecuredWebsitesReportComponent extends CipherReportComponent impl
passwordRepromptService: PasswordRepromptService,
i18nService: I18nService,
syncService: SyncService,
private collectionService: CollectionService,
protected collectionService: CollectionService,
cipherFormConfigService: CipherFormConfigService,
adminConsoleCipherFormConfigService: AdminConsoleCipherFormConfigService,
) {