elody 0.0.63__py3-none-any.whl → 0.0.163__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.
Files changed (41) hide show
  1. elody/client.py +70 -23
  2. elody/csv.py +128 -33
  3. elody/error_codes.py +112 -0
  4. elody/exceptions.py +14 -0
  5. elody/job.py +95 -0
  6. elody/loader.py +33 -5
  7. elody/migration/__init__.py +0 -0
  8. elody/migration/base_object_migrator.py +18 -0
  9. elody/object_configurations/__init__.py +0 -0
  10. elody/object_configurations/base_object_configuration.py +174 -0
  11. elody/object_configurations/elody_configuration.py +144 -0
  12. elody/object_configurations/job_configuration.py +65 -0
  13. elody/policies/authentication/base_user_tenant_validation_policy.py +48 -15
  14. elody/policies/authorization/filter_generic_objects_policy.py +68 -22
  15. elody/policies/authorization/filter_generic_objects_policy_v2.py +166 -0
  16. elody/policies/authorization/generic_object_detail_policy.py +10 -27
  17. elody/policies/authorization/generic_object_mediafiles_policy.py +82 -0
  18. elody/policies/authorization/generic_object_metadata_policy.py +8 -27
  19. elody/policies/authorization/generic_object_relations_policy.py +12 -29
  20. elody/policies/authorization/generic_object_request_policy.py +56 -55
  21. elody/policies/authorization/generic_object_request_policy_v2.py +133 -0
  22. elody/policies/authorization/mediafile_derivatives_policy.py +92 -0
  23. elody/policies/authorization/mediafile_download_policy.py +71 -0
  24. elody/policies/authorization/multi_tenant_policy.py +14 -6
  25. elody/policies/authorization/tenant_request_policy.py +3 -1
  26. elody/policies/helpers.py +37 -0
  27. elody/policies/permission_handler.py +217 -199
  28. elody/policies/tenant_id_resolver.py +375 -0
  29. elody/schemas.py +0 -3
  30. elody/util.py +165 -11
  31. {elody-0.0.63.dist-info → elody-0.0.163.dist-info}/METADATA +16 -11
  32. elody-0.0.163.dist-info/RECORD +47 -0
  33. {elody-0.0.63.dist-info → elody-0.0.163.dist-info}/WHEEL +1 -1
  34. {elody-0.0.63.dist-info → elody-0.0.163.dist-info}/top_level.txt +1 -0
  35. tests/__init_.py +0 -0
  36. tests/data.py +74 -0
  37. tests/unit/__init__.py +0 -0
  38. tests/unit/test_csv.py +410 -0
  39. tests/unit/test_utils.py +293 -0
  40. elody-0.0.63.dist-info/RECORD +0 -27
  41. {elody-0.0.63.dist-info → elody-0.0.163.dist-info}/LICENSE +0 -0
@@ -1,10 +1,15 @@
1
+ import re as regex
2
+
1
3
  from abc import ABC, abstractmethod
4
+ from configuration import get_object_configuration_mapper # pyright: ignore
5
+ from copy import deepcopy
6
+ from elody.util import get_raw_id
2
7
  from inuits_policy_based_auth.contexts.user_context import ( # pyright: ignore
3
8
  UserContext,
4
9
  )
5
10
  from inuits_policy_based_auth.helpers.tenant import Tenant # pyright: ignore
6
11
  from storage.storagemanager import StorageManager # pyright: ignore
7
- from werkzeug.exceptions import Unauthorized # pyright: ignore
12
+ from werkzeug.exceptions import Forbidden # pyright: ignore
8
13
 
9
14
 
10
15
  class BaseUserTenantValidationPolicy(ABC):
@@ -14,16 +19,16 @@ class BaseUserTenantValidationPolicy(ABC):
14
19
  self.user = {}
15
20
 
16
21
  @abstractmethod
17
- def get_user(self, id: str) -> dict:
18
- pass
22
+ def get_user(self, id: str, user_context: UserContext) -> dict:
23
+ user_context.bag["roles_from_idp"] = deepcopy(user_context.x_tenant.roles)
19
24
 
20
25
  @abstractmethod
21
- def build_user_context(self, request, user_context: UserContext, user: dict):
26
+ def build_user_context_for_authenticated_user(
27
+ self, request, user_context: UserContext, user: dict
28
+ ):
22
29
  self.user = user
23
30
  user_context.x_tenant = Tenant()
24
- user_context.x_tenant.id = request.headers.get(
25
- "X-tenant-id", self.super_tenant_id
26
- )
31
+ user_context.x_tenant.id = self._determine_tenant_id(request, user)
27
32
  user_context.x_tenant.roles = self.__get_tenant_roles(
28
33
  user_context.x_tenant.id, request
29
34
  )
@@ -33,20 +38,45 @@ class BaseUserTenantValidationPolicy(ABC):
33
38
  user_context.bag["tenant_defining_entity_id"] = user_context.x_tenant.id
34
39
  user_context.bag["tenant_relation_type"] = "isIn"
35
40
  user_context.bag["user_ids"] = self.user["identifiers"]
41
+ user_context.bag["http_method"] = request.method
42
+ user_context.bag["requested_endpoint"] = request.endpoint
43
+ user_context.bag["full_path"] = request.full_path
44
+
45
+ @abstractmethod
46
+ def build_user_context_for_anonymous_user(
47
+ self, user_context: UserContext, user: dict
48
+ ):
49
+ self.user = user
50
+ user_context.id = get_raw_id(user)
51
+ user_context.x_tenant = Tenant()
52
+ user_context.x_tenant.id = self.super_tenant_id
53
+ user_context.x_tenant.roles = ["anonymous"]
54
+ user_context.x_tenant.raw = self.__get_x_tenant_raw(user_context.x_tenant.id)
55
+ user_context.tenants = [user_context.x_tenant]
56
+ user_context.bag["x_tenant_id"] = user_context.x_tenant.id
57
+ user_context.bag["tenant_defining_entity_id"] = user_context.x_tenant.id
58
+ user_context.bag["tenant_relation_type"] = "isIn"
59
+ user_context.bag["user_ids"] = self.user["identifiers"]
60
+
61
+ @abstractmethod
62
+ def _determine_tenant_id(self, request, user) -> str:
63
+ pass
36
64
 
37
65
  def __get_tenant_roles(self, x_tenant_id: str, request) -> list[str]:
38
66
  roles = self.__get_user_tenant_relation(self.super_tenant_id).get("roles", [])
39
67
  if x_tenant_id != self.super_tenant_id:
40
68
  try:
41
69
  user_tenant_relation = self.__get_user_tenant_relation(x_tenant_id)
42
- except Unauthorized as error:
70
+ except Forbidden as error:
43
71
  user_tenant_relation = {}
44
72
  if len(roles) == 0:
45
- raise Unauthorized(error.description)
73
+ raise Forbidden(error.description)
46
74
  roles.extend(user_tenant_relation.get("roles", []))
47
75
 
48
- if len(roles) == 0 and request.path != "/tenants":
49
- raise Unauthorized("User has no global roles, switch to a specific tenant.")
76
+ if len(roles) == 0 and not regex.match(
77
+ "(/[^/]+/v[0-9]+)?/tenants$", request.path
78
+ ):
79
+ raise Forbidden("User has no global roles, switch to a specific tenant.")
50
80
  return roles
51
81
 
52
82
  def __get_user_tenant_relation(self, x_tenant_id: str) -> dict:
@@ -58,18 +88,21 @@ class BaseUserTenantValidationPolicy(ABC):
58
88
 
59
89
  if not user_tenant_relation:
60
90
  if x_tenant_id != self.super_tenant_id:
61
- raise Unauthorized(f"User is not a member of tenant {x_tenant_id}.")
91
+ raise Forbidden(f"User is not a member of tenant {x_tenant_id}.")
62
92
  else:
63
93
  return {}
64
94
 
65
95
  return user_tenant_relation
66
96
 
67
97
  def __get_x_tenant_raw(self, x_tenant_id: str) -> dict:
98
+ collection = (
99
+ get_object_configuration_mapper().get("tenant").crud()["collection"]
100
+ )
68
101
  x_tenant_raw = (
69
- self.storage.get_item_from_collection_by_id("entities", x_tenant_id) or {}
102
+ self.storage.get_item_from_collection_by_id(collection, x_tenant_id) or {}
70
103
  )
71
104
  if x_tenant_raw.get("type") != "tenant":
72
- raise Unauthorized(f"No tenant {x_tenant_id} exists.")
105
+ raise Forbidden(f"No tenant {x_tenant_id} exists.")
73
106
 
74
107
  return x_tenant_raw
75
108
 
@@ -85,7 +118,7 @@ class BaseUserTenantValidationPolicy(ABC):
85
118
  tenant_defining_entity_id = relation["key"]
86
119
  break
87
120
  if not tenant_defining_entity_id:
88
- raise Unauthorized(
121
+ raise Forbidden(
89
122
  f"{x_tenant_raw['_id']} has no relation with a tenant defining entity."
90
123
  )
91
124
 
@@ -3,7 +3,7 @@ import re as regex
3
3
  from copy import deepcopy
4
4
  from elody.policies.permission_handler import (
5
5
  get_permissions,
6
- get_mask_protected_content_post_request_hook,
6
+ mask_protected_content_post_request_hook,
7
7
  )
8
8
  from flask import Request # pyright: ignore
9
9
  from inuits_policy_based_auth import BaseAuthorizationPolicy # pyright: ignore
@@ -20,9 +20,7 @@ class FilterGenericObjectsPolicy(BaseAuthorizationPolicy):
20
20
  self, policy_context: PolicyContext, user_context: UserContext, request_context
21
21
  ):
22
22
  request: Request = request_context.http_request
23
- if not user_context.auth_objects.get("token") or not regex.match(
24
- "^/[^/]+/filter$", request.path
25
- ):
23
+ if not regex.match("^(/[^/]+/v[0-9]+)?/[^/]+/filter$", request.path):
26
24
  return policy_context
27
25
 
28
26
  if not isinstance(user_context.access_restrictions.filters, list):
@@ -32,6 +30,7 @@ class FilterGenericObjectsPolicy(BaseAuthorizationPolicy):
32
30
  policy_context.access_verdict = True
33
31
  return policy_context
34
32
 
33
+ policy_context.access_verdict = False
35
34
  for role in user_context.x_tenant.roles:
36
35
  permissions = get_permissions(role, user_context)
37
36
  if not permissions:
@@ -46,7 +45,7 @@ class FilterGenericObjectsPolicy(BaseAuthorizationPolicy):
46
45
  if access_verdict != None:
47
46
  policy_context.access_verdict = access_verdict
48
47
  if not policy_context.access_verdict:
49
- return policy_context
48
+ break
50
49
 
51
50
  if policy_context.access_verdict:
52
51
  return policy_context
@@ -60,7 +59,7 @@ class FilterGenericObjectsPolicy(BaseAuthorizationPolicy):
60
59
  type_filter = filter
61
60
  elif (
62
61
  filter["type"] == "selection"
63
- and filter["parent_key"] == ""
62
+ and filter.get("parent_key", "") == ""
64
63
  and filter["key"] == "type"
65
64
  ):
66
65
  type_filter = filter
@@ -69,12 +68,26 @@ class FilterGenericObjectsPolicy(BaseAuthorizationPolicy):
69
68
  user_context.access_restrictions.filters.append( # pyright: ignore
70
69
  {
71
70
  "type": "selection",
72
- "parent_key": "relations",
73
- "key": user_context.bag["tenant_relation_type"],
71
+ "key": "type",
72
+ # TODO refactor this in a more generic way
74
73
  "value": [
75
- user_context.bag.get(
76
- "tenant_defining_entity_id", user_context.x_tenant.id
77
- )
74
+ "language",
75
+ "type",
76
+ "collectionForm",
77
+ "institution",
78
+ "tag",
79
+ "triple",
80
+ "person",
81
+ "externalRecord",
82
+ "verzameling",
83
+ "arches_record",
84
+ "photographer",
85
+ "creator",
86
+ "assetPart",
87
+ "set",
88
+ "license",
89
+ "mediafile",
90
+ "share_link",
78
91
  ],
79
92
  "match_exact": True,
80
93
  }
@@ -103,24 +116,57 @@ class PostRequestRules:
103
116
  type_filter_values.remove(type_filter_value)
104
117
  continue
105
118
 
106
- restrictions = permissions["read"][type_filter_value].get(
107
- "restrictions", {}
108
- )
109
- for parent_key in restrictions.keys():
110
- for restriction in restrictions[parent_key]:
119
+ restrictions_grouped_by_index = {}
120
+ schemas = permissions["read"][type_filter_value]
121
+ for schema in schemas.keys():
122
+ restrictions = schemas[schema].get("object_restrictions", {})
123
+ for restricted_key, restricting_value in restrictions.items():
124
+ index, restricted_key = restricted_key.split(":")
125
+ prefix = ""
126
+ if restricted_key[0] == "!":
127
+ restricted_key = restricted_key[1:]
128
+ prefix += "!"
129
+ if restricted_key[0] == "?":
130
+ restricted_key = restricted_key[1:]
131
+ prefix += "?"
132
+ key = f"{schema}|{restricted_key}"
133
+
134
+ if group := restrictions_grouped_by_index.get(index):
135
+ group["key"].append(key)
136
+ else:
137
+ restrictions_grouped_by_index.update(
138
+ {
139
+ index: {
140
+ "key": [key],
141
+ "value": restricting_value,
142
+ "prefix": prefix,
143
+ }
144
+ }
145
+ )
146
+
147
+ for restriction in restrictions_grouped_by_index.values():
148
+ user_context.access_restrictions.filters.append( # pyright: ignore
149
+ {
150
+ "type": "selection",
151
+ "key": restriction["key"],
152
+ "value": restriction["value"],
153
+ "match_exact": True,
154
+ "operator": "or" if restriction["prefix"] == "?" else "and",
155
+ }
156
+ )
157
+ if restriction["prefix"] == "?":
111
158
  user_context.access_restrictions.filters.append( # pyright: ignore
112
159
  {
113
- "type": "selection",
114
- "parent_key": parent_key if parent_key != "root" else "",
160
+ "type": "text",
115
161
  "key": restriction["key"],
116
- "value": restriction["value"],
117
- "match_exact": True,
162
+ "value": "",
163
+ "operator": "or",
118
164
  }
119
165
  )
120
166
 
121
167
  if len(type_filter_values) == 0:
122
- return None
168
+ return False
123
169
  user_context.access_restrictions.post_request_hook = (
124
- get_mask_protected_content_post_request_hook(user_context, permissions)
170
+ mask_protected_content_post_request_hook(user_context, permissions)
125
171
  )
126
172
  return True
@@ -0,0 +1,166 @@
1
+ import re as regex
2
+
3
+ from copy import deepcopy
4
+ from elody.policies.permission_handler import (
5
+ get_permissions,
6
+ mask_protected_content_post_request_hook,
7
+ )
8
+ from flask import Request # pyright: ignore
9
+ from inuits_policy_based_auth import BaseAuthorizationPolicy # pyright: ignore
10
+ from inuits_policy_based_auth.contexts.policy_context import ( # pyright: ignore
11
+ PolicyContext,
12
+ )
13
+ from inuits_policy_based_auth.contexts.user_context import ( # pyright: ignore
14
+ UserContext,
15
+ )
16
+
17
+
18
+ class FilterGenericObjectsPolicyV2(BaseAuthorizationPolicy):
19
+ def authorize(
20
+ self, policy_context: PolicyContext, user_context: UserContext, request_context
21
+ ):
22
+ request: Request = request_context.http_request
23
+ if not regex.match("^(/[^/]+/v[0-9]+)?/[^/]+/filter$", request.path):
24
+ return policy_context
25
+
26
+ if not isinstance(user_context.access_restrictions.filters, list):
27
+ user_context.access_restrictions.filters = []
28
+ type_filter, filters = self.__split_type_filter(
29
+ user_context, deepcopy(request.json or [])
30
+ )
31
+ if not type_filter:
32
+ policy_context.access_verdict = True
33
+ return policy_context
34
+
35
+ policy_context.access_verdict = False
36
+ for role in user_context.x_tenant.roles:
37
+ permissions = get_permissions(role, user_context)
38
+ if not permissions:
39
+ continue
40
+
41
+ rules = [PostRequestRules]
42
+ access_verdict = None
43
+ for rule in rules:
44
+ access_verdict = rule().apply(
45
+ type_filter["value"], filters, user_context, request, permissions
46
+ )
47
+ if access_verdict != None:
48
+ policy_context.access_verdict = access_verdict
49
+ if not policy_context.access_verdict:
50
+ break
51
+
52
+ if policy_context.access_verdict:
53
+ return policy_context
54
+
55
+ return policy_context
56
+
57
+ def __split_type_filter(self, user_context: UserContext, request_body: list):
58
+ type_filter = None
59
+ for filter in request_body:
60
+ if filter["type"] == "type":
61
+ type_filter = filter
62
+ elif filter["type"] == "selection" and filter["key"] == "type":
63
+ type_filter = filter
64
+ elif item_types := filter.get("item_types"):
65
+ type_filter = {
66
+ "type": "selection",
67
+ "key": "type",
68
+ "value": item_types,
69
+ "match_exact": True,
70
+ }
71
+
72
+ if not type_filter:
73
+ if tenant_relation_type := user_context.bag.get("tenant_relation_type"):
74
+ user_context.access_restrictions.filters.append( # pyright: ignore
75
+ {
76
+ "type": "selection",
77
+ "key": tenant_relation_type,
78
+ "value": [
79
+ user_context.bag.get(
80
+ "tenant_defining_entity_id", user_context.x_tenant.id
81
+ )
82
+ ],
83
+ "match_exact": True,
84
+ }
85
+ )
86
+ return None, request_body
87
+
88
+ try:
89
+ request_body.remove(type_filter)
90
+ except ValueError:
91
+ pass
92
+ return type_filter, request_body
93
+
94
+
95
+ class PostRequestRules:
96
+ def apply(
97
+ self,
98
+ type_filter_values: str | list[str],
99
+ filters: list[dict],
100
+ user_context: UserContext,
101
+ request: Request,
102
+ permissions,
103
+ ) -> bool | None:
104
+ if request.method != "POST":
105
+ return None
106
+
107
+ if isinstance(type_filter_values, str):
108
+ type_filter_values = [type_filter_values]
109
+
110
+ type_filter_values_copy = deepcopy(type_filter_values)
111
+ for type_filter_value in type_filter_values_copy:
112
+ if type_filter_value not in permissions["read"].keys():
113
+ type_filter_values.remove(type_filter_value)
114
+ continue
115
+
116
+ restrictions_grouped_by_index = {}
117
+ schemas = permissions["read"][type_filter_value]
118
+ for schema in schemas.keys():
119
+ restrictions = schemas[schema].get("object_restrictions", {})
120
+ for restricted_key, restricting_value in restrictions.items():
121
+ index, restricted_key = restricted_key.split(":")
122
+ key = f"{schema}|{restricted_key}"
123
+ if group := restrictions_grouped_by_index.get(index):
124
+ group["key"].append(key)
125
+ else:
126
+ restrictions_grouped_by_index.update(
127
+ {
128
+ index: {
129
+ "key": [key],
130
+ "value": restricting_value,
131
+ }
132
+ }
133
+ )
134
+
135
+ # support false soft call read responses
136
+ for filter in filters:
137
+ key = filter.get("key", "")
138
+ if isinstance(key, list):
139
+ key = ",".join(key)
140
+ for restriction in restrictions_grouped_by_index.values():
141
+ if key not in ",".join(restriction["key"]):
142
+ continue
143
+ values = (
144
+ filter["value"]
145
+ if isinstance(filter["value"], list)
146
+ else [filter["value"]]
147
+ )
148
+ if not any(value in restriction["value"] for value in values):
149
+ return False
150
+
151
+ for restriction in restrictions_grouped_by_index.values():
152
+ user_context.access_restrictions.filters.append( # pyright: ignore
153
+ {
154
+ "type": "selection",
155
+ "key": restriction["key"],
156
+ "value": restriction["value"],
157
+ "match_exact": True,
158
+ }
159
+ )
160
+
161
+ if len(type_filter_values) == 0:
162
+ return False
163
+ user_context.access_restrictions.post_request_hook = (
164
+ mask_protected_content_post_request_hook(user_context, permissions)
165
+ )
166
+ return True
@@ -1,12 +1,11 @@
1
1
  import re as regex
2
2
 
3
+ from elody.policies.helpers import get_content, get_item
3
4
  from elody.policies.permission_handler import (
4
- get_mask_protected_content_post_request_hook,
5
5
  get_permissions,
6
6
  handle_single_item_request,
7
7
  )
8
8
  from flask import Request # pyright: ignore
9
- from flask_restful import abort # pyright: ignore
10
9
  from inuits_policy_based_auth import BaseAuthorizationPolicy # pyright: ignore
11
10
  from inuits_policy_based_auth.contexts.policy_context import ( # pyright: ignore
12
11
  PolicyContext,
@@ -22,27 +21,10 @@ class GenericObjectDetailPolicy(BaseAuthorizationPolicy):
22
21
  self, policy_context: PolicyContext, user_context: UserContext, request_context
23
22
  ):
24
23
  request: Request = request_context.http_request
25
- if not user_context.auth_objects.get("token") or not regex.match(
26
- "^/[^/]+/[^/]+$|^/ngsi-ld/v1/entities/[^/]+$", request.path
27
- ):
24
+ if not regex.match("^(/[^/]+/v[0-9]+)?/[^/]+/[^/]+$", request.path):
28
25
  return policy_context
29
26
 
30
- view_args = request.view_args or {}
31
- collection = "entities"
32
- if not request.path.startswith("/ngsi-ld/v1/entities"):
33
- collection = request.path.split("/")[1]
34
- id = view_args.get("id")
35
- item = (
36
- StorageManager()
37
- .get_db_engine()
38
- .get_item_from_collection_by_id(view_args.get("collection", collection), id)
39
- )
40
- if not item:
41
- abort(
42
- 404,
43
- message=f"Item with id {id} doesn't exist in collection {collection}",
44
- )
45
-
27
+ item = get_item(StorageManager(), user_context.bag, request.view_args)
46
28
  for role in user_context.x_tenant.roles:
47
29
  permissions = get_permissions(role, user_context)
48
30
  if not permissions:
@@ -61,7 +43,7 @@ class GenericObjectDetailPolicy(BaseAuthorizationPolicy):
61
43
  if access_verdict != None:
62
44
  policy_context.access_verdict = access_verdict
63
45
  if not policy_context.access_verdict:
64
- return policy_context
46
+ break
65
47
 
66
48
  if policy_context.access_verdict:
67
49
  return policy_context
@@ -75,6 +57,7 @@ class PostRequestRules:
75
57
  ) -> bool | None:
76
58
  if request.method != "POST":
77
59
  return None
60
+
78
61
  return handle_single_item_request(user_context, item, permissions, "create")
79
62
 
80
63
 
@@ -85,9 +68,6 @@ class GetRequestRules:
85
68
  if request.method != "GET":
86
69
  return None
87
70
 
88
- user_context.access_restrictions.post_request_hook = (
89
- get_mask_protected_content_post_request_hook(user_context, permissions)
90
- )
91
71
  return handle_single_item_request(user_context, item, permissions, "read")
92
72
 
93
73
 
@@ -98,8 +78,9 @@ class PutRequestRules:
98
78
  if request.method != "PUT":
99
79
  return None
100
80
 
81
+ content = get_content(item, request, request.json)
101
82
  return handle_single_item_request(
102
- user_context, item, permissions, "update", request.json
83
+ user_context, item, permissions, "update", content
103
84
  )
104
85
 
105
86
 
@@ -110,8 +91,9 @@ class PatchRequestRules:
110
91
  if request.method != "PATCH":
111
92
  return None
112
93
 
94
+ content = get_content(item, request, request.json)
113
95
  return handle_single_item_request(
114
- user_context, item, permissions, "update", request.json
96
+ user_context, item, permissions, "update", content
115
97
  )
116
98
 
117
99
 
@@ -121,4 +103,5 @@ class DeleteRequestRules:
121
103
  ) -> bool | None:
122
104
  if request.method != "DELETE":
123
105
  return None
106
+
124
107
  return handle_single_item_request(user_context, item, permissions, "delete")
@@ -0,0 +1,82 @@
1
+ import re as regex
2
+
3
+ from elody.error_codes import ErrorCode, get_error_code, get_read, get_write
4
+ from elody.policies.permission_handler import (
5
+ get_permissions,
6
+ handle_single_item_request,
7
+ )
8
+ from flask import Request # pyright: ignore
9
+ from flask_restful import abort # pyright: ignore
10
+ from inuits_policy_based_auth import BaseAuthorizationPolicy # pyright: ignore
11
+ from inuits_policy_based_auth.contexts.policy_context import ( # pyright: ignore
12
+ PolicyContext,
13
+ )
14
+ from inuits_policy_based_auth.contexts.user_context import ( # pyright: ignore
15
+ UserContext,
16
+ )
17
+ from storage.storagemanager import StorageManager # pyright: ignore
18
+
19
+
20
+ class GenericObjectMediafilesPolicy(BaseAuthorizationPolicy):
21
+ def authorize(
22
+ self, policy_context: PolicyContext, user_context: UserContext, request_context
23
+ ):
24
+ request: Request = request_context.http_request
25
+ if not regex.match("^(/[^/]+/v[0-9]+)?/[^/]+/[^/]+/mediafiles$", request.path):
26
+ return policy_context
27
+
28
+ view_args = request.view_args or {}
29
+ collection = view_args.get("collection", request.path.split("/")[-3])
30
+ id = view_args.get("id")
31
+ item = (
32
+ StorageManager()
33
+ .get_db_engine()
34
+ .get_item_from_collection_by_id(collection, id)
35
+ )
36
+ if not item:
37
+ abort(
38
+ 404,
39
+ message=f"{get_error_code(ErrorCode.ITEM_NOT_FOUND_IN_COLLECTION, get_read())} | id:{id} | collection:{collection} - Item with id {id} doesn't exist in collection {collection}",
40
+ )
41
+
42
+ for role in user_context.x_tenant.roles:
43
+ permissions = get_permissions(role, user_context)
44
+ if not permissions:
45
+ continue
46
+
47
+ rules = [
48
+ PostRequestRules,
49
+ GetRequestRules,
50
+ ]
51
+ access_verdict = None
52
+ for rule in rules:
53
+ access_verdict = rule().apply(item, user_context, request, permissions)
54
+ if access_verdict != None:
55
+ policy_context.access_verdict = access_verdict
56
+ if not policy_context.access_verdict:
57
+ break
58
+
59
+ if policy_context.access_verdict:
60
+ return policy_context
61
+
62
+ return policy_context
63
+
64
+
65
+ class PostRequestRules:
66
+ def apply(
67
+ self, item, user_context: UserContext, request: Request, permissions
68
+ ) -> bool | None:
69
+ if request.method != "POST":
70
+ return None
71
+
72
+ return handle_single_item_request(user_context, item, permissions, "create")
73
+
74
+
75
+ class GetRequestRules:
76
+ def apply(
77
+ self, item, user_context: UserContext, request: Request, permissions
78
+ ) -> bool | None:
79
+ if request.method != "GET":
80
+ return None
81
+
82
+ return handle_single_item_request(user_context, item, permissions, "read")