1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-17 08:43:33 +00:00

[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 <contact@willmartian.com>
Co-authored-by: Thomas Rittson <31796059+eliykat@users.noreply.github.com>
This commit is contained in:
cd-bitwarden
2023-10-16 10:29:03 -04:00
committed by GitHub
parent 2dc94ede97
commit c3856ce821
26 changed files with 243 additions and 61 deletions

View File

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

View File

@@ -7,6 +7,11 @@
[(open)]="open"
[exactMatch]="true"
>
<i
slot="end"
*ngIf="!activeOrganization.enabled"
class="bwi bwi-exclamation-triangle tw-my-auto !tw-text-danger"
></i>
<ng-container *ngIf="organizations$ | async as organizations">
<bit-nav-item
*ngFor="let org of organizations"
@@ -16,6 +21,11 @@
(mainContentClicked)="toggle()"
[hideActiveStyles]="true"
>
<i
slot="end"
*ngIf="org.enabled == false"
class="bwi bwi-exclamation-triangle !tw-text-danger"
></i>
</bit-nav-item>
</ng-container>
<bit-nav-item

View File

@@ -12,8 +12,14 @@ import type { Organization } from "@bitwarden/common/admin-console/models/domain
export class OrgSwitcherComponent {
protected organizations$: Observable<Organization[]> =
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<Organization> = combineLatest([
this.route.paramMap,
this.organizations$,

View File

@@ -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,
},
});
}

View File

@@ -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) {

View File

@@ -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<ProjectView>;
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,
},
});
}

View File

@@ -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<void>();
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,
},
});

View File

@@ -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,
},
});
}

View File

@@ -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) {

View File

@@ -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,
},
});
}

View File

@@ -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) {

View File

@@ -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,
},
});
}

View File

@@ -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<void> = new Subject<void>();
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,
},
});
}

View File

@@ -0,0 +1,7 @@
<sm-header [title]="organizationName$ | async">
<sm-new-menu></sm-new-menu>
</sm-header>
<bit-no-items [icon]="NoAccess">
<ng-container slot="title">{{ "organizationIsDisabled" | i18n }}</ng-container>
<ng-container slot="description">{{ "secretsAccessSuspended" | i18n }}</ng-container>
</bit-no-items>

View File

@@ -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)
);
}

View File

@@ -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: [],

View File

@@ -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,
},
],
},