mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2025-04-02 13:45:10 -05:00
Dont perform permissions queries by default
This commit is contained in:
parent
15fb3e5328
commit
88a5a2049b
@ -5,11 +5,15 @@ describe('document-detail', () => {
|
|||||||
this.modifiedDocuments = []
|
this.modifiedDocuments = []
|
||||||
|
|
||||||
cy.fixture('documents/documents.json').then((documentsJson) => {
|
cy.fixture('documents/documents.json').then((documentsJson) => {
|
||||||
cy.intercept('GET', 'http://localhost:8000/api/documents/1/', (req) => {
|
cy.intercept(
|
||||||
let response = { ...documentsJson }
|
'GET',
|
||||||
response = response.results.find((d) => d.id == 1)
|
'http://localhost:8000/api/documents/1/?full_perms=true',
|
||||||
req.reply(response)
|
(req) => {
|
||||||
})
|
let response = { ...documentsJson }
|
||||||
|
response = response.results.find((d) => d.id == 1)
|
||||||
|
req.reply(response)
|
||||||
|
}
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
cy.intercept('PUT', 'http://localhost:8000/api/documents/1/', (req) => {
|
cy.intercept('PUT', 'http://localhost:8000/api/documents/1/', (req) => {
|
||||||
|
@ -21,6 +21,7 @@
|
|||||||
"original_file_name": "2022-03-22 no latin title.pdf",
|
"original_file_name": "2022-03-22 no latin title.pdf",
|
||||||
"archived_file_name": "2022-03-22 no latin title.pdf",
|
"archived_file_name": "2022-03-22 no latin title.pdf",
|
||||||
"owner": null,
|
"owner": null,
|
||||||
|
"user_can_change": true,
|
||||||
"permissions": {
|
"permissions": {
|
||||||
"view": {
|
"view": {
|
||||||
"users": [],
|
"users": [],
|
||||||
@ -68,6 +69,7 @@
|
|||||||
"original_file_name": "2022-03-23 lorem ipsum dolor sit amet.pdf",
|
"original_file_name": "2022-03-23 lorem ipsum dolor sit amet.pdf",
|
||||||
"archived_file_name": "2022-03-23 llorem ipsum dolor sit amet.pdf",
|
"archived_file_name": "2022-03-23 llorem ipsum dolor sit amet.pdf",
|
||||||
"owner": null,
|
"owner": null,
|
||||||
|
"user_can_change": true,
|
||||||
"permissions": {
|
"permissions": {
|
||||||
"view": {
|
"view": {
|
||||||
"users": [],
|
"users": [],
|
||||||
@ -98,6 +100,7 @@
|
|||||||
"original_file_name": "2022-03-24 dolor.pdf",
|
"original_file_name": "2022-03-24 dolor.pdf",
|
||||||
"archived_file_name": "2022-03-24 dolor.pdf",
|
"archived_file_name": "2022-03-24 dolor.pdf",
|
||||||
"owner": null,
|
"owner": null,
|
||||||
|
"user_can_change": true,
|
||||||
"permissions": {
|
"permissions": {
|
||||||
"view": {
|
"view": {
|
||||||
"users": [],
|
"users": [],
|
||||||
@ -128,6 +131,7 @@
|
|||||||
"original_file_name": "2022-06-01 sit amet.pdf",
|
"original_file_name": "2022-06-01 sit amet.pdf",
|
||||||
"archived_file_name": "2022-06-01 sit amet.pdf",
|
"archived_file_name": "2022-06-01 sit amet.pdf",
|
||||||
"owner": null,
|
"owner": null,
|
||||||
|
"user_can_change": true,
|
||||||
"permissions": {
|
"permissions": {
|
||||||
"view": {
|
"view": {
|
||||||
"users": [],
|
"users": [],
|
||||||
|
@ -122,7 +122,8 @@ export abstract class ManagementListComponent<T extends ObjectWithId>
|
|||||||
null,
|
null,
|
||||||
this.sortField,
|
this.sortField,
|
||||||
this.sortReverse,
|
this.sortReverse,
|
||||||
this._nameFilter
|
this._nameFilter,
|
||||||
|
true
|
||||||
)
|
)
|
||||||
.subscribe((c) => {
|
.subscribe((c) => {
|
||||||
this.data = c.results
|
this.data = c.results
|
||||||
|
@ -16,4 +16,6 @@ export interface ObjectWithPermissions extends ObjectWithId {
|
|||||||
owner?: number
|
owner?: number
|
||||||
|
|
||||||
permissions?: PermissionsObject
|
permissions?: PermissionsObject
|
||||||
|
|
||||||
|
user_can_change?: boolean
|
||||||
}
|
}
|
||||||
|
@ -58,17 +58,24 @@ export class PermissionsService {
|
|||||||
action: string,
|
action: string,
|
||||||
object: ObjectWithPermissions
|
object: ObjectWithPermissions
|
||||||
): boolean {
|
): boolean {
|
||||||
let actionObject = null
|
if (action === PermissionAction.View) {
|
||||||
if (action === PermissionAction.View) actionObject = object.permissions.view
|
return (
|
||||||
else if (action === PermissionAction.Change)
|
this.currentUserOwnsObject(object) ||
|
||||||
actionObject = object.permissions.change
|
object.permissions?.view.users.includes(this.currentUser.id) ||
|
||||||
if (!actionObject) return false
|
object.permissions?.view.groups.filter((g) =>
|
||||||
return (
|
this.currentUser.groups.includes(g)
|
||||||
this.currentUserOwnsObject(object) ||
|
).length > 0
|
||||||
actionObject.users.includes(this.currentUser.id) ||
|
)
|
||||||
actionObject.groups.filter((g) => this.currentUser.groups.includes(g))
|
} else if (action === PermissionAction.Change) {
|
||||||
.length > 0
|
return (
|
||||||
)
|
this.currentUserOwnsObject(object) ||
|
||||||
|
object.user_can_change ||
|
||||||
|
object.permissions?.change.users.includes(this.currentUser.id) ||
|
||||||
|
object.permissions?.change.groups.filter((g) =>
|
||||||
|
this.currentUser.groups.includes(g)
|
||||||
|
).length > 0
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public getPermissionCode(
|
public getPermissionCode(
|
||||||
|
@ -9,11 +9,15 @@ export abstract class AbstractNameFilterService<
|
|||||||
pageSize?: number,
|
pageSize?: number,
|
||||||
sortField?: string,
|
sortField?: string,
|
||||||
sortReverse?: boolean,
|
sortReverse?: boolean,
|
||||||
nameFilter?: string
|
nameFilter?: string,
|
||||||
|
fullPerms?: boolean
|
||||||
) {
|
) {
|
||||||
let params = {}
|
let params = {}
|
||||||
if (nameFilter) {
|
if (nameFilter) {
|
||||||
params = { name__icontains: nameFilter }
|
params['name__icontains'] = nameFilter
|
||||||
|
}
|
||||||
|
if (fullPerms) {
|
||||||
|
params['full_perms'] = true
|
||||||
}
|
}
|
||||||
return this.list(page, pageSize, sortField, sortReverse, params)
|
return this.list(page, pageSize, sortField, sortReverse, params)
|
||||||
}
|
}
|
||||||
|
@ -113,6 +113,14 @@ export class DocumentService extends AbstractPaperlessService<PaperlessDocument>
|
|||||||
}).pipe(map((response) => response.results.map((doc) => doc.id)))
|
}).pipe(map((response) => response.results.map((doc) => doc.id)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get(id: number): Observable<PaperlessDocument> {
|
||||||
|
return this.http.get<PaperlessDocument>(this.getResourceUrl(id), {
|
||||||
|
params: {
|
||||||
|
full_perms: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
getPreviewUrl(id: number, original: boolean = false): string {
|
getPreviewUrl(id: number, original: boolean = false): string {
|
||||||
let url = this.getResourceUrl(id, 'preview')
|
let url = this.getResourceUrl(id, 'preview')
|
||||||
if (this._searchQuery) url += `#search="${this._searchQuery}"`
|
if (this._searchQuery) url += `#search="${this._searchQuery}"`
|
||||||
|
@ -14,6 +14,7 @@ from django.contrib.auth.models import Group
|
|||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.utils.text import slugify
|
from django.utils.text import slugify
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
|
from guardian.core import ObjectPermissionChecker
|
||||||
from guardian.shortcuts import get_users_with_perms
|
from guardian.shortcuts import get_users_with_perms
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from rest_framework.fields import SerializerMethodField
|
from rest_framework.fields import SerializerMethodField
|
||||||
@ -149,11 +150,21 @@ class SetPermissionsMixin:
|
|||||||
class OwnedObjectSerializer(serializers.ModelSerializer, SetPermissionsMixin):
|
class OwnedObjectSerializer(serializers.ModelSerializer, SetPermissionsMixin):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
self.user = kwargs.pop("user", None)
|
self.user = kwargs.pop("user", None)
|
||||||
|
full_perms = kwargs.pop("full_perms", False)
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
try:
|
||||||
|
if full_perms:
|
||||||
|
self.fields.pop("user_can_change")
|
||||||
|
else:
|
||||||
|
self.fields.pop("permissions")
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
def get_permissions(self, obj):
|
def get_permissions(self, obj):
|
||||||
view_codename = f"view_{obj.__class__.__name__.lower()}"
|
view_codename = f"view_{obj.__class__.__name__.lower()}"
|
||||||
change_codename = f"change_{obj.__class__.__name__.lower()}"
|
change_codename = f"change_{obj.__class__.__name__.lower()}"
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"view": {
|
"view": {
|
||||||
"users": get_users_with_perms(
|
"users": get_users_with_perms(
|
||||||
@ -179,7 +190,19 @@ class OwnedObjectSerializer(serializers.ModelSerializer, SetPermissionsMixin):
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def get_user_can_change(self, obj):
|
||||||
|
checker = ObjectPermissionChecker(self.user) if self.user is not None else None
|
||||||
|
return (
|
||||||
|
obj.owner is None
|
||||||
|
or obj.owner == self.user
|
||||||
|
or (
|
||||||
|
self.user is not None
|
||||||
|
and checker.has_perm(f"change_{obj.__class__.__name__.lower()}", obj)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
permissions = SerializerMethodField(read_only=True)
|
permissions = SerializerMethodField(read_only=True)
|
||||||
|
user_can_change = SerializerMethodField(read_only=True)
|
||||||
|
|
||||||
set_permissions = serializers.DictField(
|
set_permissions = serializers.DictField(
|
||||||
label="Set permissions",
|
label="Set permissions",
|
||||||
@ -235,6 +258,7 @@ class CorrespondentSerializer(MatchingModelSerializer, OwnedObjectSerializer):
|
|||||||
"last_correspondence",
|
"last_correspondence",
|
||||||
"owner",
|
"owner",
|
||||||
"permissions",
|
"permissions",
|
||||||
|
"user_can_change",
|
||||||
"set_permissions",
|
"set_permissions",
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -252,6 +276,7 @@ class DocumentTypeSerializer(MatchingModelSerializer, OwnedObjectSerializer):
|
|||||||
"document_count",
|
"document_count",
|
||||||
"owner",
|
"owner",
|
||||||
"permissions",
|
"permissions",
|
||||||
|
"user_can_change",
|
||||||
"set_permissions",
|
"set_permissions",
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -303,6 +328,7 @@ class TagSerializerVersion1(MatchingModelSerializer, OwnedObjectSerializer):
|
|||||||
"document_count",
|
"document_count",
|
||||||
"owner",
|
"owner",
|
||||||
"permissions",
|
"permissions",
|
||||||
|
"user_can_change",
|
||||||
"set_permissions",
|
"set_permissions",
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -338,6 +364,7 @@ class TagSerializer(MatchingModelSerializer, OwnedObjectSerializer):
|
|||||||
"document_count",
|
"document_count",
|
||||||
"owner",
|
"owner",
|
||||||
"permissions",
|
"permissions",
|
||||||
|
"user_can_change",
|
||||||
"set_permissions",
|
"set_permissions",
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -437,6 +464,7 @@ class DocumentSerializer(OwnedObjectSerializer, DynamicFieldsModelSerializer):
|
|||||||
"archived_file_name",
|
"archived_file_name",
|
||||||
"owner",
|
"owner",
|
||||||
"permissions",
|
"permissions",
|
||||||
|
"user_can_change",
|
||||||
"set_permissions",
|
"set_permissions",
|
||||||
"notes",
|
"notes",
|
||||||
)
|
)
|
||||||
@ -464,6 +492,7 @@ class SavedViewSerializer(OwnedObjectSerializer):
|
|||||||
"filter_rules",
|
"filter_rules",
|
||||||
"owner",
|
"owner",
|
||||||
"permissions",
|
"permissions",
|
||||||
|
"user_can_change",
|
||||||
"set_permissions",
|
"set_permissions",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -783,6 +812,7 @@ class StoragePathSerializer(MatchingModelSerializer, OwnedObjectSerializer):
|
|||||||
"document_count",
|
"document_count",
|
||||||
"owner",
|
"owner",
|
||||||
"permissions",
|
"permissions",
|
||||||
|
"user_can_change",
|
||||||
"set_permissions",
|
"set_permissions",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -3396,6 +3396,36 @@ class TestApiAuth(DirectoriesMixin, APITestCase):
|
|||||||
status.HTTP_404_NOT_FOUND,
|
status.HTTP_404_NOT_FOUND,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_dynamic_permissions_fields(self):
|
||||||
|
Document.objects.create(title="Test", content="content 1", checksum="1")
|
||||||
|
|
||||||
|
user1 = User.objects.create_superuser(username="test1")
|
||||||
|
self.client.force_authenticate(user1)
|
||||||
|
|
||||||
|
response = self.client.get(
|
||||||
|
"/api/documents/",
|
||||||
|
format="json",
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
|
resp_data = response.json()
|
||||||
|
|
||||||
|
self.assertNotIn("permissions", resp_data["results"][0])
|
||||||
|
self.assertIn("user_can_change", resp_data["results"][0])
|
||||||
|
|
||||||
|
response = self.client.get(
|
||||||
|
"/api/documents/?full_perms=true",
|
||||||
|
format="json",
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
|
resp_data = response.json()
|
||||||
|
|
||||||
|
self.assertIn("permissions", resp_data["results"][0])
|
||||||
|
self.assertNotIn("user_can_change", resp_data["results"][0])
|
||||||
|
|
||||||
|
|
||||||
class TestApiRemoteVersion(DirectoriesMixin, APITestCase):
|
class TestApiRemoteVersion(DirectoriesMixin, APITestCase):
|
||||||
ENDPOINT = "/api/remote_version/"
|
ENDPOINT = "/api/remote_version/"
|
||||||
|
@ -157,6 +157,10 @@ class PassUserMixin(CreateModelMixin):
|
|||||||
|
|
||||||
def get_serializer(self, *args, **kwargs):
|
def get_serializer(self, *args, **kwargs):
|
||||||
kwargs.setdefault("user", self.request.user)
|
kwargs.setdefault("user", self.request.user)
|
||||||
|
kwargs.setdefault(
|
||||||
|
"full_perms",
|
||||||
|
self.request.query_params.get("full_perms", False),
|
||||||
|
)
|
||||||
return super().get_serializer(*args, **kwargs)
|
return super().get_serializer(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
@ -274,6 +278,10 @@ class DocumentViewSet(
|
|||||||
kwargs.setdefault("context", self.get_serializer_context())
|
kwargs.setdefault("context", self.get_serializer_context())
|
||||||
kwargs.setdefault("fields", fields)
|
kwargs.setdefault("fields", fields)
|
||||||
kwargs.setdefault("truncate_content", truncate_content.lower() in ["true", "1"])
|
kwargs.setdefault("truncate_content", truncate_content.lower() in ["true", "1"])
|
||||||
|
kwargs.setdefault(
|
||||||
|
"full_perms",
|
||||||
|
self.request.query_params.get("full_perms", False),
|
||||||
|
)
|
||||||
return serializer_class(*args, **kwargs)
|
return serializer_class(*args, **kwargs)
|
||||||
|
|
||||||
def update(self, request, *args, **kwargs):
|
def update(self, request, *args, **kwargs):
|
||||||
|
Loading…
x
Reference in New Issue
Block a user