mirror of
				https://github.com/paperless-ngx/paperless-ngx.git
				synced 2025-10-30 03:56:23 -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
	 shamoon
					shamoon