Refactor permissions API endpoints, UI group permissions

This commit is contained in:
Michael Shamoon
2022-12-07 21:11:47 -08:00
parent 4016649a18
commit 692f43f43e
29 changed files with 353 additions and 139 deletions

View File

@@ -11,11 +11,19 @@
<app-input-text *ngIf="patternRequired" i18n-title title="Matching pattern" formControlName="match" [error]="error?.match"></app-input-text>
<app-input-check *ngIf="patternRequired" i18n-title title="Case insensitive" formControlName="is_insensitive" novalidate></app-input-check>
<div *ifOwner="object?.owner">
<div *ifOwner="object">
<h5 i18n>Permissions</h5>
<div formGroupName="set_permissions">
<app-share-user type="view" formControlName="view"></app-share-user>
<app-share-user type="change" formControlName="change"></app-share-user>
<h6 i18n>View</h6>
<div formGroupName="view">
<app-permissions-user type="view" formControlName="users"></app-permissions-user>
<app-permissions-group type="view" formControlName="groups"></app-permissions-group>
</div>
<h6 i18n>Edit</h6>
<div formGroupName="change">
<app-permissions-user type="change" formControlName="users"></app-permissions-user>
<app-permissions-group type="change" formControlName="groups"></app-permissions-group>
</div>
</div>
</div>

View File

@@ -31,8 +31,14 @@ export class CorrespondentEditDialogComponent extends EditDialogComponent<Paperl
match: new FormControl(''),
is_insensitive: new FormControl(true),
set_permissions: new FormGroup({
view: new FormControl(null),
change: new FormControl(null),
view: new FormGroup({
users: new FormControl(null),
groups: new FormControl(null),
}),
change: new FormGroup({
users: new FormControl(null),
groups: new FormControl(null),
}),
}),
})
}

View File

@@ -11,11 +11,19 @@
<app-input-text *ngIf="patternRequired" i18n-title title="Matching pattern" formControlName="match" [error]="error?.match"></app-input-text>
<app-input-check *ngIf="patternRequired" i18n-title title="Case insensitive" formControlName="is_insensitive"></app-input-check>
<div *ifOwner="object?.owner">
<div *ifOwner="object">
<h5 i18n>Permissions</h5>
<div formGroupName="set_permissions">
<app-share-user type="view" formControlName="view"></app-share-user>
<app-share-user type="change" formControlName="change"></app-share-user>
<h6 i18n>View</h6>
<div formGroupName="view">
<app-permissions-user type="view" formControlName="users"></app-permissions-user>
<app-permissions-group type="view" formControlName="groups"></app-permissions-group>
</div>
<h6 i18n>Edit</h6>
<div formGroupName="change">
<app-permissions-user type="change" formControlName="users"></app-permissions-user>
<app-permissions-group type="change" formControlName="groups"></app-permissions-group>
</div>
</div>
</div>

View File

@@ -31,8 +31,14 @@ export class DocumentTypeEditDialogComponent extends EditDialogComponent<Paperle
match: new FormControl(''),
is_insensitive: new FormControl(true),
set_permissions: new FormGroup({
view: new FormControl(null),
change: new FormControl(null),
view: new FormGroup({
users: new FormControl(null),
groups: new FormControl(null),
}),
change: new FormGroup({
users: new FormControl(null),
groups: new FormControl(null),
}),
}),
})
}

View File

@@ -39,14 +39,7 @@ export abstract class EditDialogComponent<
ngOnInit(): void {
if (this.object != null) {
if (this.object['permissions']) {
this.object['set_permissions'] = {
view: (this.object as ObjectWithPermissions).permissions
.filter((p) => (p[1] as string).includes('view'))
.map((p) => p[0]),
change: (this.object as ObjectWithPermissions).permissions
.filter((p) => (p[1] as string).includes('change'))
.map((p) => p[0]),
}
this.object['set_permissions'] = this.object['permissions']
}
this.objectForm.patchValue(this.object)
}

View File

@@ -16,11 +16,19 @@
<app-input-text *ngIf="patternRequired" i18n-title title="Matching pattern" formControlName="match" [error]="error?.match"></app-input-text>
<app-input-check *ngIf="patternRequired" i18n-title title="Case insensitive" formControlName="is_insensitive"></app-input-check>
<div *ifOwner="object?.owner">
<div *ifOwner="object">
<h5 i18n>Permissions</h5>
<div formGroupName="set_permissions">
<app-share-user type="view" formControlName="view"></app-share-user>
<app-share-user type="change" formControlName="change"></app-share-user>
<h6 i18n>View</h6>
<div formGroupName="view">
<app-permissions-user type="view" formControlName="users"></app-permissions-user>
<app-permissions-group type="view" formControlName="groups"></app-permissions-group>
</div>
<h6 i18n>Edit</h6>
<div formGroupName="change">
<app-permissions-user type="change" formControlName="users"></app-permissions-user>
<app-permissions-group type="change" formControlName="groups"></app-permissions-group>
</div>
</div>
</div>

View File

@@ -42,8 +42,14 @@ export class StoragePathEditDialogComponent extends EditDialogComponent<Paperles
match: new FormControl(''),
is_insensitive: new FormControl(true),
set_permissions: new FormGroup({
view: new FormControl(null),
change: new FormControl(null),
view: new FormGroup({
users: new FormControl(null),
groups: new FormControl(null),
}),
change: new FormGroup({
users: new FormControl(null),
groups: new FormControl(null),
}),
}),
})
}

View File

@@ -14,11 +14,19 @@
<app-input-text *ngIf="patternRequired" i18n-title title="Matching pattern" formControlName="match" [error]="error?.match"></app-input-text>
<app-input-check *ngIf="patternRequired" i18n-title title="Case insensitive" formControlName="is_insensitive"></app-input-check>
<div *ifOwner="object?.owner">
<div *ifOwner="object">
<h5 i18n>Permissions</h5>
<div formGroupName="set_permissions">
<app-share-user type="view" formControlName="view"></app-share-user>
<app-share-user type="change" formControlName="change"></app-share-user>
<h6 i18n>View</h6>
<div formGroupName="view">
<app-permissions-user type="view" formControlName="users"></app-permissions-user>
<app-permissions-group type="view" formControlName="groups"></app-permissions-group>
</div>
<h6 i18n>Edit</h6>
<div formGroupName="change">
<app-permissions-user type="change" formControlName="users"></app-permissions-user>
<app-permissions-group type="change" formControlName="groups"></app-permissions-group>
</div>
</div>
</div>

View File

@@ -34,8 +34,14 @@ export class TagEditDialogComponent extends EditDialogComponent<PaperlessTag> {
match: new FormControl(''),
is_insensitive: new FormControl(true),
set_permissions: new FormGroup({
view: new FormControl(null),
change: new FormControl(null),
view: new FormGroup({
users: new FormControl(null),
groups: new FormControl(null),
}),
change: new FormGroup({
users: new FormControl(null),
groups: new FormControl(null),
}),
}),
})
}

View File

@@ -0,0 +1,15 @@
<div class="mb-3 paperless-input-select">
<label class="form-label" [for]="inputId">{{title}}</label>
<div>
<ng-select name="inputId" [(ngModel)]="value"
[disabled]="disabled"
clearable="true"
[items]="groups"
multiple="true"
bindLabel="name"
bindValue="id"
(change)="onChange(value)">
</ng-select>
</div>
<small *ngIf="hint" class="form-text text-muted">{{hint}}</small>
</div>

View File

@@ -0,0 +1,48 @@
import { Component, forwardRef, Input, OnInit } from '@angular/core'
import { NG_VALUE_ACCESSOR } from '@angular/forms'
import { first } from 'rxjs/operators'
import { PaperlessGroup } from 'src/app/data/paperless-group'
import { GroupService } from 'src/app/services/rest/group.service'
import { SettingsService } from 'src/app/services/settings.service'
import { AbstractInputComponent } from '../abstract-input'
@Component({
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => PermissionsGroupComponent),
multi: true,
},
],
selector: 'app-permissions-group',
templateUrl: './permissions-group.component.html',
styleUrls: ['./permissions-group.component.scss'],
})
export class PermissionsGroupComponent
extends AbstractInputComponent<PaperlessGroup>
implements OnInit
{
groups: PaperlessGroup[]
@Input()
type: string
constructor(groupService: GroupService, settings: SettingsService) {
super()
groupService
.listAll()
.pipe(first())
.subscribe((result) => (this.groups = result.results))
}
ngOnInit(): void {
if (this.type == 'view') {
this.title = $localize`Groups can view`
} else if (this.type == 'change') {
this.title = $localize`Groups can edit`
this.hint = $localize`Edit permissions also grant viewing permissions`
}
super.ngOnInit()
}
}

View File

@@ -3,21 +3,22 @@ import { NG_VALUE_ACCESSOR } from '@angular/forms'
import { first } from 'rxjs/operators'
import { PaperlessUser } from 'src/app/data/paperless-user'
import { UserService } from 'src/app/services/rest/user.service'
import { SettingsService } from 'src/app/services/settings.service'
import { AbstractInputComponent } from '../abstract-input'
@Component({
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => ShareUserComponent),
useExisting: forwardRef(() => PermissionsUserComponent),
multi: true,
},
],
selector: 'app-share-user',
templateUrl: './share-user.component.html',
styleUrls: ['./share-user.component.scss'],
selector: 'app-permissions-user',
templateUrl: './permissions-user.component.html',
styleUrls: ['./permissions-user.component.scss'],
})
export class ShareUserComponent
export class PermissionsUserComponent
extends AbstractInputComponent<PaperlessUser>
implements OnInit
{
@@ -26,12 +27,17 @@ export class ShareUserComponent
@Input()
type: string
constructor(userService: UserService) {
constructor(userService: UserService, settings: SettingsService) {
super()
userService
.listAll()
.pipe(first())
.subscribe((result) => (this.users = result.results))
.subscribe(
(result) =>
(this.users = result.results.filter(
(u) => u.id !== settings.currentUser.id
))
)
}
ngOnInit(): void {

View File

@@ -156,18 +156,18 @@ export class PermissionsSelectComponent
if (this._inheritedPermissions.length == 0) return false
else if (actionKey) {
return this._inheritedPermissions.includes(
this.permissionsService.getPermissionCode({
action: PermissionAction[actionKey],
type: PermissionType[typeKey],
})
this.permissionsService.getPermissionCode(
PermissionAction[actionKey],
PermissionType[typeKey]
)
)
} else {
return Object.values(PermissionAction).every((action) => {
return this._inheritedPermissions.includes(
this.permissionsService.getPermissionCode({
action: action as PermissionAction,
type: PermissionType[typeKey],
})
this.permissionsService.getPermissionCode(
action as PermissionAction,
PermissionType[typeKey]
)
)
})
}

View File

@@ -5,7 +5,7 @@
<div class="input-group-text" i18n>of {{previewNumPages}}</div>
</div>
<button *ifOwner="document?.owner" type="button" class="btn btn-sm btn-outline-danger me-2 ms-auto" (click)="delete()">
<button *ifOwner="document" type="button" class="btn btn-sm btn-outline-danger me-2 ms-auto" (click)="delete()">
<svg class="buttonicon" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#trash" />
</svg><span class="d-none d-lg-inline ps-1" i18n>Delete</span>
@@ -28,7 +28,7 @@
</div>
<button *ifOwner="document?.owner" type="button" class="btn btn-sm btn-outline-primary me-2" (click)="redoOcr()">
<button *ifOwner="document" type="button" class="btn btn-sm btn-outline-primary me-2" (click)="redoOcr()">
<svg class="buttonicon" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#arrow-counterclockwise" />
</svg><span class="d-none d-lg-inline ps-1" i18n>Redo OCR</span>
@@ -178,12 +178,20 @@
</ng-template>
</li>
<li [ngbNavItem]="6" *ifOwner="document?.owner">
<li [ngbNavItem]="6" *ifOwner="document">
<a ngbNavLink i18n>Permissions</a>
<ng-template ngbNavContent>
<div formGroupName="set_permissions">
<app-share-user type="view" formControlName="view"></app-share-user>
<app-share-user type="change" formControlName="change"></app-share-user>
<h6 i18n>View</h6>
<div formGroupName="view">
<app-permissions-user type="view" formControlName="users"></app-permissions-user>
<app-permissions-group type="view" formControlName="groups"></app-permissions-group>
</div>
<h6 i18n>Edit</h6>
<div formGroupName="change">
<app-permissions-user type="change" formControlName="users"></app-permissions-user>
<app-permissions-group type="change" formControlName="groups"></app-permissions-group>
</div>
</div>
</ng-template>
</li>
@@ -191,7 +199,7 @@
<div [ngbNavOutlet]="nav" class="mt-2"></div>
<ng-container action="PermissionAction.Change" *ifObjectPermissions="document">
<ng-container *ifObjectPermissions="{ object: document, action: PermissionAction.Change }">
<button type="button" class="btn btn-outline-secondary" (click)="discard()" i18n [disabled]="networkActive || !(isDirty$ | async)">Discard</button>&nbsp;
<button type="button" class="btn btn-outline-primary" (click)="saveEditNext()" *ngIf="hasNext()" i18n [disabled]="networkActive || !(isDirty$ | async) || error">Save & next</button>&nbsp;
<button type="submit" class="btn btn-primary" *ifPermissions="{ action: PermissionAction.Change, type: PermissionType.Document }" i18n [disabled]="networkActive || !(isDirty$ | async) || error">Save</button>&nbsp;

View File

@@ -85,8 +85,14 @@ export class DocumentDetailComponent
archive_serial_number: new FormControl(),
tags: new FormControl([]),
set_permissions: new FormGroup({
view: new FormControl(null),
change: new FormControl(null),
view: new FormGroup({
users: new FormControl(null),
groups: new FormControl(null),
}),
change: new FormGroup({
users: new FormControl(null),
groups: new FormControl(null),
}),
}),
})
@@ -235,14 +241,7 @@ export class DocumentDetailComponent
storage_path: doc.storage_path,
archive_serial_number: doc.archive_serial_number,
tags: [...doc.tags],
set_permissions: {
view: doc.permissions
.filter((p) => (p[1] as string).includes('view'))
.map((p) => p[0]),
change: doc.permissions
.filter((p) => (p[1] as string).includes('change'))
.map((p) => p[0]),
},
set_permissions: doc.permissions,
})
this.isDirty$ = dirtyCheck(
@@ -297,14 +296,7 @@ export class DocumentDetailComponent
},
})
this.title = this.documentTitlePipe.transform(doc.title)
doc['set_permissions'] = {
view: doc.permissions
.filter((p) => (p[1] as string).includes('view'))
.map((p) => p[0]),
change: doc.permissions
.filter((p) => (p[1] as string).includes('change'))
.map((p) => p[0]),
}
doc['set_permissions'] = doc.permissions
this.documentForm.patchValue(doc)
if (!this.userCanEdit) this.documentForm.disable()
}
@@ -586,10 +578,10 @@ export class DocumentDetailComponent
get commentsEnabled(): boolean {
return (
this.settings.get(SETTINGS_KEYS.COMMENTS_ENABLED) &&
this.permissionsService.currentUserCan({
action: PermissionAction.View,
type: PermissionType.Document,
})
this.permissionsService.currentUserCan(
PermissionAction.View,
PermissionType.Document
)
)
}

View File

@@ -222,9 +222,7 @@ export abstract class ManagementListComponent<T extends ObjectWithId>
}
userCanDelete(object: ObjectWithPermissions): boolean {
return (
!object.owner || this.permissionsService.currentUserIsOwner(object.owner)
)
return this.permissionsService.currentUserOwnsObject(object)
}
userCanEdit(object: ObjectWithPermissions): boolean {