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;
+}