diff --git a/src-ui/src/app/app.component.ts b/src-ui/src/app/app.component.ts
index 320e9393a..01eac1297 100644
--- a/src-ui/src/app/app.component.ts
+++ b/src-ui/src/app/app.component.ts
@@ -81,10 +81,10 @@ export class AppComponent implements OnInit, OnDestroy {
this.showNotification(SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_SUCCESS)
) {
if (
- this.permissionsService.currentUserCan({
- action: PermissionAction.View,
- type: PermissionType.Document,
- })
+ this.permissionsService.currentUserCan(
+ PermissionAction.View,
+ PermissionType.Document
+ )
) {
this.toastService.show({
title: $localize`Document added`,
@@ -246,10 +246,10 @@ export class AppComponent implements OnInit, OnDestroy {
public get dragDropEnabled(): boolean {
return (
!this.router.url.includes('dashboard') &&
- this.permissionsService.currentUserCan({
- action: PermissionAction.Add,
- type: PermissionType.Document,
- })
+ this.permissionsService.currentUserCan(
+ PermissionAction.Add,
+ PermissionType.Document
+ )
)
}
diff --git a/src-ui/src/app/app.module.ts b/src-ui/src/app/app.module.ts
index 60a6f4844..9e97ac90b 100644
--- a/src-ui/src/app/app.module.ts
+++ b/src-ui/src/app/app.module.ts
@@ -84,6 +84,10 @@ import { GroupEditDialogComponent } from './components/common/edit-dialog/group-
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 { 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 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 localeTr from '@angular/common/locales/tr'
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(localeCs)
@@ -198,7 +199,8 @@ function initializeApp(settings: SettingsService) {
PermissionsSelectComponent,
MailAccountEditDialogComponent,
MailRuleEditDialogComponent,
- ShareUserComponent,
+ PermissionsUserComponent,
+ PermissionsGroupComponent,
IfOwnerDirective,
IfObjectPermissionsDirective,
],
diff --git a/src-ui/src/app/components/common/edit-dialog/correspondent-edit-dialog/correspondent-edit-dialog.component.html b/src-ui/src/app/components/common/edit-dialog/correspondent-edit-dialog/correspondent-edit-dialog.component.html
index 83f24812f..3867146a3 100644
--- a/src-ui/src/app/components/common/edit-dialog/correspondent-edit-dialog/correspondent-edit-dialog.component.html
+++ b/src-ui/src/app/components/common/edit-dialog/correspondent-edit-dialog/correspondent-edit-dialog.component.html
@@ -11,11 +11,19 @@
-
+
diff --git a/src-ui/src/app/components/common/edit-dialog/correspondent-edit-dialog/correspondent-edit-dialog.component.ts b/src-ui/src/app/components/common/edit-dialog/correspondent-edit-dialog/correspondent-edit-dialog.component.ts
index a7c3eb606..5320def27 100644
--- a/src-ui/src/app/components/common/edit-dialog/correspondent-edit-dialog/correspondent-edit-dialog.component.ts
+++ b/src-ui/src/app/components/common/edit-dialog/correspondent-edit-dialog/correspondent-edit-dialog.component.ts
@@ -31,8 +31,14 @@ export class CorrespondentEditDialogComponent extends EditDialogComponent
-
+
diff --git a/src-ui/src/app/components/common/edit-dialog/document-type-edit-dialog/document-type-edit-dialog.component.ts b/src-ui/src/app/components/common/edit-dialog/document-type-edit-dialog/document-type-edit-dialog.component.ts
index ef4d0a864..81854df34 100644
--- a/src-ui/src/app/components/common/edit-dialog/document-type-edit-dialog/document-type-edit-dialog.component.ts
+++ b/src-ui/src/app/components/common/edit-dialog/document-type-edit-dialog/document-type-edit-dialog.component.ts
@@ -31,8 +31,14 @@ export class DocumentTypeEditDialogComponent extends EditDialogComponent
(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.object['set_permissions'] = this.object['permissions']
}
this.objectForm.patchValue(this.object)
}
diff --git a/src-ui/src/app/components/common/edit-dialog/storage-path-edit-dialog/storage-path-edit-dialog.component.html b/src-ui/src/app/components/common/edit-dialog/storage-path-edit-dialog/storage-path-edit-dialog.component.html
index a0e141907..24379a52d 100644
--- a/src-ui/src/app/components/common/edit-dialog/storage-path-edit-dialog/storage-path-edit-dialog.component.html
+++ b/src-ui/src/app/components/common/edit-dialog/storage-path-edit-dialog/storage-path-edit-dialog.component.html
@@ -16,11 +16,19 @@
-
+
diff --git a/src-ui/src/app/components/common/edit-dialog/storage-path-edit-dialog/storage-path-edit-dialog.component.ts b/src-ui/src/app/components/common/edit-dialog/storage-path-edit-dialog/storage-path-edit-dialog.component.ts
index 7c4898703..42d533c96 100644
--- a/src-ui/src/app/components/common/edit-dialog/storage-path-edit-dialog/storage-path-edit-dialog.component.ts
+++ b/src-ui/src/app/components/common/edit-dialog/storage-path-edit-dialog/storage-path-edit-dialog.component.ts
@@ -42,8 +42,14 @@ export class StoragePathEditDialogComponent extends EditDialogComponent
-
+
diff --git a/src-ui/src/app/components/common/edit-dialog/tag-edit-dialog/tag-edit-dialog.component.ts b/src-ui/src/app/components/common/edit-dialog/tag-edit-dialog/tag-edit-dialog.component.ts
index 0414052a0..8b79d36bc 100644
--- a/src-ui/src/app/components/common/edit-dialog/tag-edit-dialog/tag-edit-dialog.component.ts
+++ b/src-ui/src/app/components/common/edit-dialog/tag-edit-dialog/tag-edit-dialog.component.ts
@@ -34,8 +34,14 @@ export class TagEditDialogComponent extends EditDialogComponent
{
match: new FormControl(''),
is_insensitive: new FormControl(true),
set_permissions: new FormGroup({
- view: new FormControl(null),
- change: new FormControl(null),
+ view: new FormGroup({
+ users: new FormControl(null),
+ groups: new FormControl(null),
+ }),
+ change: new FormGroup({
+ users: new FormControl(null),
+ groups: new FormControl(null),
+ }),
}),
})
}
diff --git a/src-ui/src/app/components/common/input/permissions-group/permissions-group.component.html b/src-ui/src/app/components/common/input/permissions-group/permissions-group.component.html
new file mode 100644
index 000000000..1b74b1eb4
--- /dev/null
+++ b/src-ui/src/app/components/common/input/permissions-group/permissions-group.component.html
@@ -0,0 +1,15 @@
+
diff --git a/src-ui/src/app/components/common/input/share-user/share-user.component.scss b/src-ui/src/app/components/common/input/permissions-group/permissions-group.component.scss
similarity index 100%
rename from src-ui/src/app/components/common/input/share-user/share-user.component.scss
rename to src-ui/src/app/components/common/input/permissions-group/permissions-group.component.scss
diff --git a/src-ui/src/app/components/common/input/permissions-group/permissions-group.component.ts b/src-ui/src/app/components/common/input/permissions-group/permissions-group.component.ts
new file mode 100644
index 000000000..bef172747
--- /dev/null
+++ b/src-ui/src/app/components/common/input/permissions-group/permissions-group.component.ts
@@ -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
+ 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()
+ }
+}
diff --git a/src-ui/src/app/components/common/input/share-user/share-user.component.html b/src-ui/src/app/components/common/input/permissions-user/permissions-user.component.html
similarity index 100%
rename from src-ui/src/app/components/common/input/share-user/share-user.component.html
rename to src-ui/src/app/components/common/input/permissions-user/permissions-user.component.html
diff --git a/src-ui/src/app/components/common/input/permissions-user/permissions-user.component.scss b/src-ui/src/app/components/common/input/permissions-user/permissions-user.component.scss
new file mode 100644
index 000000000..e69de29bb
diff --git a/src-ui/src/app/components/common/input/share-user/share-user.component.ts b/src-ui/src/app/components/common/input/permissions-user/permissions-user.component.ts
similarity index 63%
rename from src-ui/src/app/components/common/input/share-user/share-user.component.ts
rename to src-ui/src/app/components/common/input/permissions-user/permissions-user.component.ts
index d132b3964..87ce08f08 100644
--- a/src-ui/src/app/components/common/input/share-user/share-user.component.ts
+++ b/src-ui/src/app/components/common/input/permissions-user/permissions-user.component.ts
@@ -3,21 +3,22 @@ import { NG_VALUE_ACCESSOR } from '@angular/forms'
import { first } from 'rxjs/operators'
import { PaperlessUser } from 'src/app/data/paperless-user'
import { UserService } from 'src/app/services/rest/user.service'
+import { SettingsService } from 'src/app/services/settings.service'
import { AbstractInputComponent } from '../abstract-input'
@Component({
providers: [
{
provide: NG_VALUE_ACCESSOR,
- useExisting: forwardRef(() => ShareUserComponent),
+ useExisting: forwardRef(() => PermissionsUserComponent),
multi: true,
},
],
- selector: 'app-share-user',
- templateUrl: './share-user.component.html',
- styleUrls: ['./share-user.component.scss'],
+ selector: 'app-permissions-user',
+ templateUrl: './permissions-user.component.html',
+ styleUrls: ['./permissions-user.component.scss'],
})
-export class ShareUserComponent
+export class PermissionsUserComponent
extends AbstractInputComponent
implements OnInit
{
@@ -26,12 +27,17 @@ export class ShareUserComponent
@Input()
type: string
- constructor(userService: UserService) {
+ constructor(userService: UserService, settings: SettingsService) {
super()
userService
.listAll()
.pipe(first())
- .subscribe((result) => (this.users = result.results))
+ .subscribe(
+ (result) =>
+ (this.users = result.results.filter(
+ (u) => u.id !== settings.currentUser.id
+ ))
+ )
}
ngOnInit(): void {
diff --git a/src-ui/src/app/components/common/permissions-select/permissions-select.component.ts b/src-ui/src/app/components/common/permissions-select/permissions-select.component.ts
index 56ba619bb..673de6cb6 100644
--- a/src-ui/src/app/components/common/permissions-select/permissions-select.component.ts
+++ b/src-ui/src/app/components/common/permissions-select/permissions-select.component.ts
@@ -156,18 +156,18 @@ export class PermissionsSelectComponent
if (this._inheritedPermissions.length == 0) return false
else if (actionKey) {
return this._inheritedPermissions.includes(
- this.permissionsService.getPermissionCode({
- action: PermissionAction[actionKey],
- type: PermissionType[typeKey],
- })
+ this.permissionsService.getPermissionCode(
+ PermissionAction[actionKey],
+ PermissionType[typeKey]
+ )
)
} else {
return Object.values(PermissionAction).every((action) => {
return this._inheritedPermissions.includes(
- this.permissionsService.getPermissionCode({
- action: action as PermissionAction,
- type: PermissionType[typeKey],
- })
+ this.permissionsService.getPermissionCode(
+ action as PermissionAction,
+ PermissionType[typeKey]
+ )
)
})
}
diff --git a/src-ui/src/app/components/document-detail/document-detail.component.html b/src-ui/src/app/components/document-detail/document-detail.component.html
index 6b729ee4c..d2100b59c 100644
--- a/src-ui/src/app/components/document-detail/document-detail.component.html
+++ b/src-ui/src/app/components/document-detail/document-detail.component.html
@@ -5,7 +5,7 @@
of {{previewNumPages}}
-
-
+
Redo OCR
@@ -178,12 +178,20 @@
-
+
Permissions
@@ -191,7 +199,7 @@
-
+
Discard
Save & next
Save
diff --git a/src-ui/src/app/components/document-detail/document-detail.component.ts b/src-ui/src/app/components/document-detail/document-detail.component.ts
index 8d564f4b5..740ef46f9 100644
--- a/src-ui/src/app/components/document-detail/document-detail.component.ts
+++ b/src-ui/src/app/components/document-detail/document-detail.component.ts
@@ -85,8 +85,14 @@ export class DocumentDetailComponent
archive_serial_number: new FormControl(),
tags: new FormControl([]),
set_permissions: new FormGroup({
- view: new FormControl(null),
- change: new FormControl(null),
+ view: new FormGroup({
+ 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,
archive_serial_number: doc.archive_serial_number,
tags: [...doc.tags],
- set_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]),
- },
+ set_permissions: doc.permissions,
})
this.isDirty$ = dirtyCheck(
@@ -297,14 +296,7 @@ export class DocumentDetailComponent
},
})
this.title = this.documentTitlePipe.transform(doc.title)
- doc['set_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]),
- }
+ doc['set_permissions'] = doc.permissions
this.documentForm.patchValue(doc)
if (!this.userCanEdit) this.documentForm.disable()
}
@@ -586,10 +578,10 @@ export class DocumentDetailComponent
get commentsEnabled(): boolean {
return (
this.settings.get(SETTINGS_KEYS.COMMENTS_ENABLED) &&
- this.permissionsService.currentUserCan({
- action: PermissionAction.View,
- type: PermissionType.Document,
- })
+ this.permissionsService.currentUserCan(
+ PermissionAction.View,
+ PermissionType.Document
+ )
)
}
diff --git a/src-ui/src/app/components/manage/management-list/management-list.component.ts b/src-ui/src/app/components/manage/management-list/management-list.component.ts
index fd63a44b6..7695c4abb 100644
--- a/src-ui/src/app/components/manage/management-list/management-list.component.ts
+++ b/src-ui/src/app/components/manage/management-list/management-list.component.ts
@@ -222,9 +222,7 @@ export abstract class ManagementListComponent
}
userCanDelete(object: ObjectWithPermissions): boolean {
- return (
- !object.owner || this.permissionsService.currentUserIsOwner(object.owner)
- )
+ return this.permissionsService.currentUserOwnsObject(object)
}
userCanEdit(object: ObjectWithPermissions): boolean {
diff --git a/src-ui/src/app/data/object-with-permissions.ts b/src-ui/src/app/data/object-with-permissions.ts
index a95ada157..dce0cc02a 100644
--- a/src-ui/src/app/data/object-with-permissions.ts
+++ b/src-ui/src/app/data/object-with-permissions.ts
@@ -1,8 +1,19 @@
import { ObjectWithId } from './object-with-id'
import { PaperlessUser } from './paperless-user'
+export interface PermissionsObject {
+ view: {
+ users: Array
+ groups: Array
+ }
+ change: {
+ users: Array
+ groups: Array
+ }
+}
+
export interface ObjectWithPermissions extends ObjectWithId {
owner?: PaperlessUser
- permissions?: Array<[number, string]>
+ permissions?: PermissionsObject
}
diff --git a/src-ui/src/app/directives/if-object-permissions.directive.ts b/src-ui/src/app/directives/if-object-permissions.directive.ts
index c897b848d..ccc90b70b 100644
--- a/src-ui/src/app/directives/if-object-permissions.directive.ts
+++ b/src-ui/src/app/directives/if-object-permissions.directive.ts
@@ -1,5 +1,6 @@
import {
Directive,
+ EmbeddedViewRef,
Input,
OnChanges,
OnInit,
@@ -18,10 +19,12 @@ import {
export class IfObjectPermissionsDirective implements OnInit, OnChanges {
// The role the user must have
@Input()
- ifObjectPermissions: ObjectWithPermissions
+ ifObjectPermissions: {
+ object: ObjectWithPermissions
+ action: PermissionAction
+ }
- @Input()
- action: PermissionAction
+ createdView: EmbeddedViewRef
/**
* @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 {
if (
- !this.ifObjectPermissions ||
+ !this.ifObjectPermissions?.object ||
this.permissionsService.currentUserHasObjectPermissions(
- this.action,
- this.ifObjectPermissions
+ this.ifObjectPermissions.action,
+ this.ifObjectPermissions.object
)
) {
- this.viewContainerRef.createEmbeddedView(this.templateRef)
+ if (!this.createdView)
+ this.createdView = this.viewContainerRef.createEmbeddedView(
+ this.templateRef
+ )
} else {
this.viewContainerRef.clear()
}
diff --git a/src-ui/src/app/directives/if-owner.directive.ts b/src-ui/src/app/directives/if-owner.directive.ts
index 86fcd3457..082cc1679 100644
--- a/src-ui/src/app/directives/if-owner.directive.ts
+++ b/src-ui/src/app/directives/if-owner.directive.ts
@@ -1,12 +1,13 @@
import {
Directive,
+ EmbeddedViewRef,
Input,
OnChanges,
OnInit,
TemplateRef,
ViewContainerRef,
} from '@angular/core'
-import { PaperlessUser } from '../data/paperless-user'
+import { ObjectWithPermissions } from '../data/object-with-permissions'
import { PermissionsService } from '../services/permissions.service'
@Directive({
@@ -15,7 +16,9 @@ import { PermissionsService } from '../services/permissions.service'
export class IfOwnerDirective implements OnInit, OnChanges {
// The role the user must have
@Input()
- ifOwner: PaperlessUser
+ ifOwner: ObjectWithPermissions
+
+ createdView: EmbeddedViewRef
/**
* @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 {
- if (
- !this.ifOwner ||
- this.permissionsService.currentUserIsOwner(this.ifOwner)
- ) {
- this.viewContainerRef.createEmbeddedView(this.templateRef)
+ if (this.permissionsService.currentUserOwnsObject(this.ifOwner)) {
+ if (!this.createdView)
+ this.createdView = this.viewContainerRef.createEmbeddedView(
+ this.templateRef
+ )
} else {
this.viewContainerRef.clear()
}
diff --git a/src-ui/src/app/directives/if-permissions.directive.ts b/src-ui/src/app/directives/if-permissions.directive.ts
index da93b65fc..6dedaee25 100644
--- a/src-ui/src/app/directives/if-permissions.directive.ts
+++ b/src-ui/src/app/directives/if-permissions.directive.ts
@@ -6,17 +6,19 @@ import {
TemplateRef,
} from '@angular/core'
import {
- PaperlessPermission,
+ PermissionAction,
PermissionsService,
+ PermissionType,
} from '../services/permissions.service'
@Directive({
selector: '[ifPermissions]',
})
export class IfPermissionsDirective implements OnInit {
- // The role the user must have
@Input()
- ifPermissions: Array | PaperlessPermission
+ ifPermissions:
+ | Array<{ action: PermissionAction; type: PermissionType }>
+ | { action: PermissionAction; type: PermissionType }
/**
* @param {ViewContainerRef} viewContainerRef -- The location where we need to render the templateRef
@@ -33,8 +35,8 @@ export class IfPermissionsDirective implements OnInit {
if (
[]
.concat(this.ifPermissions)
- .every((perm: PaperlessPermission) =>
- this.permissionsService.currentUserCan(perm)
+ .every((perm: { action: PermissionAction; type: PermissionType }) =>
+ this.permissionsService.currentUserCan(perm.action, perm.type)
)
) {
this.viewContainerRef.createEmbeddedView(this.templateRef)
diff --git a/src-ui/src/app/guards/permissions.guard.ts b/src-ui/src/app/guards/permissions.guard.ts
index 39536ed55..916408fe2 100644
--- a/src-ui/src/app/guards/permissions.guard.ts
+++ b/src-ui/src/app/guards/permissions.guard.ts
@@ -22,7 +22,10 @@ export class PermissionsGuard implements CanActivate {
state: RouterStateSnapshot
): boolean | UrlTree {
if (
- !this.permissionsService.currentUserCan(route.data.requiredPermission)
+ !this.permissionsService.currentUserCan(
+ route.data.requiredPermission.action,
+ route.data.requiredPermission.type
+ )
) {
this.toastService.showError(
$localize`You don't have permissions to do that`
diff --git a/src-ui/src/app/services/permissions.service.ts b/src-ui/src/app/services/permissions.service.ts
index 0f7edee22..b9bad0d96 100644
--- a/src-ui/src/app/services/permissions.service.ts
+++ b/src-ui/src/app/services/permissions.service.ts
@@ -25,11 +25,6 @@ export enum PermissionType {
Admin = '%s_logentry',
}
-export interface PaperlessPermission {
- action: PermissionAction
- type: PermissionType
-}
-
@Injectable({
providedIn: 'root',
})
@@ -42,25 +37,34 @@ export class PermissionsService {
this.currentUser = currentUser
}
- public currentUserCan(permission: PaperlessPermission): boolean {
- return this.permissions.includes(this.getPermissionCode(permission))
+ public currentUserCan(
+ action: PermissionAction,
+ type: PermissionType
+ ): boolean {
+ return this.permissions.includes(this.getPermissionCode(action, type))
}
- public currentUserIsOwner(owner: PaperlessUser): boolean {
- return owner?.id === this.currentUser.id
+ public currentUserOwnsObject(object: ObjectWithPermissions): boolean {
+ return !object || !object.owner || object.owner.id === this.currentUser.id
}
public currentUserHasObjectPermissions(
action: string,
object: ObjectWithPermissions
): boolean {
- return (object.permissions[action] as Array)?.includes(
- this.currentUser.id
+ return (
+ this.currentUserOwnsObject(object) ||
+ (object.permissions[action]['users'] as Array)?.includes(
+ this.currentUser.id
+ )
)
}
- public getPermissionCode(permission: PaperlessPermission): string {
- return permission.type.replace('%s', permission.action)
+ public getPermissionCode(
+ action: PermissionAction,
+ type: PermissionType
+ ): string {
+ return type.replace('%s', action)
}
public getPermissionKeys(permissionStr: string): {
diff --git a/src/documents/serialisers.py b/src/documents/serialisers.py
index 43f3a758e..9ffa29c21 100644
--- a/src/documents/serialisers.py
+++ b/src/documents/serialisers.py
@@ -28,7 +28,7 @@ from .models import UiSettings
from .models import PaperlessTask
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 remove_perm
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.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
@@ -83,14 +85,46 @@ class MatchingModelSerializer(serializers.ModelSerializer):
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):
def get_permissions(self, obj):
- content_type = ContentType.objects.get_for_model(obj)
- user_object_perms = UserObjectPermission.objects.filter(
- object_pk=obj.pk,
- content_type=content_type,
- ).values_list("user", "permission__codename")
- return list(user_object_perms)
+ view_codename = f"view_{obj.__class__.__name__.lower()}"
+ change_codename = f"change_{obj.__class__.__name__.lower()}"
+ return {
+ "view": {
+ "users": get_users_with_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)
@@ -111,19 +145,34 @@ class OwnedObjectSerializer(serializers.ModelSerializer):
)
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):
- user_dict = {
- "view": User.objects.none(),
- "change": User.objects.none(),
+ permissions_dict = {
+ "view": {
+ "users": User.objects.none(),
+ "groups": Group.objects.none(),
+ },
+ "change": {
+ "users": User.objects.none(),
+ "groups": Group.objects.none(),
+ },
}
if set_permissions is not None:
- if "view" in set_permissions:
- view_list = set_permissions["view"]
- user_dict["view"] = self._validate_user_ids(view_list)
- if "change" in set_permissions:
- change_list = set_permissions["change"]
- user_dict["change"] = self._validate_user_ids(change_list)
- return user_dict
+ for action in permissions_dict:
+ users = set_permissions[action]["users"]
+ permissions_dict[action]["users"] = self._validate_user_ids(users)
+ groups = set_permissions[action]["groups"]
+ permissions_dict[action]["groups"] = self._validate_group_ids(groups)
+ return permissions_dict
def __init__(self, *args, **kwargs):
self.user = kwargs.pop("user", None)
@@ -132,7 +181,8 @@ class OwnedObjectSerializer(serializers.ModelSerializer):
def _set_permissions(self, permissions, object):
for action in permissions:
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(
object,
only_with_perms_in=[permission],
@@ -148,6 +198,23 @@ class OwnedObjectSerializer(serializers.ModelSerializer):
user,
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):
if self.user and (
diff --git a/src/documents/tests/test_api.py b/src/documents/tests/test_api.py
index e161edfac..5cfc13672 100644
--- a/src/documents/tests/test_api.py
+++ b/src/documents/tests/test_api.py
@@ -158,7 +158,7 @@ class TestDocumentApi(DirectoriesMixin, APITestCase):
response = self.client.get("/api/documents/?fields=", format="json")
self.assertEqual(response.status_code, 200)
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")
self.assertEqual(response.status_code, 200)