diff --git a/apps/web/config/cloud.json b/apps/web/config/cloud.json index 1418276d38..088b52f6dc 100644 --- a/apps/web/config/cloud.json +++ b/apps/web/config/cloud.json @@ -16,7 +16,6 @@ "proxyEvents": "https://events.bitwarden.com" }, "flags": { - "showTrial": false, - "scim": true + "showTrial": false } } diff --git a/apps/web/config/development.json b/apps/web/config/development.json index c1e7bbf08f..e3048db7a2 100644 --- a/apps/web/config/development.json +++ b/apps/web/config/development.json @@ -10,7 +10,6 @@ "proxyNotifications": "http://localhost:61840" }, "flags": { - "showTrial": true, - "scim": true + "showTrial": true } } diff --git a/apps/web/config/qa.json b/apps/web/config/qa.json index 6f7b413579..4371ea1ff9 100644 --- a/apps/web/config/qa.json +++ b/apps/web/config/qa.json @@ -10,7 +10,6 @@ "proxyEvents": "https://events.qa.bitwarden.pw" }, "flags": { - "showTrial": true, - "scim": true + "showTrial": true } } diff --git a/apps/web/config/selfhosted.json b/apps/web/config/selfhosted.json index 460e87ae84..3ba61fda59 100644 --- a/apps/web/config/selfhosted.json +++ b/apps/web/config/selfhosted.json @@ -7,7 +7,6 @@ "port": 8081 }, "flags": { - "showTrial": false, - "scim": true + "showTrial": false } } diff --git a/apps/web/src/app/components/organization-switcher.component.ts b/apps/web/src/app/components/organization-switcher.component.ts index 543aa7059b..a935cd2b89 100644 --- a/apps/web/src/app/components/organization-switcher.component.ts +++ b/apps/web/src/app/components/organization-switcher.component.ts @@ -5,7 +5,7 @@ import { OrganizationService } from "@bitwarden/common/abstractions/organization import { Utils } from "@bitwarden/common/misc/utils"; import { Organization } from "@bitwarden/common/models/domain/organization"; -import { NavigationPermissionsService } from "../organizations/services/navigation-permissions.service"; +import { canAccessOrgAdmin } from "../organizations/navigation-permissions"; @Component({ selector: "app-organization-switcher", @@ -26,7 +26,7 @@ export class OrganizationSwitcherComponent implements OnInit { async load() { const orgs = await this.organizationService.getAll(); this.organizations = orgs - .filter((org) => NavigationPermissionsService.canAccessAdmin(org)) + .filter(canAccessOrgAdmin) .sort(Utils.getSortFunction(this.i18nService, "name")); this.loaded = true; diff --git a/apps/web/src/app/layouts/navbar.component.ts b/apps/web/src/app/layouts/navbar.component.ts index d35929b6b4..c5a6b03cad 100644 --- a/apps/web/src/app/layouts/navbar.component.ts +++ b/apps/web/src/app/layouts/navbar.component.ts @@ -12,7 +12,7 @@ import { Utils } from "@bitwarden/common/misc/utils"; import { Organization } from "@bitwarden/common/models/domain/organization"; import { Provider } from "@bitwarden/common/models/domain/provider"; -import { NavigationPermissionsService as OrgNavigationPermissionsService } from "../organizations/services/navigation-permissions.service"; +import { canAccessOrgAdmin } from "../organizations/navigation-permissions"; @Component({ selector: "app-navbar", @@ -69,9 +69,7 @@ export class NavbarComponent implements OnInit { async buildOrganizations() { const allOrgs = await this.organizationService.getAll(); - return allOrgs - .filter((org) => OrgNavigationPermissionsService.canAccessAdmin(org)) - .sort(Utils.getSortFunction(this.i18nService, "name")); + return allOrgs.filter(canAccessOrgAdmin).sort(Utils.getSortFunction(this.i18nService, "name")); } lock() { diff --git a/apps/web/src/app/organizations/guards/org-permissions.guard.spec.ts b/apps/web/src/app/organizations/guards/org-permissions.guard.spec.ts new file mode 100644 index 0000000000..03d91822a5 --- /dev/null +++ b/apps/web/src/app/organizations/guards/org-permissions.guard.spec.ts @@ -0,0 +1,163 @@ +import { + ActivatedRouteSnapshot, + convertToParamMap, + Router, + RouterStateSnapshot, +} from "@angular/router"; +import { mock, MockProxy } from "jest-mock-extended"; + +import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; +import { OrganizationService } from "@bitwarden/common/abstractions/organization.service"; +import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; +import { SyncService } from "@bitwarden/common/abstractions/sync.service"; +import { OrganizationUserType } from "@bitwarden/common/enums/organizationUserType"; +import { Organization } from "@bitwarden/common/models/domain/organization"; + +import { OrganizationPermissionsGuard } from "./org-permissions.guard"; + +const orgFactory = (props: Partial = {}) => + Object.assign( + new Organization(), + { + id: "myOrgId", + enabled: true, + type: OrganizationUserType.Admin, + }, + props + ); + +describe("Organization Permissions Guard", () => { + let router: MockProxy; + let organizationService: MockProxy; + let state: MockProxy; + let route: MockProxy; + + let organizationPermissionsGuard: OrganizationPermissionsGuard; + + beforeEach(() => { + router = mock(); + organizationService = mock(); + state = mock(); + route = mock({ + params: { + organizationId: orgFactory().id, + }, + data: { + organizationPermissions: null, + }, + }); + + organizationPermissionsGuard = new OrganizationPermissionsGuard( + router, + organizationService, + mock(), + mock(), + mock() + ); + }); + + it("blocks navigation if organization does not exist", async () => { + organizationService.get.mockResolvedValue(null); + + const actual = await organizationPermissionsGuard.canActivate(route, state); + + expect(actual).not.toBe(true); + }); + + it("permits navigation if no permissions are specified", async () => { + const org = orgFactory(); + organizationService.get.calledWith(org.id).mockResolvedValue(org); + + const actual = await organizationPermissionsGuard.canActivate(route, state); + + expect(actual).toBe(true); + }); + + it("permits navigation if the user has permissions", async () => { + const permissionsCallback = jest.fn(); + permissionsCallback.mockImplementation((org) => true); + route.data = { + organizationPermissions: permissionsCallback, + }; + + const org = orgFactory(); + organizationService.get.calledWith(org.id).mockResolvedValue(org); + + const actual = await organizationPermissionsGuard.canActivate(route, state); + + expect(permissionsCallback).toHaveBeenCalled(); + expect(actual).toBe(true); + }); + + describe("if the user does not have permissions", () => { + it("and there is no Item ID, block navigation", async () => { + const permissionsCallback = jest.fn(); + permissionsCallback.mockImplementation((org) => false); + route.data = { + organizationPermissions: permissionsCallback, + }; + + state = mock({ + root: mock({ + queryParamMap: convertToParamMap({}), + }), + }); + + const org = orgFactory(); + organizationService.get.calledWith(org.id).mockResolvedValue(org); + + const actual = await organizationPermissionsGuard.canActivate(route, state); + + expect(permissionsCallback).toHaveBeenCalled(); + expect(actual).not.toBe(true); + }); + + it("and there is an Item ID, redirect to the item in the individual vault", async () => { + route.data = { + organizationPermissions: (org: Organization) => false, + }; + state = mock({ + root: mock({ + queryParamMap: convertToParamMap({ + itemId: "myItemId", + }), + }), + }); + const org = orgFactory(); + organizationService.get.calledWith(org.id).mockResolvedValue(org); + + const actual = await organizationPermissionsGuard.canActivate(route, state); + + expect(router.createUrlTree).toHaveBeenCalledWith(["/vault"], { + queryParams: { itemId: "myItemId" }, + }); + expect(actual).not.toBe(true); + }); + }); + + describe("given a disabled organization", () => { + it("blocks navigation if user is not an owner", async () => { + const org = orgFactory({ + type: OrganizationUserType.Admin, + enabled: false, + }); + organizationService.get.calledWith(org.id).mockResolvedValue(org); + + const actual = await organizationPermissionsGuard.canActivate(route, state); + + expect(actual).not.toBe(true); + }); + + it("permits navigation if user is an owner", async () => { + const org = orgFactory({ + type: OrganizationUserType.Owner, + enabled: false, + }); + organizationService.get.calledWith(org.id).mockResolvedValue(org); + + const actual = await organizationPermissionsGuard.canActivate(route, state); + + expect(actual).toBe(true); + }); + }); +}); diff --git a/apps/web/src/app/organizations/guards/permissions.guard.ts b/apps/web/src/app/organizations/guards/org-permissions.guard.ts similarity index 76% rename from apps/web/src/app/organizations/guards/permissions.guard.ts rename to apps/web/src/app/organizations/guards/org-permissions.guard.ts index 2ced429c76..31642a44a4 100644 --- a/apps/web/src/app/organizations/guards/permissions.guard.ts +++ b/apps/web/src/app/organizations/guards/org-permissions.guard.ts @@ -5,12 +5,14 @@ import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; import { OrganizationService } from "@bitwarden/common/abstractions/organization.service"; import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; import { SyncService } from "@bitwarden/common/abstractions/sync.service"; -import { Permissions } from "@bitwarden/common/enums/permissions"; +import { Organization } from "@bitwarden/common/models/domain/organization"; + +import { canAccessOrgAdmin } from "../navigation-permissions"; @Injectable({ providedIn: "root", }) -export class PermissionsGuard implements CanActivate { +export class OrganizationPermissionsGuard implements CanActivate { constructor( private router: Router, private organizationService: OrganizationService, @@ -39,8 +41,11 @@ export class PermissionsGuard implements CanActivate { return this.router.createUrlTree(["/"]); } - const permissions = route.data == null ? [] : (route.data.permissions as Permissions[]); - if (permissions != null && !org.hasAnyPermission(permissions)) { + const permissionsCallback: (organization: Organization) => boolean = + route.data?.organizationPermissions; + const hasPermissions = permissionsCallback == null || permissionsCallback(org); + + if (!hasPermissions) { // Handle linkable ciphers for organizations the user only has view access to // https://bitwarden.atlassian.net/browse/EC-203 const cipherId = @@ -54,7 +59,9 @@ export class PermissionsGuard implements CanActivate { } this.platformUtilsService.showToast("error", null, this.i18nService.t("accessDenied")); - return this.router.createUrlTree(["/"]); + return canAccessOrgAdmin(org) + ? this.router.createUrlTree(["/organizations", org.id]) + : this.router.createUrlTree(["/"]); } return true; diff --git a/apps/web/src/app/organizations/layouts/organization-layout.component.ts b/apps/web/src/app/organizations/layouts/organization-layout.component.ts index c9dcbd5cf3..d5a0a668da 100644 --- a/apps/web/src/app/organizations/layouts/organization-layout.component.ts +++ b/apps/web/src/app/organizations/layouts/organization-layout.component.ts @@ -5,7 +5,11 @@ import { BroadcasterService } from "@bitwarden/common/abstractions/broadcaster.s import { OrganizationService } from "@bitwarden/common/abstractions/organization.service"; import { Organization } from "@bitwarden/common/models/domain/organization"; -import { NavigationPermissionsService } from "../services/navigation-permissions.service"; +import { + canAccessManageTab, + canAccessSettingsTab, + canAccessToolsTab, +} from "../navigation-permissions"; const BroadcasterSubscriptionId = "OrganizationLayoutComponent"; @@ -51,15 +55,15 @@ export class OrganizationLayoutComponent implements OnInit, OnDestroy { } get showManageTab(): boolean { - return NavigationPermissionsService.canAccessManage(this.organization); + return canAccessManageTab(this.organization); } get showToolsTab(): boolean { - return NavigationPermissionsService.canAccessTools(this.organization); + return canAccessToolsTab(this.organization); } get showSettingsTab(): boolean { - return NavigationPermissionsService.canAccessSettings(this.organization); + return canAccessSettingsTab(this.organization); } get toolsRoute(): string { diff --git a/apps/web/src/app/organizations/manage/groups.component.ts b/apps/web/src/app/organizations/manage/groups.component.ts index fbf009762c..d09acfae30 100644 --- a/apps/web/src/app/organizations/manage/groups.component.ts +++ b/apps/web/src/app/organizations/manage/groups.component.ts @@ -1,12 +1,11 @@ import { Component, OnInit, ViewChild, ViewContainerRef } from "@angular/core"; -import { ActivatedRoute, Router } from "@angular/router"; +import { ActivatedRoute } from "@angular/router"; import { first } from "rxjs/operators"; import { ModalService } from "@bitwarden/angular/services/modal.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/abstractions/log.service"; -import { OrganizationService } from "@bitwarden/common/abstractions/organization.service"; import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; import { SearchService } from "@bitwarden/common/abstractions/search.service"; import { Utils } from "@bitwarden/common/misc/utils"; @@ -41,20 +40,13 @@ export class GroupsComponent implements OnInit { private i18nService: I18nService, private modalService: ModalService, private platformUtilsService: PlatformUtilsService, - private router: Router, private searchService: SearchService, - private logService: LogService, - private organizationService: OrganizationService + private logService: LogService ) {} async ngOnInit() { this.route.parent.parent.params.subscribe(async (params) => { this.organizationId = params.organizationId; - const organization = await this.organizationService.get(this.organizationId); - if (organization == null || !organization.useGroups) { - this.router.navigate(["/organizations", this.organizationId]); - return; - } await this.load(); this.route.queryParams.pipe(first()).subscribe(async (qParams) => { this.searchText = qParams.search; diff --git a/apps/web/src/app/organizations/manage/manage.component.html b/apps/web/src/app/organizations/manage/manage.component.html index 5d119bd4bb..b96b8b0859 100644 --- a/apps/web/src/app/organizations/manage/manage.component.html +++ b/apps/web/src/app/organizations/manage/manage.component.html @@ -24,7 +24,7 @@ routerLink="groups" class="list-group-item" routerLinkActive="active" - *ngIf="organization.canManageGroups && accessGroups" + *ngIf="organization.canManageGroups" > {{ "groups" | i18n }} @@ -32,7 +32,7 @@ routerLink="policies" class="list-group-item" routerLinkActive="active" - *ngIf="organization.canManagePolicies && accessPolicies" + *ngIf="organization.canManagePolicies" > {{ "policies" | i18n }} @@ -40,7 +40,7 @@ routerLink="sso" class="list-group-item" routerLinkActive="active" - *ngIf="organization.canManageSso && accessSso" + *ngIf="organization.canManageSso" > {{ "singleSignOn" | i18n }} @@ -48,7 +48,7 @@ routerLink="scim" class="list-group-item" routerLinkActive="active" - *ngIf="organization.canManageScim && accessScim" + *ngIf="organization.canManageScim" > {{ "scim" | i18n }} @@ -56,7 +56,7 @@ routerLink="events" class="list-group-item" routerLinkActive="active" - *ngIf="organization.canAccessEventLogs && accessEvents" + *ngIf="organization.canAccessEventLogs" > {{ "eventLogs" | i18n }} diff --git a/apps/web/src/app/organizations/manage/manage.component.ts b/apps/web/src/app/organizations/manage/manage.component.ts index 85ba511021..f9026f5570 100644 --- a/apps/web/src/app/organizations/manage/manage.component.ts +++ b/apps/web/src/app/organizations/manage/manage.component.ts @@ -4,35 +4,18 @@ import { ActivatedRoute } from "@angular/router"; import { OrganizationService } from "@bitwarden/common/abstractions/organization.service"; import { Organization } from "@bitwarden/common/models/domain/organization"; -import { flagEnabled } from "../../../utils/flags"; - @Component({ selector: "app-org-manage", templateUrl: "manage.component.html", }) export class ManageComponent implements OnInit { organization: Organization; - accessPolicies = false; - accessGroups = false; - accessEvents = false; - accessSso = false; - accessScim = false; constructor(private route: ActivatedRoute, private organizationService: OrganizationService) {} ngOnInit() { this.route.parent.params.subscribe(async (params) => { this.organization = await this.organizationService.get(params.organizationId); - this.accessPolicies = this.organization.usePolicies; - this.accessSso = this.organization.useSso; - this.accessEvents = this.organization.useEvents; - this.accessGroups = this.organization.useGroups; - - if (flagEnabled("scim")) { - this.accessScim = this.organization.useScim; - } else { - this.accessScim = false; - } }); } } diff --git a/apps/web/src/app/organizations/manage/people.component.ts b/apps/web/src/app/organizations/manage/people.component.ts index 1763e9d414..cca25d4dfd 100644 --- a/apps/web/src/app/organizations/manage/people.component.ts +++ b/apps/web/src/app/organizations/manage/people.component.ts @@ -113,10 +113,6 @@ export class PeopleComponent this.route.parent.parent.params.subscribe(async (params) => { this.organizationId = params.organizationId; const organization = await this.organizationService.get(this.organizationId); - if (!organization.canManageUsers) { - this.router.navigate(["../collections"], { relativeTo: this.route }); - return; - } this.accessEvents = organization.useEvents; this.accessGroups = organization.useGroups; this.canResetPassword = organization.canManageUsersPassword; diff --git a/apps/web/src/app/organizations/manage/policies.component.ts b/apps/web/src/app/organizations/manage/policies.component.ts index dde0c3def4..17cc3b7bbe 100644 --- a/apps/web/src/app/organizations/manage/policies.component.ts +++ b/apps/web/src/app/organizations/manage/policies.component.ts @@ -43,11 +43,6 @@ export class PoliciesComponent implements OnInit { this.route.parent.parent.params.subscribe(async (params) => { this.organizationId = params.organizationId; this.organization = await this.organizationService.get(this.organizationId); - if (this.organization == null || !this.organization.usePolicies) { - this.router.navigate(["/organizations", this.organizationId]); - return; - } - this.policies = this.policyListService.getPolicies(); await this.load(); diff --git a/apps/web/src/app/organizations/navigation-permissions.ts b/apps/web/src/app/organizations/navigation-permissions.ts new file mode 100644 index 0000000000..903efbdee2 --- /dev/null +++ b/apps/web/src/app/organizations/navigation-permissions.ts @@ -0,0 +1,29 @@ +import { Organization } from "@bitwarden/common/models/domain/organization"; + +export function canAccessToolsTab(org: Organization): boolean { + return org.canAccessImportExport || org.canAccessReports; +} + +export function canAccessSettingsTab(org: Organization): boolean { + return org.isOwner; +} + +export function canAccessManageTab(org: Organization): boolean { + return ( + org.canCreateNewCollections || + org.canEditAnyCollection || + org.canDeleteAnyCollection || + org.canEditAssignedCollections || + org.canDeleteAssignedCollections || + org.canAccessEventLogs || + org.canManageGroups || + org.canManageUsers || + org.canManagePolicies || + org.canManageSso || + org.canManageScim + ); +} + +export function canAccessOrgAdmin(org: Organization): boolean { + return canAccessToolsTab(org) || canAccessSettingsTab(org) || canAccessManageTab(org); +} diff --git a/apps/web/src/app/organizations/organization-routing.module.ts b/apps/web/src/app/organizations/organization-routing.module.ts index d820ed09bd..5ce58d1753 100644 --- a/apps/web/src/app/organizations/organization-routing.module.ts +++ b/apps/web/src/app/organizations/organization-routing.module.ts @@ -2,9 +2,9 @@ import { NgModule } from "@angular/core"; import { RouterModule, Routes } from "@angular/router"; import { AuthGuard } from "@bitwarden/angular/guards/auth.guard"; -import { Permissions } from "@bitwarden/common/enums/permissions"; +import { Organization } from "@bitwarden/common/models/domain/organization"; -import { PermissionsGuard } from "./guards/permissions.guard"; +import { OrganizationPermissionsGuard } from "./guards/org-permissions.guard"; import { OrganizationLayoutComponent } from "./layouts/organization-layout.component"; import { CollectionsComponent } from "./manage/collections.component"; import { EventsComponent } from "./manage/events.component"; @@ -12,7 +12,12 @@ import { GroupsComponent } from "./manage/groups.component"; import { ManageComponent } from "./manage/manage.component"; import { PeopleComponent } from "./manage/people.component"; import { PoliciesComponent } from "./manage/policies.component"; -import { NavigationPermissionsService } from "./services/navigation-permissions.service"; +import { + canAccessOrgAdmin, + canAccessManageTab, + canAccessSettingsTab, + canAccessToolsTab, +} from "./navigation-permissions"; import { AccountComponent } from "./settings/account.component"; import { OrganizationBillingComponent } from "./settings/organization-billing.component"; import { OrganizationSubscriptionComponent } from "./settings/organization-subscription.component"; @@ -30,9 +35,9 @@ const routes: Routes = [ { path: ":organizationId", component: OrganizationLayoutComponent, - canActivate: [AuthGuard, PermissionsGuard], + canActivate: [AuthGuard, OrganizationPermissionsGuard], data: { - permissions: NavigationPermissionsService.getPermissions("admin"), + organizationPermissions: canAccessOrgAdmin, }, children: [ { path: "", pathMatch: "full", redirectTo: "vault" }, @@ -43,8 +48,10 @@ const routes: Routes = [ { path: "tools", component: ToolsComponent, - canActivate: [PermissionsGuard], - data: { permissions: NavigationPermissionsService.getPermissions("tools") }, + canActivate: [OrganizationPermissionsGuard], + data: { + organizationPermissions: canAccessToolsTab, + }, children: [ { path: "", @@ -61,46 +68,46 @@ const routes: Routes = [ { path: "exposed-passwords-report", component: ExposedPasswordsReportComponent, - canActivate: [PermissionsGuard], + canActivate: [OrganizationPermissionsGuard], data: { titleId: "exposedPasswordsReport", - permissions: [Permissions.AccessReports], + organizationPermissions: (org: Organization) => org.canAccessReports, }, }, { path: "inactive-two-factor-report", component: InactiveTwoFactorReportComponent, - canActivate: [PermissionsGuard], + canActivate: [OrganizationPermissionsGuard], data: { titleId: "inactive2faReport", - permissions: [Permissions.AccessReports], + organizationPermissions: (org: Organization) => org.canAccessReports, }, }, { path: "reused-passwords-report", component: ReusedPasswordsReportComponent, - canActivate: [PermissionsGuard], + canActivate: [OrganizationPermissionsGuard], data: { titleId: "reusedPasswordsReport", - permissions: [Permissions.AccessReports], + organizationPermissions: (org: Organization) => org.canAccessReports, }, }, { path: "unsecured-websites-report", component: UnsecuredWebsitesReportComponent, - canActivate: [PermissionsGuard], + canActivate: [OrganizationPermissionsGuard], data: { titleId: "unsecuredWebsitesReport", - permissions: [Permissions.AccessReports], + organizationPermissions: (org: Organization) => org.canAccessReports, }, }, { path: "weak-passwords-report", component: WeakPasswordsReportComponent, - canActivate: [PermissionsGuard], + canActivate: [OrganizationPermissionsGuard], data: { titleId: "weakPasswordsReport", - permissions: [Permissions.AccessReports], + organizationPermissions: (org: Organization) => org.canAccessReports, }, }, ], @@ -108,9 +115,9 @@ const routes: Routes = [ { path: "manage", component: ManageComponent, - canActivate: [PermissionsGuard], + canActivate: [OrganizationPermissionsGuard], data: { - permissions: NavigationPermissionsService.getPermissions("manage"), + organizationPermissions: canAccessManageTab, }, children: [ { @@ -121,52 +128,52 @@ const routes: Routes = [ { path: "collections", component: CollectionsComponent, - canActivate: [PermissionsGuard], + canActivate: [OrganizationPermissionsGuard], data: { titleId: "collections", - permissions: [ - Permissions.CreateNewCollections, - Permissions.EditAnyCollection, - Permissions.DeleteAnyCollection, - Permissions.EditAssignedCollections, - Permissions.DeleteAssignedCollections, - ], + organizationPermissions: (org: Organization) => + org.canCreateNewCollections || + org.canEditAnyCollection || + org.canDeleteAnyCollection || + org.canEditAssignedCollections || + org.canDeleteAssignedCollections, }, }, { path: "events", component: EventsComponent, - canActivate: [PermissionsGuard], + canActivate: [OrganizationPermissionsGuard], data: { titleId: "eventLogs", - permissions: [Permissions.AccessEventLogs], + organizationPermissions: (org: Organization) => org.canAccessEventLogs, }, }, { path: "groups", component: GroupsComponent, - canActivate: [PermissionsGuard], + canActivate: [OrganizationPermissionsGuard], data: { titleId: "groups", - permissions: [Permissions.ManageGroups], + organizationPermissions: (org: Organization) => org.canManageGroups, }, }, { path: "people", component: PeopleComponent, - canActivate: [PermissionsGuard], + canActivate: [OrganizationPermissionsGuard], data: { titleId: "people", - permissions: [Permissions.ManageUsers, Permissions.ManageUsersPassword], + organizationPermissions: (org: Organization) => + org.canManageUsers || org.canManageUsersPassword, }, }, { path: "policies", component: PoliciesComponent, - canActivate: [PermissionsGuard], + canActivate: [OrganizationPermissionsGuard], data: { titleId: "policies", - permissions: [Permissions.ManagePolicies], + organizationPermissions: (org: Organization) => org.canManagePolicies, }, }, ], @@ -174,8 +181,8 @@ const routes: Routes = [ { path: "settings", component: SettingsComponent, - canActivate: [PermissionsGuard], - data: { permissions: NavigationPermissionsService.getPermissions("settings") }, + canActivate: [OrganizationPermissionsGuard], + data: { organizationPermissions: canAccessSettingsTab }, children: [ { path: "", pathMatch: "full", redirectTo: "account" }, { path: "account", component: AccountComponent, data: { titleId: "myOrganization" } }, @@ -187,8 +194,11 @@ const routes: Routes = [ { path: "billing", component: OrganizationBillingComponent, - canActivate: [PermissionsGuard], - data: { titleId: "billing", permissions: [Permissions.ManageBilling] }, + canActivate: [OrganizationPermissionsGuard], + data: { + titleId: "billing", + organizationPermissions: (org: Organization) => org.canManageBilling, + }, }, { path: "subscription", diff --git a/apps/web/src/app/organizations/services/navigation-permissions.service.ts b/apps/web/src/app/organizations/services/navigation-permissions.service.ts deleted file mode 100644 index cddd480ceb..0000000000 --- a/apps/web/src/app/organizations/services/navigation-permissions.service.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { Permissions } from "@bitwarden/common/enums/permissions"; -import { Organization } from "@bitwarden/common/models/domain/organization"; - -const permissions = { - manage: [ - Permissions.CreateNewCollections, - Permissions.EditAnyCollection, - Permissions.DeleteAnyCollection, - Permissions.EditAssignedCollections, - Permissions.DeleteAssignedCollections, - Permissions.AccessEventLogs, - Permissions.ManageGroups, - Permissions.ManageUsers, - Permissions.ManagePolicies, - Permissions.ManageSso, - Permissions.ManageScim, - ], - tools: [Permissions.AccessImportExport, Permissions.AccessReports], - settings: [Permissions.ManageOrganization], -}; - -export class NavigationPermissionsService { - static getPermissions(route: keyof typeof permissions | "admin") { - if (route === "admin") { - return Object.values(permissions).reduce((previous, current) => previous.concat(current), []); - } - - return permissions[route]; - } - - static canAccessAdmin(organization: Organization): boolean { - return ( - this.canAccessTools(organization) || - this.canAccessSettings(organization) || - this.canAccessManage(organization) - ); - } - - static canAccessTools(organization: Organization): boolean { - return organization.hasAnyPermission(NavigationPermissionsService.getPermissions("tools")); - } - - static canAccessSettings(organization: Organization): boolean { - return organization.hasAnyPermission(NavigationPermissionsService.getPermissions("settings")); - } - - static canAccessManage(organization: Organization): boolean { - return organization.hasAnyPermission(NavigationPermissionsService.getPermissions("manage")); - } -} diff --git a/apps/web/src/app/organizations/tools/import-export/org-import-export-routing.module.ts b/apps/web/src/app/organizations/tools/import-export/org-import-export-routing.module.ts index 8570a066ee..145afc3cb7 100644 --- a/apps/web/src/app/organizations/tools/import-export/org-import-export-routing.module.ts +++ b/apps/web/src/app/organizations/tools/import-export/org-import-export-routing.module.ts @@ -1,9 +1,9 @@ import { NgModule } from "@angular/core"; import { RouterModule, Routes } from "@angular/router"; -import { Permissions } from "@bitwarden/common/enums/permissions"; +import { Organization } from "@bitwarden/common/models/domain/organization"; -import { PermissionsGuard } from "../../guards/permissions.guard"; +import { OrganizationPermissionsGuard } from "../../guards/org-permissions.guard"; import { OrganizationExportComponent } from "./org-export.component"; import { OrganizationImportComponent } from "./org-import.component"; @@ -12,19 +12,19 @@ const routes: Routes = [ { path: "import", component: OrganizationImportComponent, - canActivate: [PermissionsGuard], + canActivate: [OrganizationPermissionsGuard], data: { titleId: "importData", - permissions: [Permissions.AccessImportExport], + organizationPermissions: (org: Organization) => org.canAccessImportExport, }, }, { path: "export", component: OrganizationExportComponent, - canActivate: [PermissionsGuard], + canActivate: [OrganizationPermissionsGuard], data: { titleId: "exportVault", - permissions: [Permissions.AccessImportExport], + organizationPermissions: (org: Organization) => org.canAccessImportExport, }, }, ]; diff --git a/apps/web/src/utils/flags.ts b/apps/web/src/utils/flags.ts index 5e7671e8b3..d6d9d0fe0b 100644 --- a/apps/web/src/utils/flags.ts +++ b/apps/web/src/utils/flags.ts @@ -1,6 +1,5 @@ export type Flags = { showTrial?: boolean; - scim?: boolean; }; export type FlagName = keyof Flags; diff --git a/bitwarden_license/bit-web/jest.config.js b/bitwarden_license/bit-web/jest.config.js new file mode 100644 index 0000000000..84c6866a7d --- /dev/null +++ b/bitwarden_license/bit-web/jest.config.js @@ -0,0 +1,15 @@ +const { pathsToModuleNameMapper } = require("ts-jest"); + +const { compilerOptions } = require("./tsconfig"); + +const sharedConfig = require("../../libs/shared/jest.config.base"); + +module.exports = { + ...sharedConfig, + preset: "jest-preset-angular", + setupFilesAfterEnv: ["../../apps/web/test.setup.ts"], + moduleNameMapper: pathsToModuleNameMapper(compilerOptions?.paths || {}, { + prefix: "/", + }), + modulePathIgnorePatterns: ["jslib"], +}; diff --git a/bitwarden_license/bit-web/src/app/organizations/organizations-routing.module.ts b/bitwarden_license/bit-web/src/app/organizations/organizations-routing.module.ts index 1bfd51b7d3..525ad0f18d 100644 --- a/bitwarden_license/bit-web/src/app/organizations/organizations-routing.module.ts +++ b/bitwarden_license/bit-web/src/app/organizations/organizations-routing.module.ts @@ -2,12 +2,12 @@ import { NgModule } from "@angular/core"; import { RouterModule, Routes } from "@angular/router"; import { AuthGuard } from "@bitwarden/angular/guards/auth.guard"; -import { Permissions } from "@bitwarden/common/enums/permissions"; +import { Organization } from "@bitwarden/common/models/domain/organization"; -import { PermissionsGuard } from "src/app/organizations/guards/permissions.guard"; +import { OrganizationPermissionsGuard } from "src/app/organizations/guards/org-permissions.guard"; import { OrganizationLayoutComponent } from "src/app/organizations/layouts/organization-layout.component"; import { ManageComponent } from "src/app/organizations/manage/manage.component"; -import { NavigationPermissionsService } from "src/app/organizations/services/navigation-permissions.service"; +import { canAccessManageTab } from "src/app/organizations/navigation-permissions"; import { ScimComponent } from "./manage/scim.component"; import { SsoComponent } from "./manage/sso.component"; @@ -16,30 +16,30 @@ const routes: Routes = [ { path: "organizations/:organizationId", component: OrganizationLayoutComponent, - canActivate: [AuthGuard, PermissionsGuard], + canActivate: [AuthGuard, OrganizationPermissionsGuard], children: [ { path: "manage", component: ManageComponent, - canActivate: [PermissionsGuard], + canActivate: [OrganizationPermissionsGuard], data: { - permissions: NavigationPermissionsService.getPermissions("manage"), + organizationPermissions: canAccessManageTab, }, children: [ { path: "sso", component: SsoComponent, - canActivate: [PermissionsGuard], + canActivate: [OrganizationPermissionsGuard], data: { - permissions: [Permissions.ManageSso], + organizationPermissions: (org: Organization) => org.canManageSso, }, }, { path: "scim", component: ScimComponent, - canActivate: [PermissionsGuard], + canActivate: [OrganizationPermissionsGuard], data: { - permissions: [Permissions.ManageScim], + organizationPermissions: (org: Organization) => org.canManageScim, }, }, ], diff --git a/bitwarden_license/bit-web/src/app/providers/guards/provider-permissions.guard.spec.ts b/bitwarden_license/bit-web/src/app/providers/guards/provider-permissions.guard.spec.ts new file mode 100644 index 0000000000..8c2b8e5a5b --- /dev/null +++ b/bitwarden_license/bit-web/src/app/providers/guards/provider-permissions.guard.spec.ts @@ -0,0 +1,124 @@ +import { ActivatedRouteSnapshot, Router } from "@angular/router"; +import { mock, MockProxy } from "jest-mock-extended"; + +import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; +import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; +import { ProviderService } from "@bitwarden/common/abstractions/provider.service"; +import { ProviderUserType } from "@bitwarden/common/enums/providerUserType"; +import { Provider } from "@bitwarden/common/models/domain/provider"; + +import { ProviderPermissionsGuard } from "./provider-permissions.guard"; + +const providerFactory = (props: Partial = {}) => + Object.assign( + new Provider(), + { + id: "myProviderId", + enabled: true, + type: ProviderUserType.ServiceUser, + }, + props + ); + +describe("Provider Permissions Guard", () => { + let router: MockProxy; + let providerService: MockProxy; + let route: MockProxy; + + let providerPermissionsGuard: ProviderPermissionsGuard; + + beforeEach(() => { + router = mock(); + providerService = mock(); + route = mock({ + params: { + providerId: providerFactory().id, + }, + data: { + providerPermissions: null, + }, + }); + + providerPermissionsGuard = new ProviderPermissionsGuard( + providerService, + router, + mock(), + mock() + ); + }); + + it("blocks navigation if provider does not exist", async () => { + providerService.get.mockResolvedValue(null); + + const actual = await providerPermissionsGuard.canActivate(route); + + expect(actual).not.toBe(true); + }); + + it("permits navigation if no permissions are specified", async () => { + const provider = providerFactory(); + providerService.get.calledWith(provider.id).mockResolvedValue(provider); + + const actual = await providerPermissionsGuard.canActivate(route); + + expect(actual).toBe(true); + }); + + it("permits navigation if the user has permissions", async () => { + const permissionsCallback = jest.fn(); + permissionsCallback.mockImplementation((provider) => true); + route.data = { + providerPermissions: permissionsCallback, + }; + + const provider = providerFactory(); + providerService.get.calledWith(provider.id).mockResolvedValue(provider); + + const actual = await providerPermissionsGuard.canActivate(route); + + expect(permissionsCallback).toHaveBeenCalled(); + expect(actual).toBe(true); + }); + + it("blocks navigation if the user does not have permissions", async () => { + const permissionsCallback = jest.fn(); + permissionsCallback.mockImplementation((org) => false); + route.data = { + providerPermissions: permissionsCallback, + }; + + const provider = providerFactory(); + providerService.get.calledWith(provider.id).mockResolvedValue(provider); + + const actual = await providerPermissionsGuard.canActivate(route); + + expect(permissionsCallback).toHaveBeenCalled(); + expect(actual).not.toBe(true); + }); + + describe("given a disabled organization", () => { + it("blocks navigation if user is not an admin", async () => { + const org = providerFactory({ + type: ProviderUserType.ServiceUser, + enabled: false, + }); + providerService.get.calledWith(org.id).mockResolvedValue(org); + + const actual = await providerPermissionsGuard.canActivate(route); + + expect(actual).not.toBe(true); + }); + + it("permits navigation if user is an admin", async () => { + const org = providerFactory({ + type: ProviderUserType.ProviderAdmin, + enabled: false, + }); + providerService.get.calledWith(org.id).mockResolvedValue(org); + + const actual = await providerPermissionsGuard.canActivate(route); + + expect(actual).toBe(true); + }); + }); +}); diff --git a/bitwarden_license/bit-web/src/app/providers/guards/provider.guard.ts b/bitwarden_license/bit-web/src/app/providers/guards/provider-permissions.guard.ts similarity index 54% rename from bitwarden_license/bit-web/src/app/providers/guards/provider.guard.ts rename to bitwarden_license/bit-web/src/app/providers/guards/provider-permissions.guard.ts index ece28a9cb0..01c6bb3c62 100644 --- a/bitwarden_license/bit-web/src/app/providers/guards/provider.guard.ts +++ b/bitwarden_license/bit-web/src/app/providers/guards/provider-permissions.guard.ts @@ -4,26 +4,34 @@ import { ActivatedRouteSnapshot, CanActivate, Router } from "@angular/router"; import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; import { ProviderService } from "@bitwarden/common/abstractions/provider.service"; +import { Provider } from "@bitwarden/common/models/domain/provider"; @Injectable() -export class ProviderGuard implements CanActivate { +export class ProviderPermissionsGuard implements CanActivate { constructor( + private providerService: ProviderService, private router: Router, private platformUtilsService: PlatformUtilsService, - private i18nService: I18nService, - private providerService: ProviderService + private i18nService: I18nService ) {} async canActivate(route: ActivatedRouteSnapshot) { const provider = await this.providerService.get(route.params.providerId); if (provider == null) { - this.router.navigate(["/"]); - return false; + return this.router.createUrlTree(["/"]); } + if (!provider.isProviderAdmin && !provider.enabled) { this.platformUtilsService.showToast("error", null, this.i18nService.t("providerIsDisabled")); - this.router.navigate(["/"]); - return false; + return this.router.createUrlTree(["/"]); + } + + const permissionsCallback: (provider: Provider) => boolean = route.data?.providerPermissions; + const hasSpecifiedPermissions = permissionsCallback == null || permissionsCallback(provider); + + if (!hasSpecifiedPermissions) { + this.platformUtilsService.showToast("error", null, this.i18nService.t("accessDenied")); + return this.router.createUrlTree(["/providers", provider.id]); } return true; diff --git a/bitwarden_license/bit-web/src/app/providers/guards/provider-type.guard.ts b/bitwarden_license/bit-web/src/app/providers/guards/provider-type.guard.ts deleted file mode 100644 index 730573b946..0000000000 --- a/bitwarden_license/bit-web/src/app/providers/guards/provider-type.guard.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { Injectable } from "@angular/core"; -import { ActivatedRouteSnapshot, CanActivate, Router } from "@angular/router"; - -import { ProviderService } from "@bitwarden/common/abstractions/provider.service"; -import { Permissions } from "@bitwarden/common/enums/permissions"; - -@Injectable() -export class PermissionsGuard implements CanActivate { - constructor(private providerService: ProviderService, private router: Router) {} - - async canActivate(route: ActivatedRouteSnapshot) { - const provider = await this.providerService.get(route.params.providerId); - const permissions = route.data == null ? null : (route.data.permissions as Permissions[]); - - if ( - (permissions.indexOf(Permissions.AccessEventLogs) !== -1 && provider.canAccessEventLogs) || - (permissions.indexOf(Permissions.ManageProvider) !== -1 && provider.isProviderAdmin) || - (permissions.indexOf(Permissions.ManageUsers) !== -1 && provider.canManageUsers) - ) { - return true; - } - - this.router.navigate(["/providers", provider.id]); - return false; - } -} diff --git a/bitwarden_license/bit-web/src/app/providers/providers-routing.module.ts b/bitwarden_license/bit-web/src/app/providers/providers-routing.module.ts index c521d9b537..6090ed8c70 100644 --- a/bitwarden_license/bit-web/src/app/providers/providers-routing.module.ts +++ b/bitwarden_license/bit-web/src/app/providers/providers-routing.module.ts @@ -2,15 +2,14 @@ import { NgModule } from "@angular/core"; import { RouterModule, Routes } from "@angular/router"; import { AuthGuard } from "@bitwarden/angular/guards/auth.guard"; -import { Permissions } from "@bitwarden/common/enums/permissions"; +import { Provider } from "@bitwarden/common/models/domain/provider"; import { FrontendLayoutComponent } from "src/app/layouts/frontend-layout.component"; import { ProvidersComponent } from "src/app/providers/providers.component"; import { ClientsComponent } from "./clients/clients.component"; import { CreateOrganizationComponent } from "./clients/create-organization.component"; -import { PermissionsGuard } from "./guards/provider-type.guard"; -import { ProviderGuard } from "./guards/provider.guard"; +import { ProviderPermissionsGuard } from "./guards/provider-permissions.guard"; import { AcceptProviderComponent } from "./manage/accept-provider.component"; import { EventsComponent } from "./manage/events.component"; import { ManageComponent } from "./manage/manage.component"; @@ -54,7 +53,7 @@ const routes: Routes = [ { path: ":providerId", component: ProvidersLayoutComponent, - canActivate: [ProviderGuard], + canActivate: [ProviderPermissionsGuard], children: [ { path: "", pathMatch: "full", redirectTo: "clients" }, { path: "clients/create", component: CreateOrganizationComponent }, @@ -71,19 +70,19 @@ const routes: Routes = [ { path: "people", component: PeopleComponent, - canActivate: [PermissionsGuard], + canActivate: [ProviderPermissionsGuard], data: { titleId: "people", - permissions: [Permissions.ManageUsers], + providerPermissions: (provider: Provider) => provider.canManageUsers, }, }, { path: "events", component: EventsComponent, - canActivate: [PermissionsGuard], + canActivate: [ProviderPermissionsGuard], data: { titleId: "eventLogs", - permissions: [Permissions.AccessEventLogs], + providerPermissions: (provider: Provider) => provider.canAccessEventLogs, }, }, ], @@ -100,10 +99,10 @@ const routes: Routes = [ { path: "account", component: AccountComponent, - canActivate: [PermissionsGuard], + canActivate: [ProviderPermissionsGuard], data: { titleId: "myProvider", - permissions: [Permissions.ManageProvider], + providerPermissions: (provider: Provider) => provider.isProviderAdmin, }, }, ], diff --git a/bitwarden_license/bit-web/src/app/providers/providers.module.ts b/bitwarden_license/bit-web/src/app/providers/providers.module.ts index d7dc71300e..7cc3210a71 100644 --- a/bitwarden_license/bit-web/src/app/providers/providers.module.ts +++ b/bitwarden_license/bit-web/src/app/providers/providers.module.ts @@ -10,8 +10,7 @@ import { OssModule } from "src/app/oss.module"; import { AddOrganizationComponent } from "./clients/add-organization.component"; import { ClientsComponent } from "./clients/clients.component"; import { CreateOrganizationComponent } from "./clients/create-organization.component"; -import { PermissionsGuard } from "./guards/provider-type.guard"; -import { ProviderGuard } from "./guards/provider.guard"; +import { ProviderPermissionsGuard } from "./guards/provider-permissions.guard"; import { AcceptProviderComponent } from "./manage/accept-provider.component"; import { BulkConfirmComponent } from "./manage/bulk/bulk-confirm.component"; import { BulkRemoveComponent } from "./manage/bulk/bulk-remove.component"; @@ -46,7 +45,7 @@ import { SetupComponent } from "./setup/setup.component"; SetupProviderComponent, UserAddEditComponent, ], - providers: [WebProviderService, ProviderGuard, PermissionsGuard], + providers: [WebProviderService, ProviderPermissionsGuard], }) export class ProvidersModule { constructor(modalService: ModalService, componentFactoryResolver: ComponentFactoryResolver) { diff --git a/bitwarden_license/bit-web/tsconfig.spec.json b/bitwarden_license/bit-web/tsconfig.spec.json new file mode 100644 index 0000000000..6ac7373f38 --- /dev/null +++ b/bitwarden_license/bit-web/tsconfig.spec.json @@ -0,0 +1,4 @@ +{ + "extends": "./tsconfig.json", + "files": ["../../apps/web/test.setup.ts"] +} diff --git a/jest.config.js b/jest.config.js index 3ca7b5d6e9..e8f4ce151d 100644 --- a/jest.config.js +++ b/jest.config.js @@ -13,6 +13,7 @@ module.exports = { "/apps/browser/jest.config.js", "/apps/cli/jest.config.js", "/apps/web/jest.config.js", + "/bitwarden_license/bit-web/jest.config.js", "/libs/angular/jest.config.js", "/libs/common/jest.config.js", diff --git a/libs/common/src/enums/permissions.ts b/libs/common/src/enums/permissions.ts deleted file mode 100644 index 46ee906620..0000000000 --- a/libs/common/src/enums/permissions.ts +++ /dev/null @@ -1,29 +0,0 @@ -export enum Permissions { - AccessEventLogs, - AccessImportExport, - AccessReports, - /** - * @deprecated Sep 29 2021: This permission has been split out to `createNewCollections`, `editAnyCollection`, and - * `deleteAnyCollection`. It exists here for backwards compatibility with Server versions <= 1.43.0 - */ - ManageAllCollections, - /** - * @deprecated Sep 29 2021: This permission has been split out to `editAssignedCollections` and - * `deleteAssignedCollections`. It exists here for backwards compatibility with Server versions <= 1.43.0 - */ - ManageAssignedCollections, - ManageGroups, - ManageOrganization, - ManagePolicies, - ManageProvider, - ManageUsers, - ManageUsersPassword, - CreateNewCollections, - EditAnyCollection, - DeleteAnyCollection, - EditAssignedCollections, - DeleteAssignedCollections, - ManageSso, - ManageBilling, - ManageScim, -} diff --git a/libs/common/src/models/domain/organization.ts b/libs/common/src/models/domain/organization.ts index bfee2584f2..2e55250731 100644 --- a/libs/common/src/models/domain/organization.ts +++ b/libs/common/src/models/domain/organization.ts @@ -1,6 +1,5 @@ import { OrganizationUserStatusType } from "../../enums/organizationUserStatusType"; import { OrganizationUserType } from "../../enums/organizationUserType"; -import { Permissions } from "../../enums/permissions"; import { ProductType } from "../../enums/productType"; import { PermissionsApi } from "../api/permissionsApi"; import { OrganizationData } from "../data/organizationData"; @@ -114,7 +113,7 @@ export class Organization { } get canAccessEventLogs() { - return this.isAdmin || this.permissions.accessEventLogs; + return (this.isAdmin || this.permissions.accessEventLogs) && this.useEvents; } get canAccessImportExport() { @@ -168,11 +167,11 @@ export class Organization { } get canManageGroups() { - return this.isAdmin || this.permissions.manageGroups; + return (this.isAdmin || this.permissions.manageGroups) && this.useGroups; } get canManageSso() { - return this.isAdmin || this.permissions.manageSso; + return (this.isAdmin || this.permissions.manageSso) && this.useSso; } get canManageScim() { @@ -180,7 +179,7 @@ export class Organization { } get canManagePolicies() { - return this.isAdmin || this.permissions.managePolicies; + return (this.isAdmin || this.permissions.managePolicies) && this.usePolicies; } get canManageUsers() { @@ -195,30 +194,6 @@ export class Organization { return this.canManagePolicies; } - hasAnyPermission(permissions: Permissions[]) { - const specifiedPermissions = - (permissions.includes(Permissions.AccessEventLogs) && this.canAccessEventLogs) || - (permissions.includes(Permissions.AccessImportExport) && this.canAccessImportExport) || - (permissions.includes(Permissions.AccessReports) && this.canAccessReports) || - (permissions.includes(Permissions.CreateNewCollections) && this.canCreateNewCollections) || - (permissions.includes(Permissions.EditAnyCollection) && this.canEditAnyCollection) || - (permissions.includes(Permissions.DeleteAnyCollection) && this.canDeleteAnyCollection) || - (permissions.includes(Permissions.EditAssignedCollections) && - this.canEditAssignedCollections) || - (permissions.includes(Permissions.DeleteAssignedCollections) && - this.canDeleteAssignedCollections) || - (permissions.includes(Permissions.ManageGroups) && this.canManageGroups) || - (permissions.includes(Permissions.ManageOrganization) && this.isOwner) || - (permissions.includes(Permissions.ManagePolicies) && this.canManagePolicies) || - (permissions.includes(Permissions.ManageUsers) && this.canManageUsers) || - (permissions.includes(Permissions.ManageUsersPassword) && this.canManageUsersPassword) || - (permissions.includes(Permissions.ManageSso) && this.canManageSso) || - (permissions.includes(Permissions.ManageScim) && this.canManageScim) || - (permissions.includes(Permissions.ManageBilling) && this.canManageBilling); - - return specifiedPermissions && (this.enabled || this.isOwner); - } - get canManageBilling() { return this.isOwner && (this.isProviderUser || !this.hasProvider); } diff --git a/package-lock.json b/package-lock.json index 47061648a1..6719545488 100644 --- a/package-lock.json +++ b/package-lock.json @@ -143,7 +143,7 @@ "husky": "^8.0.1", "jasmine-core": "^3.7.1", "jasmine-spec-reporter": "^7.0.0", - "jest-mock-extended": "^2.0.6", + "jest-mock-extended": "2.0.6", "jest-preset-angular": "^12.1.0", "lint-staged": "^13.0.3", "mini-css-extract-plugin": "^2.4.5", @@ -28286,9 +28286,9 @@ } }, "node_modules/jest-mock-extended": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/jest-mock-extended/-/jest-mock-extended-2.0.7.tgz", - "integrity": "sha512-h8brJJN5BZb03hTwplvt+raT6Nj0U2U71Z26Py12Qc3kvYnAjDW/zSuQJLnXCNyyufy592VC9k3X7AOz+2H52g==", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/jest-mock-extended/-/jest-mock-extended-2.0.6.tgz", + "integrity": "sha512-KoDdjqwIp2phaOWB0hr4O+9HF7hIJx7O+Reefi3iGrNhUpzVkod9UozYTSanvbNvjFYIEH6noA2tIjc8IDpadw==", "dev": true, "dependencies": { "ts-essentials": "^7.0.3" @@ -64464,9 +64464,9 @@ } }, "jest-mock-extended": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/jest-mock-extended/-/jest-mock-extended-2.0.7.tgz", - "integrity": "sha512-h8brJJN5BZb03hTwplvt+raT6Nj0U2U71Z26Py12Qc3kvYnAjDW/zSuQJLnXCNyyufy592VC9k3X7AOz+2H52g==", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/jest-mock-extended/-/jest-mock-extended-2.0.6.tgz", + "integrity": "sha512-KoDdjqwIp2phaOWB0hr4O+9HF7hIJx7O+Reefi3iGrNhUpzVkod9UozYTSanvbNvjFYIEH6noA2tIjc8IDpadw==", "dev": true, "requires": { "ts-essentials": "^7.0.3" diff --git a/package.json b/package.json index 31c558d2d6..4a3ed190cd 100644 --- a/package.json +++ b/package.json @@ -106,7 +106,7 @@ "husky": "^8.0.1", "jasmine-core": "^3.7.1", "jasmine-spec-reporter": "^7.0.0", - "jest-mock-extended": "^2.0.6", + "jest-mock-extended": "2.0.6", "jest-preset-angular": "^12.1.0", "lint-staged": "^13.0.3", "mini-css-extract-plugin": "^2.4.5",