Fix: Dont attempt to retrieve objects for which user doesnt have global permissions (#5612)

This commit is contained in:
shamoon 2024-02-01 01:20:14 -08:00 committed by GitHub
parent 4996b7e5f7
commit 5e3d1b26e7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 375 additions and 143 deletions

View File

@ -62,22 +62,24 @@
<i-bs width="1em" height="1em" name="check"></i-bs> <i-bs width="1em" height="1em" name="check"></i-bs>
} }
</div> </div>
<div class="me-1 w-100"> @if (permissionsService.currentUserCan(PermissionAction.View, PermissionType.User)) {
<ng-select <div class="me-1 w-100">
name="user" <ng-select
class="user-select small" name="user"
[(ngModel)]="selectionModel.includeUsers" class="user-select small"
[disabled]="disabled" [(ngModel)]="selectionModel.includeUsers"
[clearable]="false" [disabled]="disabled"
[items]="users" [clearable]="false"
bindLabel="username" [items]="users"
multiple="true" bindLabel="username"
bindValue="id" multiple="true"
placeholder="Users" bindValue="id"
i18n-placeholder placeholder="Users"
(change)="onUserSelect()"> i18n-placeholder
</ng-select> (change)="onUserSelect()">
</div> </ng-select>
</div>
}
</button> </button>
@if (selectionModel.ownerFilter === OwnerFilterType.NONE || selectionModel.ownerFilter === OwnerFilterType.NOT_SELF) { @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"> <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">

View File

@ -67,7 +67,7 @@ export class PermissionsFilterDropdownComponent extends ComponentWithPermissions
} }
constructor( constructor(
permissionsService: PermissionsService, public permissionsService: PermissionsService,
userService: UserService, userService: UserService,
private settingsService: SettingsService private settingsService: SettingsService
) { ) {

View File

@ -15,8 +15,14 @@
<tr> <tr>
<th scope="col" i18n>Created</th> <th scope="col" i18n>Created</th>
<th scope="col" i18n>Title</th> <th scope="col" i18n>Title</th>
<th scope="col" class="d-none d-md-table-cell" i18n>Tags</th> @if (permissionsService.currentUserCan(PermissionAction.View, PermissionType.Tag)) {
<th scope="col" class="d-none d-md-table-cell" i18n>Correspondent</th> <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> </tr>
</thead> </thead>
<tbody> <tbody>
@ -26,13 +32,15 @@
<td class="py-2 py-md-3"> <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> <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>
<td class="py-2 py-md-3 d-none d-md-table-cell"> @if (permissionsService.currentUserCan(PermissionAction.View, PermissionType.Tag)) {
@for (t of doc.tags$ | async; track t) { <td class="py-2 py-md-3 d-none d-md-table-cell">
<pngx-tag [tag]="t" class="ms-1" (click)="clickTag(t, $event)"></pngx-tag> @for (t of doc.tags$ | async; track t) {
} <pngx-tag [tag]="t" class="ms-1" (click)="clickTag(t, $event)"></pngx-tag>
</td> }
</td>
}
<td class="position-relative py-2 py-md-3 d-none d-md-table-cell"> <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> <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"> <div class="btn-group position-absolute top-50 end-0 translate-middle-y">

View 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 { ComponentWithPermissions } from 'src/app/components/with-permissions/with-permissions.component'
import { NgbPopover } from '@ng-bootstrap/ng-bootstrap' import { NgbPopover } from '@ng-bootstrap/ng-bootstrap'
import { queryParamsFromFilterRules } from 'src/app/utils/query-params' import { queryParamsFromFilterRules } from 'src/app/utils/query-params'
import { PermissionsService } from 'src/app/services/permissions.service'
@Component({ @Component({
selector: 'pngx-saved-view-widget', selector: 'pngx-saved-view-widget',
@ -40,7 +41,8 @@ export class SavedViewWidgetComponent
private list: DocumentListViewService, private list: DocumentListViewService,
private consumerStatusService: ConsumerStatusService, private consumerStatusService: ConsumerStatusService,
public openDocumentsService: OpenDocumentsService, public openDocumentsService: OpenDocumentsService,
public documentListViewService: DocumentListViewService public documentListViewService: DocumentListViewService,
public permissionsService: PermissionsService
) { ) {
super() super()
} }

View File

@ -1,5 +1,8 @@
import { DatePipe } from '@angular/common' import { DatePipe } from '@angular/common'
import { HttpClientTestingModule } from '@angular/common/http/testing' import {
HttpClientTestingModule,
HttpTestingController,
} from '@angular/common/http/testing'
import { import {
ComponentFixture, ComponentFixture,
TestBed, TestBed,
@ -71,6 +74,7 @@ import { CustomFieldDataType } from 'src/app/data/custom-field'
import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service' import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service'
import { PdfViewerComponent } from '../common/pdf-viewer/pdf-viewer.component' import { PdfViewerComponent } from '../common/pdf-viewer/pdf-viewer.component'
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons' import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
import { environment } from 'src/environments/environment'
const doc: Document = { const doc: Document = {
id: 3, id: 3,
@ -136,6 +140,7 @@ describe('DocumentDetailComponent', () => {
let documentListViewService: DocumentListViewService let documentListViewService: DocumentListViewService
let settingsService: SettingsService let settingsService: SettingsService
let customFieldsService: CustomFieldsService let customFieldsService: CustomFieldsService
let httpTestingController: HttpTestingController
let currentUserCan = true let currentUserCan = true
let currentUserHasObjectPermissions = true let currentUserHasObjectPermissions = true
@ -266,6 +271,7 @@ describe('DocumentDetailComponent', () => {
settingsService.currentUser = { id: 1 } settingsService.currentUser = { id: 1 }
customFieldsService = TestBed.inject(CustomFieldsService) customFieldsService = TestBed.inject(CustomFieldsService)
fixture = TestBed.createComponent(DocumentDetailComponent) fixture = TestBed.createComponent(DocumentDetailComponent)
httpTestingController = TestBed.inject(HttpTestingController)
component = fixture.componentInstance component = fixture.componentInstance
}) })
@ -350,6 +356,26 @@ describe('DocumentDetailComponent', () => {
expect(component.documentForm.disabled).toBeTruthy() 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', () => { it('should support creating document type', () => {
initNormally() initNormally()
let openModal: NgbModalRef let openModal: NgbModalRef

View File

@ -250,25 +250,50 @@ export class DocumentDetailComponent
Object.assign(this.document, docValues) Object.assign(this.document, docValues)
}) })
this.correspondentService if (
.listAll() this.permissionsService.currentUserCan(
.pipe(first(), takeUntil(this.unsubscribeNotifier)) PermissionAction.View,
.subscribe((result) => (this.correspondents = result.results)) PermissionType.Correspondent
)
this.documentTypeService ) {
.listAll() this.correspondentService
.pipe(first(), takeUntil(this.unsubscribeNotifier)) .listAll()
.subscribe((result) => (this.documentTypes = result.results)) .pipe(first(), takeUntil(this.unsubscribeNotifier))
.subscribe((result) => (this.correspondents = result.results))
this.storagePathService }
.listAll() if (
.pipe(first(), takeUntil(this.unsubscribeNotifier)) this.permissionsService.currentUserCan(
.subscribe((result) => (this.storagePaths = result.results)) PermissionAction.View,
PermissionType.DocumentType
this.userService )
.listAll() ) {
.pipe(first(), takeUntil(this.unsubscribeNotifier)) this.documentTypeService
.subscribe((result) => (this.users = result.results)) .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() this.getCustomFields()

View File

@ -17,51 +17,59 @@
</div> </div>
<div class="d-flex align-items-center gap-2" *pngxIfPermissions="{ action: PermissionAction.Change, type: PermissionType.Document }"> <div class="d-flex align-items-center gap-2" *pngxIfPermissions="{ action: PermissionAction.Change, type: PermissionType.Document }">
<label class="me-2" i18n>Edit:</label> <label class="me-2" i18n>Edit:</label>
<pngx-filterable-dropdown title="Tags" icon="tag-fill" i18n-title @if (permissionService.currentUserCan(PermissionAction.View, PermissionType.Tag)) {
filterPlaceholder="Filter tags" i18n-filterPlaceholder <pngx-filterable-dropdown title="Tags" icon="tag-fill" i18n-title
[items]="tags" filterPlaceholder="Filter tags" i18n-filterPlaceholder
[disabled]="!userCanEditAll" [items]="tags"
[editing]="true" [disabled]="!userCanEditAll"
[manyToOne]="true" [editing]="true"
[applyOnClose]="applyOnClose" [manyToOne]="true"
(opened)="openTagsDropdown()" [applyOnClose]="applyOnClose"
[(selectionModel)]="tagSelectionModel" (opened)="openTagsDropdown()"
[documentCounts]="tagDocumentCounts" [(selectionModel)]="tagSelectionModel"
(apply)="setTags($event)"> [documentCounts]="tagDocumentCounts"
</pngx-filterable-dropdown> (apply)="setTags($event)">
<pngx-filterable-dropdown title="Correspondent" icon="person-fill" i18n-title </pngx-filterable-dropdown>
filterPlaceholder="Filter correspondents" i18n-filterPlaceholder }
[items]="correspondents" @if (permissionService.currentUserCan(PermissionAction.View, PermissionType.Correspondent)) {
[disabled]="!userCanEditAll" <pngx-filterable-dropdown title="Correspondent" icon="person-fill" i18n-title
[editing]="true" filterPlaceholder="Filter correspondents" i18n-filterPlaceholder
[applyOnClose]="applyOnClose" [items]="correspondents"
(opened)="openCorrespondentDropdown()" [disabled]="!userCanEditAll"
[(selectionModel)]="correspondentSelectionModel" [editing]="true"
[documentCounts]="correspondentDocumentCounts" [applyOnClose]="applyOnClose"
(apply)="setCorrespondents($event)"> (opened)="openCorrespondentDropdown()"
</pngx-filterable-dropdown> [(selectionModel)]="correspondentSelectionModel"
<pngx-filterable-dropdown title="Document type" icon="file-earmark-fill" i18n-title [documentCounts]="correspondentDocumentCounts"
filterPlaceholder="Filter document types" i18n-filterPlaceholder (apply)="setCorrespondents($event)">
[items]="documentTypes" </pngx-filterable-dropdown>
[disabled]="!userCanEditAll" }
[editing]="true" @if (permissionService.currentUserCan(PermissionAction.View, PermissionType.DocumentType)) {
[applyOnClose]="applyOnClose" <pngx-filterable-dropdown title="Document type" icon="file-earmark-fill" i18n-title
(opened)="openDocumentTypeDropdown()" filterPlaceholder="Filter document types" i18n-filterPlaceholder
[(selectionModel)]="documentTypeSelectionModel" [items]="documentTypes"
[documentCounts]="documentTypeDocumentCounts" [disabled]="!userCanEditAll"
(apply)="setDocumentTypes($event)"> [editing]="true"
</pngx-filterable-dropdown> [applyOnClose]="applyOnClose"
<pngx-filterable-dropdown title="Storage path" icon="folder-fill" i18n-title (opened)="openDocumentTypeDropdown()"
filterPlaceholder="Filter storage paths" i18n-filterPlaceholder [(selectionModel)]="documentTypeSelectionModel"
[items]="storagePaths" [documentCounts]="documentTypeDocumentCounts"
[disabled]="!userCanEditAll" (apply)="setDocumentTypes($event)">
[editing]="true" </pngx-filterable-dropdown>
[applyOnClose]="applyOnClose" }
(opened)="openStoragePathDropdown()" @if (permissionService.currentUserCan(PermissionAction.View, PermissionType.StoragePath)) {
[(selectionModel)]="storagePathsSelectionModel" <pngx-filterable-dropdown title="Storage path" icon="folder-fill" i18n-title
[documentCounts]="storagePathDocumentCounts" filterPlaceholder="Filter storage paths" i18n-filterPlaceholder
(apply)="setStoragePaths($event)"> [items]="storagePaths"
</pngx-filterable-dropdown> [disabled]="!userCanEditAll"
[editing]="true"
[applyOnClose]="applyOnClose"
(opened)="openStoragePathDropdown()"
[(selectionModel)]="storagePathsSelectionModel"
[documentCounts]="storagePathDocumentCounts"
(apply)="setStoragePaths($event)">
</pngx-filterable-dropdown>
}
</div> </div>
<div class="d-flex align-items-center gap-2 ms-auto"> <div class="d-flex align-items-center gap-2 ms-auto">
<div class="btn-toolbar"> <div class="btn-toolbar">

View File

@ -868,4 +868,22 @@ describe('BulkEditorComponent', () => {
`${environment.apiBaseUrl}documents/?page=1&page_size=100000&fields=id` `${environment.apiBaseUrl}documents/?page=1&page_size=100000&fields=id`
) // listAllFilteredIds ) // 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/`
)
})
}) })

View File

@ -115,22 +115,50 @@ export class BulkEditorComponent
} }
ngOnInit() { ngOnInit() {
this.tagService if (
.listAll() this.permissionService.currentUserCan(
.pipe(first()) PermissionAction.View,
.subscribe((result) => (this.tags = result.results)) PermissionType.Tag
this.correspondentService )
.listAll() ) {
.pipe(first()) this.tagService
.subscribe((result) => (this.correspondents = result.results)) .listAll()
this.documentTypeService .pipe(first())
.listAll() .subscribe((result) => (this.tags = result.results))
.pipe(first()) }
.subscribe((result) => (this.documentTypes = result.results)) if (
this.storagePathService this.permissionService.currentUserCan(
.listAll() PermissionAction.View,
.pipe(first()) PermissionType.Correspondent
.subscribe((result) => (this.storagePaths = result.results)) )
) {
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 this.downloadForm
.get('downloadFileTypeArchive') .get('downloadFileTypeArchive')

View File

@ -79,7 +79,7 @@ export class DocumentCardSmallComponent extends ComponentWithPermissions {
getTagsLimited$() { getTagsLimited$() {
const limit = this.document.notes.length > 0 ? 6 : 7 const limit = this.document.notes.length > 0 ? 6 : 7
return this.document.tags$.pipe( return this.document.tags$?.pipe(
map((tags) => { map((tags) => {
if (tags.length > limit) { if (tags.length > limit) {
this.moreTags = tags.length - (limit - 1) this.moreTags = tags.length - (limit - 1)

View File

@ -29,7 +29,8 @@
<div class="col-auto"> <div class="col-auto">
<div class="d-flex flex-wrap gap-3"> <div class="d-flex flex-wrap gap-3">
<div class="d-flex flex-wrap gap-2"> <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 filterPlaceholder="Filter tags" i18n-filterPlaceholder
[items]="tags" [items]="tags"
[manyToOne]="true" [manyToOne]="true"
@ -37,31 +38,38 @@
(selectionModelChange)="updateRules()" (selectionModelChange)="updateRules()"
(opened)="onTagsDropdownOpen()" (opened)="onTagsDropdownOpen()"
[documentCounts]="tagDocumentCounts" [documentCounts]="tagDocumentCounts"
[allowSelectNone]="true"></pngx-filterable-dropdown> [allowSelectNone]="true"></pngx-filterable-dropdown>
<pngx-filterable-dropdown class="flex-fill" title="Correspondent" icon="person-fill" i18n-title }
@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 filterPlaceholder="Filter correspondents" i18n-filterPlaceholder
[items]="correspondents" [items]="correspondents"
[(selectionModel)]="correspondentSelectionModel" [(selectionModel)]="correspondentSelectionModel"
(selectionModelChange)="updateRules()" (selectionModelChange)="updateRules()"
(opened)="onCorrespondentDropdownOpen()" (opened)="onCorrespondentDropdownOpen()"
[documentCounts]="correspondentDocumentCounts" [documentCounts]="correspondentDocumentCounts"
[allowSelectNone]="true"></pngx-filterable-dropdown> [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 @if (permissionsService.currentUserCan(PermissionAction.View, PermissionType.DocumentType)) {
[items]="documentTypes" <pngx-filterable-dropdown class="flex-fill" title="Document type" icon="file-earmark-fill" i18n-title
[(selectionModel)]="documentTypeSelectionModel" filterPlaceholder="Filter document types" i18n-filterPlaceholder
(selectionModelChange)="updateRules()" [items]="documentTypes"
(opened)="onDocumentTypeDropdownOpen()" [(selectionModel)]="documentTypeSelectionModel"
[documentCounts]="documentTypeDocumentCounts" (selectionModelChange)="updateRules()"
[allowSelectNone]="true"></pngx-filterable-dropdown> (opened)="onDocumentTypeDropdownOpen()"
<pngx-filterable-dropdown class="flex-fill" title="Storage path" icon="folder-fill" i18n-title [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 filterPlaceholder="Filter storage paths" i18n-filterPlaceholder
[items]="storagePaths" [items]="storagePaths"
[(selectionModel)]="storagePathSelectionModel" [(selectionModel)]="storagePathSelectionModel"
(selectionModelChange)="updateRules()" (selectionModelChange)="updateRules()"
(opened)="onStoragePathDropdownOpen()" (opened)="onStoragePathDropdownOpen()"
[documentCounts]="storagePathDocumentCounts" [documentCounts]="storagePathDocumentCounts"
[allowSelectNone]="true"></pngx-filterable-dropdown> [allowSelectNone]="true"></pngx-filterable-dropdown>
}
</div> </div>
<div class="d-flex flex-wrap gap-2"> <div class="d-flex flex-wrap gap-2">
<pngx-date-dropdown <pngx-date-dropdown

View File

@ -1,5 +1,8 @@
import { DatePipe } from '@angular/common' import { DatePipe } from '@angular/common'
import { HttpClientTestingModule } from '@angular/common/http/testing' import {
HttpClientTestingModule,
HttpTestingController,
} from '@angular/common/http/testing'
import { import {
ComponentFixture, ComponentFixture,
fakeAsync, fakeAsync,
@ -78,6 +81,11 @@ import {
} from '../../common/permissions-filter-dropdown/permissions-filter-dropdown.component' } from '../../common/permissions-filter-dropdown/permissions-filter-dropdown.component'
import { FilterEditorComponent } from './filter-editor.component' import { FilterEditorComponent } from './filter-editor.component'
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons' 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[] = [ const tags: Tag[] = [
{ {
@ -135,6 +143,8 @@ describe('FilterEditorComponent', () => {
let fixture: ComponentFixture<FilterEditorComponent> let fixture: ComponentFixture<FilterEditorComponent>
let documentService: DocumentService let documentService: DocumentService
let settingsService: SettingsService let settingsService: SettingsService
let permissionsService: PermissionsService
let httpTestingController: HttpTestingController
beforeEach(fakeAsync(() => { beforeEach(fakeAsync(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
@ -199,6 +209,15 @@ describe('FilterEditorComponent', () => {
documentService = TestBed.inject(DocumentService) documentService = TestBed.inject(DocumentService)
settingsService = TestBed.inject(SettingsService) settingsService = TestBed.inject(SettingsService)
settingsService.currentUser = users[0] 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) fixture = TestBed.createComponent(FilterEditorComponent)
component = fixture.componentInstance component = fixture.componentInstance
component.filterRules = [] component.filterRules = []
@ -206,6 +225,24 @@ describe('FilterEditorComponent', () => {
tick() 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 // SET filterRules
it('should ingest text filter rules for doc title', fakeAsync(() => { it('should ingest text filter rules for doc title', fakeAsync(() => {

View File

@ -70,6 +70,12 @@ import {
OwnerFilterType, OwnerFilterType,
PermissionsSelectionModel, PermissionsSelectionModel,
} from '../../common/permissions-filter-dropdown/permissions-filter-dropdown.component' } 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 = 'title'
const TEXT_FILTER_TARGET_TITLE_CONTENT = 'title-content' const TEXT_FILTER_TARGET_TITLE_CONTENT = 'title-content'
@ -155,7 +161,10 @@ const DEFAULT_TEXT_FILTER_MODIFIER_OPTIONS = [
templateUrl: './filter-editor.component.html', templateUrl: './filter-editor.component.html',
styleUrls: ['./filter-editor.component.scss'], styleUrls: ['./filter-editor.component.scss'],
}) })
export class FilterEditorComponent implements OnInit, OnDestroy { export class FilterEditorComponent
extends ComponentWithPermissions
implements OnInit, OnDestroy
{
generateFilterName() { generateFilterName() {
if (this.filterRules.length == 1) { if (this.filterRules.length == 1) {
let rule = this.filterRules[0] let rule = this.filterRules[0]
@ -224,8 +233,11 @@ export class FilterEditorComponent implements OnInit, OnDestroy {
private tagService: TagService, private tagService: TagService,
private correspondentService: CorrespondentService, private correspondentService: CorrespondentService,
private documentService: DocumentService, private documentService: DocumentService,
private storagePathService: StoragePathService private storagePathService: StoragePathService,
) {} public permissionsService: PermissionsService
) {
super()
}
@ViewChild('textFilterInput') @ViewChild('textFilterInput')
textFilterInput: ElementRef textFilterInput: ElementRef
@ -872,18 +884,46 @@ export class FilterEditorComponent implements OnInit, OnDestroy {
subscription: Subscription subscription: Subscription
ngOnInit() { ngOnInit() {
this.tagService if (
.listAll() this.permissionsService.currentUserCan(
.subscribe((result) => (this.tags = result.results)) PermissionAction.View,
this.correspondentService PermissionType.Tag
.listAll() )
.subscribe((result) => (this.correspondents = result.results)) ) {
this.documentTypeService this.tagService
.listAll() .listAll()
.subscribe((result) => (this.documentTypes = result.results)) .subscribe((result) => (this.tags = result.results))
this.storagePathService }
.listAll() if (
.subscribe((result) => (this.storagePaths = result.results)) 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>() this.textFilterDebounce = new Subject<string>()

View File

@ -13,6 +13,11 @@ import { TagService } from './tag.service'
import { DocumentSuggestions } from 'src/app/data/document-suggestions' import { DocumentSuggestions } from 'src/app/data/document-suggestions'
import { queryParamsFromFilterRules } from '../../utils/query-params' import { queryParamsFromFilterRules } from '../../utils/query-params'
import { StoragePathService } from './storage-path.service' import { StoragePathService } from './storage-path.service'
import {
PermissionAction,
PermissionType,
PermissionsService,
} from '../permissions.service'
export const DOCUMENT_SORT_FIELDS = [ export const DOCUMENT_SORT_FIELDS = [
{ field: 'archive_serial_number', name: $localize`ASN` }, { field: 'archive_serial_number', name: $localize`ASN` },
@ -57,21 +62,40 @@ export class DocumentService extends AbstractPaperlessService<Document> {
private correspondentService: CorrespondentService, private correspondentService: CorrespondentService,
private documentTypeService: DocumentTypeService, private documentTypeService: DocumentTypeService,
private tagService: TagService, private tagService: TagService,
private storagePathService: StoragePathService private storagePathService: StoragePathService,
private permissionsService: PermissionsService
) { ) {
super(http, 'documents') super(http, 'documents')
} }
addObservablesToDocument(doc: Document) { addObservablesToDocument(doc: Document) {
if (doc.correspondent) { if (
doc.correspondent &&
this.permissionsService.currentUserCan(
PermissionAction.View,
PermissionType.Correspondent
)
) {
doc.correspondent$ = this.correspondentService.getCached( doc.correspondent$ = this.correspondentService.getCached(
doc.correspondent 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) 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 doc.tags$ = this.tagService
.getCachedMany(doc.tags) .getCachedMany(doc.tags)
.pipe( .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) doc.storage_path$ = this.storagePathService.getCached(doc.storage_path)
} }
return doc return doc