Restructued is-enterprise-org.guard to be a function. (#10134)
Added test cases for IsEnterpriseOrgGuard Co-authored-by: Tom <ttalty@bitwarden.com> Co-authored-by: Tom <144813356+ttalty@users.noreply.github.com>
This commit is contained in:
parent
97f44ace66
commit
9a6b37d019
|
@ -0,0 +1,126 @@
|
||||||
|
import { Component } from "@angular/core";
|
||||||
|
import { TestBed } from "@angular/core/testing";
|
||||||
|
import { provideRouter } from "@angular/router";
|
||||||
|
import { RouterTestingHarness } from "@angular/router/testing";
|
||||||
|
import { MockProxy, any, mock } from "jest-mock-extended";
|
||||||
|
|
||||||
|
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||||
|
import { OrganizationUserType } from "@bitwarden/common/admin-console/enums";
|
||||||
|
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||||
|
import { ProductTierType } from "@bitwarden/common/billing/enums";
|
||||||
|
import { DialogService } from "@bitwarden/components";
|
||||||
|
|
||||||
|
import { isEnterpriseOrgGuard } from "./is-enterprise-org.guard";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
template: "<h1>This is the home screen!</h1>",
|
||||||
|
})
|
||||||
|
export class HomescreenComponent {}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
template: "<h1>This component can only be accessed by a enterprise organization!</h1>",
|
||||||
|
})
|
||||||
|
export class IsEnterpriseOrganizationComponent {}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
template: "<h1>This is the organization upgrade screen!</h1>",
|
||||||
|
})
|
||||||
|
export class OrganizationUpgradeScreenComponent {}
|
||||||
|
|
||||||
|
const orgFactory = (props: Partial<Organization> = {}) =>
|
||||||
|
Object.assign(
|
||||||
|
new Organization(),
|
||||||
|
{
|
||||||
|
id: "myOrgId",
|
||||||
|
enabled: true,
|
||||||
|
type: OrganizationUserType.Admin,
|
||||||
|
},
|
||||||
|
props,
|
||||||
|
);
|
||||||
|
|
||||||
|
describe("Is Enterprise Org Guard", () => {
|
||||||
|
let organizationService: MockProxy<OrganizationService>;
|
||||||
|
let dialogService: MockProxy<DialogService>;
|
||||||
|
let routerHarness: RouterTestingHarness;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
organizationService = mock<OrganizationService>();
|
||||||
|
dialogService = mock<DialogService>();
|
||||||
|
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
providers: [
|
||||||
|
{ provide: OrganizationService, useValue: organizationService },
|
||||||
|
{ provide: DialogService, useValue: dialogService },
|
||||||
|
provideRouter([
|
||||||
|
{
|
||||||
|
path: "",
|
||||||
|
component: HomescreenComponent,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "organizations/:organizationId/enterpriseOrgsOnly",
|
||||||
|
component: IsEnterpriseOrganizationComponent,
|
||||||
|
canActivate: [isEnterpriseOrgGuard()],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "organizations/:organizationId/billing/subscription",
|
||||||
|
component: OrganizationUpgradeScreenComponent,
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
routerHarness = await RouterTestingHarness.create();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("redirects to `/` if the organization id provided is not found", async () => {
|
||||||
|
const org = orgFactory();
|
||||||
|
organizationService.get.calledWith(org.id).mockResolvedValue(null);
|
||||||
|
await routerHarness.navigateByUrl(`organizations/${org.id}/enterpriseOrgsOnly`);
|
||||||
|
expect(routerHarness.routeNativeElement?.querySelector("h1")?.textContent?.trim() ?? "").toBe(
|
||||||
|
"This is the home screen!",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it.each([
|
||||||
|
ProductTierType.Free,
|
||||||
|
ProductTierType.Families,
|
||||||
|
ProductTierType.Teams,
|
||||||
|
ProductTierType.TeamsStarter,
|
||||||
|
])(
|
||||||
|
"shows a dialog to users of a not enterprise organization and does not proceed with navigation for productTierType '%s'",
|
||||||
|
async (productTierType) => {
|
||||||
|
const org = orgFactory({
|
||||||
|
type: OrganizationUserType.User,
|
||||||
|
productTierType: productTierType,
|
||||||
|
});
|
||||||
|
organizationService.get.calledWith(org.id).mockResolvedValue(org);
|
||||||
|
await routerHarness.navigateByUrl(`organizations/${org.id}/enterpriseOrgsOnly`);
|
||||||
|
expect(dialogService.openSimpleDialog).toHaveBeenCalled();
|
||||||
|
expect(
|
||||||
|
routerHarness.routeNativeElement?.querySelector("h1")?.textContent?.trim() ?? "",
|
||||||
|
).not.toBe("This component can only be accessed by a enterprise organization!");
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
it("redirects users with billing access to the billing screen to upgrade", async () => {
|
||||||
|
const org = orgFactory({
|
||||||
|
type: OrganizationUserType.Owner,
|
||||||
|
productTierType: ProductTierType.Teams,
|
||||||
|
});
|
||||||
|
organizationService.get.calledWith(org.id).mockResolvedValue(org);
|
||||||
|
dialogService.openSimpleDialog.calledWith(any()).mockResolvedValue(true);
|
||||||
|
await routerHarness.navigateByUrl(`organizations/${org.id}/enterpriseOrgsOnly`);
|
||||||
|
expect(routerHarness.routeNativeElement?.querySelector("h1")?.textContent?.trim() ?? "").toBe(
|
||||||
|
"This is the organization upgrade screen!",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("proceeds with navigation if the organization in question is a enterprise organization", async () => {
|
||||||
|
const org = orgFactory({ productTierType: ProductTierType.Enterprise });
|
||||||
|
organizationService.get.calledWith(org.id).mockResolvedValue(org);
|
||||||
|
await routerHarness.navigateByUrl(`organizations/${org.id}/enterpriseOrgsOnly`);
|
||||||
|
expect(routerHarness.routeNativeElement?.querySelector("h1")?.textContent?.trim() ?? "").toBe(
|
||||||
|
"This component can only be accessed by a enterprise organization!",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,44 +1,45 @@
|
||||||
import { Injectable } from "@angular/core";
|
import { inject } from "@angular/core";
|
||||||
import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from "@angular/router";
|
import {
|
||||||
import { firstValueFrom } from "rxjs";
|
ActivatedRouteSnapshot,
|
||||||
|
CanActivateFn,
|
||||||
|
Router,
|
||||||
|
RouterStateSnapshot,
|
||||||
|
} from "@angular/router";
|
||||||
|
|
||||||
|
import { canAccessFeature } from "@bitwarden/angular/platform/guard/feature-flag.guard";
|
||||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||||
import { ProductTierType } from "@bitwarden/common/billing/enums";
|
import { ProductTierType } from "@bitwarden/common/billing/enums";
|
||||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
|
||||||
import { DialogService } from "@bitwarden/components";
|
import { DialogService } from "@bitwarden/components";
|
||||||
|
|
||||||
@Injectable({
|
/**
|
||||||
providedIn: "root",
|
* `CanActivateFn` that checks if the organization matching the id in the URL
|
||||||
})
|
* parameters is of enterprise type. If the organization is not enterprise instructions are
|
||||||
export class IsEnterpriseOrgGuard implements CanActivate {
|
* provided on how to upgrade into an enterprise organization, and the user is redirected
|
||||||
constructor(
|
* if they have access to upgrade the organization. If the organization is
|
||||||
private router: Router,
|
* enterprise routing proceeds."
|
||||||
private organizationService: OrganizationService,
|
*/
|
||||||
private dialogService: DialogService,
|
export function isEnterpriseOrgGuard(): CanActivateFn {
|
||||||
private configService: ConfigService,
|
return async (route: ActivatedRouteSnapshot, _state: RouterStateSnapshot) => {
|
||||||
) {}
|
const router = inject(Router);
|
||||||
|
const organizationService = inject(OrganizationService);
|
||||||
|
const dialogService = inject(DialogService);
|
||||||
|
|
||||||
async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
|
const org = await organizationService.get(route.params.organizationId);
|
||||||
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) {
|
if (org == null) {
|
||||||
return this.router.createUrlTree(["/"]);
|
return router.createUrlTree(["/"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Remove on "MemberAccessReport" feature flag cleanup
|
||||||
|
if (!canAccessFeature(FeatureFlag.MemberAccessReport)) {
|
||||||
|
return router.createUrlTree(["/"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (org.productTierType != ProductTierType.Enterprise) {
|
if (org.productTierType != ProductTierType.Enterprise) {
|
||||||
// Users without billing permission can't access billing
|
// Users without billing permission can't access billing
|
||||||
if (!org.canEditSubscription) {
|
if (!org.canEditSubscription) {
|
||||||
await this.dialogService.openSimpleDialog({
|
await dialogService.openSimpleDialog({
|
||||||
title: { key: "upgradeOrganizationEnterprise" },
|
title: { key: "upgradeOrganizationEnterprise" },
|
||||||
content: { key: "onlyAvailableForEnterpriseOrganization" },
|
content: { key: "onlyAvailableForEnterpriseOrganization" },
|
||||||
acceptButtonText: { key: "ok" },
|
acceptButtonText: { key: "ok" },
|
||||||
|
@ -47,7 +48,7 @@ export class IsEnterpriseOrgGuard implements CanActivate {
|
||||||
});
|
});
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
const upgradeConfirmed = await this.dialogService.openSimpleDialog({
|
const upgradeConfirmed = await dialogService.openSimpleDialog({
|
||||||
title: { key: "upgradeOrganizationEnterprise" },
|
title: { key: "upgradeOrganizationEnterprise" },
|
||||||
content: { key: "onlyAvailableForEnterpriseOrganization" },
|
content: { key: "onlyAvailableForEnterpriseOrganization" },
|
||||||
acceptButtonText: { key: "upgradeOrganization" },
|
acceptButtonText: { key: "upgradeOrganization" },
|
||||||
|
@ -55,7 +56,7 @@ export class IsEnterpriseOrgGuard implements CanActivate {
|
||||||
icon: "bwi-arrow-circle-up",
|
icon: "bwi-arrow-circle-up",
|
||||||
});
|
});
|
||||||
if (upgradeConfirmed) {
|
if (upgradeConfirmed) {
|
||||||
await this.router.navigate(["organizations", org.id, "billing", "subscription"], {
|
await router.navigate(["organizations", org.id, "billing", "subscription"], {
|
||||||
queryParams: { upgrade: true, productTierType: ProductTierType.Enterprise },
|
queryParams: { upgrade: true, productTierType: ProductTierType.Enterprise },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -63,5 +64,5 @@ export class IsEnterpriseOrgGuard implements CanActivate {
|
||||||
}
|
}
|
||||||
|
|
||||||
return org.productTierType == ProductTierType.Enterprise;
|
return org.productTierType == ProductTierType.Enterprise;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { RouterModule, Routes } from "@angular/router";
|
||||||
|
|
||||||
import { AuthGuard } from "@bitwarden/angular/auth/guards";
|
import { AuthGuard } from "@bitwarden/angular/auth/guards";
|
||||||
import { canAccessSettingsTab } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
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 { 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 { 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";
|
import { OrganizationLayoutComponent } from "@bitwarden/web-vault/app/admin-console/organizations/layouts/organization-layout.component";
|
||||||
|
|
||||||
|
@ -72,7 +72,7 @@ const routes: Routes = [
|
||||||
data: {
|
data: {
|
||||||
titleId: "memberAccessReport",
|
titleId: "memberAccessReport",
|
||||||
},
|
},
|
||||||
canActivate: [IsEnterpriseOrgGuard],
|
canActivate: [isEnterpriseOrgGuard()],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in New Issue