diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 1b1d05ed78..6b6a905bfd 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -29,6 +29,7 @@ libs/common/src/models/export @bitwarden/team-tools-dev libs/common/src/tools @bitwarden/team-tools-dev libs/importer @bitwarden/team-tools-dev libs/tools @bitwarden/team-tools-dev +bitwarden_license/bit-web/src/app/tools @bitwarden/team-tools-dev ## Localization/Crowdin (Tools team) apps/browser/src/_locales @bitwarden/team-tools-dev diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index f61a1396ed..66a768a0fa 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -8445,6 +8445,9 @@ "memberAccessReportDesc": { "message": "Ensure members have access to the right credentials and their accounts are secure. Use this report to obtain a CSV of member access and account configurations." }, + "memberAccessReportPageDesc": { + "message": "Audit organization member access across groups, collections, and collection items. The CSV export provides a detailed breakdown per member, including information on collection permissions and account configurations." + }, "higherKDFIterations": { "message": "Higher KDF iterations can help protect your master password from being brute forced by an attacker." }, diff --git a/bitwarden_license/bit-web/src/app/admin-console/organizations/organizations-routing.module.ts b/bitwarden_license/bit-web/src/app/admin-console/organizations/organizations-routing.module.ts index acb4fb6ecd..1b31341e0d 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/organizations/organizations-routing.module.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/organizations/organizations-routing.module.ts @@ -3,6 +3,7 @@ import { RouterModule, Routes } from "@angular/router"; import { AuthGuard } from "@bitwarden/angular/auth/guards"; import { canAccessSettingsTab } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { IsEnterpriseOrgGuard } from "@bitwarden/web-vault/app/admin-console/organizations/guards/is-enterprise-org.guard"; import { organizationPermissionsGuard } from "@bitwarden/web-vault/app/admin-console/organizations/guards/org-permissions.guard"; import { OrganizationLayoutComponent } from "@bitwarden/web-vault/app/admin-console/organizations/layouts/organization-layout.component"; @@ -58,6 +59,23 @@ const routes: Routes = [ }, ], }, + { + path: "reporting/reports", + canActivate: [AuthGuard, organizationPermissionsGuard((org) => org.canAccessReports)], + children: [ + { + path: "member-access-report", + loadComponent: () => + import( + "../../tools/reports/member-access-report/member-access-report.component" + ).then((mod) => mod.MemberAccessReportComponent), + data: { + titleId: "memberAccessReport", + }, + canActivate: [IsEnterpriseOrgGuard], + }, + ], + }, ], }, ]; diff --git a/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/member-access-report.component.html b/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/member-access-report.component.html new file mode 100644 index 0000000000..9950bce6ef --- /dev/null +++ b/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/member-access-report.component.html @@ -0,0 +1,50 @@ + + + + + + +
+

+ {{ "memberAccessReportPageDesc" | i18n }} +

+
+ + + + + Members + Groups + Collections + Items + + + + + +
+ +
+ + +
+ {{ r.email }} +
+
+
+ + {{ r.groups }} + {{ r.collections }} + {{ r.items }} + +
+
diff --git a/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/member-access-report.component.ts b/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/member-access-report.component.ts new file mode 100644 index 0000000000..5e5a506b2f --- /dev/null +++ b/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/member-access-report.component.ts @@ -0,0 +1,33 @@ +import { Component, OnInit } from "@angular/core"; +import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; +import { FormControl } from "@angular/forms"; +import { debounceTime } from "rxjs"; + +import { SearchModule, TableDataSource } from "@bitwarden/components"; +import { HeaderModule } from "@bitwarden/web-vault/app/layouts/header/header.module"; +import { SharedModule } from "@bitwarden/web-vault/app/shared"; + +import { MemberAccessReportService } from "./member-access-report.service"; +import { MemberAccessReportView } from "./view/member-access-report.view"; + +@Component({ + selector: "member-access-report", + templateUrl: "member-access-report.component.html", + imports: [SharedModule, SearchModule, HeaderModule], + standalone: true, +}) +export class MemberAccessReportComponent implements OnInit { + protected dataSource = new TableDataSource(); + protected searchControl = new FormControl("", { nonNullable: true }); + + constructor(protected reportService: MemberAccessReportService) { + // Connect the search input to the table dataSource filter input + this.searchControl.valueChanges + .pipe(debounceTime(200), takeUntilDestroyed()) + .subscribe((v) => (this.dataSource.filter = v)); + } + + ngOnInit() { + this.dataSource.data = this.reportService.getMemberAccessMockData(); + } +} diff --git a/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/member-access-report.service.ts b/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/member-access-report.service.ts new file mode 100644 index 0000000000..d77e93b3d0 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/member-access-report.service.ts @@ -0,0 +1,39 @@ +import { Injectable } from "@angular/core"; + +import { MemberAccessReportView } from "./view/member-access-report.view"; + +@Injectable({ providedIn: "root" }) +export class MemberAccessReportService { + //Temporary method to provide mock data for test purposes only + getMemberAccessMockData(): MemberAccessReportView[] { + const memberAccess = new MemberAccessReportView(); + memberAccess.email = "sjohnson@email.com"; + memberAccess.name = "Sarah Johnson"; + memberAccess.groups = 3; + memberAccess.collections = 12; + memberAccess.items = 3; + + const memberAccess2 = new MemberAccessReportView(); + memberAccess2.email = "jlull@email.com"; + memberAccess2.name = "James Lull"; + memberAccess2.groups = 2; + memberAccess2.collections = 24; + memberAccess2.items = 2; + + const memberAccess3 = new MemberAccessReportView(); + memberAccess3.email = "bwilliams@email.com"; + memberAccess3.name = "Beth Williams"; + memberAccess3.groups = 6; + memberAccess3.collections = 12; + memberAccess3.items = 1; + + const memberAccess4 = new MemberAccessReportView(); + memberAccess4.email = "rwilliams@email.com"; + memberAccess4.name = "Ray Williams"; + memberAccess4.groups = 5; + memberAccess4.collections = 21; + memberAccess4.items = 2; + + return [memberAccess, memberAccess2, memberAccess3, memberAccess4]; + } +} diff --git a/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/view/member-access-report.view.ts b/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/view/member-access-report.view.ts new file mode 100644 index 0000000000..bc9947b3b3 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/view/member-access-report.view.ts @@ -0,0 +1,7 @@ +export class MemberAccessReportView { + name: string; + email: string; + collections: number; + groups: number; + items: number; +}