1
0
mirror of https://github.com/bitwarden/browser.git synced 2024-09-27 04:03:00 +02:00

[EC-416] Refactor organization permission checks (#3252)

* Replace Permissions enum and helper methods with callbacks

* Remove scim feature flag

* Check if org has feature enabled as part of canManage checks

* Pin jest-mock-extended at v2.0.6 to fix compilation error
This commit is contained in:
Thomas Rittson 2022-08-16 00:08:06 +10:00 committed by GitHub
parent 96d5f50c7f
commit d30701ada7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 474 additions and 282 deletions

View File

@ -16,7 +16,6 @@
"proxyEvents": "https://events.bitwarden.com" "proxyEvents": "https://events.bitwarden.com"
}, },
"flags": { "flags": {
"showTrial": false, "showTrial": false
"scim": true
} }
} }

View File

@ -10,7 +10,6 @@
"proxyNotifications": "http://localhost:61840" "proxyNotifications": "http://localhost:61840"
}, },
"flags": { "flags": {
"showTrial": true, "showTrial": true
"scim": true
} }
} }

View File

@ -10,7 +10,6 @@
"proxyEvents": "https://events.qa.bitwarden.pw" "proxyEvents": "https://events.qa.bitwarden.pw"
}, },
"flags": { "flags": {
"showTrial": true, "showTrial": true
"scim": true
} }
} }

View File

@ -7,7 +7,6 @@
"port": 8081 "port": 8081
}, },
"flags": { "flags": {
"showTrial": false, "showTrial": false
"scim": true
} }
} }

View File

@ -5,7 +5,7 @@ import { OrganizationService } from "@bitwarden/common/abstractions/organization
import { Utils } from "@bitwarden/common/misc/utils"; import { Utils } from "@bitwarden/common/misc/utils";
import { Organization } from "@bitwarden/common/models/domain/organization"; import { Organization } from "@bitwarden/common/models/domain/organization";
import { NavigationPermissionsService } from "../organizations/services/navigation-permissions.service"; import { canAccessOrgAdmin } from "../organizations/navigation-permissions";
@Component({ @Component({
selector: "app-organization-switcher", selector: "app-organization-switcher",
@ -26,7 +26,7 @@ export class OrganizationSwitcherComponent implements OnInit {
async load() { async load() {
const orgs = await this.organizationService.getAll(); const orgs = await this.organizationService.getAll();
this.organizations = orgs this.organizations = orgs
.filter((org) => NavigationPermissionsService.canAccessAdmin(org)) .filter(canAccessOrgAdmin)
.sort(Utils.getSortFunction(this.i18nService, "name")); .sort(Utils.getSortFunction(this.i18nService, "name"));
this.loaded = true; this.loaded = true;

View File

@ -12,7 +12,7 @@ import { Utils } from "@bitwarden/common/misc/utils";
import { Organization } from "@bitwarden/common/models/domain/organization"; import { Organization } from "@bitwarden/common/models/domain/organization";
import { Provider } from "@bitwarden/common/models/domain/provider"; 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({ @Component({
selector: "app-navbar", selector: "app-navbar",
@ -69,9 +69,7 @@ export class NavbarComponent implements OnInit {
async buildOrganizations() { async buildOrganizations() {
const allOrgs = await this.organizationService.getAll(); const allOrgs = await this.organizationService.getAll();
return allOrgs return allOrgs.filter(canAccessOrgAdmin).sort(Utils.getSortFunction(this.i18nService, "name"));
.filter((org) => OrgNavigationPermissionsService.canAccessAdmin(org))
.sort(Utils.getSortFunction(this.i18nService, "name"));
} }
lock() { lock() {

View File

@ -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<Organization> = {}) =>
Object.assign(
new Organization(),
{
id: "myOrgId",
enabled: true,
type: OrganizationUserType.Admin,
},
props
);
describe("Organization Permissions Guard", () => {
let router: MockProxy<Router>;
let organizationService: MockProxy<OrganizationService>;
let state: MockProxy<RouterStateSnapshot>;
let route: MockProxy<ActivatedRouteSnapshot>;
let organizationPermissionsGuard: OrganizationPermissionsGuard;
beforeEach(() => {
router = mock<Router>();
organizationService = mock<OrganizationService>();
state = mock<RouterStateSnapshot>();
route = mock<ActivatedRouteSnapshot>({
params: {
organizationId: orgFactory().id,
},
data: {
organizationPermissions: null,
},
});
organizationPermissionsGuard = new OrganizationPermissionsGuard(
router,
organizationService,
mock<PlatformUtilsService>(),
mock<I18nService>(),
mock<SyncService>()
);
});
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<RouterStateSnapshot>({
root: mock<ActivatedRouteSnapshot>({
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<RouterStateSnapshot>({
root: mock<ActivatedRouteSnapshot>({
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);
});
});
});

View File

@ -5,12 +5,14 @@ import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { OrganizationService } from "@bitwarden/common/abstractions/organization.service"; import { OrganizationService } from "@bitwarden/common/abstractions/organization.service";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { SyncService } from "@bitwarden/common/abstractions/sync.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({ @Injectable({
providedIn: "root", providedIn: "root",
}) })
export class PermissionsGuard implements CanActivate { export class OrganizationPermissionsGuard implements CanActivate {
constructor( constructor(
private router: Router, private router: Router,
private organizationService: OrganizationService, private organizationService: OrganizationService,
@ -39,8 +41,11 @@ export class PermissionsGuard implements CanActivate {
return this.router.createUrlTree(["/"]); return this.router.createUrlTree(["/"]);
} }
const permissions = route.data == null ? [] : (route.data.permissions as Permissions[]); const permissionsCallback: (organization: Organization) => boolean =
if (permissions != null && !org.hasAnyPermission(permissions)) { route.data?.organizationPermissions;
const hasPermissions = permissionsCallback == null || permissionsCallback(org);
if (!hasPermissions) {
// Handle linkable ciphers for organizations the user only has view access to // Handle linkable ciphers for organizations the user only has view access to
// https://bitwarden.atlassian.net/browse/EC-203 // https://bitwarden.atlassian.net/browse/EC-203
const cipherId = const cipherId =
@ -54,7 +59,9 @@ export class PermissionsGuard implements CanActivate {
} }
this.platformUtilsService.showToast("error", null, this.i18nService.t("accessDenied")); 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; return true;

View File

@ -5,7 +5,11 @@ import { BroadcasterService } from "@bitwarden/common/abstractions/broadcaster.s
import { OrganizationService } from "@bitwarden/common/abstractions/organization.service"; import { OrganizationService } from "@bitwarden/common/abstractions/organization.service";
import { Organization } from "@bitwarden/common/models/domain/organization"; 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"; const BroadcasterSubscriptionId = "OrganizationLayoutComponent";
@ -51,15 +55,15 @@ export class OrganizationLayoutComponent implements OnInit, OnDestroy {
} }
get showManageTab(): boolean { get showManageTab(): boolean {
return NavigationPermissionsService.canAccessManage(this.organization); return canAccessManageTab(this.organization);
} }
get showToolsTab(): boolean { get showToolsTab(): boolean {
return NavigationPermissionsService.canAccessTools(this.organization); return canAccessToolsTab(this.organization);
} }
get showSettingsTab(): boolean { get showSettingsTab(): boolean {
return NavigationPermissionsService.canAccessSettings(this.organization); return canAccessSettingsTab(this.organization);
} }
get toolsRoute(): string { get toolsRoute(): string {

View File

@ -1,12 +1,11 @@
import { Component, OnInit, ViewChild, ViewContainerRef } from "@angular/core"; 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 { first } from "rxjs/operators";
import { ModalService } from "@bitwarden/angular/services/modal.service"; import { ModalService } from "@bitwarden/angular/services/modal.service";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/abstractions/log.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 { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { SearchService } from "@bitwarden/common/abstractions/search.service"; import { SearchService } from "@bitwarden/common/abstractions/search.service";
import { Utils } from "@bitwarden/common/misc/utils"; import { Utils } from "@bitwarden/common/misc/utils";
@ -41,20 +40,13 @@ export class GroupsComponent implements OnInit {
private i18nService: I18nService, private i18nService: I18nService,
private modalService: ModalService, private modalService: ModalService,
private platformUtilsService: PlatformUtilsService, private platformUtilsService: PlatformUtilsService,
private router: Router,
private searchService: SearchService, private searchService: SearchService,
private logService: LogService, private logService: LogService
private organizationService: OrganizationService
) {} ) {}
async ngOnInit() { async ngOnInit() {
this.route.parent.parent.params.subscribe(async (params) => { this.route.parent.parent.params.subscribe(async (params) => {
this.organizationId = params.organizationId; 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(); await this.load();
this.route.queryParams.pipe(first()).subscribe(async (qParams) => { this.route.queryParams.pipe(first()).subscribe(async (qParams) => {
this.searchText = qParams.search; this.searchText = qParams.search;

View File

@ -24,7 +24,7 @@
routerLink="groups" routerLink="groups"
class="list-group-item" class="list-group-item"
routerLinkActive="active" routerLinkActive="active"
*ngIf="organization.canManageGroups && accessGroups" *ngIf="organization.canManageGroups"
> >
{{ "groups" | i18n }} {{ "groups" | i18n }}
</a> </a>
@ -32,7 +32,7 @@
routerLink="policies" routerLink="policies"
class="list-group-item" class="list-group-item"
routerLinkActive="active" routerLinkActive="active"
*ngIf="organization.canManagePolicies && accessPolicies" *ngIf="organization.canManagePolicies"
> >
{{ "policies" | i18n }} {{ "policies" | i18n }}
</a> </a>
@ -40,7 +40,7 @@
routerLink="sso" routerLink="sso"
class="list-group-item" class="list-group-item"
routerLinkActive="active" routerLinkActive="active"
*ngIf="organization.canManageSso && accessSso" *ngIf="organization.canManageSso"
> >
{{ "singleSignOn" | i18n }} {{ "singleSignOn" | i18n }}
</a> </a>
@ -48,7 +48,7 @@
routerLink="scim" routerLink="scim"
class="list-group-item" class="list-group-item"
routerLinkActive="active" routerLinkActive="active"
*ngIf="organization.canManageScim && accessScim" *ngIf="organization.canManageScim"
> >
{{ "scim" | i18n }} {{ "scim" | i18n }}
</a> </a>
@ -56,7 +56,7 @@
routerLink="events" routerLink="events"
class="list-group-item" class="list-group-item"
routerLinkActive="active" routerLinkActive="active"
*ngIf="organization.canAccessEventLogs && accessEvents" *ngIf="organization.canAccessEventLogs"
> >
{{ "eventLogs" | i18n }} {{ "eventLogs" | i18n }}
</a> </a>

View File

@ -4,35 +4,18 @@ import { ActivatedRoute } from "@angular/router";
import { OrganizationService } from "@bitwarden/common/abstractions/organization.service"; import { OrganizationService } from "@bitwarden/common/abstractions/organization.service";
import { Organization } from "@bitwarden/common/models/domain/organization"; import { Organization } from "@bitwarden/common/models/domain/organization";
import { flagEnabled } from "../../../utils/flags";
@Component({ @Component({
selector: "app-org-manage", selector: "app-org-manage",
templateUrl: "manage.component.html", templateUrl: "manage.component.html",
}) })
export class ManageComponent implements OnInit { export class ManageComponent implements OnInit {
organization: Organization; organization: Organization;
accessPolicies = false;
accessGroups = false;
accessEvents = false;
accessSso = false;
accessScim = false;
constructor(private route: ActivatedRoute, private organizationService: OrganizationService) {} constructor(private route: ActivatedRoute, private organizationService: OrganizationService) {}
ngOnInit() { ngOnInit() {
this.route.parent.params.subscribe(async (params) => { this.route.parent.params.subscribe(async (params) => {
this.organization = await this.organizationService.get(params.organizationId); 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;
}
}); });
} }
} }

View File

@ -113,10 +113,6 @@ export class PeopleComponent
this.route.parent.parent.params.subscribe(async (params) => { this.route.parent.parent.params.subscribe(async (params) => {
this.organizationId = params.organizationId; this.organizationId = params.organizationId;
const organization = await this.organizationService.get(this.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.accessEvents = organization.useEvents;
this.accessGroups = organization.useGroups; this.accessGroups = organization.useGroups;
this.canResetPassword = organization.canManageUsersPassword; this.canResetPassword = organization.canManageUsersPassword;

View File

@ -43,11 +43,6 @@ export class PoliciesComponent implements OnInit {
this.route.parent.parent.params.subscribe(async (params) => { this.route.parent.parent.params.subscribe(async (params) => {
this.organizationId = params.organizationId; this.organizationId = params.organizationId;
this.organization = await this.organizationService.get(this.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(); this.policies = this.policyListService.getPolicies();
await this.load(); await this.load();

View File

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

View File

@ -2,9 +2,9 @@ import { NgModule } from "@angular/core";
import { RouterModule, Routes } from "@angular/router"; import { RouterModule, Routes } from "@angular/router";
import { AuthGuard } from "@bitwarden/angular/guards/auth.guard"; 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 { OrganizationLayoutComponent } from "./layouts/organization-layout.component";
import { CollectionsComponent } from "./manage/collections.component"; import { CollectionsComponent } from "./manage/collections.component";
import { EventsComponent } from "./manage/events.component"; import { EventsComponent } from "./manage/events.component";
@ -12,7 +12,12 @@ import { GroupsComponent } from "./manage/groups.component";
import { ManageComponent } from "./manage/manage.component"; import { ManageComponent } from "./manage/manage.component";
import { PeopleComponent } from "./manage/people.component"; import { PeopleComponent } from "./manage/people.component";
import { PoliciesComponent } from "./manage/policies.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 { AccountComponent } from "./settings/account.component";
import { OrganizationBillingComponent } from "./settings/organization-billing.component"; import { OrganizationBillingComponent } from "./settings/organization-billing.component";
import { OrganizationSubscriptionComponent } from "./settings/organization-subscription.component"; import { OrganizationSubscriptionComponent } from "./settings/organization-subscription.component";
@ -30,9 +35,9 @@ const routes: Routes = [
{ {
path: ":organizationId", path: ":organizationId",
component: OrganizationLayoutComponent, component: OrganizationLayoutComponent,
canActivate: [AuthGuard, PermissionsGuard], canActivate: [AuthGuard, OrganizationPermissionsGuard],
data: { data: {
permissions: NavigationPermissionsService.getPermissions("admin"), organizationPermissions: canAccessOrgAdmin,
}, },
children: [ children: [
{ path: "", pathMatch: "full", redirectTo: "vault" }, { path: "", pathMatch: "full", redirectTo: "vault" },
@ -43,8 +48,10 @@ const routes: Routes = [
{ {
path: "tools", path: "tools",
component: ToolsComponent, component: ToolsComponent,
canActivate: [PermissionsGuard], canActivate: [OrganizationPermissionsGuard],
data: { permissions: NavigationPermissionsService.getPermissions("tools") }, data: {
organizationPermissions: canAccessToolsTab,
},
children: [ children: [
{ {
path: "", path: "",
@ -61,46 +68,46 @@ const routes: Routes = [
{ {
path: "exposed-passwords-report", path: "exposed-passwords-report",
component: ExposedPasswordsReportComponent, component: ExposedPasswordsReportComponent,
canActivate: [PermissionsGuard], canActivate: [OrganizationPermissionsGuard],
data: { data: {
titleId: "exposedPasswordsReport", titleId: "exposedPasswordsReport",
permissions: [Permissions.AccessReports], organizationPermissions: (org: Organization) => org.canAccessReports,
}, },
}, },
{ {
path: "inactive-two-factor-report", path: "inactive-two-factor-report",
component: InactiveTwoFactorReportComponent, component: InactiveTwoFactorReportComponent,
canActivate: [PermissionsGuard], canActivate: [OrganizationPermissionsGuard],
data: { data: {
titleId: "inactive2faReport", titleId: "inactive2faReport",
permissions: [Permissions.AccessReports], organizationPermissions: (org: Organization) => org.canAccessReports,
}, },
}, },
{ {
path: "reused-passwords-report", path: "reused-passwords-report",
component: ReusedPasswordsReportComponent, component: ReusedPasswordsReportComponent,
canActivate: [PermissionsGuard], canActivate: [OrganizationPermissionsGuard],
data: { data: {
titleId: "reusedPasswordsReport", titleId: "reusedPasswordsReport",
permissions: [Permissions.AccessReports], organizationPermissions: (org: Organization) => org.canAccessReports,
}, },
}, },
{ {
path: "unsecured-websites-report", path: "unsecured-websites-report",
component: UnsecuredWebsitesReportComponent, component: UnsecuredWebsitesReportComponent,
canActivate: [PermissionsGuard], canActivate: [OrganizationPermissionsGuard],
data: { data: {
titleId: "unsecuredWebsitesReport", titleId: "unsecuredWebsitesReport",
permissions: [Permissions.AccessReports], organizationPermissions: (org: Organization) => org.canAccessReports,
}, },
}, },
{ {
path: "weak-passwords-report", path: "weak-passwords-report",
component: WeakPasswordsReportComponent, component: WeakPasswordsReportComponent,
canActivate: [PermissionsGuard], canActivate: [OrganizationPermissionsGuard],
data: { data: {
titleId: "weakPasswordsReport", titleId: "weakPasswordsReport",
permissions: [Permissions.AccessReports], organizationPermissions: (org: Organization) => org.canAccessReports,
}, },
}, },
], ],
@ -108,9 +115,9 @@ const routes: Routes = [
{ {
path: "manage", path: "manage",
component: ManageComponent, component: ManageComponent,
canActivate: [PermissionsGuard], canActivate: [OrganizationPermissionsGuard],
data: { data: {
permissions: NavigationPermissionsService.getPermissions("manage"), organizationPermissions: canAccessManageTab,
}, },
children: [ children: [
{ {
@ -121,52 +128,52 @@ const routes: Routes = [
{ {
path: "collections", path: "collections",
component: CollectionsComponent, component: CollectionsComponent,
canActivate: [PermissionsGuard], canActivate: [OrganizationPermissionsGuard],
data: { data: {
titleId: "collections", titleId: "collections",
permissions: [ organizationPermissions: (org: Organization) =>
Permissions.CreateNewCollections, org.canCreateNewCollections ||
Permissions.EditAnyCollection, org.canEditAnyCollection ||
Permissions.DeleteAnyCollection, org.canDeleteAnyCollection ||
Permissions.EditAssignedCollections, org.canEditAssignedCollections ||
Permissions.DeleteAssignedCollections, org.canDeleteAssignedCollections,
],
}, },
}, },
{ {
path: "events", path: "events",
component: EventsComponent, component: EventsComponent,
canActivate: [PermissionsGuard], canActivate: [OrganizationPermissionsGuard],
data: { data: {
titleId: "eventLogs", titleId: "eventLogs",
permissions: [Permissions.AccessEventLogs], organizationPermissions: (org: Organization) => org.canAccessEventLogs,
}, },
}, },
{ {
path: "groups", path: "groups",
component: GroupsComponent, component: GroupsComponent,
canActivate: [PermissionsGuard], canActivate: [OrganizationPermissionsGuard],
data: { data: {
titleId: "groups", titleId: "groups",
permissions: [Permissions.ManageGroups], organizationPermissions: (org: Organization) => org.canManageGroups,
}, },
}, },
{ {
path: "people", path: "people",
component: PeopleComponent, component: PeopleComponent,
canActivate: [PermissionsGuard], canActivate: [OrganizationPermissionsGuard],
data: { data: {
titleId: "people", titleId: "people",
permissions: [Permissions.ManageUsers, Permissions.ManageUsersPassword], organizationPermissions: (org: Organization) =>
org.canManageUsers || org.canManageUsersPassword,
}, },
}, },
{ {
path: "policies", path: "policies",
component: PoliciesComponent, component: PoliciesComponent,
canActivate: [PermissionsGuard], canActivate: [OrganizationPermissionsGuard],
data: { data: {
titleId: "policies", titleId: "policies",
permissions: [Permissions.ManagePolicies], organizationPermissions: (org: Organization) => org.canManagePolicies,
}, },
}, },
], ],
@ -174,8 +181,8 @@ const routes: Routes = [
{ {
path: "settings", path: "settings",
component: SettingsComponent, component: SettingsComponent,
canActivate: [PermissionsGuard], canActivate: [OrganizationPermissionsGuard],
data: { permissions: NavigationPermissionsService.getPermissions("settings") }, data: { organizationPermissions: canAccessSettingsTab },
children: [ children: [
{ path: "", pathMatch: "full", redirectTo: "account" }, { path: "", pathMatch: "full", redirectTo: "account" },
{ path: "account", component: AccountComponent, data: { titleId: "myOrganization" } }, { path: "account", component: AccountComponent, data: { titleId: "myOrganization" } },
@ -187,8 +194,11 @@ const routes: Routes = [
{ {
path: "billing", path: "billing",
component: OrganizationBillingComponent, component: OrganizationBillingComponent,
canActivate: [PermissionsGuard], canActivate: [OrganizationPermissionsGuard],
data: { titleId: "billing", permissions: [Permissions.ManageBilling] }, data: {
titleId: "billing",
organizationPermissions: (org: Organization) => org.canManageBilling,
},
}, },
{ {
path: "subscription", path: "subscription",

View File

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

View File

@ -1,9 +1,9 @@
import { NgModule } from "@angular/core"; import { NgModule } from "@angular/core";
import { RouterModule, Routes } from "@angular/router"; 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 { OrganizationExportComponent } from "./org-export.component";
import { OrganizationImportComponent } from "./org-import.component"; import { OrganizationImportComponent } from "./org-import.component";
@ -12,19 +12,19 @@ const routes: Routes = [
{ {
path: "import", path: "import",
component: OrganizationImportComponent, component: OrganizationImportComponent,
canActivate: [PermissionsGuard], canActivate: [OrganizationPermissionsGuard],
data: { data: {
titleId: "importData", titleId: "importData",
permissions: [Permissions.AccessImportExport], organizationPermissions: (org: Organization) => org.canAccessImportExport,
}, },
}, },
{ {
path: "export", path: "export",
component: OrganizationExportComponent, component: OrganizationExportComponent,
canActivate: [PermissionsGuard], canActivate: [OrganizationPermissionsGuard],
data: { data: {
titleId: "exportVault", titleId: "exportVault",
permissions: [Permissions.AccessImportExport], organizationPermissions: (org: Organization) => org.canAccessImportExport,
}, },
}, },
]; ];

View File

@ -1,6 +1,5 @@
export type Flags = { export type Flags = {
showTrial?: boolean; showTrial?: boolean;
scim?: boolean;
}; };
export type FlagName = keyof Flags; export type FlagName = keyof Flags;

View File

@ -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: "<rootDir>/",
}),
modulePathIgnorePatterns: ["jslib"],
};

View File

@ -2,12 +2,12 @@ import { NgModule } from "@angular/core";
import { RouterModule, Routes } from "@angular/router"; import { RouterModule, Routes } from "@angular/router";
import { AuthGuard } from "@bitwarden/angular/guards/auth.guard"; 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 { OrganizationLayoutComponent } from "src/app/organizations/layouts/organization-layout.component";
import { ManageComponent } from "src/app/organizations/manage/manage.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 { ScimComponent } from "./manage/scim.component";
import { SsoComponent } from "./manage/sso.component"; import { SsoComponent } from "./manage/sso.component";
@ -16,30 +16,30 @@ const routes: Routes = [
{ {
path: "organizations/:organizationId", path: "organizations/:organizationId",
component: OrganizationLayoutComponent, component: OrganizationLayoutComponent,
canActivate: [AuthGuard, PermissionsGuard], canActivate: [AuthGuard, OrganizationPermissionsGuard],
children: [ children: [
{ {
path: "manage", path: "manage",
component: ManageComponent, component: ManageComponent,
canActivate: [PermissionsGuard], canActivate: [OrganizationPermissionsGuard],
data: { data: {
permissions: NavigationPermissionsService.getPermissions("manage"), organizationPermissions: canAccessManageTab,
}, },
children: [ children: [
{ {
path: "sso", path: "sso",
component: SsoComponent, component: SsoComponent,
canActivate: [PermissionsGuard], canActivate: [OrganizationPermissionsGuard],
data: { data: {
permissions: [Permissions.ManageSso], organizationPermissions: (org: Organization) => org.canManageSso,
}, },
}, },
{ {
path: "scim", path: "scim",
component: ScimComponent, component: ScimComponent,
canActivate: [PermissionsGuard], canActivate: [OrganizationPermissionsGuard],
data: { data: {
permissions: [Permissions.ManageScim], organizationPermissions: (org: Organization) => org.canManageScim,
}, },
}, },
], ],

View File

@ -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<Provider> = {}) =>
Object.assign(
new Provider(),
{
id: "myProviderId",
enabled: true,
type: ProviderUserType.ServiceUser,
},
props
);
describe("Provider Permissions Guard", () => {
let router: MockProxy<Router>;
let providerService: MockProxy<ProviderService>;
let route: MockProxy<ActivatedRouteSnapshot>;
let providerPermissionsGuard: ProviderPermissionsGuard;
beforeEach(() => {
router = mock<Router>();
providerService = mock<ProviderService>();
route = mock<ActivatedRouteSnapshot>({
params: {
providerId: providerFactory().id,
},
data: {
providerPermissions: null,
},
});
providerPermissionsGuard = new ProviderPermissionsGuard(
providerService,
router,
mock<PlatformUtilsService>(),
mock<I18nService>()
);
});
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);
});
});
});

View File

@ -4,26 +4,34 @@ import { ActivatedRouteSnapshot, CanActivate, Router } from "@angular/router";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { ProviderService } from "@bitwarden/common/abstractions/provider.service"; import { ProviderService } from "@bitwarden/common/abstractions/provider.service";
import { Provider } from "@bitwarden/common/models/domain/provider";
@Injectable() @Injectable()
export class ProviderGuard implements CanActivate { export class ProviderPermissionsGuard implements CanActivate {
constructor( constructor(
private providerService: ProviderService,
private router: Router, private router: Router,
private platformUtilsService: PlatformUtilsService, private platformUtilsService: PlatformUtilsService,
private i18nService: I18nService, private i18nService: I18nService
private providerService: ProviderService
) {} ) {}
async canActivate(route: ActivatedRouteSnapshot) { async canActivate(route: ActivatedRouteSnapshot) {
const provider = await this.providerService.get(route.params.providerId); const provider = await this.providerService.get(route.params.providerId);
if (provider == null) { if (provider == null) {
this.router.navigate(["/"]); return this.router.createUrlTree(["/"]);
return false;
} }
if (!provider.isProviderAdmin && !provider.enabled) { if (!provider.isProviderAdmin && !provider.enabled) {
this.platformUtilsService.showToast("error", null, this.i18nService.t("providerIsDisabled")); this.platformUtilsService.showToast("error", null, this.i18nService.t("providerIsDisabled"));
this.router.navigate(["/"]); return this.router.createUrlTree(["/"]);
return false; }
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; return true;

View File

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

View File

@ -2,15 +2,14 @@ import { NgModule } from "@angular/core";
import { RouterModule, Routes } from "@angular/router"; import { RouterModule, Routes } from "@angular/router";
import { AuthGuard } from "@bitwarden/angular/guards/auth.guard"; 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 { FrontendLayoutComponent } from "src/app/layouts/frontend-layout.component";
import { ProvidersComponent } from "src/app/providers/providers.component"; import { ProvidersComponent } from "src/app/providers/providers.component";
import { ClientsComponent } from "./clients/clients.component"; import { ClientsComponent } from "./clients/clients.component";
import { CreateOrganizationComponent } from "./clients/create-organization.component"; import { CreateOrganizationComponent } from "./clients/create-organization.component";
import { PermissionsGuard } from "./guards/provider-type.guard"; import { ProviderPermissionsGuard } from "./guards/provider-permissions.guard";
import { ProviderGuard } from "./guards/provider.guard";
import { AcceptProviderComponent } from "./manage/accept-provider.component"; import { AcceptProviderComponent } from "./manage/accept-provider.component";
import { EventsComponent } from "./manage/events.component"; import { EventsComponent } from "./manage/events.component";
import { ManageComponent } from "./manage/manage.component"; import { ManageComponent } from "./manage/manage.component";
@ -54,7 +53,7 @@ const routes: Routes = [
{ {
path: ":providerId", path: ":providerId",
component: ProvidersLayoutComponent, component: ProvidersLayoutComponent,
canActivate: [ProviderGuard], canActivate: [ProviderPermissionsGuard],
children: [ children: [
{ path: "", pathMatch: "full", redirectTo: "clients" }, { path: "", pathMatch: "full", redirectTo: "clients" },
{ path: "clients/create", component: CreateOrganizationComponent }, { path: "clients/create", component: CreateOrganizationComponent },
@ -71,19 +70,19 @@ const routes: Routes = [
{ {
path: "people", path: "people",
component: PeopleComponent, component: PeopleComponent,
canActivate: [PermissionsGuard], canActivate: [ProviderPermissionsGuard],
data: { data: {
titleId: "people", titleId: "people",
permissions: [Permissions.ManageUsers], providerPermissions: (provider: Provider) => provider.canManageUsers,
}, },
}, },
{ {
path: "events", path: "events",
component: EventsComponent, component: EventsComponent,
canActivate: [PermissionsGuard], canActivate: [ProviderPermissionsGuard],
data: { data: {
titleId: "eventLogs", titleId: "eventLogs",
permissions: [Permissions.AccessEventLogs], providerPermissions: (provider: Provider) => provider.canAccessEventLogs,
}, },
}, },
], ],
@ -100,10 +99,10 @@ const routes: Routes = [
{ {
path: "account", path: "account",
component: AccountComponent, component: AccountComponent,
canActivate: [PermissionsGuard], canActivate: [ProviderPermissionsGuard],
data: { data: {
titleId: "myProvider", titleId: "myProvider",
permissions: [Permissions.ManageProvider], providerPermissions: (provider: Provider) => provider.isProviderAdmin,
}, },
}, },
], ],

View File

@ -10,8 +10,7 @@ import { OssModule } from "src/app/oss.module";
import { AddOrganizationComponent } from "./clients/add-organization.component"; import { AddOrganizationComponent } from "./clients/add-organization.component";
import { ClientsComponent } from "./clients/clients.component"; import { ClientsComponent } from "./clients/clients.component";
import { CreateOrganizationComponent } from "./clients/create-organization.component"; import { CreateOrganizationComponent } from "./clients/create-organization.component";
import { PermissionsGuard } from "./guards/provider-type.guard"; import { ProviderPermissionsGuard } from "./guards/provider-permissions.guard";
import { ProviderGuard } from "./guards/provider.guard";
import { AcceptProviderComponent } from "./manage/accept-provider.component"; import { AcceptProviderComponent } from "./manage/accept-provider.component";
import { BulkConfirmComponent } from "./manage/bulk/bulk-confirm.component"; import { BulkConfirmComponent } from "./manage/bulk/bulk-confirm.component";
import { BulkRemoveComponent } from "./manage/bulk/bulk-remove.component"; import { BulkRemoveComponent } from "./manage/bulk/bulk-remove.component";
@ -46,7 +45,7 @@ import { SetupComponent } from "./setup/setup.component";
SetupProviderComponent, SetupProviderComponent,
UserAddEditComponent, UserAddEditComponent,
], ],
providers: [WebProviderService, ProviderGuard, PermissionsGuard], providers: [WebProviderService, ProviderPermissionsGuard],
}) })
export class ProvidersModule { export class ProvidersModule {
constructor(modalService: ModalService, componentFactoryResolver: ComponentFactoryResolver) { constructor(modalService: ModalService, componentFactoryResolver: ComponentFactoryResolver) {

View File

@ -0,0 +1,4 @@
{
"extends": "./tsconfig.json",
"files": ["../../apps/web/test.setup.ts"]
}

View File

@ -13,6 +13,7 @@ module.exports = {
"<rootDir>/apps/browser/jest.config.js", "<rootDir>/apps/browser/jest.config.js",
"<rootDir>/apps/cli/jest.config.js", "<rootDir>/apps/cli/jest.config.js",
"<rootDir>/apps/web/jest.config.js", "<rootDir>/apps/web/jest.config.js",
"<rootDir>/bitwarden_license/bit-web/jest.config.js",
"<rootDir>/libs/angular/jest.config.js", "<rootDir>/libs/angular/jest.config.js",
"<rootDir>/libs/common/jest.config.js", "<rootDir>/libs/common/jest.config.js",

View File

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

View File

@ -1,6 +1,5 @@
import { OrganizationUserStatusType } from "../../enums/organizationUserStatusType"; import { OrganizationUserStatusType } from "../../enums/organizationUserStatusType";
import { OrganizationUserType } from "../../enums/organizationUserType"; import { OrganizationUserType } from "../../enums/organizationUserType";
import { Permissions } from "../../enums/permissions";
import { ProductType } from "../../enums/productType"; import { ProductType } from "../../enums/productType";
import { PermissionsApi } from "../api/permissionsApi"; import { PermissionsApi } from "../api/permissionsApi";
import { OrganizationData } from "../data/organizationData"; import { OrganizationData } from "../data/organizationData";
@ -114,7 +113,7 @@ export class Organization {
} }
get canAccessEventLogs() { get canAccessEventLogs() {
return this.isAdmin || this.permissions.accessEventLogs; return (this.isAdmin || this.permissions.accessEventLogs) && this.useEvents;
} }
get canAccessImportExport() { get canAccessImportExport() {
@ -168,11 +167,11 @@ export class Organization {
} }
get canManageGroups() { get canManageGroups() {
return this.isAdmin || this.permissions.manageGroups; return (this.isAdmin || this.permissions.manageGroups) && this.useGroups;
} }
get canManageSso() { get canManageSso() {
return this.isAdmin || this.permissions.manageSso; return (this.isAdmin || this.permissions.manageSso) && this.useSso;
} }
get canManageScim() { get canManageScim() {
@ -180,7 +179,7 @@ export class Organization {
} }
get canManagePolicies() { get canManagePolicies() {
return this.isAdmin || this.permissions.managePolicies; return (this.isAdmin || this.permissions.managePolicies) && this.usePolicies;
} }
get canManageUsers() { get canManageUsers() {
@ -195,30 +194,6 @@ export class Organization {
return this.canManagePolicies; 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() { get canManageBilling() {
return this.isOwner && (this.isProviderUser || !this.hasProvider); return this.isOwner && (this.isProviderUser || !this.hasProvider);
} }

14
package-lock.json generated
View File

@ -143,7 +143,7 @@
"husky": "^8.0.1", "husky": "^8.0.1",
"jasmine-core": "^3.7.1", "jasmine-core": "^3.7.1",
"jasmine-spec-reporter": "^7.0.0", "jasmine-spec-reporter": "^7.0.0",
"jest-mock-extended": "^2.0.6", "jest-mock-extended": "2.0.6",
"jest-preset-angular": "^12.1.0", "jest-preset-angular": "^12.1.0",
"lint-staged": "^13.0.3", "lint-staged": "^13.0.3",
"mini-css-extract-plugin": "^2.4.5", "mini-css-extract-plugin": "^2.4.5",
@ -28286,9 +28286,9 @@
} }
}, },
"node_modules/jest-mock-extended": { "node_modules/jest-mock-extended": {
"version": "2.0.7", "version": "2.0.6",
"resolved": "https://registry.npmjs.org/jest-mock-extended/-/jest-mock-extended-2.0.7.tgz", "resolved": "https://registry.npmjs.org/jest-mock-extended/-/jest-mock-extended-2.0.6.tgz",
"integrity": "sha512-h8brJJN5BZb03hTwplvt+raT6Nj0U2U71Z26Py12Qc3kvYnAjDW/zSuQJLnXCNyyufy592VC9k3X7AOz+2H52g==", "integrity": "sha512-KoDdjqwIp2phaOWB0hr4O+9HF7hIJx7O+Reefi3iGrNhUpzVkod9UozYTSanvbNvjFYIEH6noA2tIjc8IDpadw==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"ts-essentials": "^7.0.3" "ts-essentials": "^7.0.3"
@ -64464,9 +64464,9 @@
} }
}, },
"jest-mock-extended": { "jest-mock-extended": {
"version": "2.0.7", "version": "2.0.6",
"resolved": "https://registry.npmjs.org/jest-mock-extended/-/jest-mock-extended-2.0.7.tgz", "resolved": "https://registry.npmjs.org/jest-mock-extended/-/jest-mock-extended-2.0.6.tgz",
"integrity": "sha512-h8brJJN5BZb03hTwplvt+raT6Nj0U2U71Z26Py12Qc3kvYnAjDW/zSuQJLnXCNyyufy592VC9k3X7AOz+2H52g==", "integrity": "sha512-KoDdjqwIp2phaOWB0hr4O+9HF7hIJx7O+Reefi3iGrNhUpzVkod9UozYTSanvbNvjFYIEH6noA2tIjc8IDpadw==",
"dev": true, "dev": true,
"requires": { "requires": {
"ts-essentials": "^7.0.3" "ts-essentials": "^7.0.3"

View File

@ -106,7 +106,7 @@
"husky": "^8.0.1", "husky": "^8.0.1",
"jasmine-core": "^3.7.1", "jasmine-core": "^3.7.1",
"jasmine-spec-reporter": "^7.0.0", "jasmine-spec-reporter": "^7.0.0",
"jest-mock-extended": "^2.0.6", "jest-mock-extended": "2.0.6",
"jest-preset-angular": "^12.1.0", "jest-preset-angular": "^12.1.0",
"lint-staged": "^13.0.3", "lint-staged": "^13.0.3",
"mini-css-extract-plugin": "^2.4.5", "mini-css-extract-plugin": "^2.4.5",