[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:
parent
96d5f50c7f
commit
d30701ada7
|
@ -16,7 +16,6 @@
|
||||||
"proxyEvents": "https://events.bitwarden.com"
|
"proxyEvents": "https://events.bitwarden.com"
|
||||||
},
|
},
|
||||||
"flags": {
|
"flags": {
|
||||||
"showTrial": false,
|
"showTrial": false
|
||||||
"scim": true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,6 @@
|
||||||
"proxyNotifications": "http://localhost:61840"
|
"proxyNotifications": "http://localhost:61840"
|
||||||
},
|
},
|
||||||
"flags": {
|
"flags": {
|
||||||
"showTrial": true,
|
"showTrial": true
|
||||||
"scim": true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,6 @@
|
||||||
"proxyEvents": "https://events.qa.bitwarden.pw"
|
"proxyEvents": "https://events.qa.bitwarden.pw"
|
||||||
},
|
},
|
||||||
"flags": {
|
"flags": {
|
||||||
"showTrial": true,
|
"showTrial": true
|
||||||
"scim": true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,6 @@
|
||||||
"port": 8081
|
"port": 8081
|
||||||
},
|
},
|
||||||
"flags": {
|
"flags": {
|
||||||
"showTrial": false,
|
"showTrial": false
|
||||||
"scim": true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -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;
|
|
@ -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 {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
|
@ -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",
|
||||||
|
|
|
@ -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"));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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"],
|
||||||
|
};
|
|
@ -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,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
|
@ -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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -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;
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"extends": "./tsconfig.json",
|
||||||
|
"files": ["../../apps/web/test.setup.ts"]
|
||||||
|
}
|
|
@ -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",
|
||||||
|
|
|
@ -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,
|
|
||||||
}
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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",
|
||||||
|
|
Loading…
Reference in New Issue