]> git.ipfire.org Git - thirdparty/paperless-ngx.git/commitdiff
disable document form components when no object permissions
authorMichael Shamoon <4887959+shamoon@users.noreply.github.com>
Wed, 7 Dec 2022 23:46:52 +0000 (15:46 -0800)
committerMichael Shamoon <4887959+shamoon@users.noreply.github.com>
Wed, 7 Dec 2022 23:46:52 +0000 (15:46 -0800)
18 files changed:
src-ui/src/app/app.module.ts
src-ui/src/app/components/common/edit-dialog/correspondent-edit-dialog/correspondent-edit-dialog.component.html
src-ui/src/app/components/common/edit-dialog/document-type-edit-dialog/document-type-edit-dialog.component.html
src-ui/src/app/components/common/edit-dialog/storage-path-edit-dialog/storage-path-edit-dialog.component.html
src-ui/src/app/components/common/edit-dialog/tag-edit-dialog/tag-edit-dialog.component.html
src-ui/src/app/components/common/input/date/date.component.html
src-ui/src/app/components/common/input/number/number.component.html
src-ui/src/app/components/common/input/select/select.component.html
src-ui/src/app/components/common/input/tags/tags.component.html
src-ui/src/app/components/common/input/tags/tags.component.ts
src-ui/src/app/components/common/input/text/text.component.html
src-ui/src/app/components/document-detail/document-detail.component.html
src-ui/src/app/components/document-detail/document-detail.component.ts
src-ui/src/app/directives/if-object-permissions.directive.ts [new file with mode: 0644]
src-ui/src/app/directives/if-owner.directive.ts
src-ui/src/app/services/permissions.service.ts
src-ui/src/app/services/settings.service.ts
src-ui/src/styles.scss

index 183a6ad255527f240d7477872610300d2e716a5f..60a6f4844ee22d448f41777575df9693637c91b5 100644 (file)
@@ -106,6 +106,7 @@ import localeTr from '@angular/common/locales/tr'
 import localeZh from '@angular/common/locales/zh'
 import { ShareUserComponent } from './components/common/input/share-user/share-user.component'
 import { IfOwnerDirective } from './directives/if-owner.directive'
+import { IfObjectPermissionsDirective } from './directives/if-object-permissions.directive'
 
 registerLocaleData(localeBe)
 registerLocaleData(localeCs)
@@ -199,6 +200,7 @@ function initializeApp(settings: SettingsService) {
     MailRuleEditDialogComponent,
     ShareUserComponent,
     IfOwnerDirective,
+    IfObjectPermissionsDirective,
   ],
   imports: [
     BrowserModule,
index 857bd2f1c5a39246f090781826222d9154477e54..83f24812f5ff858b59901f1245ead71ea94a590b 100644 (file)
@@ -11,7 +11,7 @@
     <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?.owner">
       <h5 i18n>Permissions</h5>
       <div formGroupName="set_permissions">
         <app-share-user type="view" formControlName="view"></app-share-user>
index b7d7f1335ee3ef81bbabb0430e002485096643ff..3aa196f31e85407be5daa64044c15ad1e196cb9e 100644 (file)
@@ -11,7 +11,7 @@
       <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?.owner">
         <h5 i18n>Permissions</h5>
         <div formGroupName="set_permissions">
           <app-share-user type="view" formControlName="view"></app-share-user>
index e122b8c00568de6e3b4bb8e808a2b3f2d6380be4..a0e141907bba92e3010880c9a9eb2afa345e0238 100644 (file)
@@ -16,7 +16,7 @@
     <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?.owner">
       <h5 i18n>Permissions</h5>
       <div formGroupName="set_permissions">
         <app-share-user type="view" formControlName="view"></app-share-user>
index a4a7fbbf546221b2ad35e7a52a10735c4e3d7308..4e6d0e22ffcef450e9551493e686b5c43eaf2ba1 100644 (file)
@@ -14,7 +14,7 @@
       <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?.owner">
         <h5 i18n>Permissions</h5>
         <div formGroupName="set_permissions">
           <app-share-user type="view" formControlName="view"></app-share-user>
index 926429a8d13044431d381470735fe40f9bff43b5..dfc3950aad99b8cca0eb933af47fbe3c2b36c2ed 100644 (file)
@@ -3,8 +3,8 @@
   <div class="input-group" [class.is-invalid]="error">
     <input class="form-control" [class.is-invalid]="error" [placeholder]="placeholder" [id]="inputId" maxlength="10"
           (dateSelect)="onChange(value)" (change)="onChange(value)" (keypress)="onKeyPress($event)" (paste)="onPaste($event)"
-          name="dp" [(ngModel)]="value" ngbDatepicker #datePicker="ngbDatepicker" #datePickerContent="ngModel">
-    <button class="btn btn-outline-secondary calendar" (click)="datePicker.toggle()" type="button">
+          name="dp" [(ngModel)]="value" ngbDatepicker #datePicker="ngbDatepicker" #datePickerContent="ngModel" [disabled]="disabled">
+    <button class="btn btn-outline-secondary calendar" (click)="datePicker.toggle()" type="button" [disabled]="disabled">
       <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-calendar" viewBox="0 0 16 16">
         <path d="M3.5 0a.5.5 0 0 1 .5.5V1h8V.5a.5.5 0 0 1 1 0V1h1a2 2 0 0 1 2 2v11a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V3a2 2 0 0 1 2-2h1V.5a.5.5 0 0 1 .5-.5zM1 4v10a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V4H1z"/>
       </svg>
index ceff40250cf2ca931c1f42a6c167619c196f9bee..85b1012aaae46993f05b024ce8e872d9b47def62 100644 (file)
@@ -1,8 +1,8 @@
 <div class="mb-3">
   <label class="form-label" [for]="inputId">{{title}}</label>
   <div class="input-group" [class.is-invalid]="error">
-    <input type="number" class="form-control" [id]="inputId" [(ngModel)]="value" (change)="onChange(value)" [class.is-invalid]="error">
-    <button *ngIf="showAdd" class="btn btn-outline-secondary" type="button" id="button-addon1" (click)="nextAsn()" [disabled]="value">+1</button>
+    <input type="number" class="form-control" [id]="inputId" [(ngModel)]="value" (change)="onChange(value)" [class.is-invalid]="error" [disabled]="disabled">
+    <button *ngIf="showAdd" class="btn btn-outline-secondary" type="button" id="button-addon1" (click)="nextAsn()" [disabled]="disabled">+1</button>
   </div>
   <div class="invalid-feedback">
     {{error}}
index d775f4ffa45517e990aa4e87e19a08942b65081b..6bb5f503693be099113ba76029c66fa4c97985f5 100644 (file)
@@ -20,7 +20,7 @@
         (clear)="clearLastSearchTerm()"
         (blur)="onBlur()">
       </ng-select>
-      <button *ngIf="allowCreateNew" class="btn btn-outline-secondary" type="button" (click)="addItem()">
+      <button *ngIf="allowCreateNew" class="btn btn-outline-secondary" type="button" (click)="addItem()" [disabled]="disabled">
         <svg class="buttonicon" fill="currentColor">
           <use xlink:href="assets/bootstrap-icons.svg#plus" />
         </svg>
index 14de0f98a23f6f1971882ff6ca7c3748a4baf193..ad0a6868b4bc070846a2a32b9c06e11ee50cc9fa 100644 (file)
@@ -3,6 +3,7 @@
 
   <div class="input-group flex-nowrap">
     <ng-select name="tags" [items]="tags" bindLabel="name" bindValue="id" [(ngModel)]="value"
+      [disabled]="disabled"
       [multiple]="true"
       [closeOnSelect]="false"
       [clearSearchOnAdd]="true"
@@ -31,7 +32,7 @@
       </ng-template>
     </ng-select>
 
-    <button *ngIf="allowCreate" class="btn btn-outline-secondary" type="button" (click)="createTag()">
+    <button *ngIf="allowCreate" class="btn btn-outline-secondary" type="button" (click)="createTag()" [disabled]="disabled">
       <svg class="buttonicon" fill="currentColor">
         <use xlink:href="assets/bootstrap-icons.svg#plus" />
       </svg>
index b6cd1413d0b446a9ee3734bd5acfb6a69eb7f378..3f606e03a19d28008a61d6445d785a5ae2fb013d 100644 (file)
@@ -74,6 +74,7 @@ export class TagsComponent implements OnInit, ControlValueAccessor {
   }
 
   removeTag(id) {
+    if (this.disabled) return
     let index = this.value.indexOf(id)
     if (index > -1) {
       let oldValue = this.value
index 41f02df64e2f74abaa3376cddc5c7623b8246b0c..92925efc342250f87877e226138b71287bd3cd6b 100644 (file)
@@ -1,6 +1,6 @@
 <div class="mb-3">
   <label class="form-label" [for]="inputId">{{title}}</label>
-  <input #inputField type="text" class="form-control" [class.is-invalid]="error" [id]="inputId" [(ngModel)]="value" (change)="onChange(value)">
+  <input #inputField type="text" class="form-control" [class.is-invalid]="error" [id]="inputId" [(ngModel)]="value" (change)="onChange(value)" [disabled]="disabled">
   <small *ngIf="hint" class="form-text text-muted" [innerHTML]="hint | safeHtml"></small>
   <div class="invalid-feedback">
     {{error}}
index 96a4e471f42077e7ffb16579fd986e0cd81ddcf1..6b729ee4c944e0afda05b8c5b7b5fd5de4b9ed02 100644 (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()" *ifPermissions="{ action: PermissionAction.Delete, type: PermissionType.Document }">
+    <button *ifOwner="document?.owner" 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 type="button" class="btn btn-sm btn-outline-primary me-2" (click)="redoOcr()">
+    <button *ifOwner="document?.owner" 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>
 
             <div [ngbNavOutlet]="nav" class="mt-2"></div>
 
-            <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;
+            <ng-container action="PermissionAction.Change" *ifObjectPermissions="document">
+                <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;
+            </ng-container>
         </form>
     </div>
 
index 07f03ece6e8127017d49a56ad934b62a849d1e24..8d564f4b537249dcbbea15de536484b49d6e8df4 100644 (file)
@@ -306,6 +306,7 @@ export class DocumentDetailComponent
         .map((p) => p[0]),
     }
     this.documentForm.patchValue(doc)
+    if (!this.userCanEdit) this.documentForm.disable()
   }
 
   createDocumentType(newName: string) {
@@ -591,4 +592,14 @@ export class DocumentDetailComponent
       })
     )
   }
+
+  get userCanEdit(): boolean {
+    return (
+      !this.document ||
+      this.permissionsService.currentUserHasObjectPermissions(
+        PermissionAction.Change,
+        this.document
+      )
+    )
+  }
 }
diff --git a/src-ui/src/app/directives/if-object-permissions.directive.ts b/src-ui/src/app/directives/if-object-permissions.directive.ts
new file mode 100644 (file)
index 0000000..c897b84
--- /dev/null
@@ -0,0 +1,54 @@
+import {
+  Directive,
+  Input,
+  OnChanges,
+  OnInit,
+  TemplateRef,
+  ViewContainerRef,
+} from '@angular/core'
+import { ObjectWithPermissions } from '../data/object-with-permissions'
+import {
+  PermissionAction,
+  PermissionsService,
+} from '../services/permissions.service'
+
+@Directive({
+  selector: '[ifObjectPermissions]',
+})
+export class IfObjectPermissionsDirective implements OnInit, OnChanges {
+  // The role the user must have
+  @Input()
+  ifObjectPermissions: ObjectWithPermissions
+
+  @Input()
+  action: PermissionAction
+
+  /**
+   * @param {ViewContainerRef} viewContainerRef -- The location where we need to render the templateRef
+   * @param {TemplateRef<any>} templateRef -- The templateRef to be potentially rendered
+   * @param {PermissionsService} permissionsService -- Will give us access to the permissions a user has
+   */
+  constructor(
+    private viewContainerRef: ViewContainerRef,
+    private templateRef: TemplateRef<any>,
+    private permissionsService: PermissionsService
+  ) {}
+
+  public ngOnInit(): void {
+    if (
+      !this.ifObjectPermissions ||
+      this.permissionsService.currentUserHasObjectPermissions(
+        this.action,
+        this.ifObjectPermissions
+      )
+    ) {
+      this.viewContainerRef.createEmbeddedView(this.templateRef)
+    } else {
+      this.viewContainerRef.clear()
+    }
+  }
+
+  public ngOnChanges(): void {
+    this.ngOnInit()
+  }
+}
index 51a2c910f3d2719c2cbc90b2d53b22fe17cfbf90..86fcd34579816147dbecb06a5b7bcac13aa96a60 100644 (file)
@@ -7,7 +7,7 @@ import {
   ViewContainerRef,
 } from '@angular/core'
 import { PaperlessUser } from '../data/paperless-user'
-import { SettingsService } from '../services/settings.service'
+import { PermissionsService } from '../services/permissions.service'
 
 @Directive({
   selector: '[ifOwner]',
@@ -25,11 +25,14 @@ export class IfOwnerDirective implements OnInit, OnChanges {
   constructor(
     private viewContainerRef: ViewContainerRef,
     private templateRef: TemplateRef<any>,
-    private settings: SettingsService
+    private permissionsService: PermissionsService
   ) {}
 
   public ngOnInit(): void {
-    if (!this.ifOwner || this.ifOwner?.id === this.settings.currentUser.id) {
+    if (
+      !this.ifOwner ||
+      this.permissionsService.currentUserIsOwner(this.ifOwner)
+    ) {
       this.viewContainerRef.createEmbeddedView(this.templateRef)
     } else {
       this.viewContainerRef.clear()
index 4302f1b27f15a9b7b5ed6515dc6c4966875c2418..0f7edee220c06433e242e74adc10a44ad18a2790 100644 (file)
@@ -1,4 +1,6 @@
 import { Injectable } from '@angular/core'
+import { ObjectWithPermissions } from '../data/object-with-permissions'
+import { PaperlessUser } from '../data/paperless-user'
 
 export enum PermissionAction {
   Add = 'add',
@@ -33,15 +35,30 @@ export interface PaperlessPermission {
 })
 export class PermissionsService {
   private permissions: string[]
+  private currentUser: PaperlessUser
 
-  public initialize(permissions: string[]) {
+  public initialize(permissions: string[], currentUser: PaperlessUser) {
     this.permissions = permissions
+    this.currentUser = currentUser
   }
 
   public currentUserCan(permission: PaperlessPermission): boolean {
     return this.permissions.includes(this.getPermissionCode(permission))
   }
 
+  public currentUserIsOwner(owner: PaperlessUser): boolean {
+    return owner?.id === this.currentUser.id
+  }
+
+  public currentUserHasObjectPermissions(
+    action: string,
+    object: ObjectWithPermissions
+  ): boolean {
+    return (object.permissions[action] as Array<number>)?.includes(
+      this.currentUser.id
+    )
+  }
+
   public getPermissionCode(permission: PaperlessPermission): string {
     return permission.type.replace('%s', permission.action)
   }
index fbf8b9320ad376fab5ea8132d59a9a8da34e0dea..72a34ea15aca5fa6aaf16cfd6ca650fcf1b8fffa 100644 (file)
@@ -79,7 +79,10 @@ export class SettingsService {
           id: uisettings['user_id'],
           username: uisettings['username'],
         }
-        this.permissionsService.initialize(uisettings.permissions)
+        this.permissionsService.initialize(
+          uisettings.permissions,
+          this.currentUser
+        )
       })
     )
   }
index ab62dff251c769731e41bccdd7c3b4d29732d78a..a9169f038e95249909d1769f3428db6f748c1692 100644 (file)
@@ -255,6 +255,7 @@ a, a:hover,
 .paperless-input-tags {
   .ng-select.ng-select-multiple .ng-select-container .ng-value-container .ng-value {
     background-color: transparent;
+    border-color: transparent;
   }
 
   .ng-select.ng-select-multiple .ng-select-container .ng-value-container {