mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2025-07-28 18:24:38 -05:00
Support edit permissions for mail rules and accounts
This commit is contained in:
@@ -6,7 +6,7 @@
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="col-md-4">
|
||||
<pngx-input-text i18n-title title="Name" formControlName="name" [error]="error?.name"></pngx-input-text>
|
||||
<pngx-input-select i18n-title title="Account" [items]="accounts" formControlName="account"></pngx-input-select>
|
||||
<pngx-input-text i18n-title title="Folder" formControlName="folder" i18n-hint hint="Subfolders must be separated by a delimiter, often a dot ('.') or slash ('/'), but it varies by mail server." [error]="error?.folder"></pngx-input-text>
|
||||
@@ -15,7 +15,7 @@
|
||||
<pngx-input-select i18n-title title="Consumption scope" [items]="consumptionScopeOptions" formControlName="consumption_scope" i18n-hint hint="See docs for .eml processing requirements"></pngx-input-select>
|
||||
<pngx-input-number i18n-title title="Rule order" formControlName="order" [showAdd]="false" [error]="error?.order"></pngx-input-number>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="col-md-4">
|
||||
<p class="small" i18n>Paperless will only process mails that match <em>all</em> of the filters specified below.</p>
|
||||
<pngx-input-text i18n-title title="Filter from" formControlName="filter_from" [error]="error?.filter_from"></pngx-input-text>
|
||||
<pngx-input-text i18n-title title="Filter to" formControlName="filter_to" [error]="error?.filter_to"></pngx-input-text>
|
||||
@@ -23,7 +23,7 @@
|
||||
<pngx-input-text i18n-title title="Filter body" formControlName="filter_body" [error]="error?.filter_body"></pngx-input-text>
|
||||
<pngx-input-text i18n-title title="Filter attachment filename" formControlName="filter_attachment_filename" i18n-hint hint="Only consume documents which entirely match this filename if specified. Wildcards such as *.pdf or *invoice* are allowed. Case insensitive." [error]="error?.filter_attachment_filename"></pngx-input-text>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="col-md-4">
|
||||
<pngx-input-select i18n-title title="Action" [items]="actionOptions" formControlName="action" i18n-hint hint="Action is only performed when documents are consumed from the mail. Mails without attachments remain entirely untouched."></pngx-input-select>
|
||||
<pngx-input-text i18n-title title="Action parameter" *ngIf="showActionParamField" formControlName="action_parameter" [error]="error?.action_parameter"></pngx-input-text>
|
||||
<pngx-input-select i18n-title title="Assign title from" [items]="metadataTitleOptions" formControlName="assign_title_from"></pngx-input-select>
|
||||
|
@@ -5,7 +5,7 @@
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
|
||||
<p class="mb-3" *ngIf="message" [innerHTML]="message | safeHtml"></p>
|
||||
<p *ngIf="!object && message" class="mb-3" [innerHTML]="message | safeHtml"></p>
|
||||
|
||||
<form [formGroup]="form">
|
||||
<pngx-permissions-form [users]="users" formControlName="permissions_form"></pngx-permissions-form>
|
||||
|
@@ -19,7 +19,7 @@ const set_permissions = {
|
||||
users: [1],
|
||||
groups: [],
|
||||
},
|
||||
edit: {
|
||||
change: {
|
||||
users: [1],
|
||||
groups: [],
|
||||
},
|
||||
@@ -78,6 +78,10 @@ describe('PermissionsDialogComponent', () => {
|
||||
})
|
||||
|
||||
it('should return permissions', () => {
|
||||
expect(component.permissions).toEqual({
|
||||
owner: null,
|
||||
set_permissions: null,
|
||||
})
|
||||
component.form.get('permissions_form').setValue(set_permissions)
|
||||
expect(component.permissions).toEqual(set_permissions)
|
||||
})
|
||||
@@ -87,4 +91,16 @@ describe('PermissionsDialogComponent', () => {
|
||||
component.cancelClicked()
|
||||
expect(closeSpy).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should support edit permissions on object', () => {
|
||||
let obj = {
|
||||
id: 1,
|
||||
name: 'account1',
|
||||
owner: set_permissions.owner,
|
||||
permissions: set_permissions.set_permissions,
|
||||
}
|
||||
component.object = obj
|
||||
expect(component.title).toEqual(`Edit permissions for ${obj.name}`)
|
||||
expect(component.permissions).toEqual(set_permissions)
|
||||
})
|
||||
})
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import { Component, EventEmitter, Input, Output } from '@angular/core'
|
||||
import { FormControl, FormGroup } from '@angular/forms'
|
||||
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { ObjectWithPermissions } from 'src/app/data/object-with-permissions'
|
||||
import { PaperlessUser } from 'src/app/data/paperless-user'
|
||||
import { UserService } from 'src/app/services/rest/user.service'
|
||||
|
||||
@@ -11,6 +12,7 @@ import { UserService } from 'src/app/services/rest/user.service'
|
||||
})
|
||||
export class PermissionsDialogComponent {
|
||||
users: PaperlessUser[]
|
||||
private o: ObjectWithPermissions = undefined
|
||||
|
||||
constructor(
|
||||
public activeModal: NgbActiveModal,
|
||||
@@ -19,11 +21,24 @@ export class PermissionsDialogComponent {
|
||||
this.userService.listAll().subscribe((r) => (this.users = r.results))
|
||||
}
|
||||
|
||||
@Output()
|
||||
public confirmClicked = new EventEmitter()
|
||||
|
||||
@Input()
|
||||
title = $localize`Set Permissions`
|
||||
title = $localize`Set permissions`
|
||||
|
||||
set object(o: ObjectWithPermissions) {
|
||||
this.o = o
|
||||
this.title = $localize`Edit permissions for ` + o['name']
|
||||
this.form.patchValue({
|
||||
permissions_form: {
|
||||
owner: o.owner,
|
||||
set_permissions: o.permissions,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
get object(): ObjectWithPermissions {
|
||||
return this.o
|
||||
}
|
||||
|
||||
form = new FormGroup({
|
||||
permissions_form: new FormControl(),
|
||||
@@ -39,7 +54,6 @@ export class PermissionsDialogComponent {
|
||||
}
|
||||
}
|
||||
|
||||
@Input()
|
||||
message = $localize`Note that permissions set here will override any existing permissions`
|
||||
|
||||
cancelClicked() {
|
||||
|
@@ -343,6 +343,7 @@
|
||||
<div class="col">
|
||||
<div class="btn-group">
|
||||
<button *pngxIfPermissions="{ action: PermissionAction.Change, type: PermissionType.MailAccount }" [disabled]="!userCanEdit(account)" class="btn btn-sm btn-primary" type="button" (click)="editMailAccount(account)" i18n>Edit</button>
|
||||
<button *pngxIfOwner="rule" class="btn btn-sm btn-primary" type="button" (click)="editPermissions(account)" i18n>Permissions</button>
|
||||
<button *pngxIfPermissions="{ action: PermissionAction.Delete, type: PermissionType.MailAccount }" [disabled]="!userIsOwner(account)" class="btn btn-sm btn-outline-danger" type="button" (click)="deleteMailAccount(account)" i18n>Delete</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -380,6 +381,7 @@
|
||||
<div class="col">
|
||||
<div class="btn-group">
|
||||
<button *pngxIfPermissions="{ action: PermissionAction.Change, type: PermissionType.MailRule }" [disabled]="!userCanEdit(rule)" class="btn btn-sm btn-primary" type="button" (click)="editMailRule(rule)" i18n>Edit</button>
|
||||
<button *pngxIfOwner="rule" class="btn btn-sm btn-primary" type="button" (click)="editPermissions(rule)" i18n>Permissions</button>
|
||||
<button *pngxIfPermissions="{ action: PermissionAction.Delete, type: PermissionType.MailRule }" [disabled]="!userIsOwner(rule)" class="btn btn-sm btn-outline-danger" type="button" (click)="deleteMailRule(rule)" i18n>Delete</button>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -52,6 +52,9 @@ import { TagsComponent } from '../../common/input/tags/tags.component'
|
||||
import { TextComponent } from '../../common/input/text/text.component'
|
||||
import { PageHeaderComponent } from '../../common/page-header/page-header.component'
|
||||
import { SettingsComponent } from './settings.component'
|
||||
import { PermissionsDialogComponent } from '../../common/permissions-dialog/permissions-dialog.component'
|
||||
import { PermissionsFormComponent } from '../../common/input/permissions/permissions-form/permissions-form.component'
|
||||
import { IfOwnerDirective } from 'src/app/directives/if-owner.directive'
|
||||
|
||||
const savedViews = [
|
||||
{ id: 1, name: 'view1' },
|
||||
@@ -70,8 +73,8 @@ const mailAccounts = [
|
||||
{ id: 2, name: 'account2' },
|
||||
]
|
||||
const mailRules = [
|
||||
{ id: 1, name: 'rule1', owner: 1 },
|
||||
{ id: 2, name: 'rule2', owner: 2 },
|
||||
{ id: 1, name: 'rule1', owner: 1, account: 1 },
|
||||
{ id: 2, name: 'rule2', owner: 2, account: 2 },
|
||||
]
|
||||
|
||||
describe('SettingsComponent', () => {
|
||||
@@ -110,6 +113,9 @@ describe('SettingsComponent', () => {
|
||||
MailRuleEditDialogComponent,
|
||||
PermissionsUserComponent,
|
||||
PermissionsGroupComponent,
|
||||
IfOwnerDirective,
|
||||
PermissionsDialogComponent,
|
||||
PermissionsFormComponent,
|
||||
],
|
||||
providers: [CustomDatePipe, DatePipe, PermissionsGuard],
|
||||
imports: [
|
||||
@@ -591,4 +597,69 @@ describe('SettingsComponent', () => {
|
||||
expect(listAllSpy).toHaveBeenCalled()
|
||||
expect(toastInfoSpy).toHaveBeenCalledWith('Deleted mail rule')
|
||||
})
|
||||
|
||||
it('should support edit permissions on mail rule objects', () => {
|
||||
completeSetup()
|
||||
const perms = {
|
||||
owner: 99,
|
||||
set_permissions: {
|
||||
view: {
|
||||
users: [1],
|
||||
groups: [2],
|
||||
},
|
||||
change: {
|
||||
users: [3],
|
||||
groups: [4],
|
||||
},
|
||||
},
|
||||
}
|
||||
let modal: NgbModalRef
|
||||
modalService.activeInstances.subscribe((refs) => (modal = refs[0]))
|
||||
const toastErrorSpy = jest.spyOn(toastService, 'showError')
|
||||
const toastInfoSpy = jest.spyOn(toastService, 'showInfo')
|
||||
const rulePatchSpy = jest.spyOn(mailRuleService, 'patch')
|
||||
component.editPermissions(mailRules[0] as PaperlessMailRule)
|
||||
expect(modal).not.toBeUndefined()
|
||||
let dialog = modal.componentInstance as PermissionsDialogComponent
|
||||
expect(dialog.object).toEqual(mailRules[0])
|
||||
|
||||
rulePatchSpy.mockReturnValueOnce(
|
||||
throwError(() => new Error('error saving perms'))
|
||||
)
|
||||
dialog.confirmClicked.emit(perms)
|
||||
expect(rulePatchSpy).toHaveBeenCalled()
|
||||
expect(toastErrorSpy).toHaveBeenCalled()
|
||||
rulePatchSpy.mockReturnValueOnce(of(mailRules[0] as PaperlessMailRule))
|
||||
dialog.confirmClicked.emit(perms)
|
||||
expect(toastInfoSpy).toHaveBeenCalledWith('Permissions updated')
|
||||
|
||||
modalService.dismissAll()
|
||||
})
|
||||
|
||||
it('should support edit permissions on mail account objects', () => {
|
||||
completeSetup()
|
||||
const perms = {
|
||||
owner: 99,
|
||||
set_permissions: {
|
||||
view: {
|
||||
users: [1],
|
||||
groups: [2],
|
||||
},
|
||||
change: {
|
||||
users: [3],
|
||||
groups: [4],
|
||||
},
|
||||
},
|
||||
}
|
||||
let modal: NgbModalRef
|
||||
modalService.activeInstances.subscribe((refs) => (modal = refs[0]))
|
||||
const accountPatchSpy = jest.spyOn(mailAccountService, 'patch')
|
||||
component.editPermissions(mailAccounts[0] as PaperlessMailAccount)
|
||||
expect(modal).not.toBeUndefined()
|
||||
let dialog = modal.componentInstance as PermissionsDialogComponent
|
||||
expect(dialog.object).toEqual(mailAccounts[0])
|
||||
dialog = modal.componentInstance as PermissionsDialogComponent
|
||||
dialog.confirmClicked.emit(perms)
|
||||
expect(accountPatchSpy).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
@@ -51,6 +51,8 @@ import {
|
||||
PermissionType,
|
||||
PermissionsService,
|
||||
} from 'src/app/services/permissions.service'
|
||||
import { PermissionsDialogComponent } from '../../common/permissions-dialog/permissions-dialog.component'
|
||||
import { AbstractPaperlessService } from 'src/app/services/rest/abstract-paperless-service'
|
||||
|
||||
enum SettingsNavIDs {
|
||||
General = 1,
|
||||
@@ -307,14 +309,14 @@ export class SettingsComponent
|
||||
(!this.mailAccounts || !this.mailRules)
|
||||
) {
|
||||
this.mailAccountService
|
||||
.listAll()
|
||||
.listAll(null, null, { full_perms: true })
|
||||
.pipe(first())
|
||||
.subscribe({
|
||||
next: (r) => {
|
||||
this.mailAccounts = r.results
|
||||
|
||||
this.mailRuleService
|
||||
.listAll()
|
||||
.listAll(null, null, { full_perms: true })
|
||||
.pipe(first())
|
||||
.subscribe({
|
||||
next: (r) => {
|
||||
@@ -889,10 +891,12 @@ export class SettingsComponent
|
||||
$localize`Saved account "${newMailAccount.name}".`
|
||||
)
|
||||
this.mailAccountService.clearCache()
|
||||
this.mailAccountService.listAll().subscribe((r) => {
|
||||
this.mailAccounts = r.results
|
||||
this.initialize()
|
||||
})
|
||||
this.mailAccountService
|
||||
.listAll(null, null, { full_perms: true })
|
||||
.subscribe((r) => {
|
||||
this.mailAccounts = r.results
|
||||
this.initialize()
|
||||
})
|
||||
})
|
||||
modal.componentInstance.failed
|
||||
.pipe(takeUntil(this.unsubscribeNotifier))
|
||||
@@ -917,10 +921,12 @@ export class SettingsComponent
|
||||
modal.close()
|
||||
this.toastService.showInfo($localize`Deleted mail account`)
|
||||
this.mailAccountService.clearCache()
|
||||
this.mailAccountService.listAll().subscribe((r) => {
|
||||
this.mailAccounts = r.results
|
||||
this.initialize(true)
|
||||
})
|
||||
this.mailAccountService
|
||||
.listAll(null, null, { full_perms: true })
|
||||
.subscribe((r) => {
|
||||
this.mailAccounts = r.results
|
||||
this.initialize(true)
|
||||
})
|
||||
},
|
||||
error: (e) => {
|
||||
this.toastService.showError(
|
||||
@@ -946,11 +952,13 @@ export class SettingsComponent
|
||||
.subscribe((newMailRule) => {
|
||||
this.toastService.showInfo($localize`Saved rule "${newMailRule.name}".`)
|
||||
this.mailRuleService.clearCache()
|
||||
this.mailRuleService.listAll().subscribe((r) => {
|
||||
this.mailRules = r.results
|
||||
this.mailRuleService
|
||||
.listAll(null, null, { full_perms: true })
|
||||
.subscribe((r) => {
|
||||
this.mailRules = r.results
|
||||
|
||||
this.initialize(true)
|
||||
})
|
||||
this.initialize(true)
|
||||
})
|
||||
})
|
||||
modal.componentInstance.failed
|
||||
.pipe(takeUntil(this.unsubscribeNotifier))
|
||||
@@ -975,10 +983,12 @@ export class SettingsComponent
|
||||
modal.close()
|
||||
this.toastService.showInfo($localize`Deleted mail rule`)
|
||||
this.mailRuleService.clearCache()
|
||||
this.mailRuleService.listAll().subscribe((r) => {
|
||||
this.mailRules = r.results
|
||||
this.initialize(true)
|
||||
})
|
||||
this.mailRuleService
|
||||
.listAll(null, null, { full_perms: true })
|
||||
.subscribe((r) => {
|
||||
this.mailRules = r.results
|
||||
this.initialize(true)
|
||||
})
|
||||
},
|
||||
error: (e) => {
|
||||
this.toastService.showError($localize`Error deleting mail rule.`, e)
|
||||
@@ -986,4 +996,30 @@ export class SettingsComponent
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
editPermissions(object: PaperlessMailRule | PaperlessMailAccount) {
|
||||
const modal = this.modalService.open(PermissionsDialogComponent, {
|
||||
backdrop: 'static',
|
||||
})
|
||||
const dialog: PermissionsDialogComponent =
|
||||
modal.componentInstance as PermissionsDialogComponent
|
||||
dialog.object = object
|
||||
modal.componentInstance.confirmClicked.subscribe((permissions) => {
|
||||
modal.componentInstance.buttonsEnabled = false
|
||||
const service: AbstractPaperlessService<
|
||||
PaperlessMailRule | PaperlessMailAccount
|
||||
> = 'account' in object ? this.mailRuleService : this.mailAccountService
|
||||
object.owner = permissions['owner']
|
||||
object['set_permissions'] = permissions['set_permissions']
|
||||
service.patch(object).subscribe({
|
||||
next: () => {
|
||||
this.toastService.showInfo($localize`Permissions updated`)
|
||||
modal.close()
|
||||
},
|
||||
error: (e) => {
|
||||
this.toastService.showError($localize`Error updating permissions`, e)
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user