Merge pull request #2147 from paperless-ngx/feature-permissions

Feature: multi-user permissions
This commit is contained in:
shamoon
2023-02-17 07:21:18 -08:00
committed by GitHub
139 changed files with 5081 additions and 779 deletions

View File

@@ -5,7 +5,7 @@
<div class="input-group-text" i18n>of {{previewNumPages}}</div>
</div>
<button type="button" class="btn btn-sm btn-outline-danger me-2 ms-auto" (click)="delete()">
<button type="button" class="btn btn-sm btn-outline-danger me-2 ms-auto" (click)="delete()" [disabled]="!userIsOwner">
<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>
@@ -20,7 +20,7 @@
</a>
<div class="btn-group" ngbDropdown role="group" *ngIf="metadata?.has_archive_version">
<button class="btn btn-sm btn-outline-primary dropdown-toggle-split" ngbDropdownToggle></button>
<button class="btn btn-sm btn-outline-primary dropdown-toggle" ngbDropdownToggle></button>
<div class="dropdown-menu shadow" ngbDropdownMenu>
<a ngbDropdownItem [href]="downloadOriginalUrl" i18n>Download original</a>
</div>
@@ -28,7 +28,7 @@
</div>
<button type="button" class="btn btn-sm btn-outline-primary me-2" (click)="redoOcr()">
<button type="button" class="btn btn-sm btn-outline-primary me-2" (click)="redoOcr()" [disabled]="!userCanEdit">
<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>
@@ -170,19 +170,31 @@
</div>
</ng-template>
</li>
<li [ngbNavItem]="5" *ngIf="commentsEnabled">
<a ngbNavLink i18n>Comments</a>
<ng-template ngbNavContent>
<app-document-comments [documentId]="documentId"></app-document-comments>
</ng-template>
</li>
<li [ngbNavItem]="6" *appIfOwner="document">
<a ngbNavLink i18n>Permissions</a>
<ng-template ngbNavContent>
<div class="mb-3">
<app-permissions-form [users]="users" formControlName="permissions_form"></app-permissions-form>
</div>
</ng-template>
</li>
</ul>
<div [ngbNavOutlet]="nav" class="mt-2"></div>
<button type="button" class="btn btn-outline-secondary" (click)="discard()" i18n [disabled]="networkActive || (isDirty$ | async) !== true">Discard</button>&nbsp;
<button type="button" class="btn btn-outline-primary" (click)="saveEditNext()" *ngIf="hasNext()" i18n [disabled]="networkActive || (isDirty$ | async) !== true || error">Save & next</button>&nbsp;
<button type="submit" class="btn btn-primary" i18n [disabled]="networkActive || (isDirty$ | async) !== true || error">Save</button>&nbsp;
<ng-container>
<button type="button" class="btn btn-outline-secondary" (click)="discard()" i18n [disabled]="!userCanEdit || networkActive || (isDirty$ | async) !== true">Discard</button>&nbsp;
<button type="button" class="btn btn-outline-primary" (click)="saveEditNext()" *ngIf="hasNext()" i18n [disabled]="!userCanEdit || networkActive || (isDirty$ | async) !== true || error">Save & next</button>&nbsp;
<button type="submit" class="btn btn-primary" *appIfPermissions="{ action: PermissionAction.Change, type: PermissionType.Document }" i18n [disabled]="!userCanEdit || networkActive || (isDirty$ | async) !== true || error">Save</button>&nbsp;
</ng-container>
</form>
</div>

View File

@@ -35,6 +35,13 @@ import { StoragePathService } from 'src/app/services/rest/storage-path.service'
import { PaperlessStoragePath } from 'src/app/data/paperless-storage-path'
import { StoragePathEditDialogComponent } from '../common/edit-dialog/storage-path-edit-dialog/storage-path-edit-dialog.component'
import { SETTINGS_KEYS } from 'src/app/data/paperless-uisettings'
import {
PermissionAction,
PermissionsService,
PermissionType,
} from 'src/app/services/permissions.service'
import { PaperlessUser } from 'src/app/data/paperless-user'
import { UserService } from 'src/app/services/rest/user.service'
@Component({
selector: 'app-document-detail',
@@ -58,6 +65,7 @@ export class DocumentDetailComponent
document: PaperlessDocument
metadata: PaperlessDocumentMetadata
suggestions: PaperlessDocumentSuggestions
users: PaperlessUser[]
title: string
titleSubject: Subject<string> = new Subject()
@@ -78,6 +86,7 @@ export class DocumentDetailComponent
storage_path: new FormControl(),
archive_serial_number: new FormControl(),
tags: new FormControl([]),
permissions_form: new FormControl(null),
})
previewCurrentPage: number = 1
@@ -106,6 +115,9 @@ export class DocumentDetailComponent
}
}
PermissionAction = PermissionAction
PermissionType = PermissionType
constructor(
private documentsService: DocumentService,
private route: ActivatedRoute,
@@ -118,7 +130,9 @@ export class DocumentDetailComponent
private documentTitlePipe: DocumentTitlePipe,
private toastService: ToastService,
private settings: SettingsService,
private storagePathService: StoragePathService
private storagePathService: StoragePathService,
private permissionsService: PermissionsService,
private userService: UserService
) {}
titleKeyUp(event) {
@@ -147,7 +161,13 @@ export class DocumentDetailComponent
.pipe(takeUntil(this.unsubscribeNotifier))
.subscribe(() => {
this.error = null
Object.assign(this.document, this.documentForm.value)
const docValues = Object.assign({}, this.documentForm.value)
docValues['owner'] =
this.documentForm.get('permissions_form').value['owner']
docValues['set_permissions'] =
this.documentForm.get('permissions_form').value['set_permissions']
delete docValues['permissions_form']
Object.assign(this.document, docValues)
})
this.correspondentService
@@ -165,6 +185,11 @@ export class DocumentDetailComponent
.pipe(first())
.subscribe((result) => (this.storagePaths = result.results))
this.userService
.listAll()
.pipe(first())
.subscribe((result) => (this.users = result.results))
this.route.paramMap
.pipe(
takeUntil(this.unsubscribeNotifier),
@@ -232,6 +257,10 @@ export class DocumentDetailComponent
storage_path: doc.storage_path,
archive_serial_number: doc.archive_serial_number,
tags: [...doc.tags],
permissions_form: {
owner: doc.owner,
set_permissions: doc.permissions,
},
})
this.isDirty$ = dirtyCheck(
@@ -286,7 +315,14 @@ export class DocumentDetailComponent
},
})
this.title = this.documentTitlePipe.transform(doc.title)
this.documentForm.patchValue(doc)
const docFormValues = Object.assign({}, doc)
docFormValues['permissions_form'] = {
owner: doc.owner,
set_permissions: doc.permissions,
}
this.documentForm.patchValue(docFormValues, { emitEvent: false })
if (!this.userCanEdit) this.documentForm.disable()
}
createDocumentType(newName: string) {
@@ -378,7 +414,7 @@ export class DocumentDetailComponent
.update(this.document)
.pipe(first())
.subscribe({
next: (result) => {
next: () => {
this.close()
this.networkActive = false
this.error = null
@@ -564,6 +600,36 @@ export class DocumentDetailComponent
}
get commentsEnabled(): boolean {
return this.settings.get(SETTINGS_KEYS.COMMENTS_ENABLED)
return (
this.settings.get(SETTINGS_KEYS.COMMENTS_ENABLED) &&
this.permissionsService.currentUserCan(
PermissionAction.View,
PermissionType.Document
)
)
}
get userIsOwner(): boolean {
let doc: PaperlessDocument = Object.assign({}, this.document)
// dont disable while editing
if (this.document && this.store?.value.owner) {
doc.owner = this.store?.value.owner
}
return !this.document || this.permissionsService.currentUserOwnsObject(doc)
}
get userCanEdit(): boolean {
let doc: PaperlessDocument = Object.assign({}, this.document)
// dont disable while editing
if (this.document && this.store?.value.owner) {
doc.owner = this.store?.value.owner
}
return (
!this.document ||
this.permissionsService.currentUserHasObjectPermissions(
PermissionAction.Change,
doc
)
)
}
}