Inherited permissions

This commit is contained in:
Michael Shamoon 2022-11-15 20:54:57 -08:00
parent 927a9781ad
commit 355efadf87
6 changed files with 68 additions and 18 deletions

View File

@ -25,7 +25,7 @@
<app-input-select i18n-title title="Groups" [items]="groups" multiple="true" formControlName="groups"></app-input-select> <app-input-select i18n-title title="Groups" [items]="groups" multiple="true" formControlName="groups"></app-input-select>
</div> </div>
<div class="col"> <div class="col">
<app-permissions-select i18n-title title="Permissions" formControlName="user_permissions" [error]="error?.user_permissions"></app-permissions-select> <app-permissions-select i18n-title title="Permissions" formControlName="user_permissions" [error]="error?.user_permissions" [inheritedPermissions]="inheritedPermissions"></app-permissions-select>
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,4 +1,4 @@
import { Component } from '@angular/core' import { Component, OnInit } from '@angular/core'
import { FormControl, FormGroup } from '@angular/forms' import { FormControl, FormGroup } from '@angular/forms'
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap' import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
import { first } from 'rxjs' import { first } from 'rxjs'
@ -42,8 +42,8 @@ export class UserEditDialogComponent extends EditDialogComponent<PaperlessUser>
username: new FormControl(''), username: new FormControl(''),
first_name: new FormControl(''), first_name: new FormControl(''),
last_name: new FormControl(''), last_name: new FormControl(''),
is_active: new FormControl(null), is_active: new FormControl(true),
is_superuser: new FormControl(null), is_superuser: new FormControl(false),
groups: new FormControl(null), groups: new FormControl(null),
user_permissions: new FormControl(null), user_permissions: new FormControl(null),
}) })
@ -56,4 +56,11 @@ export class UserEditDialogComponent extends EditDialogComponent<PaperlessUser>
this.objectForm.get('user_permissions').enable() this.objectForm.get('user_permissions').enable()
} }
} }
get inheritedPermissions(): string[] {
const groupsVal = this.objectForm.get('groups').value
return groupsVal !== null
? this.groups.find((g) => g.id == groupsVal)?.permissions
: []
}
} }

View File

@ -12,13 +12,13 @@
<li class="list-group-item d-flex" *ngFor="let type of PermissionType | keyvalue" [formGroupName]="type.key"> <li class="list-group-item d-flex" *ngFor="let type of PermissionType | keyvalue" [formGroupName]="type.key">
<div class="col-3">{{type.key}}:</div> <div class="col-3">{{type.key}}:</div>
<div class="col form-check form-check-inline form-switch"> <div class="col form-check form-check-inline form-switch" [ngbPopover]="inheritedWarning" [disablePopover]="!isInherited(type.key)" placement="left" triggers="mouseenter:mouseleave">
<input type="checkbox" class="form-check-input" id="{{type.key}}_all" (change)="toggleAll($event, type.key)" [checked]="typesWithAllActions.has(type.key)" [attr.disabled]="disabled ? true : null"> <input type="checkbox" class="form-check-input" id="{{type.key}}_all" (change)="toggleAll($event, type.key)" [checked]="typesWithAllActions.has(type.key)" [attr.disabled]="disabled || isInherited(type.key) ? true : null">
<label class="form-check-label visually-hidden" for="{{type.key}}_all" i18n>All</label> <label class="form-check-label visually-hidden" for="{{type.key}}_all" i18n>All</label>
</div> </div>
<div *ngFor="let action of PermissionAction | keyvalue" class="col form-check form-check-inline"> <div *ngFor="let action of PermissionAction | keyvalue" class="col form-check form-check-inline" [ngbPopover]="inheritedWarning" [disablePopover]="!isInherited(type.key, action.key)" placement="left" triggers="mouseenter:mouseleave">
<input type="checkbox" class="form-check-input" id="{{type.key}}_{{action.key}}" formControlName="{{action.key}}" [attr.disabled]="typesWithAllActions.has(type.key) || disabled ? true : null"> <input type="checkbox" class="form-check-input" id="{{type.key}}_{{action.key}}" formControlName="{{action.key}}" [attr.disabled]="isDisabled(type.key, action.key)">
<label class="form-check-label visually-hidden" for="{{type.key}}_{{action.key}}" i18n>{{action.key}}</label> <label class="form-check-label visually-hidden" for="{{type.key}}_{{action.key}}" i18n>{{action.key}}</label>
</div> </div>
</li> </li>

View File

@ -41,6 +41,18 @@ export class PermissionsSelectComponent
typesWithAllActions: Set<string> = new Set() typesWithAllActions: Set<string> = new Set()
_inheritedPermissions: string[] = []
@Input()
set inheritedPermissions(inherited: string[]) {
// remove <app_label>. from permission strings
this._inheritedPermissions = inherited?.length
? inherited.map((p) => p.replace(/.+\./, ''))
: []
}
inheritedWarning: string = $localize`Inerhited from group`
constructor(private readonly permissionsService: PermissionsService) { constructor(private readonly permissionsService: PermissionsService) {
for (const type in PermissionType) { for (const type in PermissionType) {
const control = new FormGroup({}) const control = new FormGroup({})
@ -53,18 +65,24 @@ export class PermissionsSelectComponent
writeValue(permissions: string[]): void { writeValue(permissions: string[]): void {
this.permissions = permissions this.permissions = permissions
this.permissions?.forEach((permissionStr) => { const allPerms = this._inheritedPermissions.concat(permissions)
allPerms.forEach((permissionStr) => {
const { actionKey, typeKey } = const { actionKey, typeKey } =
this.permissionsService.getPermissionKeys(permissionStr) this.permissionsService.getPermissionKeys(permissionStr)
if (actionKey && typeKey) { if (actionKey && typeKey) {
if (this.form.get(typeKey)?.get(actionKey)) { if (this.form.get(typeKey)?.get(actionKey)) {
this.form.get(typeKey).get(actionKey).setValue(true) this.form
.get(typeKey)
.get(actionKey)
.patchValue(true, { emitEvent: false })
} }
} }
}) })
Object.keys(PermissionType).forEach((type) => { Object.keys(PermissionType).forEach((type) => {
if (Object.values(this.form.get(type).value).every((val) => val)) { if (
Object.values(this.form.get(type).value).every((val) => val == true)
) {
this.typesWithAllActions.add(type) this.typesWithAllActions.add(type)
} else { } else {
this.typesWithAllActions.delete(type) this.typesWithAllActions.delete(type)
@ -96,7 +114,7 @@ export class PermissionsSelectComponent
Object.entries(newValue).forEach(([typeKey, typeValue]) => { Object.entries(newValue).forEach(([typeKey, typeValue]) => {
// e.g. [Document, { Add: true, View: true ... }] // e.g. [Document, { Add: true, View: true ... }]
const selectedActions = Object.entries(typeValue).filter( const selectedActions = Object.entries(typeValue).filter(
([actionKey, actionValue]) => actionValue ([actionKey, actionValue]) => actionValue == true
) )
selectedActions.forEach(([actionKey, actionValue]) => { selectedActions.forEach(([actionKey, actionValue]) => {
@ -129,4 +147,34 @@ export class PermissionsSelectComponent
this.typesWithAllActions.delete(type) this.typesWithAllActions.delete(type)
} }
} }
isInherited(typeKey: string, actionKey: string = null) {
if (this._inheritedPermissions.length == 0) return false
else if (actionKey) {
return this._inheritedPermissions.includes(
this.permissionsService.getPermissionCode({
action: PermissionAction[actionKey],
type: PermissionType[typeKey],
})
)
} else {
return Object.values(PermissionAction).every((action) => {
return this._inheritedPermissions.includes(
this.permissionsService.getPermissionCode({
action: action as PermissionAction,
type: PermissionType[typeKey],
})
)
})
}
}
// if checkbox is disabled either because "All", inhereted or entire component disabled
isDisabled(typeKey: string, actionKey: string) {
return this.typesWithAllActions.has(typeKey) ||
this.isInherited(typeKey, actionKey) ||
this.disabled
? true
: null
}
} }

View File

@ -42,7 +42,7 @@ export class PermissionsService {
return this.permissions.includes(this.getPermissionCode(permission)) return this.permissions.includes(this.getPermissionCode(permission))
} }
private getPermissionCode(permission: PaperlessPermission): string { public getPermissionCode(permission: PaperlessPermission): string {
return permission.type.replace('%s', permission.action) return permission.type.replace('%s', permission.action)
} }

View File

@ -6,11 +6,6 @@ from rest_framework import serializers
class UserSerializer(serializers.ModelSerializer): class UserSerializer(serializers.ModelSerializer):
groups = serializers.SlugRelatedField(
many=True,
queryset=Group.objects.all(),
slug_field="name",
)
user_permissions = serializers.SlugRelatedField( user_permissions = serializers.SlugRelatedField(
many=True, many=True,
queryset=Permission.objects.all(), queryset=Permission.objects.all(),