mirror of
				https://github.com/paperless-ngx/paperless-ngx.git
				synced 2025-10-30 03:56:23 -05:00 
			
		
		
		
	Refactor permissions API endpoints, UI group permissions
This commit is contained in:
		| @@ -81,10 +81,10 @@ export class AppComponent implements OnInit, OnDestroy { | |||||||
|           this.showNotification(SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_SUCCESS) |           this.showNotification(SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_SUCCESS) | ||||||
|         ) { |         ) { | ||||||
|           if ( |           if ( | ||||||
|             this.permissionsService.currentUserCan({ |             this.permissionsService.currentUserCan( | ||||||
|               action: PermissionAction.View, |               PermissionAction.View, | ||||||
|               type: PermissionType.Document, |               PermissionType.Document | ||||||
|             }) |             ) | ||||||
|           ) { |           ) { | ||||||
|             this.toastService.show({ |             this.toastService.show({ | ||||||
|               title: $localize`Document added`, |               title: $localize`Document added`, | ||||||
| @@ -246,10 +246,10 @@ export class AppComponent implements OnInit, OnDestroy { | |||||||
|   public get dragDropEnabled(): boolean { |   public get dragDropEnabled(): boolean { | ||||||
|     return ( |     return ( | ||||||
|       !this.router.url.includes('dashboard') && |       !this.router.url.includes('dashboard') && | ||||||
|       this.permissionsService.currentUserCan({ |       this.permissionsService.currentUserCan( | ||||||
|         action: PermissionAction.Add, |         PermissionAction.Add, | ||||||
|         type: PermissionType.Document, |         PermissionType.Document | ||||||
|       }) |       ) | ||||||
|     ) |     ) | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -84,6 +84,10 @@ import { GroupEditDialogComponent } from './components/common/edit-dialog/group- | |||||||
| import { PermissionsSelectComponent } from './components/common/permissions-select/permissions-select.component' | import { PermissionsSelectComponent } from './components/common/permissions-select/permissions-select.component' | ||||||
| import { MailAccountEditDialogComponent } from './components/common/edit-dialog/mail-account-edit-dialog/mail-account-edit-dialog.component' | import { MailAccountEditDialogComponent } from './components/common/edit-dialog/mail-account-edit-dialog/mail-account-edit-dialog.component' | ||||||
| import { MailRuleEditDialogComponent } from './components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component' | import { MailRuleEditDialogComponent } from './components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component' | ||||||
|  | import { PermissionsUserComponent } from './components/common/input/permissions-user/permissions-user.component' | ||||||
|  | import { PermissionsGroupComponent } from './components/common/input/permissions-group/permissions-group.component' | ||||||
|  | import { IfOwnerDirective } from './directives/if-owner.directive' | ||||||
|  | import { IfObjectPermissionsDirective } from './directives/if-object-permissions.directive' | ||||||
|  |  | ||||||
| import localeBe from '@angular/common/locales/be' | import localeBe from '@angular/common/locales/be' | ||||||
| import localeCs from '@angular/common/locales/cs' | import localeCs from '@angular/common/locales/cs' | ||||||
| @@ -104,9 +108,6 @@ import localeSr from '@angular/common/locales/sr' | |||||||
| import localeSv from '@angular/common/locales/sv' | import localeSv from '@angular/common/locales/sv' | ||||||
| import localeTr from '@angular/common/locales/tr' | import localeTr from '@angular/common/locales/tr' | ||||||
| import localeZh from '@angular/common/locales/zh' | import localeZh from '@angular/common/locales/zh' | ||||||
| import { ShareUserComponent } from './components/common/input/share-user/share-user.component' |  | ||||||
| import { IfOwnerDirective } from './directives/if-owner.directive' |  | ||||||
| import { IfObjectPermissionsDirective } from './directives/if-object-permissions.directive' |  | ||||||
|  |  | ||||||
| registerLocaleData(localeBe) | registerLocaleData(localeBe) | ||||||
| registerLocaleData(localeCs) | registerLocaleData(localeCs) | ||||||
| @@ -198,7 +199,8 @@ function initializeApp(settings: SettingsService) { | |||||||
|     PermissionsSelectComponent, |     PermissionsSelectComponent, | ||||||
|     MailAccountEditDialogComponent, |     MailAccountEditDialogComponent, | ||||||
|     MailRuleEditDialogComponent, |     MailRuleEditDialogComponent, | ||||||
|     ShareUserComponent, |     PermissionsUserComponent, | ||||||
|  |     PermissionsGroupComponent, | ||||||
|     IfOwnerDirective, |     IfOwnerDirective, | ||||||
|     IfObjectPermissionsDirective, |     IfObjectPermissionsDirective, | ||||||
|   ], |   ], | ||||||
|   | |||||||
| @@ -11,11 +11,19 @@ | |||||||
|     <app-input-text *ngIf="patternRequired" i18n-title title="Matching pattern" formControlName="match" [error]="error?.match"></app-input-text> |     <app-input-text *ngIf="patternRequired" i18n-title title="Matching pattern" formControlName="match" [error]="error?.match"></app-input-text> | ||||||
|     <app-input-check *ngIf="patternRequired" i18n-title title="Case insensitive" formControlName="is_insensitive" novalidate></app-input-check> |     <app-input-check *ngIf="patternRequired" i18n-title title="Case insensitive" formControlName="is_insensitive" novalidate></app-input-check> | ||||||
|  |  | ||||||
|     <div *ifOwner="object?.owner"> |     <div *ifOwner="object"> | ||||||
|       <h5 i18n>Permissions</h5> |       <h5 i18n>Permissions</h5> | ||||||
|       <div formGroupName="set_permissions"> |       <div formGroupName="set_permissions"> | ||||||
|         <app-share-user type="view" formControlName="view"></app-share-user> |         <h6 i18n>View</h6> | ||||||
|         <app-share-user type="change" formControlName="change"></app-share-user> |         <div formGroupName="view"> | ||||||
|  |           <app-permissions-user type="view" formControlName="users"></app-permissions-user> | ||||||
|  |           <app-permissions-group type="view" formControlName="groups"></app-permissions-group> | ||||||
|  |         </div> | ||||||
|  |         <h6 i18n>Edit</h6> | ||||||
|  |         <div formGroupName="change"> | ||||||
|  |           <app-permissions-user type="change" formControlName="users"></app-permissions-user> | ||||||
|  |           <app-permissions-group type="change" formControlName="groups"></app-permissions-group> | ||||||
|  |         </div> | ||||||
|       </div> |       </div> | ||||||
|     </div> |     </div> | ||||||
|  |  | ||||||
|   | |||||||
| @@ -31,8 +31,14 @@ export class CorrespondentEditDialogComponent extends EditDialogComponent<Paperl | |||||||
|       match: new FormControl(''), |       match: new FormControl(''), | ||||||
|       is_insensitive: new FormControl(true), |       is_insensitive: new FormControl(true), | ||||||
|       set_permissions: new FormGroup({ |       set_permissions: new FormGroup({ | ||||||
|         view: new FormControl(null), |         view: new FormGroup({ | ||||||
|         change: new FormControl(null), |           users: new FormControl(null), | ||||||
|  |           groups: new FormControl(null), | ||||||
|  |         }), | ||||||
|  |         change: new FormGroup({ | ||||||
|  |           users: new FormControl(null), | ||||||
|  |           groups: new FormControl(null), | ||||||
|  |         }), | ||||||
|       }), |       }), | ||||||
|     }) |     }) | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -11,11 +11,19 @@ | |||||||
|       <app-input-text *ngIf="patternRequired" i18n-title title="Matching pattern" formControlName="match" [error]="error?.match"></app-input-text> |       <app-input-text *ngIf="patternRequired" i18n-title title="Matching pattern" formControlName="match" [error]="error?.match"></app-input-text> | ||||||
|       <app-input-check *ngIf="patternRequired" i18n-title title="Case insensitive" formControlName="is_insensitive"></app-input-check> |       <app-input-check *ngIf="patternRequired" i18n-title title="Case insensitive" formControlName="is_insensitive"></app-input-check> | ||||||
|  |  | ||||||
|       <div *ifOwner="object?.owner"> |       <div *ifOwner="object"> | ||||||
|         <h5 i18n>Permissions</h5> |         <h5 i18n>Permissions</h5> | ||||||
|         <div formGroupName="set_permissions"> |         <div formGroupName="set_permissions"> | ||||||
|           <app-share-user type="view" formControlName="view"></app-share-user> |           <h6 i18n>View</h6> | ||||||
|           <app-share-user type="change" formControlName="change"></app-share-user> |           <div formGroupName="view"> | ||||||
|  |             <app-permissions-user type="view" formControlName="users"></app-permissions-user> | ||||||
|  |             <app-permissions-group type="view" formControlName="groups"></app-permissions-group> | ||||||
|  |           </div> | ||||||
|  |           <h6 i18n>Edit</h6> | ||||||
|  |           <div formGroupName="change"> | ||||||
|  |             <app-permissions-user type="change" formControlName="users"></app-permissions-user> | ||||||
|  |             <app-permissions-group type="change" formControlName="groups"></app-permissions-group> | ||||||
|  |           </div> | ||||||
|         </div> |         </div> | ||||||
|       </div> |       </div> | ||||||
|  |  | ||||||
|   | |||||||
| @@ -31,8 +31,14 @@ export class DocumentTypeEditDialogComponent extends EditDialogComponent<Paperle | |||||||
|       match: new FormControl(''), |       match: new FormControl(''), | ||||||
|       is_insensitive: new FormControl(true), |       is_insensitive: new FormControl(true), | ||||||
|       set_permissions: new FormGroup({ |       set_permissions: new FormGroup({ | ||||||
|         view: new FormControl(null), |         view: new FormGroup({ | ||||||
|         change: new FormControl(null), |           users: new FormControl(null), | ||||||
|  |           groups: new FormControl(null), | ||||||
|  |         }), | ||||||
|  |         change: new FormGroup({ | ||||||
|  |           users: new FormControl(null), | ||||||
|  |           groups: new FormControl(null), | ||||||
|  |         }), | ||||||
|       }), |       }), | ||||||
|     }) |     }) | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -39,14 +39,7 @@ export abstract class EditDialogComponent< | |||||||
|   ngOnInit(): void { |   ngOnInit(): void { | ||||||
|     if (this.object != null) { |     if (this.object != null) { | ||||||
|       if (this.object['permissions']) { |       if (this.object['permissions']) { | ||||||
|         this.object['set_permissions'] = { |         this.object['set_permissions'] = this.object['permissions'] | ||||||
|           view: (this.object as ObjectWithPermissions).permissions |  | ||||||
|             .filter((p) => (p[1] as string).includes('view')) |  | ||||||
|             .map((p) => p[0]), |  | ||||||
|           change: (this.object as ObjectWithPermissions).permissions |  | ||||||
|             .filter((p) => (p[1] as string).includes('change')) |  | ||||||
|             .map((p) => p[0]), |  | ||||||
|         } |  | ||||||
|       } |       } | ||||||
|       this.objectForm.patchValue(this.object) |       this.objectForm.patchValue(this.object) | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -16,11 +16,19 @@ | |||||||
|     <app-input-text *ngIf="patternRequired" i18n-title title="Matching pattern" formControlName="match" [error]="error?.match"></app-input-text> |     <app-input-text *ngIf="patternRequired" i18n-title title="Matching pattern" formControlName="match" [error]="error?.match"></app-input-text> | ||||||
|     <app-input-check *ngIf="patternRequired" i18n-title title="Case insensitive" formControlName="is_insensitive"></app-input-check> |     <app-input-check *ngIf="patternRequired" i18n-title title="Case insensitive" formControlName="is_insensitive"></app-input-check> | ||||||
|  |  | ||||||
|     <div *ifOwner="object?.owner"> |     <div *ifOwner="object"> | ||||||
|       <h5 i18n>Permissions</h5> |       <h5 i18n>Permissions</h5> | ||||||
|       <div formGroupName="set_permissions"> |       <div formGroupName="set_permissions"> | ||||||
|         <app-share-user type="view" formControlName="view"></app-share-user> |         <h6 i18n>View</h6> | ||||||
|         <app-share-user type="change" formControlName="change"></app-share-user> |         <div formGroupName="view"> | ||||||
|  |           <app-permissions-user type="view" formControlName="users"></app-permissions-user> | ||||||
|  |           <app-permissions-group type="view" formControlName="groups"></app-permissions-group> | ||||||
|  |         </div> | ||||||
|  |         <h6 i18n>Edit</h6> | ||||||
|  |         <div formGroupName="change"> | ||||||
|  |           <app-permissions-user type="change" formControlName="users"></app-permissions-user> | ||||||
|  |           <app-permissions-group type="change" formControlName="groups"></app-permissions-group> | ||||||
|  |         </div> | ||||||
|       </div> |       </div> | ||||||
|     </div> |     </div> | ||||||
|  |  | ||||||
|   | |||||||
| @@ -42,8 +42,14 @@ export class StoragePathEditDialogComponent extends EditDialogComponent<Paperles | |||||||
|       match: new FormControl(''), |       match: new FormControl(''), | ||||||
|       is_insensitive: new FormControl(true), |       is_insensitive: new FormControl(true), | ||||||
|       set_permissions: new FormGroup({ |       set_permissions: new FormGroup({ | ||||||
|         view: new FormControl(null), |         view: new FormGroup({ | ||||||
|         change: new FormControl(null), |           users: new FormControl(null), | ||||||
|  |           groups: new FormControl(null), | ||||||
|  |         }), | ||||||
|  |         change: new FormGroup({ | ||||||
|  |           users: new FormControl(null), | ||||||
|  |           groups: new FormControl(null), | ||||||
|  |         }), | ||||||
|       }), |       }), | ||||||
|     }) |     }) | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -14,11 +14,19 @@ | |||||||
|       <app-input-text *ngIf="patternRequired" i18n-title title="Matching pattern" formControlName="match" [error]="error?.match"></app-input-text> |       <app-input-text *ngIf="patternRequired" i18n-title title="Matching pattern" formControlName="match" [error]="error?.match"></app-input-text> | ||||||
|       <app-input-check *ngIf="patternRequired" i18n-title title="Case insensitive" formControlName="is_insensitive"></app-input-check> |       <app-input-check *ngIf="patternRequired" i18n-title title="Case insensitive" formControlName="is_insensitive"></app-input-check> | ||||||
|  |  | ||||||
|       <div *ifOwner="object?.owner"> |       <div *ifOwner="object"> | ||||||
|         <h5 i18n>Permissions</h5> |         <h5 i18n>Permissions</h5> | ||||||
|         <div formGroupName="set_permissions"> |         <div formGroupName="set_permissions"> | ||||||
|           <app-share-user type="view" formControlName="view"></app-share-user> |           <h6 i18n>View</h6> | ||||||
|           <app-share-user type="change" formControlName="change"></app-share-user> |           <div formGroupName="view"> | ||||||
|  |             <app-permissions-user type="view" formControlName="users"></app-permissions-user> | ||||||
|  |             <app-permissions-group type="view" formControlName="groups"></app-permissions-group> | ||||||
|  |           </div> | ||||||
|  |           <h6 i18n>Edit</h6> | ||||||
|  |           <div formGroupName="change"> | ||||||
|  |             <app-permissions-user type="change" formControlName="users"></app-permissions-user> | ||||||
|  |             <app-permissions-group type="change" formControlName="groups"></app-permissions-group> | ||||||
|  |           </div> | ||||||
|         </div> |         </div> | ||||||
|       </div> |       </div> | ||||||
|  |  | ||||||
|   | |||||||
| @@ -34,8 +34,14 @@ export class TagEditDialogComponent extends EditDialogComponent<PaperlessTag> { | |||||||
|       match: new FormControl(''), |       match: new FormControl(''), | ||||||
|       is_insensitive: new FormControl(true), |       is_insensitive: new FormControl(true), | ||||||
|       set_permissions: new FormGroup({ |       set_permissions: new FormGroup({ | ||||||
|         view: new FormControl(null), |         view: new FormGroup({ | ||||||
|         change: new FormControl(null), |           users: new FormControl(null), | ||||||
|  |           groups: new FormControl(null), | ||||||
|  |         }), | ||||||
|  |         change: new FormGroup({ | ||||||
|  |           users: new FormControl(null), | ||||||
|  |           groups: new FormControl(null), | ||||||
|  |         }), | ||||||
|       }), |       }), | ||||||
|     }) |     }) | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -0,0 +1,15 @@ | |||||||
|  | <div class="mb-3 paperless-input-select"> | ||||||
|  |     <label class="form-label" [for]="inputId">{{title}}</label> | ||||||
|  |       <div> | ||||||
|  |         <ng-select name="inputId" [(ngModel)]="value" | ||||||
|  |           [disabled]="disabled" | ||||||
|  |           clearable="true" | ||||||
|  |           [items]="groups" | ||||||
|  |           multiple="true" | ||||||
|  |           bindLabel="name" | ||||||
|  |           bindValue="id" | ||||||
|  |           (change)="onChange(value)"> | ||||||
|  |         </ng-select> | ||||||
|  |       </div> | ||||||
|  |     <small *ngIf="hint" class="form-text text-muted">{{hint}}</small> | ||||||
|  |   </div> | ||||||
| @@ -0,0 +1,48 @@ | |||||||
|  | import { Component, forwardRef, Input, OnInit } from '@angular/core' | ||||||
|  | import { NG_VALUE_ACCESSOR } from '@angular/forms' | ||||||
|  | import { first } from 'rxjs/operators' | ||||||
|  | import { PaperlessGroup } from 'src/app/data/paperless-group' | ||||||
|  | import { GroupService } from 'src/app/services/rest/group.service' | ||||||
|  | import { SettingsService } from 'src/app/services/settings.service' | ||||||
|  | import { AbstractInputComponent } from '../abstract-input' | ||||||
|  |  | ||||||
|  | @Component({ | ||||||
|  |   providers: [ | ||||||
|  |     { | ||||||
|  |       provide: NG_VALUE_ACCESSOR, | ||||||
|  |       useExisting: forwardRef(() => PermissionsGroupComponent), | ||||||
|  |       multi: true, | ||||||
|  |     }, | ||||||
|  |   ], | ||||||
|  |   selector: 'app-permissions-group', | ||||||
|  |   templateUrl: './permissions-group.component.html', | ||||||
|  |   styleUrls: ['./permissions-group.component.scss'], | ||||||
|  | }) | ||||||
|  | export class PermissionsGroupComponent | ||||||
|  |   extends AbstractInputComponent<PaperlessGroup> | ||||||
|  |   implements OnInit | ||||||
|  | { | ||||||
|  |   groups: PaperlessGroup[] | ||||||
|  |  | ||||||
|  |   @Input() | ||||||
|  |   type: string | ||||||
|  |  | ||||||
|  |   constructor(groupService: GroupService, settings: SettingsService) { | ||||||
|  |     super() | ||||||
|  |     groupService | ||||||
|  |       .listAll() | ||||||
|  |       .pipe(first()) | ||||||
|  |       .subscribe((result) => (this.groups = result.results)) | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   ngOnInit(): void { | ||||||
|  |     if (this.type == 'view') { | ||||||
|  |       this.title = $localize`Groups can view` | ||||||
|  |     } else if (this.type == 'change') { | ||||||
|  |       this.title = $localize`Groups can edit` | ||||||
|  |       this.hint = $localize`Edit permissions also grant viewing permissions` | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     super.ngOnInit() | ||||||
|  |   } | ||||||
|  | } | ||||||
| @@ -3,21 +3,22 @@ import { NG_VALUE_ACCESSOR } from '@angular/forms' | |||||||
| import { first } from 'rxjs/operators' | import { first } from 'rxjs/operators' | ||||||
| import { PaperlessUser } from 'src/app/data/paperless-user' | import { PaperlessUser } from 'src/app/data/paperless-user' | ||||||
| import { UserService } from 'src/app/services/rest/user.service' | import { UserService } from 'src/app/services/rest/user.service' | ||||||
|  | import { SettingsService } from 'src/app/services/settings.service' | ||||||
| import { AbstractInputComponent } from '../abstract-input' | import { AbstractInputComponent } from '../abstract-input' | ||||||
| 
 | 
 | ||||||
| @Component({ | @Component({ | ||||||
|   providers: [ |   providers: [ | ||||||
|     { |     { | ||||||
|       provide: NG_VALUE_ACCESSOR, |       provide: NG_VALUE_ACCESSOR, | ||||||
|       useExisting: forwardRef(() => ShareUserComponent), |       useExisting: forwardRef(() => PermissionsUserComponent), | ||||||
|       multi: true, |       multi: true, | ||||||
|     }, |     }, | ||||||
|   ], |   ], | ||||||
|   selector: 'app-share-user', |   selector: 'app-permissions-user', | ||||||
|   templateUrl: './share-user.component.html', |   templateUrl: './permissions-user.component.html', | ||||||
|   styleUrls: ['./share-user.component.scss'], |   styleUrls: ['./permissions-user.component.scss'], | ||||||
| }) | }) | ||||||
| export class ShareUserComponent | export class PermissionsUserComponent | ||||||
|   extends AbstractInputComponent<PaperlessUser> |   extends AbstractInputComponent<PaperlessUser> | ||||||
|   implements OnInit |   implements OnInit | ||||||
| { | { | ||||||
| @@ -26,12 +27,17 @@ export class ShareUserComponent | |||||||
|   @Input() |   @Input() | ||||||
|   type: string |   type: string | ||||||
| 
 | 
 | ||||||
|   constructor(userService: UserService) { |   constructor(userService: UserService, settings: SettingsService) { | ||||||
|     super() |     super() | ||||||
|     userService |     userService | ||||||
|       .listAll() |       .listAll() | ||||||
|       .pipe(first()) |       .pipe(first()) | ||||||
|       .subscribe((result) => (this.users = result.results)) |       .subscribe( | ||||||
|  |         (result) => | ||||||
|  |           (this.users = result.results.filter( | ||||||
|  |             (u) => u.id !== settings.currentUser.id | ||||||
|  |           )) | ||||||
|  |       ) | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   ngOnInit(): void { |   ngOnInit(): void { | ||||||
| @@ -156,18 +156,18 @@ export class PermissionsSelectComponent | |||||||
|     if (this._inheritedPermissions.length == 0) return false |     if (this._inheritedPermissions.length == 0) return false | ||||||
|     else if (actionKey) { |     else if (actionKey) { | ||||||
|       return this._inheritedPermissions.includes( |       return this._inheritedPermissions.includes( | ||||||
|         this.permissionsService.getPermissionCode({ |         this.permissionsService.getPermissionCode( | ||||||
|           action: PermissionAction[actionKey], |           PermissionAction[actionKey], | ||||||
|           type: PermissionType[typeKey], |           PermissionType[typeKey] | ||||||
|         }) |         ) | ||||||
|       ) |       ) | ||||||
|     } else { |     } else { | ||||||
|       return Object.values(PermissionAction).every((action) => { |       return Object.values(PermissionAction).every((action) => { | ||||||
|         return this._inheritedPermissions.includes( |         return this._inheritedPermissions.includes( | ||||||
|           this.permissionsService.getPermissionCode({ |           this.permissionsService.getPermissionCode( | ||||||
|             action: action as PermissionAction, |             action as PermissionAction, | ||||||
|             type: PermissionType[typeKey], |             PermissionType[typeKey] | ||||||
|           }) |           ) | ||||||
|         ) |         ) | ||||||
|       }) |       }) | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -5,7 +5,7 @@ | |||||||
|       <div class="input-group-text" i18n>of {{previewNumPages}}</div> |       <div class="input-group-text" i18n>of {{previewNumPages}}</div> | ||||||
|     </div> |     </div> | ||||||
|  |  | ||||||
|     <button *ifOwner="document?.owner" type="button" class="btn btn-sm btn-outline-danger me-2 ms-auto" (click)="delete()"> |     <button *ifOwner="document" type="button" class="btn btn-sm btn-outline-danger me-2 ms-auto" (click)="delete()"> | ||||||
|         <svg class="buttonicon" fill="currentColor"> |         <svg class="buttonicon" fill="currentColor"> | ||||||
|             <use xlink:href="assets/bootstrap-icons.svg#trash" /> |             <use xlink:href="assets/bootstrap-icons.svg#trash" /> | ||||||
|         </svg><span class="d-none d-lg-inline ps-1" i18n>Delete</span> |         </svg><span class="d-none d-lg-inline ps-1" i18n>Delete</span> | ||||||
| @@ -28,7 +28,7 @@ | |||||||
|  |  | ||||||
|     </div> |     </div> | ||||||
|  |  | ||||||
|     <button *ifOwner="document?.owner" type="button" class="btn btn-sm btn-outline-primary me-2" (click)="redoOcr()"> |     <button *ifOwner="document" type="button" class="btn btn-sm btn-outline-primary me-2" (click)="redoOcr()"> | ||||||
|         <svg class="buttonicon" fill="currentColor"> |         <svg class="buttonicon" fill="currentColor"> | ||||||
|             <use xlink:href="assets/bootstrap-icons.svg#arrow-counterclockwise" /> |             <use xlink:href="assets/bootstrap-icons.svg#arrow-counterclockwise" /> | ||||||
|         </svg><span class="d-none d-lg-inline ps-1" i18n>Redo OCR</span> |         </svg><span class="d-none d-lg-inline ps-1" i18n>Redo OCR</span> | ||||||
| @@ -178,12 +178,20 @@ | |||||||
|                     </ng-template> |                     </ng-template> | ||||||
|                 </li> |                 </li> | ||||||
|  |  | ||||||
|                 <li [ngbNavItem]="6" *ifOwner="document?.owner"> |                 <li [ngbNavItem]="6" *ifOwner="document"> | ||||||
|                     <a ngbNavLink i18n>Permissions</a> |                     <a ngbNavLink i18n>Permissions</a> | ||||||
|                     <ng-template ngbNavContent> |                     <ng-template ngbNavContent> | ||||||
|                         <div formGroupName="set_permissions"> |                         <div formGroupName="set_permissions"> | ||||||
|                             <app-share-user type="view" formControlName="view"></app-share-user> |                             <h6 i18n>View</h6> | ||||||
|                             <app-share-user type="change" formControlName="change"></app-share-user> |                             <div formGroupName="view"> | ||||||
|  |                                 <app-permissions-user type="view" formControlName="users"></app-permissions-user> | ||||||
|  |                                 <app-permissions-group type="view" formControlName="groups"></app-permissions-group> | ||||||
|  |                             </div> | ||||||
|  |                             <h6 i18n>Edit</h6> | ||||||
|  |                             <div formGroupName="change"> | ||||||
|  |                                 <app-permissions-user type="change" formControlName="users"></app-permissions-user> | ||||||
|  |                                 <app-permissions-group type="change" formControlName="groups"></app-permissions-group> | ||||||
|  |                             </div> | ||||||
|                         </div> |                         </div> | ||||||
|                     </ng-template> |                     </ng-template> | ||||||
|                 </li> |                 </li> | ||||||
| @@ -191,7 +199,7 @@ | |||||||
|  |  | ||||||
|             <div [ngbNavOutlet]="nav" class="mt-2"></div> |             <div [ngbNavOutlet]="nav" class="mt-2"></div> | ||||||
|  |  | ||||||
|             <ng-container action="PermissionAction.Change" *ifObjectPermissions="document"> |             <ng-container *ifObjectPermissions="{ object: document, action: PermissionAction.Change }"> | ||||||
|                 <button type="button" class="btn btn-outline-secondary" (click)="discard()" i18n [disabled]="networkActive || !(isDirty$ | async)">Discard</button>  |                 <button type="button" class="btn btn-outline-secondary" (click)="discard()" i18n [disabled]="networkActive || !(isDirty$ | async)">Discard</button>  | ||||||
|                 <button type="button" class="btn btn-outline-primary" (click)="saveEditNext()" *ngIf="hasNext()" i18n [disabled]="networkActive || !(isDirty$ | async) || error">Save & next</button>  |                 <button type="button" class="btn btn-outline-primary" (click)="saveEditNext()" *ngIf="hasNext()" i18n [disabled]="networkActive || !(isDirty$ | async) || error">Save & next</button>  | ||||||
|                 <button type="submit" class="btn btn-primary" *ifPermissions="{ action: PermissionAction.Change, type: PermissionType.Document }" i18n [disabled]="networkActive || !(isDirty$ | async) || error">Save</button>  |                 <button type="submit" class="btn btn-primary" *ifPermissions="{ action: PermissionAction.Change, type: PermissionType.Document }" i18n [disabled]="networkActive || !(isDirty$ | async) || error">Save</button>  | ||||||
|   | |||||||
| @@ -85,8 +85,14 @@ export class DocumentDetailComponent | |||||||
|     archive_serial_number: new FormControl(), |     archive_serial_number: new FormControl(), | ||||||
|     tags: new FormControl([]), |     tags: new FormControl([]), | ||||||
|     set_permissions: new FormGroup({ |     set_permissions: new FormGroup({ | ||||||
|       view: new FormControl(null), |       view: new FormGroup({ | ||||||
|       change: new FormControl(null), |         users: new FormControl(null), | ||||||
|  |         groups: new FormControl(null), | ||||||
|  |       }), | ||||||
|  |       change: new FormGroup({ | ||||||
|  |         users: new FormControl(null), | ||||||
|  |         groups: new FormControl(null), | ||||||
|  |       }), | ||||||
|     }), |     }), | ||||||
|   }) |   }) | ||||||
|  |  | ||||||
| @@ -235,14 +241,7 @@ export class DocumentDetailComponent | |||||||
|             storage_path: doc.storage_path, |             storage_path: doc.storage_path, | ||||||
|             archive_serial_number: doc.archive_serial_number, |             archive_serial_number: doc.archive_serial_number, | ||||||
|             tags: [...doc.tags], |             tags: [...doc.tags], | ||||||
|             set_permissions: { |             set_permissions: doc.permissions, | ||||||
|               view: doc.permissions |  | ||||||
|                 .filter((p) => (p[1] as string).includes('view')) |  | ||||||
|                 .map((p) => p[0]), |  | ||||||
|               change: doc.permissions |  | ||||||
|                 .filter((p) => (p[1] as string).includes('change')) |  | ||||||
|                 .map((p) => p[0]), |  | ||||||
|             }, |  | ||||||
|           }) |           }) | ||||||
|  |  | ||||||
|           this.isDirty$ = dirtyCheck( |           this.isDirty$ = dirtyCheck( | ||||||
| @@ -297,14 +296,7 @@ export class DocumentDetailComponent | |||||||
|         }, |         }, | ||||||
|       }) |       }) | ||||||
|     this.title = this.documentTitlePipe.transform(doc.title) |     this.title = this.documentTitlePipe.transform(doc.title) | ||||||
|     doc['set_permissions'] = { |     doc['set_permissions'] = doc.permissions | ||||||
|       view: doc.permissions |  | ||||||
|         .filter((p) => (p[1] as string).includes('view')) |  | ||||||
|         .map((p) => p[0]), |  | ||||||
|       change: doc.permissions |  | ||||||
|         .filter((p) => (p[1] as string).includes('change')) |  | ||||||
|         .map((p) => p[0]), |  | ||||||
|     } |  | ||||||
|     this.documentForm.patchValue(doc) |     this.documentForm.patchValue(doc) | ||||||
|     if (!this.userCanEdit) this.documentForm.disable() |     if (!this.userCanEdit) this.documentForm.disable() | ||||||
|   } |   } | ||||||
| @@ -586,10 +578,10 @@ export class DocumentDetailComponent | |||||||
|   get commentsEnabled(): boolean { |   get commentsEnabled(): boolean { | ||||||
|     return ( |     return ( | ||||||
|       this.settings.get(SETTINGS_KEYS.COMMENTS_ENABLED) && |       this.settings.get(SETTINGS_KEYS.COMMENTS_ENABLED) && | ||||||
|       this.permissionsService.currentUserCan({ |       this.permissionsService.currentUserCan( | ||||||
|         action: PermissionAction.View, |         PermissionAction.View, | ||||||
|         type: PermissionType.Document, |         PermissionType.Document | ||||||
|       }) |       ) | ||||||
|     ) |     ) | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -222,9 +222,7 @@ export abstract class ManagementListComponent<T extends ObjectWithId> | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   userCanDelete(object: ObjectWithPermissions): boolean { |   userCanDelete(object: ObjectWithPermissions): boolean { | ||||||
|     return ( |     return this.permissionsService.currentUserOwnsObject(object) | ||||||
|       !object.owner || this.permissionsService.currentUserIsOwner(object.owner) |  | ||||||
|     ) |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   userCanEdit(object: ObjectWithPermissions): boolean { |   userCanEdit(object: ObjectWithPermissions): boolean { | ||||||
|   | |||||||
| @@ -1,8 +1,19 @@ | |||||||
| import { ObjectWithId } from './object-with-id' | import { ObjectWithId } from './object-with-id' | ||||||
| import { PaperlessUser } from './paperless-user' | import { PaperlessUser } from './paperless-user' | ||||||
|  |  | ||||||
|  | export interface PermissionsObject { | ||||||
|  |   view: { | ||||||
|  |     users: Array<number> | ||||||
|  |     groups: Array<number> | ||||||
|  |   } | ||||||
|  |   change: { | ||||||
|  |     users: Array<number> | ||||||
|  |     groups: Array<number> | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
| export interface ObjectWithPermissions extends ObjectWithId { | export interface ObjectWithPermissions extends ObjectWithId { | ||||||
|   owner?: PaperlessUser |   owner?: PaperlessUser | ||||||
|  |  | ||||||
|   permissions?: Array<[number, string]> |   permissions?: PermissionsObject | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,5 +1,6 @@ | |||||||
| import { | import { | ||||||
|   Directive, |   Directive, | ||||||
|  |   EmbeddedViewRef, | ||||||
|   Input, |   Input, | ||||||
|   OnChanges, |   OnChanges, | ||||||
|   OnInit, |   OnInit, | ||||||
| @@ -18,10 +19,12 @@ import { | |||||||
| export class IfObjectPermissionsDirective implements OnInit, OnChanges { | export class IfObjectPermissionsDirective implements OnInit, OnChanges { | ||||||
|   // The role the user must have |   // The role the user must have | ||||||
|   @Input() |   @Input() | ||||||
|   ifObjectPermissions: ObjectWithPermissions |   ifObjectPermissions: { | ||||||
|  |     object: ObjectWithPermissions | ||||||
|  |     action: PermissionAction | ||||||
|  |   } | ||||||
|  |  | ||||||
|   @Input() |   createdView: EmbeddedViewRef<any> | ||||||
|   action: PermissionAction |  | ||||||
|  |  | ||||||
|   /** |   /** | ||||||
|    * @param {ViewContainerRef} viewContainerRef -- The location where we need to render the templateRef |    * @param {ViewContainerRef} viewContainerRef -- The location where we need to render the templateRef | ||||||
| @@ -36,13 +39,16 @@ export class IfObjectPermissionsDirective implements OnInit, OnChanges { | |||||||
|  |  | ||||||
|   public ngOnInit(): void { |   public ngOnInit(): void { | ||||||
|     if ( |     if ( | ||||||
|       !this.ifObjectPermissions || |       !this.ifObjectPermissions?.object || | ||||||
|       this.permissionsService.currentUserHasObjectPermissions( |       this.permissionsService.currentUserHasObjectPermissions( | ||||||
|         this.action, |         this.ifObjectPermissions.action, | ||||||
|         this.ifObjectPermissions |         this.ifObjectPermissions.object | ||||||
|       ) |       ) | ||||||
|     ) { |     ) { | ||||||
|       this.viewContainerRef.createEmbeddedView(this.templateRef) |       if (!this.createdView) | ||||||
|  |         this.createdView = this.viewContainerRef.createEmbeddedView( | ||||||
|  |           this.templateRef | ||||||
|  |         ) | ||||||
|     } else { |     } else { | ||||||
|       this.viewContainerRef.clear() |       this.viewContainerRef.clear() | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -1,12 +1,13 @@ | |||||||
| import { | import { | ||||||
|   Directive, |   Directive, | ||||||
|  |   EmbeddedViewRef, | ||||||
|   Input, |   Input, | ||||||
|   OnChanges, |   OnChanges, | ||||||
|   OnInit, |   OnInit, | ||||||
|   TemplateRef, |   TemplateRef, | ||||||
|   ViewContainerRef, |   ViewContainerRef, | ||||||
| } from '@angular/core' | } from '@angular/core' | ||||||
| import { PaperlessUser } from '../data/paperless-user' | import { ObjectWithPermissions } from '../data/object-with-permissions' | ||||||
| import { PermissionsService } from '../services/permissions.service' | import { PermissionsService } from '../services/permissions.service' | ||||||
|  |  | ||||||
| @Directive({ | @Directive({ | ||||||
| @@ -15,7 +16,9 @@ import { PermissionsService } from '../services/permissions.service' | |||||||
| export class IfOwnerDirective implements OnInit, OnChanges { | export class IfOwnerDirective implements OnInit, OnChanges { | ||||||
|   // The role the user must have |   // The role the user must have | ||||||
|   @Input() |   @Input() | ||||||
|   ifOwner: PaperlessUser |   ifOwner: ObjectWithPermissions | ||||||
|  |  | ||||||
|  |   createdView: EmbeddedViewRef<any> | ||||||
|  |  | ||||||
|   /** |   /** | ||||||
|    * @param {ViewContainerRef} viewContainerRef -- The location where we need to render the templateRef |    * @param {ViewContainerRef} viewContainerRef -- The location where we need to render the templateRef | ||||||
| @@ -29,11 +32,11 @@ export class IfOwnerDirective implements OnInit, OnChanges { | |||||||
|   ) {} |   ) {} | ||||||
|  |  | ||||||
|   public ngOnInit(): void { |   public ngOnInit(): void { | ||||||
|     if ( |     if (this.permissionsService.currentUserOwnsObject(this.ifOwner)) { | ||||||
|       !this.ifOwner || |       if (!this.createdView) | ||||||
|       this.permissionsService.currentUserIsOwner(this.ifOwner) |         this.createdView = this.viewContainerRef.createEmbeddedView( | ||||||
|     ) { |           this.templateRef | ||||||
|       this.viewContainerRef.createEmbeddedView(this.templateRef) |         ) | ||||||
|     } else { |     } else { | ||||||
|       this.viewContainerRef.clear() |       this.viewContainerRef.clear() | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -6,17 +6,19 @@ import { | |||||||
|   TemplateRef, |   TemplateRef, | ||||||
| } from '@angular/core' | } from '@angular/core' | ||||||
| import { | import { | ||||||
|   PaperlessPermission, |   PermissionAction, | ||||||
|   PermissionsService, |   PermissionsService, | ||||||
|  |   PermissionType, | ||||||
| } from '../services/permissions.service' | } from '../services/permissions.service' | ||||||
|  |  | ||||||
| @Directive({ | @Directive({ | ||||||
|   selector: '[ifPermissions]', |   selector: '[ifPermissions]', | ||||||
| }) | }) | ||||||
| export class IfPermissionsDirective implements OnInit { | export class IfPermissionsDirective implements OnInit { | ||||||
|   // The role the user must have |  | ||||||
|   @Input() |   @Input() | ||||||
|   ifPermissions: Array<PaperlessPermission> | PaperlessPermission |   ifPermissions: | ||||||
|  |     | Array<{ action: PermissionAction; type: PermissionType }> | ||||||
|  |     | { action: PermissionAction; type: PermissionType } | ||||||
|  |  | ||||||
|   /** |   /** | ||||||
|    * @param {ViewContainerRef} viewContainerRef -- The location where we need to render the templateRef |    * @param {ViewContainerRef} viewContainerRef -- The location where we need to render the templateRef | ||||||
| @@ -33,8 +35,8 @@ export class IfPermissionsDirective implements OnInit { | |||||||
|     if ( |     if ( | ||||||
|       [] |       [] | ||||||
|         .concat(this.ifPermissions) |         .concat(this.ifPermissions) | ||||||
|         .every((perm: PaperlessPermission) => |         .every((perm: { action: PermissionAction; type: PermissionType }) => | ||||||
|           this.permissionsService.currentUserCan(perm) |           this.permissionsService.currentUserCan(perm.action, perm.type) | ||||||
|         ) |         ) | ||||||
|     ) { |     ) { | ||||||
|       this.viewContainerRef.createEmbeddedView(this.templateRef) |       this.viewContainerRef.createEmbeddedView(this.templateRef) | ||||||
|   | |||||||
| @@ -22,7 +22,10 @@ export class PermissionsGuard implements CanActivate { | |||||||
|     state: RouterStateSnapshot |     state: RouterStateSnapshot | ||||||
|   ): boolean | UrlTree { |   ): boolean | UrlTree { | ||||||
|     if ( |     if ( | ||||||
|       !this.permissionsService.currentUserCan(route.data.requiredPermission) |       !this.permissionsService.currentUserCan( | ||||||
|  |         route.data.requiredPermission.action, | ||||||
|  |         route.data.requiredPermission.type | ||||||
|  |       ) | ||||||
|     ) { |     ) { | ||||||
|       this.toastService.showError( |       this.toastService.showError( | ||||||
|         $localize`You don't have permissions to do that` |         $localize`You don't have permissions to do that` | ||||||
|   | |||||||
| @@ -25,11 +25,6 @@ export enum PermissionType { | |||||||
|   Admin = '%s_logentry', |   Admin = '%s_logentry', | ||||||
| } | } | ||||||
|  |  | ||||||
| export interface PaperlessPermission { |  | ||||||
|   action: PermissionAction |  | ||||||
|   type: PermissionType |  | ||||||
| } |  | ||||||
|  |  | ||||||
| @Injectable({ | @Injectable({ | ||||||
|   providedIn: 'root', |   providedIn: 'root', | ||||||
| }) | }) | ||||||
| @@ -42,25 +37,34 @@ export class PermissionsService { | |||||||
|     this.currentUser = currentUser |     this.currentUser = currentUser | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   public currentUserCan(permission: PaperlessPermission): boolean { |   public currentUserCan( | ||||||
|     return this.permissions.includes(this.getPermissionCode(permission)) |     action: PermissionAction, | ||||||
|  |     type: PermissionType | ||||||
|  |   ): boolean { | ||||||
|  |     return this.permissions.includes(this.getPermissionCode(action, type)) | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   public currentUserIsOwner(owner: PaperlessUser): boolean { |   public currentUserOwnsObject(object: ObjectWithPermissions): boolean { | ||||||
|     return owner?.id === this.currentUser.id |     return !object || !object.owner || object.owner.id === this.currentUser.id | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   public currentUserHasObjectPermissions( |   public currentUserHasObjectPermissions( | ||||||
|     action: string, |     action: string, | ||||||
|     object: ObjectWithPermissions |     object: ObjectWithPermissions | ||||||
|   ): boolean { |   ): boolean { | ||||||
|     return (object.permissions[action] as Array<number>)?.includes( |     return ( | ||||||
|       this.currentUser.id |       this.currentUserOwnsObject(object) || | ||||||
|  |       (object.permissions[action]['users'] as Array<number>)?.includes( | ||||||
|  |         this.currentUser.id | ||||||
|  |       ) | ||||||
|     ) |     ) | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   public getPermissionCode(permission: PaperlessPermission): string { |   public getPermissionCode( | ||||||
|     return permission.type.replace('%s', permission.action) |     action: PermissionAction, | ||||||
|  |     type: PermissionType | ||||||
|  |   ): string { | ||||||
|  |     return type.replace('%s', action) | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   public getPermissionKeys(permissionStr: string): { |   public getPermissionKeys(permissionStr: string): { | ||||||
|   | |||||||
| @@ -28,7 +28,7 @@ from .models import UiSettings | |||||||
| from .models import PaperlessTask | from .models import PaperlessTask | ||||||
| from .parsers import is_mime_type_supported | from .parsers import is_mime_type_supported | ||||||
|  |  | ||||||
| from guardian.models import UserObjectPermission | from guardian.models import GroupObjectPermission | ||||||
| from guardian.shortcuts import assign_perm | from guardian.shortcuts import assign_perm | ||||||
| from guardian.shortcuts import remove_perm | from guardian.shortcuts import remove_perm | ||||||
| from guardian.shortcuts import get_users_with_perms | from guardian.shortcuts import get_users_with_perms | ||||||
| @@ -36,6 +36,8 @@ from guardian.shortcuts import get_users_with_perms | |||||||
| from django.contrib.contenttypes.models import ContentType | from django.contrib.contenttypes.models import ContentType | ||||||
|  |  | ||||||
| from django.contrib.auth.models import User | from django.contrib.auth.models import User | ||||||
|  | from django.contrib.auth.models import Group | ||||||
|  | from django.contrib.auth.models import Permission | ||||||
|  |  | ||||||
|  |  | ||||||
| # https://www.django-rest-framework.org/api-guide/serializers/#example | # https://www.django-rest-framework.org/api-guide/serializers/#example | ||||||
| @@ -83,14 +85,46 @@ class MatchingModelSerializer(serializers.ModelSerializer): | |||||||
|         return match |         return match | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def get_groups_with_only_permission(obj, codename): | ||||||
|  |     ctype = ContentType.objects.get_for_model(obj) | ||||||
|  |     permission = Permission.objects.get(content_type=ctype, codename=codename) | ||||||
|  |     group_object_perm_group_ids = ( | ||||||
|  |         GroupObjectPermission.objects.filter( | ||||||
|  |             object_pk=obj.pk, | ||||||
|  |             content_type=ctype, | ||||||
|  |         ) | ||||||
|  |         .filter(permission=permission) | ||||||
|  |         .values_list("group_id") | ||||||
|  |     ) | ||||||
|  |     return Group.objects.filter(id__in=group_object_perm_group_ids).distinct() | ||||||
|  |  | ||||||
|  |  | ||||||
| class OwnedObjectSerializer(serializers.ModelSerializer): | class OwnedObjectSerializer(serializers.ModelSerializer): | ||||||
|     def get_permissions(self, obj): |     def get_permissions(self, obj): | ||||||
|         content_type = ContentType.objects.get_for_model(obj) |         view_codename = f"view_{obj.__class__.__name__.lower()}" | ||||||
|         user_object_perms = UserObjectPermission.objects.filter( |         change_codename = f"change_{obj.__class__.__name__.lower()}" | ||||||
|             object_pk=obj.pk, |         return { | ||||||
|             content_type=content_type, |             "view": { | ||||||
|         ).values_list("user", "permission__codename") |                 "users": get_users_with_perms( | ||||||
|         return list(user_object_perms) |                     obj, | ||||||
|  |                     only_with_perms_in=[view_codename], | ||||||
|  |                 ).values_list("id", flat=True), | ||||||
|  |                 "groups": get_groups_with_only_permission( | ||||||
|  |                     obj, | ||||||
|  |                     codename=view_codename, | ||||||
|  |                 ).values_list("id", flat=True), | ||||||
|  |             }, | ||||||
|  |             "change": { | ||||||
|  |                 "users": get_users_with_perms( | ||||||
|  |                     obj, | ||||||
|  |                     only_with_perms_in=[change_codename], | ||||||
|  |                 ).values_list("id", flat=True), | ||||||
|  |                 "groups": get_groups_with_only_permission( | ||||||
|  |                     obj, | ||||||
|  |                     codename=change_codename, | ||||||
|  |                 ).values_list("id", flat=True), | ||||||
|  |             }, | ||||||
|  |         } | ||||||
|  |  | ||||||
|     permissions = SerializerMethodField(read_only=True) |     permissions = SerializerMethodField(read_only=True) | ||||||
|  |  | ||||||
| @@ -111,19 +145,34 @@ class OwnedObjectSerializer(serializers.ModelSerializer): | |||||||
|                 ) |                 ) | ||||||
|         return users |         return users | ||||||
|  |  | ||||||
|  |     def _validate_group_ids(self, group_ids): | ||||||
|  |         groups = Group.objects.none() | ||||||
|  |         if group_ids is not None: | ||||||
|  |             groups = Group.objects.filter(id__in=group_ids) | ||||||
|  |             if not groups.count() == len(group_ids): | ||||||
|  |                 raise serializers.ValidationError( | ||||||
|  |                     "Some groups in don't exist or were specified twice.", | ||||||
|  |                 ) | ||||||
|  |         return groups | ||||||
|  |  | ||||||
|     def validate_set_permissions(self, set_permissions): |     def validate_set_permissions(self, set_permissions): | ||||||
|         user_dict = { |         permissions_dict = { | ||||||
|             "view": User.objects.none(), |             "view": { | ||||||
|             "change": User.objects.none(), |                 "users": User.objects.none(), | ||||||
|  |                 "groups": Group.objects.none(), | ||||||
|  |             }, | ||||||
|  |             "change": { | ||||||
|  |                 "users": User.objects.none(), | ||||||
|  |                 "groups": Group.objects.none(), | ||||||
|  |             }, | ||||||
|         } |         } | ||||||
|         if set_permissions is not None: |         if set_permissions is not None: | ||||||
|             if "view" in set_permissions: |             for action in permissions_dict: | ||||||
|                 view_list = set_permissions["view"] |                 users = set_permissions[action]["users"] | ||||||
|                 user_dict["view"] = self._validate_user_ids(view_list) |                 permissions_dict[action]["users"] = self._validate_user_ids(users) | ||||||
|             if "change" in set_permissions: |                 groups = set_permissions[action]["groups"] | ||||||
|                 change_list = set_permissions["change"] |                 permissions_dict[action]["groups"] = self._validate_group_ids(groups) | ||||||
|                 user_dict["change"] = self._validate_user_ids(change_list) |         return permissions_dict | ||||||
|         return user_dict |  | ||||||
|  |  | ||||||
|     def __init__(self, *args, **kwargs): |     def __init__(self, *args, **kwargs): | ||||||
|         self.user = kwargs.pop("user", None) |         self.user = kwargs.pop("user", None) | ||||||
| @@ -132,7 +181,8 @@ class OwnedObjectSerializer(serializers.ModelSerializer): | |||||||
|     def _set_permissions(self, permissions, object): |     def _set_permissions(self, permissions, object): | ||||||
|         for action in permissions: |         for action in permissions: | ||||||
|             permission = f"{action}_{object.__class__.__name__.lower()}" |             permission = f"{action}_{object.__class__.__name__.lower()}" | ||||||
|             users_to_add = permissions[action] |             # users | ||||||
|  |             users_to_add = permissions[action]["users"] | ||||||
|             users_to_remove = get_users_with_perms( |             users_to_remove = get_users_with_perms( | ||||||
|                 object, |                 object, | ||||||
|                 only_with_perms_in=[permission], |                 only_with_perms_in=[permission], | ||||||
| @@ -148,6 +198,23 @@ class OwnedObjectSerializer(serializers.ModelSerializer): | |||||||
|                         user, |                         user, | ||||||
|                         object, |                         object, | ||||||
|                     ) |                     ) | ||||||
|  |             # groups | ||||||
|  |             groups_to_add = permissions[action]["groups"] | ||||||
|  |             groups_to_remove = get_groups_with_only_permission( | ||||||
|  |                 object, | ||||||
|  |                 permission, | ||||||
|  |             ).difference(groups_to_add) | ||||||
|  |             for group in groups_to_remove: | ||||||
|  |                 remove_perm(permission, group, object) | ||||||
|  |             for group in groups_to_add: | ||||||
|  |                 assign_perm(permission, group, object) | ||||||
|  |                 if action == "change": | ||||||
|  |                     # change gives view too | ||||||
|  |                     assign_perm( | ||||||
|  |                         f"view_{object.__class__.__name__.lower()}", | ||||||
|  |                         group, | ||||||
|  |                         object, | ||||||
|  |                     ) | ||||||
|  |  | ||||||
|     def create(self, validated_data): |     def create(self, validated_data): | ||||||
|         if self.user and ( |         if self.user and ( | ||||||
|   | |||||||
| @@ -158,7 +158,7 @@ class TestDocumentApi(DirectoriesMixin, APITestCase): | |||||||
|         response = self.client.get("/api/documents/?fields=", format="json") |         response = self.client.get("/api/documents/?fields=", format="json") | ||||||
|         self.assertEqual(response.status_code, 200) |         self.assertEqual(response.status_code, 200) | ||||||
|         results = response.data["results"] |         results = response.data["results"] | ||||||
|         self.assertEqual(results_full, results) |         self.assertEqual(len(results_full[0]), len(results[0])) | ||||||
|  |  | ||||||
|         response = self.client.get("/api/documents/?fields=dgfhs", format="json") |         response = self.client.get("/api/documents/?fields=dgfhs", format="json") | ||||||
|         self.assertEqual(response.status_code, 200) |         self.assertEqual(response.status_code, 200) | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Michael Shamoon
					Michael Shamoon