]> git.ipfire.org Git - thirdparty/paperless-ngx.git/commitdiff
Fix: Dont attempt to retrieve objects for which user doesnt have global permissions...
authorshamoon <4887959+shamoon@users.noreply.github.com>
Thu, 1 Feb 2024 09:20:14 +0000 (01:20 -0800)
committerGitHub <noreply@github.com>
Thu, 1 Feb 2024 09:20:14 +0000 (01:20 -0800)
14 files changed:
src-ui/src/app/components/common/permissions-filter-dropdown/permissions-filter-dropdown.component.html
src-ui/src/app/components/common/permissions-filter-dropdown/permissions-filter-dropdown.component.ts
src-ui/src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.html
src-ui/src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.ts
src-ui/src/app/components/document-detail/document-detail.component.spec.ts
src-ui/src/app/components/document-detail/document-detail.component.ts
src-ui/src/app/components/document-list/bulk-editor/bulk-editor.component.html
src-ui/src/app/components/document-list/bulk-editor/bulk-editor.component.spec.ts
src-ui/src/app/components/document-list/bulk-editor/bulk-editor.component.ts
src-ui/src/app/components/document-list/document-card-small/document-card-small.component.ts
src-ui/src/app/components/document-list/filter-editor/filter-editor.component.html
src-ui/src/app/components/document-list/filter-editor/filter-editor.component.spec.ts
src-ui/src/app/components/document-list/filter-editor/filter-editor.component.ts
src-ui/src/app/services/rest/document.service.ts

index f95434c8f2cd16aeb315beee42a0d6edd541fea8..d20986c57c397641a06136bfb4613a817543fa5b 100644 (file)
             <i-bs width="1em" height="1em" name="check"></i-bs>
           }
         </div>
-        <div class="me-1 w-100">
-          <ng-select
-            name="user"
-            class="user-select small"
-            [(ngModel)]="selectionModel.includeUsers"
-            [disabled]="disabled"
-            [clearable]="false"
-            [items]="users"
-            bindLabel="username"
-            multiple="true"
-            bindValue="id"
-            placeholder="Users"
-            i18n-placeholder
-            (change)="onUserSelect()">
-          </ng-select>
-        </div>
+        @if (permissionsService.currentUserCan(PermissionAction.View, PermissionType.User)) {
+          <div class="me-1 w-100">
+            <ng-select
+              name="user"
+              class="user-select small"
+              [(ngModel)]="selectionModel.includeUsers"
+              [disabled]="disabled"
+              [clearable]="false"
+              [items]="users"
+              bindLabel="username"
+              multiple="true"
+              bindValue="id"
+              placeholder="Users"
+              i18n-placeholder
+              (change)="onUserSelect()">
+            </ng-select>
+          </div>
+        }
       </button>
       @if (selectionModel.ownerFilter === OwnerFilterType.NONE || selectionModel.ownerFilter === OwnerFilterType.NOT_SELF) {
         <div class="list-group-item list-group-item-action d-flex align-items-center p-2 ps-3 border-bottom-0 border-start-0 border-end-0">
index 3f5c3e68d4439e54028b7fcf3820727125b0ea32..b0c3e8817cf182612ec684b8428328ef468f772a 100644 (file)
@@ -67,7 +67,7 @@ export class PermissionsFilterDropdownComponent extends ComponentWithPermissions
   }
 
   constructor(
-    permissionsService: PermissionsService,
+    public permissionsService: PermissionsService,
     userService: UserService,
     private settingsService: SettingsService
   ) {
index 0a7a852ed698e4c597fb3128137bda9ed42a8b97..de46991d286d3bc6be9259f2da393e0bf20f3f9b 100644 (file)
         <tr>
           <th scope="col" i18n>Created</th>
           <th scope="col" i18n>Title</th>
-          <th scope="col" class="d-none d-md-table-cell" i18n>Tags</th>
-          <th scope="col" class="d-none d-md-table-cell" i18n>Correspondent</th>
+          @if (permissionsService.currentUserCan(PermissionAction.View, PermissionType.Tag)) {
+            <th scope="col" class="d-none d-md-table-cell" i18n>Tags</th>
+          }
+          @if (permissionsService.currentUserCan(PermissionAction.View, PermissionType.Correspondent)) {
+            <th scope="col" class="d-none d-md-table-cell" i18n>Correspondent</th>
+          } @else {
+            <th scope="col" class="d-none d-md-table-cell"></th>
+          }
         </tr>
       </thead>
       <tbody>
             <td class="py-2 py-md-3">
               <a routerLink="/documents/{{doc.id}}" title="Edit" i18n-title class="btn-link text-dark text-decoration-none py-2 py-md-3">{{doc.title | documentTitle}}</a>
             </td>
-            <td class="py-2 py-md-3 d-none d-md-table-cell">
-              @for (t of doc.tags$ | async; track t) {
-                <pngx-tag [tag]="t" class="ms-1" (click)="clickTag(t, $event)"></pngx-tag>
-              }
-            </td>
+            @if (permissionsService.currentUserCan(PermissionAction.View, PermissionType.Tag)) {
+              <td class="py-2 py-md-3 d-none d-md-table-cell">
+                @for (t of doc.tags$ | async; track t) {
+                  <pngx-tag [tag]="t" class="ms-1" (click)="clickTag(t, $event)"></pngx-tag>
+                }
+              </td>
+            }
             <td class="position-relative py-2 py-md-3 d-none d-md-table-cell">
-              @if (doc.correspondent !== null) {
+              @if (permissionsService.currentUserCan(PermissionAction.View, PermissionType.Correspondent) && doc.correspondent !== null) {
                 <a class="btn-link text-dark text-decoration-none py-2 py-md-3" routerLink="/documents" [queryParams]="getCorrespondentQueryParams(doc.correspondent)">{{(doc.correspondent$ | async)?.name}}</a>
               }
               <div class="btn-group position-absolute top-50 end-0 translate-middle-y">
index aa1b160cffdd9ac05502bed2e5bef303a15b3d74..c81ea54846ee33f238058c25757595bf29540e0a 100644 (file)
@@ -22,6 +22,7 @@ import { DocumentListViewService } from 'src/app/services/document-list-view.ser
 import { ComponentWithPermissions } from 'src/app/components/with-permissions/with-permissions.component'
 import { NgbPopover } from '@ng-bootstrap/ng-bootstrap'
 import { queryParamsFromFilterRules } from 'src/app/utils/query-params'
+import { PermissionsService } from 'src/app/services/permissions.service'
 
 @Component({
   selector: 'pngx-saved-view-widget',
@@ -40,7 +41,8 @@ export class SavedViewWidgetComponent
     private list: DocumentListViewService,
     private consumerStatusService: ConsumerStatusService,
     public openDocumentsService: OpenDocumentsService,
-    public documentListViewService: DocumentListViewService
+    public documentListViewService: DocumentListViewService,
+    public permissionsService: PermissionsService
   ) {
     super()
   }
index af0e0e78e65e97044e4f0a0ae5cf5dae9a05daf3..a305889705753ea55c31d0ad3481e780db341448 100644 (file)
@@ -1,5 +1,8 @@
 import { DatePipe } from '@angular/common'
-import { HttpClientTestingModule } from '@angular/common/http/testing'
+import {
+  HttpClientTestingModule,
+  HttpTestingController,
+} from '@angular/common/http/testing'
 import {
   ComponentFixture,
   TestBed,
@@ -71,6 +74,7 @@ import { CustomFieldDataType } from 'src/app/data/custom-field'
 import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service'
 import { PdfViewerComponent } from '../common/pdf-viewer/pdf-viewer.component'
 import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
+import { environment } from 'src/environments/environment'
 
 const doc: Document = {
   id: 3,
@@ -136,6 +140,7 @@ describe('DocumentDetailComponent', () => {
   let documentListViewService: DocumentListViewService
   let settingsService: SettingsService
   let customFieldsService: CustomFieldsService
+  let httpTestingController: HttpTestingController
 
   let currentUserCan = true
   let currentUserHasObjectPermissions = true
@@ -266,6 +271,7 @@ describe('DocumentDetailComponent', () => {
     settingsService.currentUser = { id: 1 }
     customFieldsService = TestBed.inject(CustomFieldsService)
     fixture = TestBed.createComponent(DocumentDetailComponent)
+    httpTestingController = TestBed.inject(HttpTestingController)
     component = fixture.componentInstance
   })
 
@@ -350,6 +356,26 @@ describe('DocumentDetailComponent', () => {
     expect(component.documentForm.disabled).toBeTruthy()
   })
 
+  it('should not attempt to retrieve objects if user does not have permissions', () => {
+    currentUserCan = false
+    initNormally()
+    expect(component.correspondents).toBeUndefined()
+    expect(component.documentTypes).toBeUndefined()
+    expect(component.storagePaths).toBeUndefined()
+    expect(component.users).toBeUndefined()
+    httpTestingController.expectNone(`${environment.apiBaseUrl}documents/tags/`)
+    httpTestingController.expectNone(
+      `${environment.apiBaseUrl}documents/correspondents/`
+    )
+    httpTestingController.expectNone(
+      `${environment.apiBaseUrl}documents/document_types/`
+    )
+    httpTestingController.expectNone(
+      `${environment.apiBaseUrl}documents/storage_paths/`
+    )
+    currentUserCan = true
+  })
+
   it('should support creating document type', () => {
     initNormally()
     let openModal: NgbModalRef
index 0ce9fa0075f1fadde75af96e534a8c278ffeb460..0ca458a21e06d66d1974f778c94afc0dfc11d6cf 100644 (file)
@@ -250,25 +250,50 @@ export class DocumentDetailComponent
         Object.assign(this.document, docValues)
       })
 
-    this.correspondentService
-      .listAll()
-      .pipe(first(), takeUntil(this.unsubscribeNotifier))
-      .subscribe((result) => (this.correspondents = result.results))
-
-    this.documentTypeService
-      .listAll()
-      .pipe(first(), takeUntil(this.unsubscribeNotifier))
-      .subscribe((result) => (this.documentTypes = result.results))
-
-    this.storagePathService
-      .listAll()
-      .pipe(first(), takeUntil(this.unsubscribeNotifier))
-      .subscribe((result) => (this.storagePaths = result.results))
-
-    this.userService
-      .listAll()
-      .pipe(first(), takeUntil(this.unsubscribeNotifier))
-      .subscribe((result) => (this.users = result.results))
+    if (
+      this.permissionsService.currentUserCan(
+        PermissionAction.View,
+        PermissionType.Correspondent
+      )
+    ) {
+      this.correspondentService
+        .listAll()
+        .pipe(first(), takeUntil(this.unsubscribeNotifier))
+        .subscribe((result) => (this.correspondents = result.results))
+    }
+    if (
+      this.permissionsService.currentUserCan(
+        PermissionAction.View,
+        PermissionType.DocumentType
+      )
+    ) {
+      this.documentTypeService
+        .listAll()
+        .pipe(first(), takeUntil(this.unsubscribeNotifier))
+        .subscribe((result) => (this.documentTypes = result.results))
+    }
+    if (
+      this.permissionsService.currentUserCan(
+        PermissionAction.View,
+        PermissionType.StoragePath
+      )
+    ) {
+      this.storagePathService
+        .listAll()
+        .pipe(first(), takeUntil(this.unsubscribeNotifier))
+        .subscribe((result) => (this.storagePaths = result.results))
+    }
+    if (
+      this.permissionsService.currentUserCan(
+        PermissionAction.View,
+        PermissionType.User
+      )
+    ) {
+      this.userService
+        .listAll()
+        .pipe(first(), takeUntil(this.unsubscribeNotifier))
+        .subscribe((result) => (this.users = result.results))
+    }
 
     this.getCustomFields()
 
index b101c2742878fe7ec659cdc7986bb8fb265f10f9..0c261df67f3d1bdc75b181b10ba8648e3a09e2e9 100644 (file)
         </div>
         <div class="d-flex align-items-center gap-2" *pngxIfPermissions="{ action: PermissionAction.Change, type: PermissionType.Document }">
           <label class="me-2" i18n>Edit:</label>
-          <pngx-filterable-dropdown title="Tags" icon="tag-fill" i18n-title
-            filterPlaceholder="Filter tags" i18n-filterPlaceholder
-            [items]="tags"
-            [disabled]="!userCanEditAll"
-            [editing]="true"
-            [manyToOne]="true"
-            [applyOnClose]="applyOnClose"
-            (opened)="openTagsDropdown()"
-            [(selectionModel)]="tagSelectionModel"
-            [documentCounts]="tagDocumentCounts"
-            (apply)="setTags($event)">
-          </pngx-filterable-dropdown>
-          <pngx-filterable-dropdown title="Correspondent" icon="person-fill" i18n-title
-            filterPlaceholder="Filter correspondents" i18n-filterPlaceholder
-            [items]="correspondents"
-            [disabled]="!userCanEditAll"
-            [editing]="true"
-            [applyOnClose]="applyOnClose"
-            (opened)="openCorrespondentDropdown()"
-            [(selectionModel)]="correspondentSelectionModel"
-            [documentCounts]="correspondentDocumentCounts"
-            (apply)="setCorrespondents($event)">
-          </pngx-filterable-dropdown>
-          <pngx-filterable-dropdown title="Document type" icon="file-earmark-fill" i18n-title
-            filterPlaceholder="Filter document types" i18n-filterPlaceholder
-            [items]="documentTypes"
-            [disabled]="!userCanEditAll"
-            [editing]="true"
-            [applyOnClose]="applyOnClose"
-            (opened)="openDocumentTypeDropdown()"
-            [(selectionModel)]="documentTypeSelectionModel"
-            [documentCounts]="documentTypeDocumentCounts"
-            (apply)="setDocumentTypes($event)">
-          </pngx-filterable-dropdown>
-          <pngx-filterable-dropdown title="Storage path" icon="folder-fill" i18n-title
-            filterPlaceholder="Filter storage paths" i18n-filterPlaceholder
-            [items]="storagePaths"
-            [disabled]="!userCanEditAll"
-            [editing]="true"
-            [applyOnClose]="applyOnClose"
-            (opened)="openStoragePathDropdown()"
-            [(selectionModel)]="storagePathsSelectionModel"
-            [documentCounts]="storagePathDocumentCounts"
-            (apply)="setStoragePaths($event)">
-          </pngx-filterable-dropdown>
+          @if (permissionService.currentUserCan(PermissionAction.View, PermissionType.Tag)) {
+            <pngx-filterable-dropdown title="Tags" icon="tag-fill" i18n-title
+              filterPlaceholder="Filter tags" i18n-filterPlaceholder
+              [items]="tags"
+              [disabled]="!userCanEditAll"
+              [editing]="true"
+              [manyToOne]="true"
+              [applyOnClose]="applyOnClose"
+              (opened)="openTagsDropdown()"
+              [(selectionModel)]="tagSelectionModel"
+              [documentCounts]="tagDocumentCounts"
+              (apply)="setTags($event)">
+            </pngx-filterable-dropdown>
+          }
+          @if (permissionService.currentUserCan(PermissionAction.View, PermissionType.Correspondent)) {
+            <pngx-filterable-dropdown title="Correspondent" icon="person-fill" i18n-title
+              filterPlaceholder="Filter correspondents" i18n-filterPlaceholder
+              [items]="correspondents"
+              [disabled]="!userCanEditAll"
+              [editing]="true"
+              [applyOnClose]="applyOnClose"
+              (opened)="openCorrespondentDropdown()"
+              [(selectionModel)]="correspondentSelectionModel"
+              [documentCounts]="correspondentDocumentCounts"
+              (apply)="setCorrespondents($event)">
+            </pngx-filterable-dropdown>
+          }
+          @if (permissionService.currentUserCan(PermissionAction.View, PermissionType.DocumentType)) {
+            <pngx-filterable-dropdown title="Document type" icon="file-earmark-fill" i18n-title
+              filterPlaceholder="Filter document types" i18n-filterPlaceholder
+              [items]="documentTypes"
+              [disabled]="!userCanEditAll"
+              [editing]="true"
+              [applyOnClose]="applyOnClose"
+              (opened)="openDocumentTypeDropdown()"
+              [(selectionModel)]="documentTypeSelectionModel"
+              [documentCounts]="documentTypeDocumentCounts"
+              (apply)="setDocumentTypes($event)">
+            </pngx-filterable-dropdown>
+          }
+          @if (permissionService.currentUserCan(PermissionAction.View, PermissionType.StoragePath)) {
+            <pngx-filterable-dropdown title="Storage path" icon="folder-fill" i18n-title
+              filterPlaceholder="Filter storage paths" i18n-filterPlaceholder
+              [items]="storagePaths"
+              [disabled]="!userCanEditAll"
+              [editing]="true"
+              [applyOnClose]="applyOnClose"
+              (opened)="openStoragePathDropdown()"
+              [(selectionModel)]="storagePathsSelectionModel"
+              [documentCounts]="storagePathDocumentCounts"
+              (apply)="setStoragePaths($event)">
+            </pngx-filterable-dropdown>
+          }
         </div>
         <div class="d-flex align-items-center gap-2 ms-auto">
           <div class="btn-toolbar">
index af41d298cb37f23f2447000e1c6c1984f9766932..8edfcc7e1963fa2574d9e9cc3378f452bd76af99 100644 (file)
@@ -868,4 +868,22 @@ describe('BulkEditorComponent', () => {
       `${environment.apiBaseUrl}documents/?page=1&page_size=100000&fields=id`
     ) // listAllFilteredIds
   })
+
+  it('should not attempt to retrieve objects if user does not have permissions', () => {
+    jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
+    expect(component.tags).toBeUndefined()
+    expect(component.correspondents).toBeUndefined()
+    expect(component.documentTypes).toBeUndefined()
+    expect(component.storagePaths).toBeUndefined()
+    httpTestingController.expectNone(`${environment.apiBaseUrl}documents/tags/`)
+    httpTestingController.expectNone(
+      `${environment.apiBaseUrl}documents/correspondents/`
+    )
+    httpTestingController.expectNone(
+      `${environment.apiBaseUrl}documents/document_types/`
+    )
+    httpTestingController.expectNone(
+      `${environment.apiBaseUrl}documents/storage_paths/`
+    )
+  })
 })
index 91b714b247b767e18529fa27528a4cb7ff052e29..1c7e0b9c6ce2a43c3e97cf34b12892bfd3d4647c 100644 (file)
@@ -115,22 +115,50 @@ export class BulkEditorComponent
   }
 
   ngOnInit() {
-    this.tagService
-      .listAll()
-      .pipe(first())
-      .subscribe((result) => (this.tags = result.results))
-    this.correspondentService
-      .listAll()
-      .pipe(first())
-      .subscribe((result) => (this.correspondents = result.results))
-    this.documentTypeService
-      .listAll()
-      .pipe(first())
-      .subscribe((result) => (this.documentTypes = result.results))
-    this.storagePathService
-      .listAll()
-      .pipe(first())
-      .subscribe((result) => (this.storagePaths = result.results))
+    if (
+      this.permissionService.currentUserCan(
+        PermissionAction.View,
+        PermissionType.Tag
+      )
+    ) {
+      this.tagService
+        .listAll()
+        .pipe(first())
+        .subscribe((result) => (this.tags = result.results))
+    }
+    if (
+      this.permissionService.currentUserCan(
+        PermissionAction.View,
+        PermissionType.Correspondent
+      )
+    ) {
+      this.correspondentService
+        .listAll()
+        .pipe(first())
+        .subscribe((result) => (this.correspondents = result.results))
+    }
+    if (
+      this.permissionService.currentUserCan(
+        PermissionAction.View,
+        PermissionType.DocumentType
+      )
+    ) {
+      this.documentTypeService
+        .listAll()
+        .pipe(first())
+        .subscribe((result) => (this.documentTypes = result.results))
+    }
+    if (
+      this.permissionService.currentUserCan(
+        PermissionAction.View,
+        PermissionType.StoragePath
+      )
+    ) {
+      this.storagePathService
+        .listAll()
+        .pipe(first())
+        .subscribe((result) => (this.storagePaths = result.results))
+    }
 
     this.downloadForm
       .get('downloadFileTypeArchive')
index bd565a9fbd66c96cd838a9680dee61c5cf4e5b0a..2ca1a340830ed9257d2211e5888f9d7532f2d620 100644 (file)
@@ -79,7 +79,7 @@ export class DocumentCardSmallComponent extends ComponentWithPermissions {
 
   getTagsLimited$() {
     const limit = this.document.notes.length > 0 ? 6 : 7
-    return this.document.tags$.pipe(
+    return this.document.tags$?.pipe(
       map((tags) => {
         if (tags.length > limit) {
           this.moreTags = tags.length - (limit - 1)
index 08f680b07dbedc1e27142491249140bbea4a957b..89900e0876d0260933331b4aaa1a68fb350c1fe1 100644 (file)
@@ -29,7 +29,8 @@
   <div class="col-auto">
     <div class="d-flex flex-wrap gap-3">
       <div class="d-flex flex-wrap gap-2">
-        <pngx-filterable-dropdown class="flex-fill" title="Tags" icon="tag-fill" i18n-title
+        @if (permissionsService.currentUserCan(PermissionAction.View, PermissionType.Tag)) {
+          <pngx-filterable-dropdown class="flex-fill" title="Tags" icon="tag-fill" i18n-title
           filterPlaceholder="Filter tags" i18n-filterPlaceholder
           [items]="tags"
           [manyToOne]="true"
           (selectionModelChange)="updateRules()"
           (opened)="onTagsDropdownOpen()"
           [documentCounts]="tagDocumentCounts"
-        [allowSelectNone]="true"></pngx-filterable-dropdown>
-        <pngx-filterable-dropdown class="flex-fill" title="Correspondent" icon="person-fill" i18n-title
+          [allowSelectNone]="true"></pngx-filterable-dropdown>
+        }
+        @if (permissionsService.currentUserCan(PermissionAction.View, PermissionType.Correspondent)) {
+          <pngx-filterable-dropdown class="flex-fill" title="Correspondent" icon="person-fill" i18n-title
           filterPlaceholder="Filter correspondents" i18n-filterPlaceholder
           [items]="correspondents"
           [(selectionModel)]="correspondentSelectionModel"
           (selectionModelChange)="updateRules()"
           (opened)="onCorrespondentDropdownOpen()"
           [documentCounts]="correspondentDocumentCounts"
-        [allowSelectNone]="true"></pngx-filterable-dropdown>
-        <pngx-filterable-dropdown class="flex-fill" title="Document type" icon="file-earmark-fill" i18n-title
-          filterPlaceholder="Filter document types" i18n-filterPlaceholder
-          [items]="documentTypes"
-          [(selectionModel)]="documentTypeSelectionModel"
-          (selectionModelChange)="updateRules()"
-          (opened)="onDocumentTypeDropdownOpen()"
-          [documentCounts]="documentTypeDocumentCounts"
-        [allowSelectNone]="true"></pngx-filterable-dropdown>
-        <pngx-filterable-dropdown class="flex-fill" title="Storage path" icon="folder-fill" i18n-title
+          [allowSelectNone]="true"></pngx-filterable-dropdown>
+        }
+        @if (permissionsService.currentUserCan(PermissionAction.View, PermissionType.DocumentType)) {
+            <pngx-filterable-dropdown class="flex-fill" title="Document type" icon="file-earmark-fill" i18n-title
+            filterPlaceholder="Filter document types" i18n-filterPlaceholder
+            [items]="documentTypes"
+            [(selectionModel)]="documentTypeSelectionModel"
+            (selectionModelChange)="updateRules()"
+            (opened)="onDocumentTypeDropdownOpen()"
+            [documentCounts]="documentTypeDocumentCounts"
+            [allowSelectNone]="true"></pngx-filterable-dropdown>
+        }
+        @if (permissionsService.currentUserCan(PermissionAction.View, PermissionType.StoragePath)) {
+          <pngx-filterable-dropdown class="flex-fill" title="Storage path" icon="folder-fill" i18n-title
           filterPlaceholder="Filter storage paths" i18n-filterPlaceholder
           [items]="storagePaths"
           [(selectionModel)]="storagePathSelectionModel"
           (selectionModelChange)="updateRules()"
           (opened)="onStoragePathDropdownOpen()"
           [documentCounts]="storagePathDocumentCounts"
-        [allowSelectNone]="true"></pngx-filterable-dropdown>
+          [allowSelectNone]="true"></pngx-filterable-dropdown>
+        }
       </div>
       <div class="d-flex flex-wrap gap-2">
         <pngx-date-dropdown
index dc5a3383eb69884bf3870f8bec9c7cbd5fee0e50..1f7874669b1bc0905464a7e729a62737db4b8eb9 100644 (file)
@@ -1,5 +1,8 @@
 import { DatePipe } from '@angular/common'
-import { HttpClientTestingModule } from '@angular/common/http/testing'
+import {
+  HttpClientTestingModule,
+  HttpTestingController,
+} from '@angular/common/http/testing'
 import {
   ComponentFixture,
   fakeAsync,
@@ -78,6 +81,11 @@ import {
 } from '../../common/permissions-filter-dropdown/permissions-filter-dropdown.component'
 import { FilterEditorComponent } from './filter-editor.component'
 import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
+import {
+  PermissionType,
+  PermissionsService,
+} from 'src/app/services/permissions.service'
+import { environment } from 'src/environments/environment'
 
 const tags: Tag[] = [
   {
@@ -135,6 +143,8 @@ describe('FilterEditorComponent', () => {
   let fixture: ComponentFixture<FilterEditorComponent>
   let documentService: DocumentService
   let settingsService: SettingsService
+  let permissionsService: PermissionsService
+  let httpTestingController: HttpTestingController
 
   beforeEach(fakeAsync(() => {
     TestBed.configureTestingModule({
@@ -199,6 +209,15 @@ describe('FilterEditorComponent', () => {
     documentService = TestBed.inject(DocumentService)
     settingsService = TestBed.inject(SettingsService)
     settingsService.currentUser = users[0]
+    permissionsService = TestBed.inject(PermissionsService)
+    jest
+      .spyOn(permissionsService, 'currentUserCan')
+      .mockImplementation((action, type) => {
+        // a little hack-ish, permissions filter dropdown causes reactive forms issue due to ng-select
+        // trying to apply formControlName
+        return type !== PermissionType.User
+      })
+    httpTestingController = TestBed.inject(HttpTestingController)
     fixture = TestBed.createComponent(FilterEditorComponent)
     component = fixture.componentInstance
     component.filterRules = []
@@ -206,6 +225,24 @@ describe('FilterEditorComponent', () => {
     tick()
   }))
 
+  it('should not attempt to retrieve objects if user does not have permissions', () => {
+    jest.spyOn(permissionsService, 'currentUserCan').mockReset()
+    jest
+      .spyOn(permissionsService, 'currentUserCan')
+      .mockImplementation((action, type) => false)
+    component.ngOnInit()
+    httpTestingController.expectNone(`${environment.apiBaseUrl}documents/tags/`)
+    httpTestingController.expectNone(
+      `${environment.apiBaseUrl}documents/correspondents/`
+    )
+    httpTestingController.expectNone(
+      `${environment.apiBaseUrl}documents/document_types/`
+    )
+    httpTestingController.expectNone(
+      `${environment.apiBaseUrl}documents/storage_paths/`
+    )
+  })
+
   // SET filterRules
 
   it('should ingest text filter rules for doc title', fakeAsync(() => {
index 03e1db53954a08aa97480c28fe551ab019dbd28b..b11874d7c9cec0b80ade71c02d452fc8400dd9bd 100644 (file)
@@ -70,6 +70,12 @@ import {
   OwnerFilterType,
   PermissionsSelectionModel,
 } from '../../common/permissions-filter-dropdown/permissions-filter-dropdown.component'
+import {
+  PermissionAction,
+  PermissionType,
+  PermissionsService,
+} from 'src/app/services/permissions.service'
+import { ComponentWithPermissions } from '../../with-permissions/with-permissions.component'
 
 const TEXT_FILTER_TARGET_TITLE = 'title'
 const TEXT_FILTER_TARGET_TITLE_CONTENT = 'title-content'
@@ -155,7 +161,10 @@ const DEFAULT_TEXT_FILTER_MODIFIER_OPTIONS = [
   templateUrl: './filter-editor.component.html',
   styleUrls: ['./filter-editor.component.scss'],
 })
-export class FilterEditorComponent implements OnInit, OnDestroy {
+export class FilterEditorComponent
+  extends ComponentWithPermissions
+  implements OnInit, OnDestroy
+{
   generateFilterName() {
     if (this.filterRules.length == 1) {
       let rule = this.filterRules[0]
@@ -224,8 +233,11 @@ export class FilterEditorComponent implements OnInit, OnDestroy {
     private tagService: TagService,
     private correspondentService: CorrespondentService,
     private documentService: DocumentService,
-    private storagePathService: StoragePathService
-  ) {}
+    private storagePathService: StoragePathService,
+    public permissionsService: PermissionsService
+  ) {
+    super()
+  }
 
   @ViewChild('textFilterInput')
   textFilterInput: ElementRef
@@ -872,18 +884,46 @@ export class FilterEditorComponent implements OnInit, OnDestroy {
   subscription: Subscription
 
   ngOnInit() {
-    this.tagService
-      .listAll()
-      .subscribe((result) => (this.tags = result.results))
-    this.correspondentService
-      .listAll()
-      .subscribe((result) => (this.correspondents = result.results))
-    this.documentTypeService
-      .listAll()
-      .subscribe((result) => (this.documentTypes = result.results))
-    this.storagePathService
-      .listAll()
-      .subscribe((result) => (this.storagePaths = result.results))
+    if (
+      this.permissionsService.currentUserCan(
+        PermissionAction.View,
+        PermissionType.Tag
+      )
+    ) {
+      this.tagService
+        .listAll()
+        .subscribe((result) => (this.tags = result.results))
+    }
+    if (
+      this.permissionsService.currentUserCan(
+        PermissionAction.View,
+        PermissionType.Correspondent
+      )
+    ) {
+      this.correspondentService
+        .listAll()
+        .subscribe((result) => (this.correspondents = result.results))
+    }
+    if (
+      this.permissionsService.currentUserCan(
+        PermissionAction.View,
+        PermissionType.DocumentType
+      )
+    ) {
+      this.documentTypeService
+        .listAll()
+        .subscribe((result) => (this.documentTypes = result.results))
+    }
+    if (
+      this.permissionsService.currentUserCan(
+        PermissionAction.View,
+        PermissionType.StoragePath
+      )
+    ) {
+      this.storagePathService
+        .listAll()
+        .subscribe((result) => (this.storagePaths = result.results))
+    }
 
     this.textFilterDebounce = new Subject<string>()
 
index ee0f26187d1462578443f04f0666882b13dd739c..9ff99031f34a477e9016ef16dec82b1efa7a6ed5 100644 (file)
@@ -13,6 +13,11 @@ import { TagService } from './tag.service'
 import { DocumentSuggestions } from 'src/app/data/document-suggestions'
 import { queryParamsFromFilterRules } from '../../utils/query-params'
 import { StoragePathService } from './storage-path.service'
+import {
+  PermissionAction,
+  PermissionType,
+  PermissionsService,
+} from '../permissions.service'
 
 export const DOCUMENT_SORT_FIELDS = [
   { field: 'archive_serial_number', name: $localize`ASN` },
@@ -57,21 +62,40 @@ export class DocumentService extends AbstractPaperlessService<Document> {
     private correspondentService: CorrespondentService,
     private documentTypeService: DocumentTypeService,
     private tagService: TagService,
-    private storagePathService: StoragePathService
+    private storagePathService: StoragePathService,
+    private permissionsService: PermissionsService
   ) {
     super(http, 'documents')
   }
 
   addObservablesToDocument(doc: Document) {
-    if (doc.correspondent) {
+    if (
+      doc.correspondent &&
+      this.permissionsService.currentUserCan(
+        PermissionAction.View,
+        PermissionType.Correspondent
+      )
+    ) {
       doc.correspondent$ = this.correspondentService.getCached(
         doc.correspondent
       )
     }
-    if (doc.document_type) {
+    if (
+      doc.document_type &&
+      this.permissionsService.currentUserCan(
+        PermissionAction.View,
+        PermissionType.DocumentType
+      )
+    ) {
       doc.document_type$ = this.documentTypeService.getCached(doc.document_type)
     }
-    if (doc.tags) {
+    if (
+      doc.tags &&
+      this.permissionsService.currentUserCan(
+        PermissionAction.View,
+        PermissionType.Tag
+      )
+    ) {
       doc.tags$ = this.tagService
         .getCachedMany(doc.tags)
         .pipe(
@@ -80,7 +104,13 @@ export class DocumentService extends AbstractPaperlessService<Document> {
           )
         )
     }
-    if (doc.storage_path) {
+    if (
+      doc.storage_path &&
+      this.permissionsService.currentUserCan(
+        PermissionAction.View,
+        PermissionType.StoragePath
+      )
+    ) {
       doc.storage_path$ = this.storagePathService.getCached(doc.storage_path)
     }
     return doc