mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2025-04-09 09:58:20 -05:00
Fix: Dont attempt to retrieve objects for which user doesnt have global permissions (#5612)
This commit is contained in:
parent
4996b7e5f7
commit
5e3d1b26e7
@ -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">
|
||||||
|
@ -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
|
||||||
) {
|
) {
|
||||||
|
@ -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">
|
||||||
|
@ -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()
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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()
|
||||||
|
|
||||||
|
@ -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">
|
||||||
|
@ -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/`
|
||||||
|
)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
@ -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')
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
@ -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(() => {
|
||||||
|
@ -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>()
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user