mirror of
https://github.com/bitwarden/browser.git
synced 2025-02-17 01:31:25 +01:00
[AC-2504] Add new members access report card (#9335)
* Added new report card and FeatureFlag for MemberAccessReport * Add new "isEnterpriseOrgGuard" * Add member access icon * Show upgrade organization dialog for enterprise on member access report click * verify member access featureflag on enterprise org guard * add comment with TODO information for follow up task * Improved readability, removed path to wrong component and refactored buildReports to use the productType * added TODO to remove the feature flag on cleanup * changing ProductType to ProductTierType on isEnterpriseOrgGuard
This commit is contained in:
parent
08cdecf514
commit
1a37d02556
@ -0,0 +1,67 @@
|
||||
import { Injectable } from "@angular/core";
|
||||
import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from "@angular/router";
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { ProductTierType } from "@bitwarden/common/billing/enums";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { DialogService } from "@bitwarden/components";
|
||||
|
||||
@Injectable({
|
||||
providedIn: "root",
|
||||
})
|
||||
export class IsEnterpriseOrgGuard implements CanActivate {
|
||||
constructor(
|
||||
private router: Router,
|
||||
private organizationService: OrganizationService,
|
||||
private dialogService: DialogService,
|
||||
private configService: ConfigService,
|
||||
) {}
|
||||
|
||||
async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
|
||||
const isMemberAccessReportEnabled = await firstValueFrom(
|
||||
this.configService.getFeatureFlag$(FeatureFlag.MemberAccessReport),
|
||||
);
|
||||
|
||||
// TODO: Remove on "MemberAccessReport" feature flag cleanup
|
||||
if (!isMemberAccessReportEnabled) {
|
||||
return this.router.createUrlTree(["/"]);
|
||||
}
|
||||
|
||||
const org = await this.organizationService.get(route.params.organizationId);
|
||||
|
||||
if (org == null) {
|
||||
return this.router.createUrlTree(["/"]);
|
||||
}
|
||||
|
||||
if (org.productTierType != ProductTierType.Enterprise) {
|
||||
// Users without billing permission can't access billing
|
||||
if (!org.canEditSubscription) {
|
||||
await this.dialogService.openSimpleDialog({
|
||||
title: { key: "upgradeOrganizationEnterprise" },
|
||||
content: { key: "onlyAvailableForEnterpriseOrganization" },
|
||||
acceptButtonText: { key: "ok" },
|
||||
cancelButtonText: null,
|
||||
type: "info",
|
||||
});
|
||||
return false;
|
||||
} else {
|
||||
const upgradeConfirmed = await this.dialogService.openSimpleDialog({
|
||||
title: { key: "upgradeOrganizationEnterprise" },
|
||||
content: { key: "onlyAvailableForEnterpriseOrganization" },
|
||||
acceptButtonText: { key: "upgradeOrganization" },
|
||||
type: "info",
|
||||
icon: "bwi-arrow-circle-up",
|
||||
});
|
||||
if (upgradeConfirmed) {
|
||||
await this.router.navigate(["organizations", org.id, "billing", "subscription"], {
|
||||
queryParams: { upgrade: true },
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return org.productTierType == ProductTierType.Enterprise;
|
||||
}
|
||||
}
|
@ -1,8 +1,11 @@
|
||||
import { Component, OnInit } from "@angular/core";
|
||||
import { ActivatedRoute, NavigationEnd, Router } from "@angular/router";
|
||||
import { filter, map, Observable, startWith, concatMap } from "rxjs";
|
||||
import { filter, map, Observable, startWith, concatMap, firstValueFrom } from "rxjs";
|
||||
|
||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { ProductTierType } from "@bitwarden/common/billing/enums";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
|
||||
import { ReportVariant, reports, ReportType, ReportEntry } from "../../../tools/reports";
|
||||
|
||||
@ -14,13 +17,21 @@ export class ReportsHomeComponent implements OnInit {
|
||||
reports$: Observable<ReportEntry[]>;
|
||||
homepage$: Observable<boolean>;
|
||||
|
||||
private isMemberAccessReportEnabled: boolean;
|
||||
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
private organizationService: OrganizationService,
|
||||
private router: Router,
|
||||
private configService: ConfigService,
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
async ngOnInit() {
|
||||
// TODO: Remove on "MemberAccessReport" feature flag cleanup
|
||||
this.isMemberAccessReportEnabled = await firstValueFrom(
|
||||
this.configService.getFeatureFlag$(FeatureFlag.MemberAccessReport),
|
||||
);
|
||||
|
||||
this.homepage$ = this.router.events.pipe(
|
||||
filter((event) => event instanceof NavigationEnd),
|
||||
map((event) => this.isReportsHomepageRouteUrl((event as NavigationEnd).urlAfterRedirects)),
|
||||
@ -29,16 +40,15 @@ export class ReportsHomeComponent implements OnInit {
|
||||
|
||||
this.reports$ = this.route.params.pipe(
|
||||
concatMap((params) => this.organizationService.get$(params.organizationId)),
|
||||
map((org) => this.buildReports(org.isFreeOrg)),
|
||||
map((org) => this.buildReports(org.productTierType)),
|
||||
);
|
||||
}
|
||||
|
||||
private buildReports(upgradeRequired: boolean): ReportEntry[] {
|
||||
const reportRequiresUpgrade = upgradeRequired
|
||||
? ReportVariant.RequiresUpgrade
|
||||
: ReportVariant.Enabled;
|
||||
private buildReports(productType: ProductTierType): ReportEntry[] {
|
||||
const reportRequiresUpgrade =
|
||||
productType == ProductTierType.Free ? ReportVariant.RequiresUpgrade : ReportVariant.Enabled;
|
||||
|
||||
return [
|
||||
const reportsArray = [
|
||||
{
|
||||
...reports[ReportType.ExposedPasswords],
|
||||
variant: reportRequiresUpgrade,
|
||||
@ -60,6 +70,18 @@ export class ReportsHomeComponent implements OnInit {
|
||||
variant: reportRequiresUpgrade,
|
||||
},
|
||||
];
|
||||
|
||||
if (this.isMemberAccessReportEnabled) {
|
||||
reportsArray.push({
|
||||
...reports[ReportType.MemberAccessReport],
|
||||
variant:
|
||||
productType == ProductTierType.Enterprise
|
||||
? ReportVariant.Enabled
|
||||
: ReportVariant.RequiresEnterprise,
|
||||
});
|
||||
}
|
||||
|
||||
return reportsArray;
|
||||
}
|
||||
|
||||
private isReportsHomepageRouteUrl(url: string): boolean {
|
||||
|
@ -0,0 +1,14 @@
|
||||
import { svgIcon } from "@bitwarden/components";
|
||||
|
||||
export const MemberAccess = svgIcon`
|
||||
<svg width="94" height="63" viewBox="0 0 94 63" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M70 10H2V46H12.3227H12.6806H46.2498H70V18.1286V10Z" fill="#518FFF"/>
|
||||
<path d="M65.7419 1H6C3.23858 1 1 3.23858 1 6V42.2581C1 45.0195 3.23857 47.2581 6 47.2581H12.2835H12.6401H46.0818H65.7419C68.5034 47.2581 70.7419 45.0195 70.7419 42.2581V11.9933V6C70.7419 3.23858 68.5034 1 65.7419 1Z" stroke="white" stroke-width="2"/>
|
||||
<circle cx="70.129" cy="27.0968" r="13.0968" fill="#175DDC" stroke="white" stroke-width="2"/>
|
||||
<path d="M88.9315 61.8708C73.6363 61.8708 66.0989 61.8708 50.4248 61.8708C48.57 61.8708 47.8031 59.945 48.0426 58.4704C49.725 48.1136 58.9691 40.1934 70.1207 40.1934C81.2722 40.1934 90.5164 48.1136 92.1988 58.4704C92.5526 60.6485 91.3052 61.8708 88.9315 61.8708Z" fill="#175DDC" stroke="white" stroke-width="2"/>
|
||||
<path d="M55.7419 5.61292V5.1613" stroke="white" stroke-width="2" stroke-linecap="round"/>
|
||||
<path d="M59.8064 5.61292V5.1613" stroke="white" stroke-width="2" stroke-linecap="round"/>
|
||||
<path d="M63.871 5.61292V5.1613" stroke="white" stroke-width="2" stroke-linecap="round"/>
|
||||
<line x1="2" y1="9" x2="69.7419" y2="9" stroke="white" stroke-width="2"/>
|
||||
</svg>
|
||||
`;
|
@ -1,6 +1,7 @@
|
||||
import { ReportBreach } from "./icons/report-breach.icon";
|
||||
import { ReportExposedPasswords } from "./icons/report-exposed-passwords.icon";
|
||||
import { ReportInactiveTwoFactor } from "./icons/report-inactive-two-factor.icon";
|
||||
import { MemberAccess } from "./icons/report-member-access.icon";
|
||||
import { ReportReusedPasswords } from "./icons/report-reused-passwords.icon";
|
||||
import { ReportUnsecuredWebsites } from "./icons/report-unsecured-websites.icon";
|
||||
import { ReportWeakPasswords } from "./icons/report-weak-passwords.icon";
|
||||
@ -13,6 +14,7 @@ export enum ReportType {
|
||||
UnsecuredWebsites = "unsecuredWebsites",
|
||||
Inactive2fa = "inactive2fa",
|
||||
DataBreach = "dataBreach",
|
||||
MemberAccessReport = "memberAccessReport",
|
||||
}
|
||||
|
||||
type ReportWithoutVariant = Omit<ReportEntry, "variant">;
|
||||
@ -54,4 +56,10 @@ export const reports: Record<ReportType, ReportWithoutVariant> = {
|
||||
route: "breach-report",
|
||||
icon: ReportBreach,
|
||||
},
|
||||
[ReportType.MemberAccessReport]: {
|
||||
title: "memberAccessReport",
|
||||
description: "memberAccessReportDesc",
|
||||
route: "member-access-report",
|
||||
icon: MemberAccess,
|
||||
},
|
||||
};
|
||||
|
@ -2,4 +2,5 @@ export enum ReportVariant {
|
||||
Enabled = "Enabled",
|
||||
RequiresPremium = "RequiresPremium",
|
||||
RequiresUpgrade = "RequiresUpgrade",
|
||||
RequiresEnterprise = "RequiresEnterprise",
|
||||
}
|
||||
|
@ -8384,6 +8384,12 @@
|
||||
"message": "This email address will receive all invoices pertaining to this provider",
|
||||
"description": "A hint that shows up on the Provider setup page to inform the admin the billing email will receive the provider's invoices."
|
||||
},
|
||||
"upgradeOrganizationEnterprise": {
|
||||
"message": "Identify security risks by auditing member access"
|
||||
},
|
||||
"onlyAvailableForEnterpriseOrganization": {
|
||||
"message": "Quickly view member access across the organization by upgrading to an Enterprise plan."
|
||||
},
|
||||
"date": {
|
||||
"message": "Date"
|
||||
},
|
||||
@ -8393,5 +8399,11 @@
|
||||
"invoiceNumberHeader": {
|
||||
"message": "Invoice number",
|
||||
"description": "A table header for an invoice's number"
|
||||
},
|
||||
"memberAccessReport": {
|
||||
"message": "Member access"
|
||||
},
|
||||
"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."
|
||||
}
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ export enum FeatureFlag {
|
||||
BulkDeviceApproval = "bulk-device-approval",
|
||||
EmailVerification = "email-verification",
|
||||
InlineMenuFieldQualification = "inline-menu-field-qualification",
|
||||
MemberAccessReport = "ac-2059-member-access-report",
|
||||
}
|
||||
|
||||
export type AllowedFeatureFlagTypes = boolean | number | string;
|
||||
@ -48,6 +49,7 @@ export const DefaultFeatureFlagValue = {
|
||||
[FeatureFlag.BulkDeviceApproval]: FALSE,
|
||||
[FeatureFlag.EmailVerification]: FALSE,
|
||||
[FeatureFlag.InlineMenuFieldQualification]: FALSE,
|
||||
[FeatureFlag.MemberAccessReport]: FALSE,
|
||||
} satisfies Record<FeatureFlag, AllowedFeatureFlagTypes>;
|
||||
|
||||
export type DefaultFeatureFlagValueType = typeof DefaultFeatureFlagValue;
|
||||
|
Loading…
Reference in New Issue
Block a user