mirror of
https://github.com/bitwarden/browser.git
synced 2024-11-27 12:36:14 +01:00
Implemented Custom role and permissions (#750)
* Implemented Custom role and permissions * converted Permissions interface into a class * fixed a merge issue * updated jslib * code review cleanup for Permissions * trailing commas
This commit is contained in:
parent
c3f4c6c03b
commit
dc87510a7a
2
jslib
2
jslib
@ -1 +1 @@
|
||||
Subproject commit cea09a22e533ef3598bb497ba0503c2fcd5b2dc1
|
||||
Subproject commit 6ac6df75d7a9bd5ea58f5d8310f1b3e34abd2bde
|
@ -91,9 +91,10 @@ import { UnauthGuardService } from './services/unauth-guard.service';
|
||||
|
||||
import { AuthGuardService } from 'jslib/angular/services/auth-guard.service';
|
||||
|
||||
import { OrganizationUserType } from 'jslib/enums/organizationUserType';
|
||||
import { EmergencyAccessComponent } from './settings/emergency-access.component';
|
||||
import { Permissions } from 'jslib/enums/permissions';
|
||||
|
||||
import { EmergencyAccessViewComponent } from './settings/emergency-access-view.component';
|
||||
import { EmergencyAccessComponent } from './settings/emergency-access.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
@ -259,35 +260,75 @@ const routes: Routes = [
|
||||
path: 'tools',
|
||||
component: OrgToolsComponent,
|
||||
canActivate: [OrganizationTypeGuardService],
|
||||
data: { allowedTypes: [OrganizationUserType.Owner, OrganizationUserType.Admin] },
|
||||
data: { permissions: [Permissions.AccessImportExport, Permissions.AccessReports] },
|
||||
children: [
|
||||
{ path: '', pathMatch: 'full', redirectTo: 'import' },
|
||||
{ path: 'import', component: OrgImportComponent, data: { titleId: 'importData' } },
|
||||
{ path: 'export', component: OrgExportComponent, data: { titleId: 'exportVault' } },
|
||||
{
|
||||
path: '',
|
||||
pathMatch: 'full',
|
||||
redirectTo: 'import',
|
||||
},
|
||||
{
|
||||
path: 'import',
|
||||
component: OrgImportComponent,
|
||||
canActivate: [OrganizationTypeGuardService],
|
||||
data: {
|
||||
titleId: 'importData',
|
||||
permissions: [Permissions.AccessImportExport],
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'export',
|
||||
component: OrgExportComponent,
|
||||
canActivate: [OrganizationTypeGuardService],
|
||||
data: {
|
||||
titleId: 'exportVault',
|
||||
permissions: [Permissions.AccessImportExport],
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'exposed-passwords-report',
|
||||
component: OrgExposedPasswordsReportComponent,
|
||||
data: { titleId: 'exposedPasswordsReport' },
|
||||
canActivate: [OrganizationTypeGuardService],
|
||||
data: {
|
||||
titleId: 'exposedPasswordsReport',
|
||||
permissions: [Permissions.AccessReports],
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'inactive-two-factor-report',
|
||||
component: OrgInactiveTwoFactorReportComponent,
|
||||
data: { titleId: 'inactive2faReport' },
|
||||
canActivate: [OrganizationTypeGuardService],
|
||||
data: {
|
||||
titleId: 'inactive2faReport',
|
||||
permissions: [Permissions.AccessReports],
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'reused-passwords-report',
|
||||
component: OrgReusedPasswordsReportComponent,
|
||||
data: { titleId: 'reusedPasswordsReport' },
|
||||
canActivate: [OrganizationTypeGuardService],
|
||||
data: {
|
||||
titleId: 'reusedPasswordsReport',
|
||||
permissions: [Permissions.AccessReports],
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'unsecured-websites-report',
|
||||
component: OrgUnsecuredWebsitesReportComponent,
|
||||
data: { titleId: 'unsecuredWebsitesReport' },
|
||||
canActivate: [OrganizationTypeGuardService],
|
||||
data: {
|
||||
titleId: 'unsecuredWebsitesReport',
|
||||
permissions: [Permissions.AccessReports],
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'weak-passwords-report',
|
||||
component: OrgWeakPasswordsReportComponent,
|
||||
data: { titleId: 'weakPasswordsReport' },
|
||||
canActivate: [OrganizationTypeGuardService],
|
||||
data: {
|
||||
titleId: 'weakPasswordsReport',
|
||||
permissions: [Permissions.AccessReports],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
@ -296,26 +337,73 @@ const routes: Routes = [
|
||||
component: OrgManageComponent,
|
||||
canActivate: [OrganizationTypeGuardService],
|
||||
data: {
|
||||
allowedTypes: [
|
||||
OrganizationUserType.Owner,
|
||||
OrganizationUserType.Admin,
|
||||
OrganizationUserType.Manager,
|
||||
permissions: [
|
||||
Permissions.ManageAssignedCollections,
|
||||
Permissions.ManageAllCollections,
|
||||
Permissions.AccessEventLogs,
|
||||
Permissions.ManageGroups,
|
||||
Permissions.ManageUsers,
|
||||
Permissions.ManagePolicies,
|
||||
],
|
||||
},
|
||||
children: [
|
||||
{ path: '', pathMatch: 'full', redirectTo: 'people' },
|
||||
{ path: 'collections', component: OrgManageCollectionsComponent, data: { titleId: 'collections' } },
|
||||
{ path: 'events', component: OrgEventsComponent, data: { titleId: 'eventLogs' } },
|
||||
{ path: 'groups', component: OrgGroupsComponent, data: { titleId: 'groups' } },
|
||||
{ path: 'people', component: OrgPeopleComponent, data: { titleId: 'people' } },
|
||||
{ path: 'policies', component: OrgPoliciesComponent, data: { titleId: 'policies' } },
|
||||
{
|
||||
path: '',
|
||||
pathMatch: 'full',
|
||||
redirectTo: 'people',
|
||||
},
|
||||
{
|
||||
path: 'collections',
|
||||
component: OrgManageCollectionsComponent,
|
||||
canActivate: [OrganizationTypeGuardService],
|
||||
data: {
|
||||
titleId: 'collections',
|
||||
permissions: [Permissions.ManageAssignedCollections, Permissions.ManageAllCollections],
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'events',
|
||||
component: OrgEventsComponent,
|
||||
canActivate: [OrganizationTypeGuardService],
|
||||
data: {
|
||||
titleId: 'eventLogs',
|
||||
permissions: [Permissions.AccessEventLogs],
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'groups',
|
||||
component: OrgGroupsComponent,
|
||||
canActivate: [OrganizationTypeGuardService],
|
||||
data: {
|
||||
titleId: 'groups',
|
||||
permissions: [Permissions.ManageGroups],
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'people',
|
||||
component: OrgPeopleComponent,
|
||||
canActivate: [OrganizationTypeGuardService],
|
||||
data: {
|
||||
titleId: 'people',
|
||||
permissions: [Permissions.ManageUsers],
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'policies',
|
||||
component: OrgPoliciesComponent,
|
||||
canActivate: [OrganizationTypeGuardService],
|
||||
data: {
|
||||
titleId: 'policies',
|
||||
permissions: [Permissions.ManagePolicies],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: 'settings',
|
||||
component: OrgSettingsComponent,
|
||||
canActivate: [OrganizationTypeGuardService],
|
||||
data: { allowedTypes: [OrganizationUserType.Owner] },
|
||||
data: { permissions: [Permissions.ManageOrganization] },
|
||||
children: [
|
||||
{ path: '', pathMatch: 'full', redirectTo: 'account' },
|
||||
{ path: 'account', component: OrgAccountComponent, data: { titleId: 'myOrganization' } },
|
||||
@ -340,6 +428,7 @@ const routes: Routes = [
|
||||
@NgModule({
|
||||
imports: [RouterModule.forRoot(routes, {
|
||||
useHash: true,
|
||||
paramsInheritanceStrategy: 'always',
|
||||
/*enableTracing: true,*/
|
||||
})],
|
||||
exports: [RouterModule],
|
||||
|
@ -15,21 +15,21 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ul class="nav nav-tabs" *ngIf="organization.isManager">
|
||||
<ul class="nav nav-tabs" *ngIf="showMenuBar">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" routerLink="vault" routerLinkActive="active">
|
||||
<i class="fa fa-lock" aria-hidden="true"></i>
|
||||
{{'vault' | i18n}}
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" routerLink="manage" routerLinkActive="active">
|
||||
<li class="nav-item" *ngIf="showManageTab">
|
||||
<a class="nav-link" [routerLink]="manageRoute" routerLinkActive="active">
|
||||
<i class="fa fa-sliders" aria-hidden="true"></i>
|
||||
{{'manage' | i18n}}
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item" *ngIf="organization.isAdmin">
|
||||
<a class="nav-link" routerLink="tools" routerLinkActive="active">
|
||||
<li class="nav-item" *ngIf="showToolsTab">
|
||||
<a class="nav-link" [routerLink]="toolsRoute" routerLinkActive="active">
|
||||
<i class="fa fa-wrench" aria-hidden="true"></i>
|
||||
{{'tools' | i18n}}
|
||||
</a>
|
||||
@ -43,10 +43,10 @@
|
||||
</ul>
|
||||
</div>
|
||||
<div class="ml-auto d-flex align-items-center">
|
||||
<button class="btn btn-primary" (click)="goToEnterprisePortal()" #enterpriseBtn
|
||||
[appApiAction]="enterpriseTokenPromise" *ngIf="organization.useBusinessPortal">
|
||||
<i class="fa fa-bank fa-fw" [hidden]="enterpriseBtn.loading" aria-hidden="true"></i>
|
||||
<i class="fa fa-spinner fa-spin fa-fw" [hidden]="!enterpriseBtn.loading" title="{{'loading' | i18n}}"
|
||||
<button class="btn btn-primary" (click)="goToBusinessPortal()" #businessBtn
|
||||
[appApiAction]="businessTokenPromise" *ngIf="showBusinessPortalButton">
|
||||
<i class="fa fa-bank fa-fw" [hidden]="businessBtn.loading" aria-hidden="true"></i>
|
||||
<i class="fa fa-spinner fa-spin fa-fw" [hidden]="!businessBtn.loading" title="{{'loading' | i18n}}"
|
||||
aria-hidden="true"></i>
|
||||
{{'businessPortal' | i18n}} →
|
||||
</button>
|
||||
|
@ -24,9 +24,9 @@ const BroadcasterSubscriptionId = 'OrganizationLayoutComponent';
|
||||
})
|
||||
export class OrganizationLayoutComponent implements OnInit, OnDestroy {
|
||||
organization: Organization;
|
||||
enterpriseTokenPromise: Promise<any>;
|
||||
businessTokenPromise: Promise<any>;
|
||||
private organizationId: string;
|
||||
private enterpriseUrl: string;
|
||||
private businessUrl: string;
|
||||
|
||||
constructor(private route: ActivatedRoute, private userService: UserService,
|
||||
private broadcasterService: BroadcasterService, private ngZone: NgZone,
|
||||
@ -34,11 +34,11 @@ export class OrganizationLayoutComponent implements OnInit, OnDestroy {
|
||||
private environmentService: EnvironmentService) { }
|
||||
|
||||
ngOnInit() {
|
||||
this.enterpriseUrl = 'https://portal.bitwarden.com';
|
||||
this.businessUrl = 'https://portal.bitwarden.com';
|
||||
if (this.environmentService.enterpriseUrl != null) {
|
||||
this.enterpriseUrl = this.environmentService.enterpriseUrl;
|
||||
this.businessUrl = this.environmentService.enterpriseUrl;
|
||||
} else if (this.environmentService.baseUrl != null) {
|
||||
this.enterpriseUrl = this.environmentService.baseUrl + '/portal';
|
||||
this.businessUrl = this.environmentService.baseUrl + '/portal';
|
||||
}
|
||||
|
||||
document.body.classList.remove('layout_frontend');
|
||||
@ -65,19 +65,68 @@ export class OrganizationLayoutComponent implements OnInit, OnDestroy {
|
||||
this.organization = await this.userService.getOrganization(this.organizationId);
|
||||
}
|
||||
|
||||
async goToEnterprisePortal() {
|
||||
if (this.enterpriseTokenPromise != null) {
|
||||
async goToBusinessPortal() {
|
||||
if (this.businessTokenPromise != null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
this.enterpriseTokenPromise = this.apiService.getEnterprisePortalSignInToken();
|
||||
const token = await this.enterpriseTokenPromise;
|
||||
this.businessTokenPromise = this.apiService.getEnterprisePortalSignInToken();
|
||||
const token = await this.businessTokenPromise;
|
||||
if (token != null) {
|
||||
const userId = await this.userService.getUserId();
|
||||
this.platformUtilsService.launchUri(this.enterpriseUrl + '/login?userId=' + userId +
|
||||
this.platformUtilsService.launchUri(this.businessUrl + '/login?userId=' + userId +
|
||||
'&token=' + (window as any).encodeURIComponent(token) + '&organizationId=' + this.organization.id);
|
||||
}
|
||||
} catch { }
|
||||
this.enterpriseTokenPromise = null;
|
||||
this.businessTokenPromise = null;
|
||||
}
|
||||
|
||||
get showMenuBar() {
|
||||
return this.showManageTab || this.showToolsTab || this.organization.isOwner;
|
||||
}
|
||||
|
||||
get showManageTab(): boolean {
|
||||
return this.organization.canManageUsers ||
|
||||
this.organization.canManageAssignedCollections ||
|
||||
this.organization.canManageAllCollections ||
|
||||
this.organization.canManageGroups ||
|
||||
this.organization.canManagePolicies ||
|
||||
this.organization.canAccessEventLogs;
|
||||
}
|
||||
|
||||
get showToolsTab(): boolean {
|
||||
return this.organization.canAccessImportExport || this.organization.canAccessReports;
|
||||
}
|
||||
|
||||
get showBusinessPortalButton(): boolean {
|
||||
return this.organization.useBusinessPortal && this.organization.canAccessBusinessPortal;
|
||||
}
|
||||
|
||||
get toolsRoute(): string {
|
||||
return this.organization.canAccessImportExport ?
|
||||
'tools/import' :
|
||||
'tools/exposed-passwords-report';
|
||||
}
|
||||
|
||||
get manageRoute(): string {
|
||||
let route: string;
|
||||
switch (true) {
|
||||
case this.organization.canManageUsers:
|
||||
route = 'manage/people';
|
||||
break;
|
||||
case this.organization.canManageAssignedCollections || this.organization.canManageAllCollections:
|
||||
route = 'manage/collections';
|
||||
break;
|
||||
case this.organization.canManageGroups:
|
||||
route = 'manage/groups';
|
||||
break;
|
||||
case this.organization.canManagePolicies:
|
||||
route = 'manage/policies';
|
||||
break;
|
||||
case this.organization.canAccessEventLogs:
|
||||
route = 'manage/events';
|
||||
break;
|
||||
}
|
||||
return route;
|
||||
}
|
||||
}
|
||||
|
@ -72,7 +72,7 @@ export class CollectionsComponent implements OnInit {
|
||||
async load() {
|
||||
const organization = await this.userService.getOrganization(this.organizationId);
|
||||
let response: ListResponse<CollectionResponse>;
|
||||
if (organization.isAdmin) {
|
||||
if (organization.canManageAllCollections) {
|
||||
response = await this.apiService.getCollections(this.organizationId);
|
||||
} else {
|
||||
response = await this.apiService.getUserCollections();
|
||||
|
@ -5,22 +5,23 @@
|
||||
<div class="card-header">{{'manage' | i18n}}</div>
|
||||
<div class="list-group list-group-flush">
|
||||
<a routerLink="people" class="list-group-item" routerLinkActive="active"
|
||||
*ngIf="organization.isAdmin">
|
||||
*ngIf="organization.canManageUsers">
|
||||
{{'people' | i18n}}
|
||||
</a>
|
||||
<a routerLink="collections" class="list-group-item" routerLinkActive="active">
|
||||
<a routerLink="collections" class="list-group-item" routerLinkActive="active"
|
||||
*ngIf="organization.canManageAssignedCollections || organization.canManageAllCollections">
|
||||
{{'collections' | i18n}}
|
||||
</a>
|
||||
<a routerLink="groups" class="list-group-item" routerLinkActive="active"
|
||||
*ngIf="organization.isAdmin && accessGroups">
|
||||
*ngIf="organization.canManageGroups && accessGroups">
|
||||
{{'groups' | i18n}}
|
||||
</a>
|
||||
<a routerLink="policies" class="list-group-item" routerLinkActive="active"
|
||||
*ngIf="organization.isAdmin && accessPolicies">
|
||||
*ngIf="organization.canManagePolicies && accessPolicies">
|
||||
{{'policies' | i18n}}
|
||||
</a>
|
||||
<a routerLink="events" class="list-group-item" routerLinkActive="active"
|
||||
*ngIf="organization.isAdmin && accessEvents">
|
||||
*ngIf="organization.canAccessEventLogs && accessEvents">
|
||||
{{'eventLogs' | i18n}}
|
||||
</a>
|
||||
</div>
|
||||
|
@ -69,6 +69,7 @@
|
||||
<span *ngIf="u.type === organizationUserType.Admin">{{'admin' | i18n}}</span>
|
||||
<span *ngIf="u.type === organizationUserType.Manager">{{'manager' | i18n}}</span>
|
||||
<span *ngIf="u.type === organizationUserType.User">{{'user' | i18n}}</span>
|
||||
<span *ngIf="u.type === organizationUserType.Custom">{{'custom' | i18n}}</span>
|
||||
</td>
|
||||
<td class="table-list-options">
|
||||
<div class="dropdown" appListDropdown>
|
||||
|
@ -79,7 +79,7 @@ export class PeopleComponent implements OnInit {
|
||||
this.route.parent.parent.params.subscribe(async (params) => {
|
||||
this.organizationId = params.organizationId;
|
||||
const organization = await this.userService.getOrganization(this.organizationId);
|
||||
if (!organization.isAdmin) {
|
||||
if (!organization.canManageUsers) {
|
||||
this.router.navigate(['../collections'], { relativeTo: this.route });
|
||||
return;
|
||||
}
|
||||
|
@ -63,8 +63,127 @@
|
||||
<small>{{'ownerDesc' | i18n}}</small>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-check mt-2 form-check-block">
|
||||
<input class="form-check-input" type="radio" name="userType" id="userTypeCustom"
|
||||
[value]="organizationUserType.Custom" [(ngModel)]="type">
|
||||
<label class="form-check-label" for="userTypeCustom">
|
||||
{{'custom' | i18n}}
|
||||
<small>{{'customDesc' | i18n}}</small>
|
||||
</label>
|
||||
</div>
|
||||
<ng-container *ngIf="customUserTypeSelected">
|
||||
<h3 class="mt-4 d-flex">
|
||||
<div class="mb-2">
|
||||
{{'permissions' | i18n}}
|
||||
</h3>
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<div class="mb-3">
|
||||
<label class="font-weight-bold mb-0">Manager Permissions</label>
|
||||
<hr class="my-0 mr-2" />
|
||||
<div class="form-group mb-0">
|
||||
<div class="form-check mt-1 form-check-block">
|
||||
<input class="form-check-input" type="checkbox" name="manageAssignedCollections"
|
||||
id="manageAssignedCollections"
|
||||
[(ngModel)]="permissions.manageAssignedCollections">
|
||||
<label class="form-check-label font-weight-normal"
|
||||
for="manageAssignedCollections">
|
||||
{{'manageAssignedCollections' | i18n}}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<div class="mb-3">
|
||||
<label class="font-weight-bold mb-0">Admin Permissions</label>
|
||||
<hr class="my-0 mr-2" />
|
||||
<div class="form-group mb-0">
|
||||
<div class="form-check mt-1 form-check-block">
|
||||
<input class="form-check-input" type="checkbox" name="accessBusinessPortal"
|
||||
id="accessBusinessPortal" [(ngModel)]="permissions.accessBusinessPortal">
|
||||
<label class="form-check-label font-weight-normal" for="accessBusinessPortal">
|
||||
{{'accessBusinessPortal' | i18n}}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group mb-0">
|
||||
<div class="form-check mt-1 form-check-block">
|
||||
<input class="form-check-input" type="checkbox" name="accessEventLogs"
|
||||
id="accessEventLogs" [(ngModel)]="permissions.accessEventLogs">
|
||||
<label class="form-check-label font-weight-normal" for="accessEventLogs">
|
||||
{{'accessEventLogs' | i18n}}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group mb-0">
|
||||
<div class="form-check mt-1 form-check-block">
|
||||
<input class="form-check-input" type="checkbox" name="accessImportExport"
|
||||
id="accessImportExport" [(ngModel)]="permissions.accessImportExport">
|
||||
<label class="form-check-label font-weight-normal" for="accessImportExport">
|
||||
{{'accessImportExport' | i18n}}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group mb-0">
|
||||
<div class="form-check mt-1 form-check-block">
|
||||
<input class="form-check-input" type="checkbox" name="accessReports"
|
||||
id="accessReports" [(ngModel)]="permissions.accessReports">
|
||||
<label class="form-check-label font-weight-normal" for="accessReports">
|
||||
{{'accessReports' | i18n}}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group mb-0">
|
||||
<div class="form-check mt-1 form-check-block">
|
||||
<input class="form-check-input" type="checkbox" name="manageAllCollections"
|
||||
id="manageAllCollections" [(ngModel)]="permissions.manageAllCollections">
|
||||
<label class="form-check-label font-weight-normal" for="manageAllCollections">
|
||||
{{'manageAllCollections' | i18n}}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group mb-0">
|
||||
<div class="form-check mt-1 form-check-block">
|
||||
<input class="form-check-input" type="checkbox" name="manageGroups"
|
||||
id="manageGroups" [(ngModel)]="permissions.manageGroups">
|
||||
<label class="form-check-label font-weight-normal" for="manageGroups">
|
||||
{{'manageGroups' | i18n}}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group mb-0">
|
||||
<div class="form-check mt-1 form-check-block">
|
||||
<input class="form-check-input" type="checkbox" name="manageSso"
|
||||
id="managePolicies" [(ngModel)]="permissions.manageSso">
|
||||
<label class="form-check-label font-weight-normal" for="manageSso">
|
||||
{{'manageSso' | i18n}}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group mb-0">
|
||||
<div class="form-check mt-1 form-check-block">
|
||||
<input class="form-check-input" type="checkbox" name="managePolicies"
|
||||
id="managePolicies" [(ngModel)]="permissions.managePolicies">
|
||||
<label class="form-check-label font-weight-normal" for="managePolicies">
|
||||
{{'managePolicies' | i18n}}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group mb-0">
|
||||
<div class="form-check mt-1 form-check-block">
|
||||
<input class="form-check-input" type="checkbox" name="manageUsers"
|
||||
id="manageUsers" [(ngModel)]="permissions.manageUsers">
|
||||
<label class="form-check-label font-weight-normal" for="manageUsers">
|
||||
{{'manageUsers' | i18n}}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
<h3 class="mt-4 d-flex">
|
||||
<div class="mb-3">
|
||||
{{'accessControl' | i18n}}
|
||||
<a target="_blank" rel="noopener" appA11yTitle="{{'learnMore' | i18n}}"
|
||||
href="https://bitwarden.com/help/article/user-types-access-control/#access-control">
|
||||
|
@ -23,6 +23,7 @@ import { CollectionDetailsResponse } from 'jslib/models/response/collectionRespo
|
||||
import { CollectionView } from 'jslib/models/view/collectionView';
|
||||
|
||||
import { OrganizationUserType } from 'jslib/enums/organizationUserType';
|
||||
import { PermissionsApi } from 'jslib/models/api/permissionsApi';
|
||||
|
||||
@Component({
|
||||
selector: 'app-user-add-edit',
|
||||
@ -40,12 +41,18 @@ export class UserAddEditComponent implements OnInit {
|
||||
title: string;
|
||||
emails: string;
|
||||
type: OrganizationUserType = OrganizationUserType.User;
|
||||
permissions = new PermissionsApi();
|
||||
showCustom = false;
|
||||
access: 'all' | 'selected' = 'selected';
|
||||
collections: CollectionView[] = [];
|
||||
formPromise: Promise<any>;
|
||||
deletePromise: Promise<any>;
|
||||
organizationUserType = OrganizationUserType;
|
||||
|
||||
get customUserTypeSelected(): boolean {
|
||||
return this.type === OrganizationUserType.Custom;
|
||||
}
|
||||
|
||||
constructor(private apiService: ApiService, private i18nService: I18nService,
|
||||
private analytics: Angulartics2, private toasterService: ToasterService,
|
||||
private collectionService: CollectionService, private platformUtilsService: PlatformUtilsService) { }
|
||||
@ -61,6 +68,9 @@ export class UserAddEditComponent implements OnInit {
|
||||
const user = await this.apiService.getOrganizationUser(this.organizationId, this.organizationUserId);
|
||||
this.access = user.accessAll ? 'all' : 'selected';
|
||||
this.type = user.type;
|
||||
if (user.type === OrganizationUserType.Custom) {
|
||||
this.permissions = user.permissions;
|
||||
}
|
||||
if (user.collections != null && this.collections != null) {
|
||||
user.collections.forEach((s) => {
|
||||
const collection = this.collections.filter((c) => c.id === s.id);
|
||||
@ -97,6 +107,40 @@ export class UserAddEditComponent implements OnInit {
|
||||
this.collections.forEach((c) => this.check(c, select));
|
||||
}
|
||||
|
||||
setRequestPermissions(p: PermissionsApi, clearPermissions: boolean) {
|
||||
p.accessBusinessPortal = clearPermissions ?
|
||||
false :
|
||||
this.permissions.accessBusinessPortal;
|
||||
p.accessEventLogs = this.permissions.accessEventLogs = clearPermissions ?
|
||||
false :
|
||||
this.permissions.accessEventLogs;
|
||||
p.accessImportExport = clearPermissions ?
|
||||
false :
|
||||
this.permissions.accessImportExport;
|
||||
p.accessReports = clearPermissions ?
|
||||
false :
|
||||
this.permissions.accessReports;
|
||||
p.manageAllCollections = clearPermissions ?
|
||||
false :
|
||||
this.permissions.manageAllCollections;
|
||||
p.manageAssignedCollections = clearPermissions ?
|
||||
false :
|
||||
this.permissions.manageAssignedCollections;
|
||||
p.manageGroups = clearPermissions ?
|
||||
false :
|
||||
this.permissions.manageGroups;
|
||||
p.manageSso = clearPermissions ?
|
||||
false :
|
||||
this.permissions.manageSso;
|
||||
p.managePolicies = clearPermissions ?
|
||||
false :
|
||||
this.permissions.managePolicies;
|
||||
p.manageUsers = clearPermissions ?
|
||||
false :
|
||||
this.permissions.manageUsers;
|
||||
return p;
|
||||
}
|
||||
|
||||
async submit() {
|
||||
let collections: SelectionReadOnlyRequest[] = null;
|
||||
if (this.access !== 'all') {
|
||||
@ -110,6 +154,7 @@ export class UserAddEditComponent implements OnInit {
|
||||
request.accessAll = this.access === 'all';
|
||||
request.type = this.type;
|
||||
request.collections = collections;
|
||||
request.permissions = this.setRequestPermissions(request.permissions ?? new PermissionsApi(), request.type !== OrganizationUserType.Custom);
|
||||
this.formPromise = this.apiService.putOrganizationUser(this.organizationId, this.organizationUserId,
|
||||
request);
|
||||
} else {
|
||||
@ -117,6 +162,7 @@ export class UserAddEditComponent implements OnInit {
|
||||
request.emails = this.emails.trim().split(/\s*,\s*/);
|
||||
request.accessAll = this.access === 'all';
|
||||
request.type = this.type;
|
||||
request.permissions = this.setRequestPermissions(request.permissions ?? new PermissionsApi(), request.type !== OrganizationUserType.Custom);
|
||||
request.collections = collections;
|
||||
this.formPromise = this.apiService.postOrganizationUserInvite(this.organizationId, request);
|
||||
}
|
||||
@ -148,4 +194,5 @@ export class UserAddEditComponent implements OnInit {
|
||||
this.onDeletedUser.emit();
|
||||
} catch { }
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -14,12 +14,15 @@ import {
|
||||
} from '../../tools/exposed-passwords-report.component';
|
||||
|
||||
import { CipherView } from 'jslib/models/view/cipherView';
|
||||
import { Cipher } from 'jslib/models/domain/cipher';
|
||||
|
||||
@Component({
|
||||
selector: 'app-exposed-passwords-report',
|
||||
templateUrl: '../../tools/exposed-passwords-report.component.html',
|
||||
})
|
||||
export class ExposedPasswordsReportComponent extends BaseExposedPasswordsReportComponent {
|
||||
manageableCiphers: Cipher[];
|
||||
|
||||
constructor(cipherService: CipherService, auditService: AuditService,
|
||||
componentFactoryResolver: ComponentFactoryResolver, messagingService: MessagingService,
|
||||
userService: UserService, private route: ActivatedRoute) {
|
||||
@ -29,6 +32,7 @@ export class ExposedPasswordsReportComponent extends BaseExposedPasswordsReportC
|
||||
ngOnInit() {
|
||||
this.route.parent.parent.params.subscribe(async (params) => {
|
||||
this.organization = await this.userService.getOrganization(params.organizationId);
|
||||
this.manageableCiphers = await this.cipherService.getAll();
|
||||
super.ngOnInit();
|
||||
});
|
||||
}
|
||||
@ -36,4 +40,8 @@ export class ExposedPasswordsReportComponent extends BaseExposedPasswordsReportC
|
||||
getAllCiphers(): Promise<CipherView[]> {
|
||||
return this.cipherService.getAllFromApiForOrganization(this.organization.id);
|
||||
}
|
||||
|
||||
canManageCipher(c: CipherView): boolean {
|
||||
return this.manageableCiphers.some(x => x.id === c.id);
|
||||
}
|
||||
}
|
||||
|
@ -8,6 +8,8 @@ import { CipherService } from 'jslib/abstractions/cipher.service';
|
||||
import { MessagingService } from 'jslib/abstractions/messaging.service';
|
||||
import { UserService } from 'jslib/abstractions/user.service';
|
||||
|
||||
import { Cipher } from 'jslib/models/domain/cipher';
|
||||
|
||||
import { CipherView } from 'jslib/models/view/cipherView';
|
||||
|
||||
import {
|
||||
@ -19,6 +21,8 @@ import {
|
||||
templateUrl: '../../tools/reused-passwords-report.component.html',
|
||||
})
|
||||
export class ReusedPasswordsReportComponent extends BaseReusedPasswordsReportComponent {
|
||||
manageableCiphers: Cipher[];
|
||||
|
||||
constructor(cipherService: CipherService, componentFactoryResolver: ComponentFactoryResolver,
|
||||
messagingService: MessagingService, userService: UserService,
|
||||
private route: ActivatedRoute) {
|
||||
@ -28,6 +32,7 @@ export class ReusedPasswordsReportComponent extends BaseReusedPasswordsReportCom
|
||||
async ngOnInit() {
|
||||
this.route.parent.parent.params.subscribe(async (params) => {
|
||||
this.organization = await this.userService.getOrganization(params.organizationId);
|
||||
this.manageableCiphers = await this.cipherService.getAll();
|
||||
await super.ngOnInit();
|
||||
});
|
||||
}
|
||||
@ -35,4 +40,8 @@ export class ReusedPasswordsReportComponent extends BaseReusedPasswordsReportCom
|
||||
getAllCiphers(): Promise<CipherView[]> {
|
||||
return this.cipherService.getAllFromApiForOrganization(this.organization.id);
|
||||
}
|
||||
|
||||
canManageCipher(c: CipherView): boolean {
|
||||
return this.manageableCiphers.some(x => x.id === c.id);
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,12 @@
|
||||
<div class="container page-content">
|
||||
<ng-container *ngIf="loading">
|
||||
<i class="fa fa-spinner fa-spin text-muted" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
<span class="sr-only">{{'loading' | i18n}}</span>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="!loading">
|
||||
<div class="row">
|
||||
<div class="col-3">
|
||||
<div class="card mb-4">
|
||||
<div class="card mb-4" *ngIf="organization.canAccessImportExport">
|
||||
<div class="card-header">{{'tools' | i18n}}</div>
|
||||
<div class="list-group list-group-flush">
|
||||
<a routerLink="import" class="list-group-item" routerLinkActive="active">
|
||||
@ -12,7 +17,7 @@
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card" *ngIf="organization.canAccessReports">
|
||||
<div class="card-header d-flex">
|
||||
{{'reports' | i18n}}
|
||||
<div class="ml-auto">
|
||||
@ -45,4 +50,5 @@
|
||||
<router-outlet></router-outlet>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
@ -13,6 +13,7 @@ import { UserService } from 'jslib/abstractions/user.service';
|
||||
export class ToolsComponent {
|
||||
organization: Organization;
|
||||
accessReports = false;
|
||||
loading = true;
|
||||
|
||||
constructor(private route: ActivatedRoute, private userService: UserService,
|
||||
private messagingService: MessagingService) { }
|
||||
@ -23,6 +24,7 @@ export class ToolsComponent {
|
||||
// TODO: Maybe we want to just make sure they are not on a free plan? Just compare useTotp for now
|
||||
// since all paid plans include useTotp
|
||||
this.accessReports = this.organization.useTotp;
|
||||
this.loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -9,6 +9,8 @@ import { MessagingService } from 'jslib/abstractions/messaging.service';
|
||||
import { PasswordGenerationService } from 'jslib/abstractions/passwordGeneration.service';
|
||||
import { UserService } from 'jslib/abstractions/user.service';
|
||||
|
||||
import { Cipher } from 'jslib/models/domain/cipher';
|
||||
|
||||
import { CipherView } from 'jslib/models/view/cipherView';
|
||||
|
||||
import {
|
||||
@ -20,6 +22,8 @@ import {
|
||||
templateUrl: '../../tools/weak-passwords-report.component.html',
|
||||
})
|
||||
export class WeakPasswordsReportComponent extends BaseWeakPasswordsReportComponent {
|
||||
manageableCiphers: Cipher[];
|
||||
|
||||
constructor(cipherService: CipherService, passwordGenerationService: PasswordGenerationService,
|
||||
componentFactoryResolver: ComponentFactoryResolver, messagingService: MessagingService,
|
||||
userService: UserService, private route: ActivatedRoute) {
|
||||
@ -29,6 +33,7 @@ export class WeakPasswordsReportComponent extends BaseWeakPasswordsReportCompone
|
||||
async ngOnInit() {
|
||||
this.route.parent.parent.params.subscribe(async (params) => {
|
||||
this.organization = await this.userService.getOrganization(params.organizationId);
|
||||
this.manageableCiphers = await this.cipherService.getAll();
|
||||
await super.ngOnInit();
|
||||
});
|
||||
}
|
||||
@ -36,4 +41,8 @@ export class WeakPasswordsReportComponent extends BaseWeakPasswordsReportCompone
|
||||
getAllCiphers(): Promise<CipherView[]> {
|
||||
return this.cipherService.getAllFromApiForOrganization(this.organization.id);
|
||||
}
|
||||
|
||||
canManageCipher(c: CipherView): boolean {
|
||||
return this.manageableCiphers.some(x => x.id === c.id);
|
||||
}
|
||||
}
|
||||
|
@ -46,7 +46,7 @@ export class AddEditComponent extends BaseAddEditComponent {
|
||||
protected allowOwnershipAssignment() {
|
||||
if (this.ownershipOptions != null && (this.ownershipOptions.length > 1 || !this.allowPersonal)) {
|
||||
if (this.organization != null) {
|
||||
return this.cloneMode && this.organization.isAdmin;
|
||||
return this.cloneMode && this.organization.canManageAllCollections;
|
||||
} else {
|
||||
return !this.editMode || this.cloneMode;
|
||||
}
|
||||
@ -55,14 +55,14 @@ export class AddEditComponent extends BaseAddEditComponent {
|
||||
}
|
||||
|
||||
protected loadCollections() {
|
||||
if (!this.organization.isAdmin) {
|
||||
if (!this.organization.canManageAllCollections) {
|
||||
return super.loadCollections();
|
||||
}
|
||||
return Promise.resolve(this.collections);
|
||||
}
|
||||
|
||||
protected async loadCipher() {
|
||||
if (!this.organization.isAdmin) {
|
||||
if (!this.organization.canManageAllCollections) {
|
||||
return await super.loadCipher();
|
||||
}
|
||||
const response = await this.apiService.getCipherAdmin(this.cipherId);
|
||||
@ -72,14 +72,14 @@ export class AddEditComponent extends BaseAddEditComponent {
|
||||
}
|
||||
|
||||
protected encryptCipher() {
|
||||
if (!this.organization.isAdmin) {
|
||||
if (!this.organization.canManageAllCollections) {
|
||||
return super.encryptCipher();
|
||||
}
|
||||
return this.cipherService.encrypt(this.cipher, null, this.originalCipher);
|
||||
}
|
||||
|
||||
protected async saveCipher(cipher: Cipher) {
|
||||
if (!this.organization.isAdmin || cipher.organizationId == null) {
|
||||
if (!this.organization.canManageAllCollections || cipher.organizationId == null) {
|
||||
return super.saveCipher(cipher);
|
||||
}
|
||||
if (this.editMode && !this.cloneMode) {
|
||||
@ -92,7 +92,7 @@ export class AddEditComponent extends BaseAddEditComponent {
|
||||
}
|
||||
|
||||
protected async deleteCipher() {
|
||||
if (!this.organization.isAdmin) {
|
||||
if (!this.organization.canManageAllCollections) {
|
||||
return super.deleteCipher();
|
||||
}
|
||||
return this.cipher.isDeleted ? this.apiService.deleteCipherAdmin(this.cipherId)
|
||||
|
@ -29,13 +29,13 @@ export class AttachmentsComponent extends BaseAttachmentsComponent {
|
||||
}
|
||||
|
||||
protected async reupload(attachment: AttachmentView) {
|
||||
if (this.organization.isAdmin && this.showFixOldAttachments(attachment)) {
|
||||
if (this.organization.canManageAllCollections && this.showFixOldAttachments(attachment)) {
|
||||
await super.reuploadCipherAttachment(attachment, true);
|
||||
}
|
||||
}
|
||||
|
||||
protected async loadCipher() {
|
||||
if (!this.organization.isAdmin) {
|
||||
if (!this.organization.canManageAllCollections) {
|
||||
return await super.loadCipher();
|
||||
}
|
||||
const response = await this.apiService.getCipherAdmin(this.cipherId);
|
||||
@ -43,17 +43,17 @@ export class AttachmentsComponent extends BaseAttachmentsComponent {
|
||||
}
|
||||
|
||||
protected saveCipherAttachment(file: File) {
|
||||
return this.cipherService.saveAttachmentWithServer(this.cipherDomain, file, this.organization.isAdmin);
|
||||
return this.cipherService.saveAttachmentWithServer(this.cipherDomain, file, this.organization.canManageAllCollections);
|
||||
}
|
||||
|
||||
protected deleteCipherAttachment(attachmentId: string) {
|
||||
if (!this.organization.isAdmin) {
|
||||
if (!this.organization.canManageAllCollections) {
|
||||
return super.deleteCipherAttachment(attachmentId);
|
||||
}
|
||||
return this.apiService.deleteCipherAttachmentAdmin(this.cipherId, attachmentId);
|
||||
}
|
||||
|
||||
protected showFixOldAttachments(attachment: AttachmentView) {
|
||||
return attachment.key == null && this.organization.isAdmin;
|
||||
return attachment.key == null && this.organization.canManageAllCollections;
|
||||
}
|
||||
}
|
||||
|
@ -42,7 +42,7 @@ export class CiphersComponent extends BaseCiphersComponent {
|
||||
}
|
||||
|
||||
async load(filter: (cipher: CipherView) => boolean = null) {
|
||||
if (!this.organization.isAdmin) {
|
||||
if (!this.organization.canManageAllCollections) {
|
||||
await super.load(filter, this.deleted);
|
||||
return;
|
||||
}
|
||||
@ -53,7 +53,7 @@ export class CiphersComponent extends BaseCiphersComponent {
|
||||
}
|
||||
|
||||
async applyFilter(filter: (cipher: CipherView) => boolean = null) {
|
||||
if (this.organization.isAdmin) {
|
||||
if (this.organization.canManageAllCollections) {
|
||||
await super.applyFilter(filter);
|
||||
} else {
|
||||
const f = (c: CipherView) => c.organizationId === this.organization.id && (filter == null || filter(c));
|
||||
@ -62,7 +62,7 @@ export class CiphersComponent extends BaseCiphersComponent {
|
||||
}
|
||||
|
||||
async search(timeout: number = null) {
|
||||
if (!this.organization.isAdmin) {
|
||||
if (!this.organization.canManageAllCollections) {
|
||||
return super.search(timeout);
|
||||
}
|
||||
this.searchPending = false;
|
||||
@ -89,13 +89,13 @@ export class CiphersComponent extends BaseCiphersComponent {
|
||||
}
|
||||
|
||||
protected deleteCipher(id: string) {
|
||||
if (!this.organization.isAdmin) {
|
||||
if (!this.organization.canManageAllCollections) {
|
||||
return super.deleteCipher(id, this.deleted);
|
||||
}
|
||||
return this.deleted ? this.apiService.deleteCipherAdmin(id) : this.apiService.putDeleteCipherAdmin(id);
|
||||
}
|
||||
|
||||
protected showFixOldAttachments(c: CipherView) {
|
||||
return this.organization.isAdmin && c.hasOldAttachments;
|
||||
return this.organization.canManageAllCollections && c.hasOldAttachments;
|
||||
}
|
||||
}
|
||||
|
@ -28,7 +28,7 @@ export class CollectionsComponent extends BaseCollectionsComponent {
|
||||
}
|
||||
|
||||
protected async loadCipher() {
|
||||
if (!this.organization.isAdmin) {
|
||||
if (!this.organization.canManageAllCollections) {
|
||||
return await super.loadCipher();
|
||||
}
|
||||
const response = await this.apiService.getCipherAdmin(this.cipherId);
|
||||
@ -36,21 +36,21 @@ export class CollectionsComponent extends BaseCollectionsComponent {
|
||||
}
|
||||
|
||||
protected loadCipherCollections() {
|
||||
if (!this.organization.isAdmin) {
|
||||
if (!this.organization.canManageAllCollections) {
|
||||
return super.loadCipherCollections();
|
||||
}
|
||||
return this.collectionIds;
|
||||
}
|
||||
|
||||
protected loadCollections() {
|
||||
if (!this.organization.isAdmin) {
|
||||
if (!this.organization.canManageAllCollections) {
|
||||
return super.loadCollections();
|
||||
}
|
||||
return Promise.resolve(this.collections);
|
||||
}
|
||||
|
||||
protected saveCollections() {
|
||||
if (this.organization.isAdmin) {
|
||||
if (this.organization.canManageAllCollections) {
|
||||
const request = new CipherCollectionsRequest(this.cipherDomain.collectionIds);
|
||||
return this.apiService.putCipherCollectionsAdmin(this.cipherId, request);
|
||||
} else {
|
||||
|
@ -29,7 +29,7 @@ export class GroupingsComponent extends BaseGroupingsComponent {
|
||||
}
|
||||
|
||||
async loadCollections() {
|
||||
if (!this.organization.isAdmin) {
|
||||
if (!this.organization.canManageAllCollections) {
|
||||
await super.loadCollections(this.organization.id);
|
||||
return;
|
||||
}
|
||||
|
@ -69,7 +69,7 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
|
||||
const queryParamsSub = this.route.queryParams.subscribe(async (qParams) => {
|
||||
this.ciphersComponent.searchText = this.groupingsComponent.searchText = qParams.search;
|
||||
if (!this.organization.isAdmin) {
|
||||
if (!this.organization.canManageAllCollections) {
|
||||
await this.syncService.fullSync(false);
|
||||
this.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => {
|
||||
this.ngZone.run(async () => {
|
||||
@ -233,7 +233,7 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
this.modal = this.collectionsModalRef.createComponent(factory).instance;
|
||||
const childComponent = this.modal.show<CollectionsComponent>(CollectionsComponent, this.collectionsModalRef);
|
||||
|
||||
if (this.organization.isAdmin) {
|
||||
if (this.organization.canManageAllCollections) {
|
||||
childComponent.collectionIds = cipher.collectionIds;
|
||||
childComponent.collections = this.groupingsComponent.collections.filter((c) => !c.readOnly);
|
||||
}
|
||||
@ -253,7 +253,7 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
const component = this.editCipher(null);
|
||||
component.organizationId = this.organization.id;
|
||||
component.type = this.type;
|
||||
if (this.organization.isAdmin) {
|
||||
if (this.organization.canManageAllCollections) {
|
||||
component.collections = this.groupingsComponent.collections.filter((c) => !c.readOnly);
|
||||
}
|
||||
if (this.collectionId != null) {
|
||||
@ -296,7 +296,7 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
const component = this.editCipher(cipher);
|
||||
component.cloneMode = true;
|
||||
component.organizationId = this.organization.id;
|
||||
if (this.organization.isAdmin) {
|
||||
if (this.organization.canManageAllCollections) {
|
||||
component.collections = this.groupingsComponent.collections.filter((c) => !c.readOnly);
|
||||
}
|
||||
// Regardless of Admin state, the collection Ids need to passed manually as they are not assigned value
|
||||
|
@ -7,20 +7,32 @@ import {
|
||||
|
||||
import { UserService } from 'jslib/abstractions/user.service';
|
||||
|
||||
import { OrganizationUserType } from 'jslib/enums/organizationUserType';
|
||||
import { Permissions } from 'jslib/enums/permissions';
|
||||
|
||||
@Injectable()
|
||||
export class OrganizationTypeGuardService implements CanActivate {
|
||||
constructor(private userService: UserService, private router: Router) { }
|
||||
|
||||
async canActivate(route: ActivatedRouteSnapshot) {
|
||||
const org = await this.userService.getOrganization(route.parent.params.organizationId);
|
||||
const allowedTypes = route.data == null ? null : route.data.allowedTypes as OrganizationUserType[];
|
||||
if (allowedTypes == null || allowedTypes.indexOf(org.type) === -1) {
|
||||
const org = await this.userService.getOrganization(route.params.organizationId);
|
||||
const permissions = route.data == null ? null : route.data.permissions as Permissions[];
|
||||
|
||||
if (
|
||||
(permissions.indexOf(Permissions.AccessBusinessPortal) !== -1 && org.canAccessBusinessPortal) ||
|
||||
(permissions.indexOf(Permissions.AccessEventLogs) !== -1 && org.canAccessEventLogs) ||
|
||||
(permissions.indexOf(Permissions.AccessImportExport) !== -1 && org.canAccessImportExport) ||
|
||||
(permissions.indexOf(Permissions.AccessReports) !== -1 && org.canAccessReports) ||
|
||||
(permissions.indexOf(Permissions.ManageAllCollections) !== -1 && org.canManageAllCollections) ||
|
||||
(permissions.indexOf(Permissions.ManageAssignedCollections) !== -1 && org.canManageAssignedCollections) ||
|
||||
(permissions.indexOf(Permissions.ManageGroups) !== -1 && org.canManageGroups) ||
|
||||
(permissions.indexOf(Permissions.ManageOrganization) !== -1 && org.isOwner) ||
|
||||
(permissions.indexOf(Permissions.ManagePolicies) !== -1 && org.canManagePolicies) ||
|
||||
(permissions.indexOf(Permissions.ManageUsers) !== -1 && org.canManageUsers)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
this.router.navigate(['/organizations', org.id]);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -21,7 +21,12 @@
|
||||
<app-vault-icon [cipher]="c"></app-vault-icon>
|
||||
</td>
|
||||
<td class="reduced-lh wrap">
|
||||
<ng-container *ngIf="!organization || canManageCipher(c) ; else cantManage">
|
||||
<a href="#" appStopClick (click)="selectCipher(c)" title="{{'editItem' | i18n}}">{{c.name}}</a>
|
||||
</ng-container>
|
||||
<ng-template #cantManage>
|
||||
<span>{{c.name}}</span>
|
||||
</ng-template>
|
||||
<ng-container *ngIf="!organization && c.organizationId">
|
||||
<i class="fa fa-share-alt" appStopProp title="{{'shared' | i18n}}" aria-hidden="true"></i>
|
||||
<span class="sr-only">{{'shared' | i18n}}</span>
|
||||
|
@ -61,4 +61,9 @@ export class ExposedPasswordsReportComponent extends CipherReportComponent imple
|
||||
protected getAllCiphers(): Promise<CipherView[]> {
|
||||
return this.cipherService.getAllDecrypted();
|
||||
}
|
||||
|
||||
protected canManageCipher(c: CipherView): boolean {
|
||||
// this will only ever be false from the org view;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -27,7 +27,12 @@
|
||||
<app-vault-icon [cipher]="c"></app-vault-icon>
|
||||
</td>
|
||||
<td class="reduced-lh wrap">
|
||||
<ng-container *ngIf="!organization || canManageCipher(c) ; else cantManage">
|
||||
<a href="#" appStopClick (click)="selectCipher(c)" title="{{'editItem' | i18n}}">{{c.name}}</a>
|
||||
</ng-container>
|
||||
<ng-template #cantManage>
|
||||
<span>{{c.name}}</span>
|
||||
</ng-template>
|
||||
<ng-container *ngIf="!organization && c.organizationId">
|
||||
<i class="fa fa-share-alt" appStopProp title="{{'shared' | i18n}}" aria-hidden="true"></i>
|
||||
<span class="sr-only">{{'shared' | i18n}}</span>
|
||||
|
@ -55,4 +55,9 @@ export class ReusedPasswordsReportComponent extends CipherReportComponent implem
|
||||
protected getAllCiphers(): Promise<CipherView[]> {
|
||||
return this.cipherService.getAllDecrypted();
|
||||
}
|
||||
|
||||
protected canManageCipher(c: CipherView): boolean {
|
||||
// this will only ever be false from an organization view
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -27,7 +27,12 @@
|
||||
<app-vault-icon [cipher]="c"></app-vault-icon>
|
||||
</td>
|
||||
<td class="reduced-lh wrap">
|
||||
<ng-container *ngIf="!organization || canManageCipher(c) ; else cantManage">
|
||||
<a href="#" appStopClick (click)="selectCipher(c)" title="{{'editItem' | i18n}}">{{c.name}}</a>
|
||||
</ng-container>
|
||||
<ng-template #cantManage>
|
||||
<span>{{c.name}}</span>
|
||||
</ng-template>
|
||||
<ng-container *ngIf="!organization && c.organizationId">
|
||||
<i class="fa fa-share-alt" appStopProp title="{{'shared' | i18n}}" aria-hidden="true"></i>
|
||||
<span class="sr-only">{{'shared' | i18n}}</span>
|
||||
|
@ -75,6 +75,11 @@ export class WeakPasswordsReportComponent extends CipherReportComponent implemen
|
||||
return this.cipherService.getAllDecrypted();
|
||||
}
|
||||
|
||||
protected canManageCipher(c: CipherView): boolean {
|
||||
// this will only ever be false from the org view;
|
||||
return true;
|
||||
}
|
||||
|
||||
private scoreKey(score: number): [string, string] {
|
||||
switch (score) {
|
||||
case 4:
|
||||
|
@ -31,7 +31,7 @@ export class BulkDeleteComponent {
|
||||
private apiService: ApiService) { }
|
||||
|
||||
async submit() {
|
||||
if (!this.organization || !this.organization.isAdmin) {
|
||||
if (!this.organization || !this.organization.canManageAllCollections) {
|
||||
await this.deleteCiphers();
|
||||
} else {
|
||||
await this.deleteCiphersAdmin();
|
||||
|
@ -3577,6 +3577,45 @@
|
||||
"estimatedTax": {
|
||||
"message": "Estimated tax"
|
||||
},
|
||||
"custom": {
|
||||
"message": "Custom"
|
||||
},
|
||||
"customDesc": {
|
||||
"message": "Allows more granular control of user permissions for advanced configurations."
|
||||
},
|
||||
"permissions": {
|
||||
"message": "Permissions"
|
||||
},
|
||||
"accessBusinessPortal": {
|
||||
"message": "Access Business Portal"
|
||||
},
|
||||
"accessEventLogs": {
|
||||
"message": "Access Event Logs"
|
||||
},
|
||||
"accessImportExport": {
|
||||
"message": "Access Import/Export"
|
||||
},
|
||||
"accessReports": {
|
||||
"message": "Access Reports"
|
||||
},
|
||||
"manageAllCollections": {
|
||||
"message": "Manage All Collections"
|
||||
},
|
||||
"manageAssignedCollections": {
|
||||
"message": "Manage Assigned Collections"
|
||||
},
|
||||
"manageGroups": {
|
||||
"message": "Manage Groups"
|
||||
},
|
||||
"managePolicies": {
|
||||
"message": "Manage Policies"
|
||||
},
|
||||
"manageSso": {
|
||||
"message": "Manage Sso"
|
||||
},
|
||||
"manageUsers": {
|
||||
"message": "Manage Users"
|
||||
},
|
||||
"disableRequireSsoError": {
|
||||
"message": "You must manually disable the Single Sign-On Authentication policy before this policy can be disabled."
|
||||
},
|
||||
|
@ -53,6 +53,13 @@
|
||||
"semicolon": [
|
||||
true,
|
||||
"always"
|
||||
],
|
||||
"trailing-comma": [
|
||||
true,
|
||||
{
|
||||
"multiline": "always",
|
||||
"singleline": "never"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user