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