[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"
|
||||
},
|
||||
"flags": {
|
||||
"showTrial": false,
|
||||
"scim": true
|
||||
"showTrial": false
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,7 +10,6 @@
|
|||
"proxyNotifications": "http://localhost:61840"
|
||||
},
|
||||
"flags": {
|
||||
"showTrial": true,
|
||||
"scim": true
|
||||
"showTrial": true
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,7 +10,6 @@
|
|||
"proxyEvents": "https://events.qa.bitwarden.pw"
|
||||
},
|
||||
"flags": {
|
||||
"showTrial": true,
|
||||
"scim": true
|
||||
"showTrial": true
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
"port": 8081
|
||||
},
|
||||
"flags": {
|
||||
"showTrial": false,
|
||||
"scim": true
|
||||
"showTrial": false
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ import { OrganizationService } from "@bitwarden/common/abstractions/organization
|
|||
import { Utils } from "@bitwarden/common/misc/utils";
|
||||
import { Organization } from "@bitwarden/common/models/domain/organization";
|
||||
|
||||
import { NavigationPermissionsService } from "../organizations/services/navigation-permissions.service";
|
||||
import { canAccessOrgAdmin } from "../organizations/navigation-permissions";
|
||||
|
||||
@Component({
|
||||
selector: "app-organization-switcher",
|
||||
|
@ -26,7 +26,7 @@ export class OrganizationSwitcherComponent implements OnInit {
|
|||
async load() {
|
||||
const orgs = await this.organizationService.getAll();
|
||||
this.organizations = orgs
|
||||
.filter((org) => NavigationPermissionsService.canAccessAdmin(org))
|
||||
.filter(canAccessOrgAdmin)
|
||||
.sort(Utils.getSortFunction(this.i18nService, "name"));
|
||||
|
||||
this.loaded = true;
|
||||
|
|
|
@ -12,7 +12,7 @@ import { Utils } from "@bitwarden/common/misc/utils";
|
|||
import { Organization } from "@bitwarden/common/models/domain/organization";
|
||||
import { Provider } from "@bitwarden/common/models/domain/provider";
|
||||
|
||||
import { NavigationPermissionsService as OrgNavigationPermissionsService } from "../organizations/services/navigation-permissions.service";
|
||||
import { canAccessOrgAdmin } from "../organizations/navigation-permissions";
|
||||
|
||||
@Component({
|
||||
selector: "app-navbar",
|
||||
|
@ -69,9 +69,7 @@ export class NavbarComponent implements OnInit {
|
|||
|
||||
async buildOrganizations() {
|
||||
const allOrgs = await this.organizationService.getAll();
|
||||
return allOrgs
|
||||
.filter((org) => OrgNavigationPermissionsService.canAccessAdmin(org))
|
||||
.sort(Utils.getSortFunction(this.i18nService, "name"));
|
||||
return allOrgs.filter(canAccessOrgAdmin).sort(Utils.getSortFunction(this.i18nService, "name"));
|
||||
}
|
||||
|
||||
lock() {
|
||||
|
|
|
@ -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 { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
|
||||
import { SyncService } from "@bitwarden/common/abstractions/sync.service";
|
||||
import { Permissions } from "@bitwarden/common/enums/permissions";
|
||||
import { Organization } from "@bitwarden/common/models/domain/organization";
|
||||
|
||||
import { canAccessOrgAdmin } from "../navigation-permissions";
|
||||
|
||||
@Injectable({
|
||||
providedIn: "root",
|
||||
})
|
||||
export class PermissionsGuard implements CanActivate {
|
||||
export class OrganizationPermissionsGuard implements CanActivate {
|
||||
constructor(
|
||||
private router: Router,
|
||||
private organizationService: OrganizationService,
|
||||
|
@ -39,8 +41,11 @@ export class PermissionsGuard implements CanActivate {
|
|||
return this.router.createUrlTree(["/"]);
|
||||
}
|
||||
|
||||
const permissions = route.data == null ? [] : (route.data.permissions as Permissions[]);
|
||||
if (permissions != null && !org.hasAnyPermission(permissions)) {
|
||||
const permissionsCallback: (organization: Organization) => boolean =
|
||||
route.data?.organizationPermissions;
|
||||
const hasPermissions = permissionsCallback == null || permissionsCallback(org);
|
||||
|
||||
if (!hasPermissions) {
|
||||
// Handle linkable ciphers for organizations the user only has view access to
|
||||
// https://bitwarden.atlassian.net/browse/EC-203
|
||||
const cipherId =
|
||||
|
@ -54,7 +59,9 @@ export class PermissionsGuard implements CanActivate {
|
|||
}
|
||||
|
||||
this.platformUtilsService.showToast("error", null, this.i18nService.t("accessDenied"));
|
||||
return this.router.createUrlTree(["/"]);
|
||||
return canAccessOrgAdmin(org)
|
||||
? this.router.createUrlTree(["/organizations", org.id])
|
||||
: this.router.createUrlTree(["/"]);
|
||||
}
|
||||
|
||||
return true;
|
|
@ -5,7 +5,11 @@ import { BroadcasterService } from "@bitwarden/common/abstractions/broadcaster.s
|
|||
import { OrganizationService } from "@bitwarden/common/abstractions/organization.service";
|
||||
import { Organization } from "@bitwarden/common/models/domain/organization";
|
||||
|
||||
import { NavigationPermissionsService } from "../services/navigation-permissions.service";
|
||||
import {
|
||||
canAccessManageTab,
|
||||
canAccessSettingsTab,
|
||||
canAccessToolsTab,
|
||||
} from "../navigation-permissions";
|
||||
|
||||
const BroadcasterSubscriptionId = "OrganizationLayoutComponent";
|
||||
|
||||
|
@ -51,15 +55,15 @@ export class OrganizationLayoutComponent implements OnInit, OnDestroy {
|
|||
}
|
||||
|
||||
get showManageTab(): boolean {
|
||||
return NavigationPermissionsService.canAccessManage(this.organization);
|
||||
return canAccessManageTab(this.organization);
|
||||
}
|
||||
|
||||
get showToolsTab(): boolean {
|
||||
return NavigationPermissionsService.canAccessTools(this.organization);
|
||||
return canAccessToolsTab(this.organization);
|
||||
}
|
||||
|
||||
get showSettingsTab(): boolean {
|
||||
return NavigationPermissionsService.canAccessSettings(this.organization);
|
||||
return canAccessSettingsTab(this.organization);
|
||||
}
|
||||
|
||||
get toolsRoute(): string {
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
import { Component, OnInit, ViewChild, ViewContainerRef } from "@angular/core";
|
||||
import { ActivatedRoute, Router } from "@angular/router";
|
||||
import { ActivatedRoute } from "@angular/router";
|
||||
import { first } from "rxjs/operators";
|
||||
|
||||
import { ModalService } from "@bitwarden/angular/services/modal.service";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/abstractions/log.service";
|
||||
import { OrganizationService } from "@bitwarden/common/abstractions/organization.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
|
||||
import { SearchService } from "@bitwarden/common/abstractions/search.service";
|
||||
import { Utils } from "@bitwarden/common/misc/utils";
|
||||
|
@ -41,20 +40,13 @@ export class GroupsComponent implements OnInit {
|
|||
private i18nService: I18nService,
|
||||
private modalService: ModalService,
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
private router: Router,
|
||||
private searchService: SearchService,
|
||||
private logService: LogService,
|
||||
private organizationService: OrganizationService
|
||||
private logService: LogService
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
this.route.parent.parent.params.subscribe(async (params) => {
|
||||
this.organizationId = params.organizationId;
|
||||
const organization = await this.organizationService.get(this.organizationId);
|
||||
if (organization == null || !organization.useGroups) {
|
||||
this.router.navigate(["/organizations", this.organizationId]);
|
||||
return;
|
||||
}
|
||||
await this.load();
|
||||
this.route.queryParams.pipe(first()).subscribe(async (qParams) => {
|
||||
this.searchText = qParams.search;
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
routerLink="groups"
|
||||
class="list-group-item"
|
||||
routerLinkActive="active"
|
||||
*ngIf="organization.canManageGroups && accessGroups"
|
||||
*ngIf="organization.canManageGroups"
|
||||
>
|
||||
{{ "groups" | i18n }}
|
||||
</a>
|
||||
|
@ -32,7 +32,7 @@
|
|||
routerLink="policies"
|
||||
class="list-group-item"
|
||||
routerLinkActive="active"
|
||||
*ngIf="organization.canManagePolicies && accessPolicies"
|
||||
*ngIf="organization.canManagePolicies"
|
||||
>
|
||||
{{ "policies" | i18n }}
|
||||
</a>
|
||||
|
@ -40,7 +40,7 @@
|
|||
routerLink="sso"
|
||||
class="list-group-item"
|
||||
routerLinkActive="active"
|
||||
*ngIf="organization.canManageSso && accessSso"
|
||||
*ngIf="organization.canManageSso"
|
||||
>
|
||||
{{ "singleSignOn" | i18n }}
|
||||
</a>
|
||||
|
@ -48,7 +48,7 @@
|
|||
routerLink="scim"
|
||||
class="list-group-item"
|
||||
routerLinkActive="active"
|
||||
*ngIf="organization.canManageScim && accessScim"
|
||||
*ngIf="organization.canManageScim"
|
||||
>
|
||||
{{ "scim" | i18n }}
|
||||
</a>
|
||||
|
@ -56,7 +56,7 @@
|
|||
routerLink="events"
|
||||
class="list-group-item"
|
||||
routerLinkActive="active"
|
||||
*ngIf="organization.canAccessEventLogs && accessEvents"
|
||||
*ngIf="organization.canAccessEventLogs"
|
||||
>
|
||||
{{ "eventLogs" | i18n }}
|
||||
</a>
|
||||
|
|
|
@ -4,35 +4,18 @@ import { ActivatedRoute } from "@angular/router";
|
|||
import { OrganizationService } from "@bitwarden/common/abstractions/organization.service";
|
||||
import { Organization } from "@bitwarden/common/models/domain/organization";
|
||||
|
||||
import { flagEnabled } from "../../../utils/flags";
|
||||
|
||||
@Component({
|
||||
selector: "app-org-manage",
|
||||
templateUrl: "manage.component.html",
|
||||
})
|
||||
export class ManageComponent implements OnInit {
|
||||
organization: Organization;
|
||||
accessPolicies = false;
|
||||
accessGroups = false;
|
||||
accessEvents = false;
|
||||
accessSso = false;
|
||||
accessScim = false;
|
||||
|
||||
constructor(private route: ActivatedRoute, private organizationService: OrganizationService) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.route.parent.params.subscribe(async (params) => {
|
||||
this.organization = await this.organizationService.get(params.organizationId);
|
||||
this.accessPolicies = this.organization.usePolicies;
|
||||
this.accessSso = this.organization.useSso;
|
||||
this.accessEvents = this.organization.useEvents;
|
||||
this.accessGroups = this.organization.useGroups;
|
||||
|
||||
if (flagEnabled("scim")) {
|
||||
this.accessScim = this.organization.useScim;
|
||||
} else {
|
||||
this.accessScim = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -113,10 +113,6 @@ export class PeopleComponent
|
|||
this.route.parent.parent.params.subscribe(async (params) => {
|
||||
this.organizationId = params.organizationId;
|
||||
const organization = await this.organizationService.get(this.organizationId);
|
||||
if (!organization.canManageUsers) {
|
||||
this.router.navigate(["../collections"], { relativeTo: this.route });
|
||||
return;
|
||||
}
|
||||
this.accessEvents = organization.useEvents;
|
||||
this.accessGroups = organization.useGroups;
|
||||
this.canResetPassword = organization.canManageUsersPassword;
|
||||
|
|
|
@ -43,11 +43,6 @@ export class PoliciesComponent implements OnInit {
|
|||
this.route.parent.parent.params.subscribe(async (params) => {
|
||||
this.organizationId = params.organizationId;
|
||||
this.organization = await this.organizationService.get(this.organizationId);
|
||||
if (this.organization == null || !this.organization.usePolicies) {
|
||||
this.router.navigate(["/organizations", this.organizationId]);
|
||||
return;
|
||||
}
|
||||
|
||||
this.policies = this.policyListService.getPolicies();
|
||||
|
||||
await this.load();
|
||||
|
|
|
@ -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 { AuthGuard } from "@bitwarden/angular/guards/auth.guard";
|
||||
import { Permissions } from "@bitwarden/common/enums/permissions";
|
||||
import { Organization } from "@bitwarden/common/models/domain/organization";
|
||||
|
||||
import { PermissionsGuard } from "./guards/permissions.guard";
|
||||
import { OrganizationPermissionsGuard } from "./guards/org-permissions.guard";
|
||||
import { OrganizationLayoutComponent } from "./layouts/organization-layout.component";
|
||||
import { CollectionsComponent } from "./manage/collections.component";
|
||||
import { EventsComponent } from "./manage/events.component";
|
||||
|
@ -12,7 +12,12 @@ import { GroupsComponent } from "./manage/groups.component";
|
|||
import { ManageComponent } from "./manage/manage.component";
|
||||
import { PeopleComponent } from "./manage/people.component";
|
||||
import { PoliciesComponent } from "./manage/policies.component";
|
||||
import { NavigationPermissionsService } from "./services/navigation-permissions.service";
|
||||
import {
|
||||
canAccessOrgAdmin,
|
||||
canAccessManageTab,
|
||||
canAccessSettingsTab,
|
||||
canAccessToolsTab,
|
||||
} from "./navigation-permissions";
|
||||
import { AccountComponent } from "./settings/account.component";
|
||||
import { OrganizationBillingComponent } from "./settings/organization-billing.component";
|
||||
import { OrganizationSubscriptionComponent } from "./settings/organization-subscription.component";
|
||||
|
@ -30,9 +35,9 @@ const routes: Routes = [
|
|||
{
|
||||
path: ":organizationId",
|
||||
component: OrganizationLayoutComponent,
|
||||
canActivate: [AuthGuard, PermissionsGuard],
|
||||
canActivate: [AuthGuard, OrganizationPermissionsGuard],
|
||||
data: {
|
||||
permissions: NavigationPermissionsService.getPermissions("admin"),
|
||||
organizationPermissions: canAccessOrgAdmin,
|
||||
},
|
||||
children: [
|
||||
{ path: "", pathMatch: "full", redirectTo: "vault" },
|
||||
|
@ -43,8 +48,10 @@ const routes: Routes = [
|
|||
{
|
||||
path: "tools",
|
||||
component: ToolsComponent,
|
||||
canActivate: [PermissionsGuard],
|
||||
data: { permissions: NavigationPermissionsService.getPermissions("tools") },
|
||||
canActivate: [OrganizationPermissionsGuard],
|
||||
data: {
|
||||
organizationPermissions: canAccessToolsTab,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: "",
|
||||
|
@ -61,46 +68,46 @@ const routes: Routes = [
|
|||
{
|
||||
path: "exposed-passwords-report",
|
||||
component: ExposedPasswordsReportComponent,
|
||||
canActivate: [PermissionsGuard],
|
||||
canActivate: [OrganizationPermissionsGuard],
|
||||
data: {
|
||||
titleId: "exposedPasswordsReport",
|
||||
permissions: [Permissions.AccessReports],
|
||||
organizationPermissions: (org: Organization) => org.canAccessReports,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "inactive-two-factor-report",
|
||||
component: InactiveTwoFactorReportComponent,
|
||||
canActivate: [PermissionsGuard],
|
||||
canActivate: [OrganizationPermissionsGuard],
|
||||
data: {
|
||||
titleId: "inactive2faReport",
|
||||
permissions: [Permissions.AccessReports],
|
||||
organizationPermissions: (org: Organization) => org.canAccessReports,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "reused-passwords-report",
|
||||
component: ReusedPasswordsReportComponent,
|
||||
canActivate: [PermissionsGuard],
|
||||
canActivate: [OrganizationPermissionsGuard],
|
||||
data: {
|
||||
titleId: "reusedPasswordsReport",
|
||||
permissions: [Permissions.AccessReports],
|
||||
organizationPermissions: (org: Organization) => org.canAccessReports,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "unsecured-websites-report",
|
||||
component: UnsecuredWebsitesReportComponent,
|
||||
canActivate: [PermissionsGuard],
|
||||
canActivate: [OrganizationPermissionsGuard],
|
||||
data: {
|
||||
titleId: "unsecuredWebsitesReport",
|
||||
permissions: [Permissions.AccessReports],
|
||||
organizationPermissions: (org: Organization) => org.canAccessReports,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "weak-passwords-report",
|
||||
component: WeakPasswordsReportComponent,
|
||||
canActivate: [PermissionsGuard],
|
||||
canActivate: [OrganizationPermissionsGuard],
|
||||
data: {
|
||||
titleId: "weakPasswordsReport",
|
||||
permissions: [Permissions.AccessReports],
|
||||
organizationPermissions: (org: Organization) => org.canAccessReports,
|
||||
},
|
||||
},
|
||||
],
|
||||
|
@ -108,9 +115,9 @@ const routes: Routes = [
|
|||
{
|
||||
path: "manage",
|
||||
component: ManageComponent,
|
||||
canActivate: [PermissionsGuard],
|
||||
canActivate: [OrganizationPermissionsGuard],
|
||||
data: {
|
||||
permissions: NavigationPermissionsService.getPermissions("manage"),
|
||||
organizationPermissions: canAccessManageTab,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
|
@ -121,52 +128,52 @@ const routes: Routes = [
|
|||
{
|
||||
path: "collections",
|
||||
component: CollectionsComponent,
|
||||
canActivate: [PermissionsGuard],
|
||||
canActivate: [OrganizationPermissionsGuard],
|
||||
data: {
|
||||
titleId: "collections",
|
||||
permissions: [
|
||||
Permissions.CreateNewCollections,
|
||||
Permissions.EditAnyCollection,
|
||||
Permissions.DeleteAnyCollection,
|
||||
Permissions.EditAssignedCollections,
|
||||
Permissions.DeleteAssignedCollections,
|
||||
],
|
||||
organizationPermissions: (org: Organization) =>
|
||||
org.canCreateNewCollections ||
|
||||
org.canEditAnyCollection ||
|
||||
org.canDeleteAnyCollection ||
|
||||
org.canEditAssignedCollections ||
|
||||
org.canDeleteAssignedCollections,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "events",
|
||||
component: EventsComponent,
|
||||
canActivate: [PermissionsGuard],
|
||||
canActivate: [OrganizationPermissionsGuard],
|
||||
data: {
|
||||
titleId: "eventLogs",
|
||||
permissions: [Permissions.AccessEventLogs],
|
||||
organizationPermissions: (org: Organization) => org.canAccessEventLogs,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "groups",
|
||||
component: GroupsComponent,
|
||||
canActivate: [PermissionsGuard],
|
||||
canActivate: [OrganizationPermissionsGuard],
|
||||
data: {
|
||||
titleId: "groups",
|
||||
permissions: [Permissions.ManageGroups],
|
||||
organizationPermissions: (org: Organization) => org.canManageGroups,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "people",
|
||||
component: PeopleComponent,
|
||||
canActivate: [PermissionsGuard],
|
||||
canActivate: [OrganizationPermissionsGuard],
|
||||
data: {
|
||||
titleId: "people",
|
||||
permissions: [Permissions.ManageUsers, Permissions.ManageUsersPassword],
|
||||
organizationPermissions: (org: Organization) =>
|
||||
org.canManageUsers || org.canManageUsersPassword,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "policies",
|
||||
component: PoliciesComponent,
|
||||
canActivate: [PermissionsGuard],
|
||||
canActivate: [OrganizationPermissionsGuard],
|
||||
data: {
|
||||
titleId: "policies",
|
||||
permissions: [Permissions.ManagePolicies],
|
||||
organizationPermissions: (org: Organization) => org.canManagePolicies,
|
||||
},
|
||||
},
|
||||
],
|
||||
|
@ -174,8 +181,8 @@ const routes: Routes = [
|
|||
{
|
||||
path: "settings",
|
||||
component: SettingsComponent,
|
||||
canActivate: [PermissionsGuard],
|
||||
data: { permissions: NavigationPermissionsService.getPermissions("settings") },
|
||||
canActivate: [OrganizationPermissionsGuard],
|
||||
data: { organizationPermissions: canAccessSettingsTab },
|
||||
children: [
|
||||
{ path: "", pathMatch: "full", redirectTo: "account" },
|
||||
{ path: "account", component: AccountComponent, data: { titleId: "myOrganization" } },
|
||||
|
@ -187,8 +194,11 @@ const routes: Routes = [
|
|||
{
|
||||
path: "billing",
|
||||
component: OrganizationBillingComponent,
|
||||
canActivate: [PermissionsGuard],
|
||||
data: { titleId: "billing", permissions: [Permissions.ManageBilling] },
|
||||
canActivate: [OrganizationPermissionsGuard],
|
||||
data: {
|
||||
titleId: "billing",
|
||||
organizationPermissions: (org: Organization) => org.canManageBilling,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "subscription",
|
||||
|
|
|
@ -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 { RouterModule, Routes } from "@angular/router";
|
||||
|
||||
import { Permissions } from "@bitwarden/common/enums/permissions";
|
||||
import { Organization } from "@bitwarden/common/models/domain/organization";
|
||||
|
||||
import { PermissionsGuard } from "../../guards/permissions.guard";
|
||||
import { OrganizationPermissionsGuard } from "../../guards/org-permissions.guard";
|
||||
|
||||
import { OrganizationExportComponent } from "./org-export.component";
|
||||
import { OrganizationImportComponent } from "./org-import.component";
|
||||
|
@ -12,19 +12,19 @@ const routes: Routes = [
|
|||
{
|
||||
path: "import",
|
||||
component: OrganizationImportComponent,
|
||||
canActivate: [PermissionsGuard],
|
||||
canActivate: [OrganizationPermissionsGuard],
|
||||
data: {
|
||||
titleId: "importData",
|
||||
permissions: [Permissions.AccessImportExport],
|
||||
organizationPermissions: (org: Organization) => org.canAccessImportExport,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "export",
|
||||
component: OrganizationExportComponent,
|
||||
canActivate: [PermissionsGuard],
|
||||
canActivate: [OrganizationPermissionsGuard],
|
||||
data: {
|
||||
titleId: "exportVault",
|
||||
permissions: [Permissions.AccessImportExport],
|
||||
organizationPermissions: (org: Organization) => org.canAccessImportExport,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
export type Flags = {
|
||||
showTrial?: boolean;
|
||||
scim?: boolean;
|
||||
};
|
||||
|
||||
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 { AuthGuard } from "@bitwarden/angular/guards/auth.guard";
|
||||
import { Permissions } from "@bitwarden/common/enums/permissions";
|
||||
import { Organization } from "@bitwarden/common/models/domain/organization";
|
||||
|
||||
import { PermissionsGuard } from "src/app/organizations/guards/permissions.guard";
|
||||
import { OrganizationPermissionsGuard } from "src/app/organizations/guards/org-permissions.guard";
|
||||
import { OrganizationLayoutComponent } from "src/app/organizations/layouts/organization-layout.component";
|
||||
import { ManageComponent } from "src/app/organizations/manage/manage.component";
|
||||
import { NavigationPermissionsService } from "src/app/organizations/services/navigation-permissions.service";
|
||||
import { canAccessManageTab } from "src/app/organizations/navigation-permissions";
|
||||
|
||||
import { ScimComponent } from "./manage/scim.component";
|
||||
import { SsoComponent } from "./manage/sso.component";
|
||||
|
@ -16,30 +16,30 @@ const routes: Routes = [
|
|||
{
|
||||
path: "organizations/:organizationId",
|
||||
component: OrganizationLayoutComponent,
|
||||
canActivate: [AuthGuard, PermissionsGuard],
|
||||
canActivate: [AuthGuard, OrganizationPermissionsGuard],
|
||||
children: [
|
||||
{
|
||||
path: "manage",
|
||||
component: ManageComponent,
|
||||
canActivate: [PermissionsGuard],
|
||||
canActivate: [OrganizationPermissionsGuard],
|
||||
data: {
|
||||
permissions: NavigationPermissionsService.getPermissions("manage"),
|
||||
organizationPermissions: canAccessManageTab,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: "sso",
|
||||
component: SsoComponent,
|
||||
canActivate: [PermissionsGuard],
|
||||
canActivate: [OrganizationPermissionsGuard],
|
||||
data: {
|
||||
permissions: [Permissions.ManageSso],
|
||||
organizationPermissions: (org: Organization) => org.canManageSso,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "scim",
|
||||
component: ScimComponent,
|
||||
canActivate: [PermissionsGuard],
|
||||
canActivate: [OrganizationPermissionsGuard],
|
||||
data: {
|
||||
permissions: [Permissions.ManageScim],
|
||||
organizationPermissions: (org: Organization) => org.canManageScim,
|
||||
},
|
||||
},
|
||||
],
|
||||
|
|
|
@ -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 { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
|
||||
import { ProviderService } from "@bitwarden/common/abstractions/provider.service";
|
||||
import { Provider } from "@bitwarden/common/models/domain/provider";
|
||||
|
||||
@Injectable()
|
||||
export class ProviderGuard implements CanActivate {
|
||||
export class ProviderPermissionsGuard implements CanActivate {
|
||||
constructor(
|
||||
private providerService: ProviderService,
|
||||
private router: Router,
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
private i18nService: I18nService,
|
||||
private providerService: ProviderService
|
||||
private i18nService: I18nService
|
||||
) {}
|
||||
|
||||
async canActivate(route: ActivatedRouteSnapshot) {
|
||||
const provider = await this.providerService.get(route.params.providerId);
|
||||
if (provider == null) {
|
||||
this.router.navigate(["/"]);
|
||||
return false;
|
||||
return this.router.createUrlTree(["/"]);
|
||||
}
|
||||
|
||||
if (!provider.isProviderAdmin && !provider.enabled) {
|
||||
this.platformUtilsService.showToast("error", null, this.i18nService.t("providerIsDisabled"));
|
||||
this.router.navigate(["/"]);
|
||||
return false;
|
||||
return this.router.createUrlTree(["/"]);
|
||||
}
|
||||
|
||||
const permissionsCallback: (provider: Provider) => boolean = route.data?.providerPermissions;
|
||||
const hasSpecifiedPermissions = permissionsCallback == null || permissionsCallback(provider);
|
||||
|
||||
if (!hasSpecifiedPermissions) {
|
||||
this.platformUtilsService.showToast("error", null, this.i18nService.t("accessDenied"));
|
||||
return this.router.createUrlTree(["/providers", provider.id]);
|
||||
}
|
||||
|
||||
return true;
|
|
@ -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 { AuthGuard } from "@bitwarden/angular/guards/auth.guard";
|
||||
import { Permissions } from "@bitwarden/common/enums/permissions";
|
||||
import { Provider } from "@bitwarden/common/models/domain/provider";
|
||||
|
||||
import { FrontendLayoutComponent } from "src/app/layouts/frontend-layout.component";
|
||||
import { ProvidersComponent } from "src/app/providers/providers.component";
|
||||
|
||||
import { ClientsComponent } from "./clients/clients.component";
|
||||
import { CreateOrganizationComponent } from "./clients/create-organization.component";
|
||||
import { PermissionsGuard } from "./guards/provider-type.guard";
|
||||
import { ProviderGuard } from "./guards/provider.guard";
|
||||
import { ProviderPermissionsGuard } from "./guards/provider-permissions.guard";
|
||||
import { AcceptProviderComponent } from "./manage/accept-provider.component";
|
||||
import { EventsComponent } from "./manage/events.component";
|
||||
import { ManageComponent } from "./manage/manage.component";
|
||||
|
@ -54,7 +53,7 @@ const routes: Routes = [
|
|||
{
|
||||
path: ":providerId",
|
||||
component: ProvidersLayoutComponent,
|
||||
canActivate: [ProviderGuard],
|
||||
canActivate: [ProviderPermissionsGuard],
|
||||
children: [
|
||||
{ path: "", pathMatch: "full", redirectTo: "clients" },
|
||||
{ path: "clients/create", component: CreateOrganizationComponent },
|
||||
|
@ -71,19 +70,19 @@ const routes: Routes = [
|
|||
{
|
||||
path: "people",
|
||||
component: PeopleComponent,
|
||||
canActivate: [PermissionsGuard],
|
||||
canActivate: [ProviderPermissionsGuard],
|
||||
data: {
|
||||
titleId: "people",
|
||||
permissions: [Permissions.ManageUsers],
|
||||
providerPermissions: (provider: Provider) => provider.canManageUsers,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "events",
|
||||
component: EventsComponent,
|
||||
canActivate: [PermissionsGuard],
|
||||
canActivate: [ProviderPermissionsGuard],
|
||||
data: {
|
||||
titleId: "eventLogs",
|
||||
permissions: [Permissions.AccessEventLogs],
|
||||
providerPermissions: (provider: Provider) => provider.canAccessEventLogs,
|
||||
},
|
||||
},
|
||||
],
|
||||
|
@ -100,10 +99,10 @@ const routes: Routes = [
|
|||
{
|
||||
path: "account",
|
||||
component: AccountComponent,
|
||||
canActivate: [PermissionsGuard],
|
||||
canActivate: [ProviderPermissionsGuard],
|
||||
data: {
|
||||
titleId: "myProvider",
|
||||
permissions: [Permissions.ManageProvider],
|
||||
providerPermissions: (provider: Provider) => provider.isProviderAdmin,
|
||||
},
|
||||
},
|
||||
],
|
||||
|
|
|
@ -10,8 +10,7 @@ import { OssModule } from "src/app/oss.module";
|
|||
import { AddOrganizationComponent } from "./clients/add-organization.component";
|
||||
import { ClientsComponent } from "./clients/clients.component";
|
||||
import { CreateOrganizationComponent } from "./clients/create-organization.component";
|
||||
import { PermissionsGuard } from "./guards/provider-type.guard";
|
||||
import { ProviderGuard } from "./guards/provider.guard";
|
||||
import { ProviderPermissionsGuard } from "./guards/provider-permissions.guard";
|
||||
import { AcceptProviderComponent } from "./manage/accept-provider.component";
|
||||
import { BulkConfirmComponent } from "./manage/bulk/bulk-confirm.component";
|
||||
import { BulkRemoveComponent } from "./manage/bulk/bulk-remove.component";
|
||||
|
@ -46,7 +45,7 @@ import { SetupComponent } from "./setup/setup.component";
|
|||
SetupProviderComponent,
|
||||
UserAddEditComponent,
|
||||
],
|
||||
providers: [WebProviderService, ProviderGuard, PermissionsGuard],
|
||||
providers: [WebProviderService, ProviderPermissionsGuard],
|
||||
})
|
||||
export class ProvidersModule {
|
||||
constructor(modalService: ModalService, componentFactoryResolver: ComponentFactoryResolver) {
|
||||
|
|
|
@ -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/cli/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/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 { OrganizationUserType } from "../../enums/organizationUserType";
|
||||
import { Permissions } from "../../enums/permissions";
|
||||
import { ProductType } from "../../enums/productType";
|
||||
import { PermissionsApi } from "../api/permissionsApi";
|
||||
import { OrganizationData } from "../data/organizationData";
|
||||
|
@ -114,7 +113,7 @@ export class Organization {
|
|||
}
|
||||
|
||||
get canAccessEventLogs() {
|
||||
return this.isAdmin || this.permissions.accessEventLogs;
|
||||
return (this.isAdmin || this.permissions.accessEventLogs) && this.useEvents;
|
||||
}
|
||||
|
||||
get canAccessImportExport() {
|
||||
|
@ -168,11 +167,11 @@ export class Organization {
|
|||
}
|
||||
|
||||
get canManageGroups() {
|
||||
return this.isAdmin || this.permissions.manageGroups;
|
||||
return (this.isAdmin || this.permissions.manageGroups) && this.useGroups;
|
||||
}
|
||||
|
||||
get canManageSso() {
|
||||
return this.isAdmin || this.permissions.manageSso;
|
||||
return (this.isAdmin || this.permissions.manageSso) && this.useSso;
|
||||
}
|
||||
|
||||
get canManageScim() {
|
||||
|
@ -180,7 +179,7 @@ export class Organization {
|
|||
}
|
||||
|
||||
get canManagePolicies() {
|
||||
return this.isAdmin || this.permissions.managePolicies;
|
||||
return (this.isAdmin || this.permissions.managePolicies) && this.usePolicies;
|
||||
}
|
||||
|
||||
get canManageUsers() {
|
||||
|
@ -195,30 +194,6 @@ export class Organization {
|
|||
return this.canManagePolicies;
|
||||
}
|
||||
|
||||
hasAnyPermission(permissions: Permissions[]) {
|
||||
const specifiedPermissions =
|
||||
(permissions.includes(Permissions.AccessEventLogs) && this.canAccessEventLogs) ||
|
||||
(permissions.includes(Permissions.AccessImportExport) && this.canAccessImportExport) ||
|
||||
(permissions.includes(Permissions.AccessReports) && this.canAccessReports) ||
|
||||
(permissions.includes(Permissions.CreateNewCollections) && this.canCreateNewCollections) ||
|
||||
(permissions.includes(Permissions.EditAnyCollection) && this.canEditAnyCollection) ||
|
||||
(permissions.includes(Permissions.DeleteAnyCollection) && this.canDeleteAnyCollection) ||
|
||||
(permissions.includes(Permissions.EditAssignedCollections) &&
|
||||
this.canEditAssignedCollections) ||
|
||||
(permissions.includes(Permissions.DeleteAssignedCollections) &&
|
||||
this.canDeleteAssignedCollections) ||
|
||||
(permissions.includes(Permissions.ManageGroups) && this.canManageGroups) ||
|
||||
(permissions.includes(Permissions.ManageOrganization) && this.isOwner) ||
|
||||
(permissions.includes(Permissions.ManagePolicies) && this.canManagePolicies) ||
|
||||
(permissions.includes(Permissions.ManageUsers) && this.canManageUsers) ||
|
||||
(permissions.includes(Permissions.ManageUsersPassword) && this.canManageUsersPassword) ||
|
||||
(permissions.includes(Permissions.ManageSso) && this.canManageSso) ||
|
||||
(permissions.includes(Permissions.ManageScim) && this.canManageScim) ||
|
||||
(permissions.includes(Permissions.ManageBilling) && this.canManageBilling);
|
||||
|
||||
return specifiedPermissions && (this.enabled || this.isOwner);
|
||||
}
|
||||
|
||||
get canManageBilling() {
|
||||
return this.isOwner && (this.isProviderUser || !this.hasProvider);
|
||||
}
|
||||
|
|
|
@ -143,7 +143,7 @@
|
|||
"husky": "^8.0.1",
|
||||
"jasmine-core": "^3.7.1",
|
||||
"jasmine-spec-reporter": "^7.0.0",
|
||||
"jest-mock-extended": "^2.0.6",
|
||||
"jest-mock-extended": "2.0.6",
|
||||
"jest-preset-angular": "^12.1.0",
|
||||
"lint-staged": "^13.0.3",
|
||||
"mini-css-extract-plugin": "^2.4.5",
|
||||
|
@ -28286,9 +28286,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/jest-mock-extended": {
|
||||
"version": "2.0.7",
|
||||
"resolved": "https://registry.npmjs.org/jest-mock-extended/-/jest-mock-extended-2.0.7.tgz",
|
||||
"integrity": "sha512-h8brJJN5BZb03hTwplvt+raT6Nj0U2U71Z26Py12Qc3kvYnAjDW/zSuQJLnXCNyyufy592VC9k3X7AOz+2H52g==",
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/jest-mock-extended/-/jest-mock-extended-2.0.6.tgz",
|
||||
"integrity": "sha512-KoDdjqwIp2phaOWB0hr4O+9HF7hIJx7O+Reefi3iGrNhUpzVkod9UozYTSanvbNvjFYIEH6noA2tIjc8IDpadw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ts-essentials": "^7.0.3"
|
||||
|
@ -64464,9 +64464,9 @@
|
|||
}
|
||||
},
|
||||
"jest-mock-extended": {
|
||||
"version": "2.0.7",
|
||||
"resolved": "https://registry.npmjs.org/jest-mock-extended/-/jest-mock-extended-2.0.7.tgz",
|
||||
"integrity": "sha512-h8brJJN5BZb03hTwplvt+raT6Nj0U2U71Z26Py12Qc3kvYnAjDW/zSuQJLnXCNyyufy592VC9k3X7AOz+2H52g==",
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/jest-mock-extended/-/jest-mock-extended-2.0.6.tgz",
|
||||
"integrity": "sha512-KoDdjqwIp2phaOWB0hr4O+9HF7hIJx7O+Reefi3iGrNhUpzVkod9UozYTSanvbNvjFYIEH6noA2tIjc8IDpadw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ts-essentials": "^7.0.3"
|
||||
|
|
|
@ -106,7 +106,7 @@
|
|||
"husky": "^8.0.1",
|
||||
"jasmine-core": "^3.7.1",
|
||||
"jasmine-spec-reporter": "^7.0.0",
|
||||
"jest-mock-extended": "^2.0.6",
|
||||
"jest-mock-extended": "2.0.6",
|
||||
"jest-preset-angular": "^12.1.0",
|
||||
"lint-staged": "^13.0.3",
|
||||
"mini-css-extract-plugin": "^2.4.5",
|
||||
|
|
Loading…
Reference in New Issue