From c3856ce821cab1ce4f457f78cc0b1175ad1241e2 Mon Sep 17 00:00:00 2001 From: cd-bitwarden <106776772+cd-bitwarden@users.noreply.github.com> Date: Mon, 16 Oct 2023 10:29:03 -0400 Subject: [PATCH] [SM-896] When org is disabled disable the logic and show warning symbols (#6225) * When org is disabled disable the logic and show warning symbols * fixing org enabled logic * removing unused code * Adding route gaurd logic and new org suspended page * fixing lint issue * fixing issues * Requested changes * adding back code that was accidentally removed from organization-switcher * Update bitwarden_license/bit-web/src/app/secrets-manager/shared/org-suspended.component.ts Co-authored-by: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com> * Removing unused code and updating storybook to set enabled:true * removing onDestroy * Will's suggestions * will's suggested change * fix nav-item color in story * Thomas Rittson's suggested changes * adding back removed spaces * Adding back white space * updating guard * Update bitwarden_license/bit-web/src/app/secrets-manager/guards/sm-org-enabled.guard.ts Co-authored-by: Thomas Rittson <31796059+eliykat@users.noreply.github.com> * removing ununsed data * Updating incorrect messages --------- Co-authored-by: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com> Co-authored-by: William Martin Co-authored-by: Thomas Rittson <31796059+eliykat@users.noreply.github.com> --- .../product-switcher-content.component.ts | 7 +- .../product-switcher.stories.ts | 2 +- apps/web/src/locales/en/messages.json | 12 +++ .../guards/sm-org-enabled.guard.ts | 29 ++++++ .../secrets-manager/{ => guards}/sm.guard.ts | 0 .../layout/org-switcher.component.html | 10 +++ .../layout/org-switcher.component.ts | 8 +- .../overview/overview.component.ts | 8 ++ .../dialog/project-dialog.component.ts | 10 +++ .../project/project-secrets.component.ts | 8 +- .../projects/project/project.component.ts | 8 +- .../projects/projects/projects.component.ts | 9 +- .../secrets/dialog/secret-dialog.component.ts | 6 ++ .../secrets/secrets.component.ts | 9 +- .../service-account-dialog.component.ts | 10 +++ .../service-accounts.component.ts | 9 +- .../shared/new-menu.component.ts | 13 ++- .../shared/org-suspended.component.html | 7 ++ .../shared/org-suspended.component.ts | 18 ++++ .../shared/sm-shared.module.ts | 2 + .../app/secrets-manager/sm-routing.module.ts | 89 ++++++++++--------- libs/components/src/icon/icons/index.ts | 1 + libs/components/src/icon/icons/no-access.ts | 12 +++ .../src/navigation/nav-group.component.html | 9 +- .../src/navigation/nav-item.component.html | 2 +- .../src/navigation/nav-item.stories.ts | 6 +- 26 files changed, 243 insertions(+), 61 deletions(-) create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/guards/sm-org-enabled.guard.ts rename bitwarden_license/bit-web/src/app/secrets-manager/{ => guards}/sm.guard.ts (100%) create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/shared/org-suspended.component.html create mode 100644 bitwarden_license/bit-web/src/app/secrets-manager/shared/org-suspended.component.ts create mode 100644 libs/components/src/icon/icons/no-access.ts diff --git a/apps/web/src/app/layouts/product-switcher/product-switcher-content.component.ts b/apps/web/src/app/layouts/product-switcher/product-switcher-content.component.ts index e0705dd070..7a637c642b 100644 --- a/apps/web/src/app/layouts/product-switcher/product-switcher-content.component.ts +++ b/apps/web/src/app/layouts/product-switcher/product-switcher-content.component.ts @@ -47,9 +47,10 @@ export class ProductSwitcherContentComponent { map(([orgs, paramMap]) => { const routeOrg = orgs.find((o) => o.id === paramMap.get("organizationId")); // If the active route org doesn't have access to SM, find the first org that does. - const smOrg = routeOrg?.canAccessSecretsManager - ? routeOrg - : orgs.find((o) => o.canAccessSecretsManager); + const smOrg = + routeOrg?.canAccessSecretsManager && routeOrg?.enabled == true + ? routeOrg + : orgs.find((o) => o.canAccessSecretsManager && o.enabled == true); /** * We can update this to the "satisfies" type upon upgrading to TypeScript 4.9 diff --git a/apps/web/src/app/layouts/product-switcher/product-switcher.stories.ts b/apps/web/src/app/layouts/product-switcher/product-switcher.stories.ts index 87e4202747..46a2df458b 100644 --- a/apps/web/src/app/layouts/product-switcher/product-switcher.stories.ts +++ b/apps/web/src/app/layouts/product-switcher/product-switcher.stories.ts @@ -131,5 +131,5 @@ OrgWithoutSecretsManager.args = { export const OrgWithSecretsManager = Template.bind({}); OrgWithSecretsManager.args = { - mockOrgs: [{ id: "b", canAccessSecretsManager: true }], + mockOrgs: [{ id: "b", canAccessSecretsManager: true, enabled: true }], }; diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index b9d613877f..60fed7a538 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -3638,6 +3638,18 @@ "organizationIsDisabled": { "message": "Organization suspended" }, + "secretsAccessSuspended": { + "message": "Suspended organizations cannot be accessed. Please contact your organization owner for assistance." + }, + "secretsCannotCreate": { + "message": "Secrets cannot be created in suspended organizations. Please contact your organization owner for assistance." + }, + "projectsCannotCreate": { + "message": "Projects cannot be created in suspended organizations. Please contact your organization owner for assistance." + }, + "serviceAccountsCannotCreate": { + "message": "Service accounts cannot be created in suspended organizations. Please contact your organization owner for assistance." + }, "disabledOrganizationFilterError": { "message": "Items in suspended organizations cannot be accessed. Contact your organization owner for assistance." }, diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/guards/sm-org-enabled.guard.ts b/bitwarden_license/bit-web/src/app/secrets-manager/guards/sm-org-enabled.guard.ts new file mode 100644 index 0000000000..3ff4d998a3 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/guards/sm-org-enabled.guard.ts @@ -0,0 +1,29 @@ +import { inject } from "@angular/core"; +import { ActivatedRouteSnapshot, CanActivateFn, createUrlTreeFromSnapshot } from "@angular/router"; + +import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; + +/** + * Redirects from root `/sm` to first organization with access to SM + */ +export const organizationEnabledGuard: CanActivateFn = async (route: ActivatedRouteSnapshot) => { + const syncService = inject(SyncService); + const orgService = inject(OrganizationService); + + /** Workaround to avoid service initialization race condition. */ + if ((await syncService.getLastSync()) == null) { + await syncService.fullSync(false); + } + + const org = orgService.get(route.params.organizationId); + if (org == null || !org.canAccessSecretsManager) { + return createUrlTreeFromSnapshot(route, ["/"]); + } + + if (!org.enabled) { + return createUrlTreeFromSnapshot(route, ["/sm", org.id, "organization-suspended"]); + } + + return true; +}; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/sm.guard.ts b/bitwarden_license/bit-web/src/app/secrets-manager/guards/sm.guard.ts similarity index 100% rename from bitwarden_license/bit-web/src/app/secrets-manager/sm.guard.ts rename to bitwarden_license/bit-web/src/app/secrets-manager/guards/sm.guard.ts diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/layout/org-switcher.component.html b/bitwarden_license/bit-web/src/app/secrets-manager/layout/org-switcher.component.html index e639c5f126..d7a404bf1d 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/layout/org-switcher.component.html +++ b/bitwarden_license/bit-web/src/app/secrets-manager/layout/org-switcher.component.html @@ -7,6 +7,11 @@ [(open)]="open" [exactMatch]="true" > + + = this.organizationService.organizations$.pipe( - map((orgs) => orgs.filter(this.filter).sort((a, b) => a.name.localeCompare(b.name))) + map((orgs) => + orgs + .filter((org) => this.filter(org)) + .sort((a, b) => a.name.localeCompare(b.name)) + .sort((a, b) => (a.enabled ? -1 : 1)) + ) ); + protected activeOrganization$: Observable = combineLatest([ this.route.paramMap, this.organizations$, diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.component.ts index 86fab25608..868026a843 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.component.ts @@ -70,6 +70,7 @@ export class OverviewComponent implements OnInit, OnDestroy { protected userIsAdmin: boolean; protected showOnboarding = false; protected loading = true; + protected organizationEnabled = false; protected view$: Observable<{ allProjects: ProjectListView[]; @@ -107,6 +108,7 @@ export class OverviewComponent implements OnInit, OnDestroy { this.organizationName = org.name; this.userIsAdmin = org.isAdmin; this.loading = true; + this.organizationEnabled = org.enabled; }); const projects$ = combineLatest([ @@ -208,6 +210,7 @@ export class OverviewComponent implements OnInit, OnDestroy { data: { organizationId: this.organizationId, operation: OperationType.Edit, + organizationEnabled: this.organizationEnabled, projectId: projectId, }, }); @@ -218,6 +221,7 @@ export class OverviewComponent implements OnInit, OnDestroy { data: { organizationId: this.organizationId, operation: OperationType.Add, + organizationEnabled: this.organizationEnabled, }, }); } @@ -227,6 +231,7 @@ export class OverviewComponent implements OnInit, OnDestroy { data: { organizationId: this.organizationId, operation: OperationType.Add, + organizationEnabled: this.organizationEnabled, }, }); } @@ -246,6 +251,7 @@ export class OverviewComponent implements OnInit, OnDestroy { data: { organizationId: this.organizationId, operation: OperationType.Add, + organizationEnabled: this.organizationEnabled, }, }); } @@ -256,6 +262,7 @@ export class OverviewComponent implements OnInit, OnDestroy { organizationId: this.organizationId, operation: OperationType.Edit, secretId: secretId, + organizationEnabled: this.organizationEnabled, }, }); } @@ -273,6 +280,7 @@ export class OverviewComponent implements OnInit, OnDestroy { data: { organizationId: this.organizationId, operation: OperationType.Add, + organizationEnabled: this.organizationEnabled, }, }); } diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/projects/dialog/project-dialog.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/projects/dialog/project-dialog.component.ts index a6a3c958d0..3fd723c758 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/projects/dialog/project-dialog.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/projects/dialog/project-dialog.component.ts @@ -18,6 +18,7 @@ export enum OperationType { export interface ProjectOperation { organizationId: string; operation: OperationType; + organizationEnabled: boolean; projectId?: string; } @@ -63,6 +64,15 @@ export class ProjectDialogComponent implements OnInit { } submit = async () => { + if (!this.data.organizationEnabled) { + this.platformUtilsService.showToast( + "error", + null, + this.i18nService.t("projectsCannotCreate") + ); + return; + } + this.formGroup.markAllAsTouched(); if (this.formGroup.invalid) { diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-secrets.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-secrets.component.ts index 2d1690ef0e..a952a35153 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-secrets.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-secrets.component.ts @@ -2,6 +2,7 @@ import { Component } from "@angular/core"; import { ActivatedRoute } from "@angular/router"; import { combineLatest, combineLatestWith, filter, Observable, startWith, switchMap } from "rxjs"; +import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { DialogService } from "@bitwarden/components"; @@ -31,6 +32,7 @@ export class ProjectSecretsComponent { private organizationId: string; private projectId: string; protected project$: Observable; + private organizationEnabled: boolean; constructor( private route: ActivatedRoute, @@ -38,7 +40,8 @@ export class ProjectSecretsComponent { private secretService: SecretService, private dialogService: DialogService, private platformUtilsService: PlatformUtilsService, - private i18nService: I18nService + private i18nService: I18nService, + private organizationService: OrganizationService ) {} ngOnInit() { @@ -60,6 +63,7 @@ export class ProjectSecretsComponent { switchMap(async ([_, params]) => { this.organizationId = params.organizationId; this.projectId = params.projectId; + this.organizationEnabled = this.organizationService.get(params.organizationId)?.enabled; return await this.getSecretsByProject(); }) ); @@ -75,6 +79,7 @@ export class ProjectSecretsComponent { organizationId: this.organizationId, operation: OperationType.Edit, secretId: secretId, + organizationEnabled: this.organizationEnabled, }, }); } @@ -93,6 +98,7 @@ export class ProjectSecretsComponent { organizationId: this.organizationId, operation: OperationType.Add, projectId: this.projectId, + organizationEnabled: this.organizationEnabled, }, }); } diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project.component.ts index c87d238d6a..148ccc79d2 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project.component.ts @@ -12,6 +12,7 @@ import { takeUntil, } from "rxjs"; +import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { DialogService } from "@bitwarden/components"; @@ -33,7 +34,7 @@ export class ProjectComponent implements OnInit, OnDestroy { private organizationId: string; private projectId: string; - + private organizationEnabled: boolean; private destroy$ = new Subject(); constructor( @@ -42,7 +43,8 @@ export class ProjectComponent implements OnInit, OnDestroy { private router: Router, private dialogService: DialogService, private platformUtilsService: PlatformUtilsService, - private i18nService: I18nService + private i18nService: I18nService, + private organizationService: OrganizationService ) {} ngOnInit(): void { @@ -69,6 +71,7 @@ export class ProjectComponent implements OnInit, OnDestroy { this.route.params.pipe(takeUntil(this.destroy$)).subscribe((params) => { this.organizationId = params.organizationId; this.projectId = params.projectId; + this.organizationEnabled = this.organizationService.get(params.organizationId)?.enabled; }); } @@ -82,6 +85,7 @@ export class ProjectComponent implements OnInit, OnDestroy { data: { organizationId: this.organizationId, operation: OperationType.Edit, + organizationEnabled: this.organizationEnabled, projectId: this.projectId, }, }); diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/projects/projects/projects.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/projects/projects/projects.component.ts index 7128e26a3d..1066828f21 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/projects/projects/projects.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/projects/projects/projects.component.ts @@ -2,6 +2,7 @@ import { Component, OnInit } from "@angular/core"; import { ActivatedRoute } from "@angular/router"; import { combineLatest, lastValueFrom, Observable, startWith, switchMap } from "rxjs"; +import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { DialogService } from "@bitwarden/components"; import { ProjectListView } from "../../models/view/project-list.view"; @@ -32,12 +33,14 @@ export class ProjectsComponent implements OnInit { protected search: string; private organizationId: string; + private organizationEnabled: boolean; constructor( private route: ActivatedRoute, private projectService: ProjectService, private accessPolicyService: AccessPolicyService, - private dialogService: DialogService + private dialogService: DialogService, + private organizationService: OrganizationService ) {} ngOnInit() { @@ -48,6 +51,8 @@ export class ProjectsComponent implements OnInit { ]).pipe( switchMap(async ([params]) => { this.organizationId = params.organizationId; + this.organizationEnabled = this.organizationService.get(params.organizationId)?.enabled; + return await this.getProjects(); }) ); @@ -62,6 +67,7 @@ export class ProjectsComponent implements OnInit { data: { organizationId: this.organizationId, operation: OperationType.Edit, + organizationEnabled: this.organizationEnabled, projectId: projectId, }, }); @@ -72,6 +78,7 @@ export class ProjectsComponent implements OnInit { data: { organizationId: this.organizationId, operation: OperationType.Add, + organizationEnabled: this.organizationEnabled, }, }); } diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/secrets/dialog/secret-dialog.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/secrets/dialog/secret-dialog.component.ts index 426542823f..70eca54e3c 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/secrets/dialog/secret-dialog.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/secrets/dialog/secret-dialog.component.ts @@ -29,6 +29,7 @@ export interface SecretOperation { operation: OperationType; projectId?: string; secretId?: string; + organizationEnabled: boolean; } @Component({ @@ -163,6 +164,11 @@ export class SecretDialogComponent implements OnInit { } submit = async () => { + if (!this.data.organizationEnabled) { + this.platformUtilsService.showToast("error", null, this.i18nService.t("secretsCannotCreate")); + return; + } + this.formGroup.markAllAsTouched(); if (this.formGroup.invalid) { diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/secrets/secrets.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/secrets/secrets.component.ts index 7c05f169a3..b23393de60 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/secrets/secrets.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/secrets/secrets.component.ts @@ -2,6 +2,7 @@ import { Component, OnInit } from "@angular/core"; import { ActivatedRoute } from "@angular/router"; import { combineLatestWith, Observable, startWith, switchMap } from "rxjs"; +import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { DialogService } from "@bitwarden/components"; @@ -29,13 +30,15 @@ export class SecretsComponent implements OnInit { protected search: string; private organizationId: string; + private organizationEnabled: boolean; constructor( private route: ActivatedRoute, private secretService: SecretService, private dialogService: DialogService, private platformUtilsService: PlatformUtilsService, - private i18nService: I18nService + private i18nService: I18nService, + private organizationService: OrganizationService ) {} ngOnInit() { @@ -44,6 +47,8 @@ export class SecretsComponent implements OnInit { combineLatestWith(this.route.params), switchMap(async ([_, params]) => { this.organizationId = params.organizationId; + this.organizationEnabled = this.organizationService.get(params.organizationId)?.enabled; + return await this.getSecrets(); }) ); @@ -63,6 +68,7 @@ export class SecretsComponent implements OnInit { organizationId: this.organizationId, operation: OperationType.Edit, secretId: secretId, + organizationEnabled: this.organizationEnabled, }, }); } @@ -80,6 +86,7 @@ export class SecretsComponent implements OnInit { data: { organizationId: this.organizationId, operation: OperationType.Add, + organizationEnabled: this.organizationEnabled, }, }); } diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/dialog/service-account-dialog.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/dialog/service-account-dialog.component.ts index 1f42537f95..decd042cc1 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/dialog/service-account-dialog.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/dialog/service-account-dialog.component.ts @@ -18,6 +18,7 @@ export interface ServiceAccountOperation { organizationId: string; serviceAccountId?: string; operation: OperationType; + organizationEnabled: boolean; } @Component({ @@ -62,6 +63,15 @@ export class ServiceAccountDialogComponent { } submit = async () => { + if (!this.data.organizationEnabled) { + this.platformUtilsService.showToast( + "error", + null, + this.i18nService.t("serviceAccountsCannotCreate") + ); + return; + } + this.formGroup.markAllAsTouched(); if (this.formGroup.invalid) { diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-accounts.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-accounts.component.ts index 808073ba81..bebd9ddca6 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-accounts.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-accounts.component.ts @@ -2,6 +2,7 @@ import { Component, OnInit } from "@angular/core"; import { ActivatedRoute } from "@angular/router"; import { combineLatest, Observable, startWith, switchMap } from "rxjs"; +import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { DialogService } from "@bitwarden/components"; import { @@ -30,12 +31,14 @@ export class ServiceAccountsComponent implements OnInit { protected search: string; private organizationId: string; + private organizationEnabled: boolean; constructor( private route: ActivatedRoute, private dialogService: DialogService, private accessPolicyService: AccessPolicyService, - private serviceAccountService: ServiceAccountService + private serviceAccountService: ServiceAccountService, + private organizationService: OrganizationService ) {} ngOnInit() { @@ -46,6 +49,8 @@ export class ServiceAccountsComponent implements OnInit { ]).pipe( switchMap(async ([params]) => { this.organizationId = params.organizationId; + this.organizationEnabled = this.organizationService.get(params.organizationId)?.enabled; + return await this.getServiceAccounts(); }) ); @@ -56,6 +61,7 @@ export class ServiceAccountsComponent implements OnInit { data: { organizationId: this.organizationId, operation: OperationType.Add, + organizationEnabled: this.organizationEnabled, }, }); } @@ -66,6 +72,7 @@ export class ServiceAccountsComponent implements OnInit { organizationId: this.organizationId, serviceAccountId: serviceAccountId, operation: OperationType.Edit, + organizationEnabled: this.organizationEnabled, }, }); } diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/shared/new-menu.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/shared/new-menu.component.ts index 7ecc2f917a..67a93e8ad8 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/shared/new-menu.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/shared/new-menu.component.ts @@ -2,6 +2,7 @@ import { Component, OnDestroy, OnInit } from "@angular/core"; import { ActivatedRoute } from "@angular/router"; import { Subject, takeUntil } from "rxjs"; +import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { DialogService } from "@bitwarden/components"; import { @@ -24,13 +25,18 @@ import { }) export class NewMenuComponent implements OnInit, OnDestroy { private organizationId: string; + private organizationEnabled: boolean; private destroy$: Subject = new Subject(); - - constructor(private route: ActivatedRoute, private dialogService: DialogService) {} + constructor( + private route: ActivatedRoute, + private dialogService: DialogService, + private organizationService: OrganizationService + ) {} ngOnInit() { this.route.params.pipe(takeUntil(this.destroy$)).subscribe((params: any) => { this.organizationId = params.organizationId; + this.organizationEnabled = this.organizationService.get(params.organizationId)?.enabled; }); } @@ -44,6 +50,7 @@ export class NewMenuComponent implements OnInit, OnDestroy { data: { organizationId: this.organizationId, operation: OperationType.Add, + organizationEnabled: this.organizationEnabled, }, }); } @@ -53,6 +60,7 @@ export class NewMenuComponent implements OnInit, OnDestroy { data: { organizationId: this.organizationId, operation: OperationType.Add, + organizationEnabled: this.organizationEnabled, }, }); } @@ -62,6 +70,7 @@ export class NewMenuComponent implements OnInit, OnDestroy { data: { organizationId: this.organizationId, operation: OperationType.Add, + organizationEnabled: this.organizationEnabled, }, }); } diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/shared/org-suspended.component.html b/bitwarden_license/bit-web/src/app/secrets-manager/shared/org-suspended.component.html new file mode 100644 index 0000000000..8de68f6598 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/shared/org-suspended.component.html @@ -0,0 +1,7 @@ + + + + + {{ "organizationIsDisabled" | i18n }} + {{ "secretsAccessSuspended" | i18n }} + diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/shared/org-suspended.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/shared/org-suspended.component.ts new file mode 100644 index 0000000000..73f89c0826 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/shared/org-suspended.component.ts @@ -0,0 +1,18 @@ +import { Component } from "@angular/core"; +import { ActivatedRoute } from "@angular/router"; +import { map } from "rxjs"; + +import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { Icon, Icons } from "@bitwarden/components"; + +@Component({ + templateUrl: "./org-suspended.component.html", +}) +export class OrgSuspendedComponent { + constructor(private organizationService: OrganizationService, private route: ActivatedRoute) {} + + protected NoAccess: Icon = Icons.NoAccess; + protected organizationName$ = this.route.params.pipe( + map((params) => this.organizationService.get(params.organizationId)?.name) + ); +} diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/shared/sm-shared.module.ts b/bitwarden_license/bit-web/src/app/secrets-manager/shared/sm-shared.module.ts index 6d59503b50..d2990f4c67 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/shared/sm-shared.module.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/shared/sm-shared.module.ts @@ -17,6 +17,7 @@ import { BulkConfirmationDialogComponent } from "./dialogs/bulk-confirmation-dia import { BulkStatusDialogComponent } from "./dialogs/bulk-status-dialog.component"; import { HeaderComponent } from "./header.component"; import { NewMenuComponent } from "./new-menu.component"; +import { OrgSuspendedComponent } from "./org-suspended.component"; import { ProjectsListComponent } from "./projects-list.component"; import { SecretsListComponent } from "./secrets-list.component"; @@ -55,6 +56,7 @@ import { SecretsListComponent } from "./secrets-list.component"; ProjectsListComponent, SecretsListComponent, AccessSelectorComponent, + OrgSuspendedComponent, ], providers: [], bootstrap: [], diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/sm-routing.module.ts b/bitwarden_license/bit-web/src/app/secrets-manager/sm-routing.module.ts index 5c18bab4e4..0cad3129a4 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/sm-routing.module.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/sm-routing.module.ts @@ -2,10 +2,10 @@ import { NgModule } from "@angular/core"; import { RouterModule, Routes } from "@angular/router"; import { AuthGuard } from "@bitwarden/angular/auth/guards"; -import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; -import { OrganizationPermissionsGuard } from "@bitwarden/web-vault/app/admin-console/organizations/guards/org-permissions.guard"; import { buildFlaggedRoute } from "@bitwarden/web-vault/app/oss-routing.module"; +import { organizationEnabledGuard } from "./guards/sm-org-enabled.guard"; +import { canActivateSM } from "./guards/sm.guard"; import { LayoutComponent } from "./layout/layout.component"; import { NavigationComponent } from "./layout/navigation.component"; import { OverviewModule } from "./overview/overview.module"; @@ -13,7 +13,7 @@ import { ProjectsModule } from "./projects/projects.module"; import { SecretsModule } from "./secrets/secrets.module"; import { ServiceAccountsModule } from "./service-accounts/service-accounts.module"; import { SettingsModule } from "./settings/settings.module"; -import { canActivateSM } from "./sm.guard"; +import { OrgSuspendedComponent } from "./shared/org-suspended.component"; import { TrashModule } from "./trash/trash.module"; const routes: Routes = [ @@ -29,52 +29,59 @@ const routes: Routes = [ { path: ":organizationId", component: LayoutComponent, - canActivate: [AuthGuard, OrganizationPermissionsGuard], - data: { - organizationPermissions: (org: Organization) => org.canAccessSecretsManager, - }, + canActivate: [AuthGuard], children: [ { path: "", component: NavigationComponent, outlet: "sidebar", }, - { - path: "secrets", - loadChildren: () => SecretsModule, - data: { - titleId: "secrets", - }, - }, - { - path: "projects", - loadChildren: () => ProjectsModule, - data: { - titleId: "projects", - }, - }, - { - path: "service-accounts", - loadChildren: () => ServiceAccountsModule, - data: { - titleId: "serviceAccounts", - }, - }, - { - path: "trash", - loadChildren: () => TrashModule, - data: { - titleId: "trash", - }, - }, - { - path: "settings", - loadChildren: () => SettingsModule, - }, { path: "", - loadChildren: () => OverviewModule, - pathMatch: "full", + canActivate: [organizationEnabledGuard], + children: [ + { + path: "secrets", + loadChildren: () => SecretsModule, + data: { + titleId: "secrets", + }, + }, + { + path: "projects", + loadChildren: () => ProjectsModule, + data: { + titleId: "projects", + }, + }, + { + path: "service-accounts", + loadChildren: () => ServiceAccountsModule, + data: { + titleId: "serviceAccounts", + }, + }, + { + path: "trash", + loadChildren: () => TrashModule, + data: { + titleId: "trash", + }, + }, + { + path: "settings", + loadChildren: () => SettingsModule, + }, + { + path: "", + loadChildren: () => OverviewModule, + pathMatch: "full", + }, + ], + }, + { + path: "organization-suspended", + component: OrgSuspendedComponent, }, ], }, diff --git a/libs/components/src/icon/icons/index.ts b/libs/components/src/icon/icons/index.ts index 03fdb729bc..02cb975e09 100644 --- a/libs/components/src/icon/icons/index.ts +++ b/libs/components/src/icon/icons/index.ts @@ -1 +1,2 @@ export * from "./search"; +export * from "./no-access"; diff --git a/libs/components/src/icon/icons/no-access.ts b/libs/components/src/icon/icons/no-access.ts new file mode 100644 index 0000000000..f9ad048752 --- /dev/null +++ b/libs/components/src/icon/icons/no-access.ts @@ -0,0 +1,12 @@ +import { svgIcon } from "../icon"; + +export const NoAccess = svgIcon` + + + + + + + + +`; diff --git a/libs/components/src/navigation/nav-group.component.html b/libs/components/src/navigation/nav-group.component.html index ca9a7c3aec..118f78a186 100644 --- a/libs/components/src/navigation/nav-group.component.html +++ b/libs/components/src/navigation/nav-group.component.html @@ -17,7 +17,7 @@ [bitIconButton]=" open ? 'bwi-angle-up' : variant === 'tree' ? 'bwi-angle-right' : 'bwi-angle-down' " - [buttonType]="'main'" + [buttonType]="'light'" (click)="toggle($event)" size="small" [title]="'toggleCollapse' | i18n" @@ -32,8 +32,11 @@ - - + + + + + diff --git a/libs/components/src/navigation/nav-item.component.html b/libs/components/src/navigation/nav-item.component.html index 32c8dfbf98..02705e821e 100644 --- a/libs/components/src/navigation/nav-item.component.html +++ b/libs/components/src/navigation/nav-item.component.html @@ -73,7 +73,7 @@
diff --git a/libs/components/src/navigation/nav-item.stories.ts b/libs/components/src/navigation/nav-item.stories.ts index 7fdbadce31..c8f90eabcf 100644 --- a/libs/components/src/navigation/nav-item.stories.ts +++ b/libs/components/src/navigation/nav-item.stories.ts @@ -64,7 +64,7 @@ export const WithChildButtons: Story = { slot="start" class="tw-ml-auto" [bitIconButton]="'bwi-clone'" - [buttonType]="'contrast'" + [buttonType]="'light'" size="small" aria-label="option 1" > @@ -72,7 +72,7 @@ export const WithChildButtons: Story = { slot="end" class="tw-ml-auto" [bitIconButton]="'bwi-pencil-square'" - [buttonType]="'contrast'" + [buttonType]="'light'" size="small" aria-label="option 2" > @@ -80,7 +80,7 @@ export const WithChildButtons: Story = { slot="end" class="tw-ml-auto" [bitIconButton]="'bwi-check'" - [buttonType]="'contrast'" + [buttonType]="'light'" size="small" aria-label="option 3" >