GeneralManager 0.20.0__py3-none-any.whl → 0.21.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of GeneralManager might be problematic. Click here for more details.
- general_manager/_types/general_manager.py +4 -0
- general_manager/_types/permission.py +16 -0
- general_manager/apps.py +2 -0
- general_manager/interface/read_only_interface.py +31 -14
- general_manager/permission/audit.py +380 -0
- general_manager/permission/base_permission.py +115 -19
- general_manager/permission/manager_based_permission.py +37 -4
- general_manager/permission/mutation_permission.py +68 -13
- general_manager/permission/permission_checks.py +208 -38
- general_manager/permission/permission_data_manager.py +0 -1
- general_manager/permission/utils.py +3 -3
- general_manager/public_api_registry.py +37 -0
- {generalmanager-0.20.0.dist-info → generalmanager-0.21.0.dist-info}/METADATA +1 -1
- {generalmanager-0.20.0.dist-info → generalmanager-0.21.0.dist-info}/RECORD +17 -17
- general_manager/permission/file_based_permission.py +0 -0
- {generalmanager-0.20.0.dist-info → generalmanager-0.21.0.dist-info}/WHEEL +0 -0
- {generalmanager-0.20.0.dist-info → generalmanager-0.21.0.dist-info}/licenses/LICENSE +0 -0
- {generalmanager-0.20.0.dist-info → generalmanager-0.21.0.dist-info}/top_level.txt +0 -0
|
@@ -3,11 +3,16 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
from abc import ABC, abstractmethod
|
|
6
|
-
from typing import TYPE_CHECKING, Any, Literal, TypeAlias
|
|
6
|
+
from typing import TYPE_CHECKING, Any, Literal, TypeAlias
|
|
7
7
|
|
|
8
|
-
from django.contrib.auth.models import AbstractBaseUser,
|
|
8
|
+
from django.contrib.auth.models import AbstractBaseUser, AnonymousUser
|
|
9
9
|
|
|
10
10
|
from general_manager.logging import get_logger
|
|
11
|
+
from general_manager.permission.audit import (
|
|
12
|
+
PermissionAuditEvent,
|
|
13
|
+
audit_logging_enabled,
|
|
14
|
+
emit_permission_audit_event,
|
|
15
|
+
)
|
|
11
16
|
from general_manager.permission.permission_checks import permission_functions
|
|
12
17
|
from general_manager.permission.permission_data_manager import PermissionDataManager
|
|
13
18
|
from general_manager.permission.utils import (
|
|
@@ -64,6 +69,18 @@ class BasePermission(ABC):
|
|
|
64
69
|
"""Return the user being evaluated for permission checks."""
|
|
65
70
|
return self._request_user
|
|
66
71
|
|
|
72
|
+
def describe_permissions(
|
|
73
|
+
self,
|
|
74
|
+
action: Literal["create", "read", "update", "delete"],
|
|
75
|
+
attribute: str,
|
|
76
|
+
) -> tuple[str, ...]:
|
|
77
|
+
"""Return permission expressions associated with an action/attribute pair."""
|
|
78
|
+
return ()
|
|
79
|
+
|
|
80
|
+
def _is_superuser(self) -> bool:
|
|
81
|
+
"""Return True when the current request user bypasses permission checks."""
|
|
82
|
+
return bool(getattr(self.request_user, "is_superuser", False))
|
|
83
|
+
|
|
67
84
|
@classmethod
|
|
68
85
|
def check_create_permission(
|
|
69
86
|
cls,
|
|
@@ -85,17 +102,45 @@ class BasePermission(ABC):
|
|
|
85
102
|
PermissionCheckError: If one or more attributes in `data` are denied for the resolved `request_user`.
|
|
86
103
|
"""
|
|
87
104
|
request_user = cls.get_user_with_id(request_user)
|
|
88
|
-
errors = []
|
|
89
105
|
permission_data = PermissionDataManager(permission_data=data, manager=manager)
|
|
90
106
|
Permission = cls(permission_data, request_user)
|
|
107
|
+
manager_name = manager.__name__ if manager is not None else None
|
|
108
|
+
if Permission._is_superuser():
|
|
109
|
+
if audit_logging_enabled():
|
|
110
|
+
for key in data.keys():
|
|
111
|
+
emit_permission_audit_event(
|
|
112
|
+
PermissionAuditEvent(
|
|
113
|
+
action="create",
|
|
114
|
+
attributes=(key,),
|
|
115
|
+
granted=True,
|
|
116
|
+
user=request_user,
|
|
117
|
+
manager=manager_name,
|
|
118
|
+
permissions=Permission.describe_permissions("create", key),
|
|
119
|
+
bypassed=True,
|
|
120
|
+
)
|
|
121
|
+
)
|
|
122
|
+
return
|
|
123
|
+
|
|
124
|
+
errors: list[str] = []
|
|
91
125
|
user_identifier = getattr(request_user, "id", None)
|
|
92
126
|
for key in data.keys():
|
|
93
127
|
is_allowed = Permission.check_permission("create", key)
|
|
128
|
+
if audit_logging_enabled():
|
|
129
|
+
emit_permission_audit_event(
|
|
130
|
+
PermissionAuditEvent(
|
|
131
|
+
action="create",
|
|
132
|
+
attributes=(key,),
|
|
133
|
+
granted=is_allowed,
|
|
134
|
+
user=request_user,
|
|
135
|
+
manager=manager_name,
|
|
136
|
+
permissions=Permission.describe_permissions("create", key),
|
|
137
|
+
)
|
|
138
|
+
)
|
|
94
139
|
if not is_allowed:
|
|
95
140
|
logger.info(
|
|
96
141
|
"permission denied",
|
|
97
142
|
context={
|
|
98
|
-
"manager":
|
|
143
|
+
"manager": manager_name,
|
|
99
144
|
"action": "create",
|
|
100
145
|
"attribute": key,
|
|
101
146
|
"user_id": user_identifier,
|
|
@@ -124,20 +169,47 @@ class BasePermission(ABC):
|
|
|
124
169
|
PermissionCheckError: Raised with a list of error messages when one or more fields are not permitted to be updated.
|
|
125
170
|
"""
|
|
126
171
|
request_user = cls.get_user_with_id(request_user)
|
|
127
|
-
|
|
128
|
-
errors = []
|
|
129
172
|
permission_data = PermissionDataManager.for_update(
|
|
130
173
|
base_data=old_manager_instance, update_data=data
|
|
131
174
|
)
|
|
132
175
|
Permission = cls(permission_data, request_user)
|
|
176
|
+
manager_name = old_manager_instance.__class__.__name__
|
|
177
|
+
if Permission._is_superuser():
|
|
178
|
+
if audit_logging_enabled():
|
|
179
|
+
for key in data.keys():
|
|
180
|
+
emit_permission_audit_event(
|
|
181
|
+
PermissionAuditEvent(
|
|
182
|
+
action="update",
|
|
183
|
+
attributes=(key,),
|
|
184
|
+
granted=True,
|
|
185
|
+
user=request_user,
|
|
186
|
+
manager=manager_name,
|
|
187
|
+
permissions=Permission.describe_permissions("update", key),
|
|
188
|
+
bypassed=True,
|
|
189
|
+
)
|
|
190
|
+
)
|
|
191
|
+
return
|
|
192
|
+
|
|
193
|
+
errors: list[str] = []
|
|
133
194
|
user_identifier = getattr(request_user, "id", None)
|
|
134
195
|
for key in data.keys():
|
|
135
196
|
is_allowed = Permission.check_permission("update", key)
|
|
197
|
+
if audit_logging_enabled():
|
|
198
|
+
emit_permission_audit_event(
|
|
199
|
+
PermissionAuditEvent(
|
|
200
|
+
action="update",
|
|
201
|
+
attributes=(key,),
|
|
202
|
+
granted=is_allowed,
|
|
203
|
+
user=request_user,
|
|
204
|
+
manager=manager_name,
|
|
205
|
+
permissions=Permission.describe_permissions("update", key),
|
|
206
|
+
)
|
|
207
|
+
)
|
|
136
208
|
if not is_allowed:
|
|
137
209
|
logger.info(
|
|
138
210
|
"permission denied",
|
|
139
211
|
context={
|
|
140
|
-
"manager":
|
|
212
|
+
"manager": manager_name,
|
|
141
213
|
"action": "update",
|
|
142
214
|
"attribute": key,
|
|
143
215
|
"user_id": user_identifier,
|
|
@@ -166,18 +238,45 @@ class BasePermission(ABC):
|
|
|
166
238
|
PermissionCheckError: If one or more attributes are not permitted for deletion by request_user. The exception carries the user and the list of denial messages.
|
|
167
239
|
"""
|
|
168
240
|
request_user = cls.get_user_with_id(request_user)
|
|
169
|
-
|
|
170
|
-
errors = []
|
|
171
241
|
permission_data = PermissionDataManager(manager_instance)
|
|
172
242
|
Permission = cls(permission_data, request_user)
|
|
243
|
+
manager_name = manager_instance.__class__.__name__
|
|
244
|
+
if Permission._is_superuser():
|
|
245
|
+
if audit_logging_enabled():
|
|
246
|
+
for key in manager_instance.__dict__.keys():
|
|
247
|
+
emit_permission_audit_event(
|
|
248
|
+
PermissionAuditEvent(
|
|
249
|
+
action="delete",
|
|
250
|
+
attributes=(key,),
|
|
251
|
+
granted=True,
|
|
252
|
+
user=request_user,
|
|
253
|
+
manager=manager_name,
|
|
254
|
+
permissions=Permission.describe_permissions("delete", key),
|
|
255
|
+
bypassed=True,
|
|
256
|
+
)
|
|
257
|
+
)
|
|
258
|
+
return
|
|
259
|
+
|
|
260
|
+
errors: list[str] = []
|
|
173
261
|
user_identifier = getattr(request_user, "id", None)
|
|
174
262
|
for key in manager_instance.__dict__.keys():
|
|
175
263
|
is_allowed = Permission.check_permission("delete", key)
|
|
264
|
+
if audit_logging_enabled():
|
|
265
|
+
emit_permission_audit_event(
|
|
266
|
+
PermissionAuditEvent(
|
|
267
|
+
action="delete",
|
|
268
|
+
attributes=(key,),
|
|
269
|
+
granted=is_allowed,
|
|
270
|
+
user=request_user,
|
|
271
|
+
manager=manager_name,
|
|
272
|
+
permissions=Permission.describe_permissions("delete", key),
|
|
273
|
+
)
|
|
274
|
+
)
|
|
176
275
|
if not is_allowed:
|
|
177
276
|
logger.info(
|
|
178
277
|
"permission denied",
|
|
179
278
|
context={
|
|
180
|
-
"manager":
|
|
279
|
+
"manager": manager_name,
|
|
181
280
|
"action": "delete",
|
|
182
281
|
"attribute": key,
|
|
183
282
|
"user_id": user_identifier,
|
|
@@ -251,15 +350,14 @@ class BasePermission(ABC):
|
|
|
251
350
|
Raises:
|
|
252
351
|
PermissionNotFoundError: If no permission function matches the leading name in `permission`.
|
|
253
352
|
"""
|
|
353
|
+
if self._is_superuser():
|
|
354
|
+
return {"filter": {}, "exclude": {}}
|
|
254
355
|
permission_function, *config = permission.split(":")
|
|
255
356
|
if permission_function not in permission_functions:
|
|
256
357
|
raise PermissionNotFoundError(permission)
|
|
257
358
|
permission_filter = permission_functions[permission_function][
|
|
258
359
|
"permission_filter"
|
|
259
|
-
](
|
|
260
|
-
cast(AbstractUser | AnonymousUser, self.request_user),
|
|
261
|
-
config,
|
|
262
|
-
)
|
|
360
|
+
](self.request_user, config)
|
|
263
361
|
if permission_filter is None:
|
|
264
362
|
return {"filter": {}, "exclude": {}}
|
|
265
363
|
return permission_filter
|
|
@@ -277,8 +375,6 @@ class BasePermission(ABC):
|
|
|
277
375
|
Returns:
|
|
278
376
|
bool: True when every sub-permission evaluates to True for the current user.
|
|
279
377
|
"""
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
cast(AbstractUser | AnonymousUser, self.request_user),
|
|
284
|
-
)
|
|
378
|
+
if self._is_superuser():
|
|
379
|
+
return True
|
|
380
|
+
return validate_permission_string(permission, self.instance, self.request_user)
|
|
@@ -2,14 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
from typing import TYPE_CHECKING, Literal, Optional, Dict
|
|
5
|
-
from general_manager.permission.base_permission import BasePermission
|
|
5
|
+
from general_manager.permission.base_permission import BasePermission, UserLike
|
|
6
6
|
|
|
7
7
|
if TYPE_CHECKING:
|
|
8
8
|
from general_manager.permission.permission_data_manager import (
|
|
9
9
|
PermissionDataManager,
|
|
10
10
|
)
|
|
11
11
|
from general_manager.manager.general_manager import GeneralManager
|
|
12
|
-
from django.contrib.auth.models import AbstractUser
|
|
13
12
|
|
|
14
13
|
type permission_type = Literal[
|
|
15
14
|
"create",
|
|
@@ -76,14 +75,14 @@ class ManagerBasedPermission(BasePermission):
|
|
|
76
75
|
def __init__(
|
|
77
76
|
self,
|
|
78
77
|
instance: PermissionDataManager | GeneralManager,
|
|
79
|
-
request_user:
|
|
78
|
+
request_user: UserLike,
|
|
80
79
|
) -> None:
|
|
81
80
|
"""
|
|
82
81
|
Initialise the permission object and gather default and attribute-level rules.
|
|
83
82
|
|
|
84
83
|
Parameters:
|
|
85
84
|
instance (PermissionDataManager | GeneralManager): Target data used for permission evaluation.
|
|
86
|
-
request_user (
|
|
85
|
+
request_user (UserLike): User whose permissions are being checked.
|
|
87
86
|
"""
|
|
88
87
|
super().__init__(instance, request_user)
|
|
89
88
|
self.__set_permissions()
|
|
@@ -182,6 +181,9 @@ class ManagerBasedPermission(BasePermission):
|
|
|
182
181
|
Raises:
|
|
183
182
|
UnknownPermissionActionError: If `action` is not one of "create", "read", "update", or "delete".
|
|
184
183
|
"""
|
|
184
|
+
if self._is_superuser():
|
|
185
|
+
self.__overall_results[action] = True
|
|
186
|
+
return True
|
|
185
187
|
if (
|
|
186
188
|
self.__based_on_permission
|
|
187
189
|
and not self.__based_on_permission.check_permission(action, attribute)
|
|
@@ -241,6 +243,8 @@ class ManagerBasedPermission(BasePermission):
|
|
|
241
243
|
Returns:
|
|
242
244
|
list[dict[Literal["filter", "exclude"], dict[str, str]]]: A list of dictionaries each containing "filter" and "exclude" mappings where keys are queryset lookups and values are lookup values.
|
|
243
245
|
"""
|
|
246
|
+
if self._is_superuser():
|
|
247
|
+
return [{"filter": {}, "exclude": {}}]
|
|
244
248
|
__based_on__ = self.__based_on__
|
|
245
249
|
filters: list[dict[Literal["filter", "exclude"], dict[str, str]]] = []
|
|
246
250
|
|
|
@@ -266,3 +270,32 @@ class ManagerBasedPermission(BasePermission):
|
|
|
266
270
|
filters.append(self._get_permission_filter(permission))
|
|
267
271
|
|
|
268
272
|
return filters
|
|
273
|
+
|
|
274
|
+
def describe_permissions(
|
|
275
|
+
self,
|
|
276
|
+
action: permission_type,
|
|
277
|
+
attribute: str,
|
|
278
|
+
) -> tuple[str, ...]:
|
|
279
|
+
"""Return permission expressions considered for the given action/attribute."""
|
|
280
|
+
if action == "create":
|
|
281
|
+
base_permissions: tuple[str, ...] = tuple(self.__create__)
|
|
282
|
+
elif action == "read":
|
|
283
|
+
base_permissions = tuple(self.__read__)
|
|
284
|
+
elif action == "update":
|
|
285
|
+
base_permissions = tuple(self.__update__)
|
|
286
|
+
elif action == "delete":
|
|
287
|
+
base_permissions = tuple(self.__delete__)
|
|
288
|
+
else:
|
|
289
|
+
raise UnknownPermissionActionError(action)
|
|
290
|
+
|
|
291
|
+
attribute_source = self.__attribute_permissions.get(attribute)
|
|
292
|
+
if isinstance(attribute_source, dict):
|
|
293
|
+
attribute_permissions = tuple(attribute_source.get(action, []))
|
|
294
|
+
else:
|
|
295
|
+
attribute_permissions = tuple()
|
|
296
|
+
combined = base_permissions + attribute_permissions
|
|
297
|
+
if self.__based_on_permission is not None:
|
|
298
|
+
combined += self.__based_on_permission.describe_permissions(
|
|
299
|
+
action, attribute
|
|
300
|
+
)
|
|
301
|
+
return combined
|
|
@@ -1,15 +1,26 @@
|
|
|
1
1
|
"""Permission helper for GraphQL mutations."""
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
|
-
|
|
4
|
+
|
|
5
5
|
from typing import Any
|
|
6
|
+
|
|
7
|
+
from django.contrib.auth.models import AbstractBaseUser, AnonymousUser
|
|
8
|
+
|
|
9
|
+
from general_manager.permission.audit import (
|
|
10
|
+
PermissionAuditEvent,
|
|
11
|
+
audit_logging_enabled,
|
|
12
|
+
emit_permission_audit_event,
|
|
13
|
+
)
|
|
6
14
|
from general_manager.permission.base_permission import (
|
|
7
15
|
BasePermission,
|
|
8
16
|
PermissionCheckError,
|
|
9
17
|
)
|
|
10
|
-
|
|
11
18
|
from general_manager.permission.permission_data_manager import PermissionDataManager
|
|
12
19
|
from general_manager.permission.utils import validate_permission_string
|
|
20
|
+
from general_manager.logging import get_logger
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
logger = get_logger("permission.mutation")
|
|
13
24
|
|
|
14
25
|
|
|
15
26
|
class MutationPermission:
|
|
@@ -18,18 +29,19 @@ class MutationPermission:
|
|
|
18
29
|
__mutate__: list[str]
|
|
19
30
|
|
|
20
31
|
def __init__(
|
|
21
|
-
self, data: dict[str, Any], request_user:
|
|
32
|
+
self, data: dict[str, Any], request_user: AbstractBaseUser | AnonymousUser
|
|
22
33
|
) -> None:
|
|
23
34
|
"""
|
|
24
35
|
Create a mutation permission context for the given data and user.
|
|
25
36
|
|
|
26
37
|
Parameters:
|
|
27
38
|
data (dict[str, Any]): Input payload for the mutation.
|
|
28
|
-
request_user (
|
|
39
|
+
request_user (AbstractBaseUser | AnonymousUser): User attempting the mutation.
|
|
29
40
|
"""
|
|
30
41
|
self._data: PermissionDataManager = PermissionDataManager(data)
|
|
31
42
|
self._request_user = request_user
|
|
32
43
|
self.__attribute_permissions = self.__get_attribute_permissions()
|
|
44
|
+
self._mutate_permissions: list[str] = getattr(self.__class__, "__mutate__", [])
|
|
33
45
|
|
|
34
46
|
self.__overall_result: bool | None = None
|
|
35
47
|
|
|
@@ -39,7 +51,7 @@ class MutationPermission:
|
|
|
39
51
|
return self._data
|
|
40
52
|
|
|
41
53
|
@property
|
|
42
|
-
def request_user(self) ->
|
|
54
|
+
def request_user(self) -> AbstractBaseUser | AnonymousUser:
|
|
43
55
|
"""Return the user whose permissions are being evaluated."""
|
|
44
56
|
return self._request_user
|
|
45
57
|
|
|
@@ -53,31 +65,74 @@ class MutationPermission:
|
|
|
53
65
|
attribute_permissions[attribute] = getattr(self.__class__, attribute)
|
|
54
66
|
return attribute_permissions
|
|
55
67
|
|
|
68
|
+
def describe_permissions(self, attribute: str) -> tuple[str, ...]:
|
|
69
|
+
"""Return mutate-level and attribute-specific permissions for the field."""
|
|
70
|
+
base_permissions = tuple(self._mutate_permissions)
|
|
71
|
+
attribute_permissions = tuple(self.__attribute_permissions.get(attribute, []))
|
|
72
|
+
return base_permissions + attribute_permissions
|
|
73
|
+
|
|
56
74
|
@classmethod
|
|
57
75
|
def check(
|
|
58
76
|
cls,
|
|
59
77
|
data: dict[str, Any],
|
|
60
|
-
request_user:
|
|
78
|
+
request_user: AbstractBaseUser | AnonymousUser | Any,
|
|
61
79
|
) -> None:
|
|
62
80
|
"""
|
|
63
81
|
Validate that the given user is authorized to perform the mutation described by `data`.
|
|
64
82
|
|
|
65
83
|
Parameters:
|
|
66
84
|
data (dict[str, Any]): Mutation payload mapping field names to values.
|
|
67
|
-
request_user (
|
|
85
|
+
request_user (AbstractBaseUser | AnonymousUser | Any): A user object or a user identifier; if an identifier is provided it will be resolved to a user.
|
|
68
86
|
|
|
69
87
|
Raises:
|
|
70
88
|
PermissionCheckError: Raised with the `request_user` and a list of field-level error messages when one or more fields fail their permission checks.
|
|
71
89
|
"""
|
|
72
|
-
errors = []
|
|
73
|
-
if not isinstance(request_user, (
|
|
90
|
+
errors: list[str] = []
|
|
91
|
+
if not isinstance(request_user, (AbstractBaseUser, AnonymousUser)):
|
|
74
92
|
request_user = BasePermission.get_user_with_id(request_user)
|
|
75
93
|
Permission = cls(data, request_user)
|
|
94
|
+
class_name = cls.__name__
|
|
95
|
+
is_audit_enabled = audit_logging_enabled()
|
|
96
|
+
if getattr(request_user, "is_superuser", False):
|
|
97
|
+
if is_audit_enabled:
|
|
98
|
+
for key in data:
|
|
99
|
+
emit_permission_audit_event(
|
|
100
|
+
PermissionAuditEvent(
|
|
101
|
+
action="mutation",
|
|
102
|
+
attributes=(key,),
|
|
103
|
+
granted=True,
|
|
104
|
+
user=request_user,
|
|
105
|
+
manager=class_name,
|
|
106
|
+
permissions=Permission.describe_permissions(key),
|
|
107
|
+
bypassed=True,
|
|
108
|
+
)
|
|
109
|
+
)
|
|
110
|
+
return
|
|
76
111
|
for key in data:
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
112
|
+
is_allowed = Permission.check_permission(key)
|
|
113
|
+
if is_audit_enabled:
|
|
114
|
+
emit_permission_audit_event(
|
|
115
|
+
PermissionAuditEvent(
|
|
116
|
+
action="mutation",
|
|
117
|
+
attributes=(key,),
|
|
118
|
+
granted=is_allowed,
|
|
119
|
+
user=request_user,
|
|
120
|
+
manager=class_name,
|
|
121
|
+
permissions=Permission.describe_permissions(key),
|
|
122
|
+
)
|
|
123
|
+
)
|
|
124
|
+
if not is_allowed:
|
|
125
|
+
user_identifier = getattr(request_user, "id", None)
|
|
126
|
+
logger.info(
|
|
127
|
+
"permission denied",
|
|
128
|
+
context={
|
|
129
|
+
"mutation": class_name,
|
|
130
|
+
"action": "mutation",
|
|
131
|
+
"attribute": key,
|
|
132
|
+
"user_id": user_identifier,
|
|
133
|
+
},
|
|
80
134
|
)
|
|
135
|
+
errors.append(f"Mutation permission denied for attribute '{key}'")
|
|
81
136
|
if errors:
|
|
82
137
|
raise PermissionCheckError(request_user, errors)
|
|
83
138
|
|
|
@@ -109,7 +164,7 @@ class MutationPermission:
|
|
|
109
164
|
self.__attribute_permissions[attribute]
|
|
110
165
|
)
|
|
111
166
|
|
|
112
|
-
permission = self.__check_specific_permission(self.
|
|
167
|
+
permission = self.__check_specific_permission(self._mutate_permissions)
|
|
113
168
|
self.__overall_result = permission
|
|
114
169
|
return permission and attribute_permission
|
|
115
170
|
|