elody 0.0.157__py3-none-any.whl → 0.0.159__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.
@@ -1,7 +1,6 @@
1
1
  from abc import ABC, abstractmethod
2
2
  from copy import deepcopy
3
3
  from elody.migration.base_object_migrator import BaseObjectMigrator
4
- from policy_factory import get_user_context # pyright: ignore
5
4
 
6
5
 
7
6
  class BaseObjectConfiguration(ABC):
@@ -38,6 +37,8 @@ class BaseObjectConfiguration(ABC):
38
37
  "schema": f"{flat_document.get('schema.type')}:{flat_document.get('schema.version')}",
39
38
  }
40
39
  try:
40
+ from policy_factory import get_user_context # pyright: ignore
41
+
41
42
  user_context = get_user_context()
42
43
  info_labels["http_method"] = user_context.bag.get("http_method")
43
44
  info_labels["requested_endpoint"] = user_context.bag.get(
@@ -86,6 +87,8 @@ class BaseObjectConfiguration(ABC):
86
87
 
87
88
  def _get_user_context_id(self):
88
89
  try:
90
+ from policy_factory import get_user_context # pyright: ignore
91
+
89
92
  return get_user_context().id
90
93
  except Exception:
91
94
  return None
@@ -106,6 +109,8 @@ class BaseObjectConfiguration(ABC):
106
109
 
107
110
  def _should_create_history_object(self):
108
111
  try:
112
+ from policy_factory import get_user_context # pyright: ignore
113
+
109
114
  get_user_context()
110
115
  return bool(self.crud().get("collection_history"))
111
116
  except Exception:
@@ -28,6 +28,12 @@ class JobConfiguration(ElodyConfiguration):
28
28
  def validation(self):
29
29
  return super().validation()
30
30
 
31
+ def _creator(self, post_body, *, get_user_context={}, **_):
32
+ document = super()._creator(post_body)
33
+ if email := get_user_context().email:
34
+ document["computed_values"]["created_by"] = email
35
+ return document
36
+
31
37
  def _post_crud_hook(self, *, crud, document, get_rabbit, **kwargs):
32
38
  if crud == "create":
33
39
  send_cloudevent(
@@ -45,7 +45,7 @@ class FilterGenericObjectsPolicy(BaseAuthorizationPolicy):
45
45
  if access_verdict != None:
46
46
  policy_context.access_verdict = access_verdict
47
47
  if not policy_context.access_verdict:
48
- return policy_context
48
+ break
49
49
 
50
50
  if policy_context.access_verdict:
51
51
  return policy_context
@@ -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
@@ -43,7 +43,7 @@ class GenericObjectDetailPolicy(BaseAuthorizationPolicy):
43
43
  if access_verdict != None:
44
44
  policy_context.access_verdict = access_verdict
45
45
  if not policy_context.access_verdict:
46
- return policy_context
46
+ break
47
47
 
48
48
  if policy_context.access_verdict:
49
49
  return policy_context
@@ -54,7 +54,7 @@ class GenericObjectMediafilesPolicy(BaseAuthorizationPolicy):
54
54
  if access_verdict != None:
55
55
  policy_context.access_verdict = access_verdict
56
56
  if not policy_context.access_verdict:
57
- return policy_context
57
+ break
58
58
 
59
59
  if policy_context.access_verdict:
60
60
  return policy_context
@@ -37,7 +37,7 @@ class GenericObjectMetadataPolicy(BaseAuthorizationPolicy):
37
37
  if access_verdict != None:
38
38
  policy_context.access_verdict = access_verdict
39
39
  if not policy_context.access_verdict:
40
- return policy_context
40
+ break
41
41
 
42
42
  if policy_context.access_verdict:
43
43
  return policy_context
@@ -43,7 +43,7 @@ class GenericObjectRelationsPolicy(BaseAuthorizationPolicy):
43
43
  if access_verdict != None:
44
44
  policy_context.access_verdict = access_verdict
45
45
  if not policy_context.access_verdict:
46
- return policy_context
46
+ break
47
47
 
48
48
  if policy_context.access_verdict:
49
49
  return policy_context
@@ -38,7 +38,7 @@ class GenericObjectRequestPolicy(BaseAuthorizationPolicy):
38
38
  if access_verdict != None:
39
39
  policy_context.access_verdict = access_verdict
40
40
  if not policy_context.access_verdict:
41
- return policy_context
41
+ break
42
42
 
43
43
  if policy_context.access_verdict:
44
44
  return policy_context
@@ -0,0 +1,133 @@
1
+ import re as regex
2
+
3
+ from configuration import get_object_configuration_mapper # pyright: ignore
4
+ from elody.policies.helpers import get_content
5
+ from elody.policies.permission_handler import (
6
+ get_permissions,
7
+ handle_single_item_request,
8
+ mask_protected_content_post_request_hook,
9
+ )
10
+ from flask import Request # pyright: ignore
11
+ from inuits_policy_based_auth import BaseAuthorizationPolicy # pyright: ignore
12
+ from inuits_policy_based_auth.contexts.policy_context import ( # pyright: ignore
13
+ PolicyContext,
14
+ )
15
+ from inuits_policy_based_auth.contexts.user_context import ( # pyright: ignore
16
+ UserContext,
17
+ )
18
+
19
+
20
+ class GenericObjectRequestPolicyV2(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]+)?/[^/]+$", request.path):
26
+ return policy_context
27
+
28
+ for role in user_context.x_tenant.roles:
29
+ permissions = get_permissions(role, user_context)
30
+ if not permissions:
31
+ continue
32
+
33
+ rules = [PostRequestRules, GetRequestRules]
34
+ access_verdict = None
35
+ for rule in rules:
36
+ access_verdict = rule().apply(user_context, request, permissions)
37
+ if access_verdict != None:
38
+ policy_context.access_verdict = access_verdict
39
+ if not policy_context.access_verdict:
40
+ break
41
+
42
+ if policy_context.access_verdict:
43
+ return policy_context
44
+
45
+ return policy_context
46
+
47
+
48
+ class PostRequestRules:
49
+ def apply(
50
+ self, user_context: UserContext, request: Request, permissions
51
+ ) -> bool | None:
52
+ if request.method != "POST":
53
+ return None
54
+
55
+ content = get_content(request.json, request, request.json)
56
+ schema_type = get_object_configuration_mapper().get(content["type"]).SCHEMA_TYPE
57
+ item = {**content, "schema": {"type": schema_type}}
58
+ return handle_single_item_request(
59
+ user_context, item, permissions, "create", content
60
+ )
61
+
62
+
63
+ class GetRequestRules:
64
+ def apply(
65
+ self, user_context: UserContext, request: Request, permissions
66
+ ) -> bool | None:
67
+ if request.method != "GET":
68
+ return None
69
+
70
+ type_query_parameter = request.args.get("type")
71
+ allowed_item_types = list(permissions["read"].keys())
72
+ filters = []
73
+
74
+ if type_query_parameter:
75
+ if type_query_parameter not in allowed_item_types:
76
+ return None
77
+ type_permissions = permissions["read"][type_query_parameter]
78
+ schemas = list(type_permissions.keys())
79
+ if len(schemas) > 0:
80
+ number_of_object_restrictions = len(
81
+ type_permissions[schemas[0]].get("object_restrictions", {}).keys()
82
+ )
83
+ for i in range(number_of_object_restrictions):
84
+ keys, values = [], []
85
+ for schema in schemas:
86
+ object_restrictions = type_permissions[schema].get(
87
+ "object_restrictions", {}
88
+ )
89
+ key = [
90
+ key
91
+ for key in object_restrictions.keys()
92
+ if key.startswith(f"{i}:")
93
+ ][0]
94
+ keys.append(f"{schema}|{key.split(':')[1]}")
95
+ values = object_restrictions[key]
96
+ filters.append(
97
+ {
98
+ "type": "selection",
99
+ "key": keys,
100
+ "value": values,
101
+ "match_exact": True,
102
+ }
103
+ )
104
+ filters.insert(0, {"type": "type", "value": type_query_parameter})
105
+ else:
106
+ filters.append(
107
+ {
108
+ "type": "selection",
109
+ "key": "type",
110
+ "value": allowed_item_types,
111
+ "match_exact": True,
112
+ }
113
+ )
114
+ if tenant_relation_type := user_context.bag.get("tenant_relation_type"):
115
+ filters.append(
116
+ {
117
+ "type": "selection",
118
+ "key": tenant_relation_type,
119
+ "value": [
120
+ "tenant:super",
121
+ user_context.bag.get(
122
+ "tenant_defining_entity_id", user_context.x_tenant.id
123
+ ),
124
+ ],
125
+ "match_exact": True,
126
+ }
127
+ )
128
+
129
+ user_context.access_restrictions.filters = filters
130
+ user_context.access_restrictions.post_request_hook = (
131
+ mask_protected_content_post_request_hook(user_context, permissions)
132
+ )
133
+ return True
@@ -54,7 +54,7 @@ class MediafileDerivativesPolicy(BaseAuthorizationPolicy):
54
54
  if access_verdict != None:
55
55
  policy_context.access_verdict = access_verdict
56
56
  if not policy_context.access_verdict:
57
- return policy_context
57
+ break
58
58
 
59
59
  if policy_context.access_verdict:
60
60
  return policy_context
@@ -53,7 +53,7 @@ class MediafileDownloadPolicy(BaseAuthorizationPolicy):
53
53
  if access_verdict != None:
54
54
  policy_context.access_verdict = access_verdict
55
55
  if not policy_context.access_verdict:
56
- return policy_context
56
+ break
57
57
 
58
58
  if policy_context.access_verdict:
59
59
  return policy_context
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: elody
3
- Version: 0.0.157
3
+ Version: 0.0.159
4
4
  Summary: elody SDK for Python
5
5
  Author-email: Inuits <developers@inuits.eu>
6
6
  License: GNU GENERAL PUBLIC LICENSE
@@ -12,9 +12,9 @@ elody/validator.py,sha256=G7Ya538EJHCFzOxEri2OcFMabfLBCtTKxuf4os_KuNw,260
12
12
  elody/migration/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
13
  elody/migration/base_object_migrator.py,sha256=n8uvgGfjEUy60G47RD7Y-oxp1vHLOauwPMDl87LcxtU,436
14
14
  elody/object_configurations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
- elody/object_configurations/base_object_configuration.py,sha256=2yL8cCgWyT9lwTrpwjL_6TOYynWXRj6XdaHcCGC4RPM,6373
15
+ elody/object_configurations/base_object_configuration.py,sha256=L8K2AW-A3Bo4_Q_jl3S4hnwEGuZo3XmJH5Izditf6CM,6538
16
16
  elody/object_configurations/elody_configuration.py,sha256=Bp3OSHBHuyPs-ubIULzl9wqZd3akYD2idh3mhYo7zu0,5339
17
- elody/object_configurations/job_configuration.py,sha256=FagGQ7WBAcRHTeftezGOa7ykuYQvMB2GKeri80GkcMw,1813
17
+ elody/object_configurations/job_configuration.py,sha256=simi4TBiZ5cQePlzFHAd-C-khViWN9VUbAOKq9FYwk8,2057
18
18
  elody/policies/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
19
19
  elody/policies/helpers.py,sha256=AV3wtvthJafW6ueEYGxggB5kk5knWTWzh3zq29Y1-ws,1434
20
20
  elody/policies/permission_handler.py,sha256=1aXA_xfRxdTfHZY5xRwQqJp5pjSzBrhko4eGRT38WLQ,9505
@@ -23,14 +23,16 @@ elody/policies/authentication/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5N
23
23
  elody/policies/authentication/base_user_tenant_validation_policy.py,sha256=fxvrB4iG6fehBh6b4XS8AWewtNsCABgSooma5ljjZk4,5144
24
24
  elody/policies/authentication/multi_tenant_policy.py,sha256=jmV1RTsApt2IV8BgSRcfnIjWHhDtFjW4OHJgWNUDKRw,3822
25
25
  elody/policies/authorization/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
26
- elody/policies/authorization/filter_generic_objects_policy.py,sha256=SXoUqSBk8_t7gDiM7nxztWwer8R8WU_qfKwqKyb-lXA,6534
27
- elody/policies/authorization/generic_object_detail_policy.py,sha256=BB9uvTRujvmZobRENTNsZnu2zfqDsv8lYvDXOPe5Sso,3471
28
- elody/policies/authorization/generic_object_mediafiles_policy.py,sha256=yHQ_ePUU3xgjf6v6zLF0Cay_RUJLsRfDYulvBkUDIso,2899
29
- elody/policies/authorization/generic_object_metadata_policy.py,sha256=neVuUdK3D5kOhBwKNM44eoQoSY1Fy6o8WV657eoM4WM,2799
30
- elody/policies/authorization/generic_object_relations_policy.py,sha256=CQaE0tpOwz54VWNpqpZLa1C-dROdD1s281fOorjV_mg,3724
31
- elody/policies/authorization/generic_object_request_policy.py,sha256=3H-exSOZYWhlrQ1vGkk0Dfw4XsKCG_NPxdq86DgfFMo,4879
32
- elody/policies/authorization/mediafile_derivatives_policy.py,sha256=1pIK7SHufB_Lu1aoj8gh2XGeLi3H-hTiBDYS7XMtz40,3177
33
- elody/policies/authorization/mediafile_download_policy.py,sha256=1wbPFruSkS_Wjj4rt39SnLsagXoXSJpY1tkOxCGhjf0,2547
26
+ elody/policies/authorization/filter_generic_objects_policy.py,sha256=XjKGM__WKG7n-QaopQGwSyvamQkye1DD6a-8mx2Xkwc,6518
27
+ elody/policies/authorization/filter_generic_objects_policy_v2.py,sha256=96lNmlxgzN8Lu4mXLRxAN-_pfGnK8BF5bXa-iSJofE0,6356
28
+ elody/policies/authorization/generic_object_detail_policy.py,sha256=kk49gIyOCasHH1U8T1-dJUQSW-R37N4wPQi0tMTWvBA,3455
29
+ elody/policies/authorization/generic_object_mediafiles_policy.py,sha256=bo9OL13-6vLhfG6dc_hg_EfynOk0fUbTZg-DmwTtBkU,2883
30
+ elody/policies/authorization/generic_object_metadata_policy.py,sha256=K-tNO1QIrlACupFSiZ12gH-N7T0s3y2hpPIpiKC_maQ,2783
31
+ elody/policies/authorization/generic_object_relations_policy.py,sha256=NRhywIntTpR69R_Lf1-sKfgWdXd7NW2K-lL8cGqWhhQ,3708
32
+ elody/policies/authorization/generic_object_request_policy.py,sha256=H9VGCMLyXO1k4n6ZOHOKcQ6K5ofLjjKe9KVTHH2FPIY,4863
33
+ elody/policies/authorization/generic_object_request_policy_v2.py,sha256=0bAjeq2uDunUDybOYApmNCpaaeCq97kNJEoqSfEUNMg,5051
34
+ elody/policies/authorization/mediafile_derivatives_policy.py,sha256=-i_JRgMcLjDQ85rzjBcrm3hXevAoTn5Z6itgt83K2Ik,3161
35
+ elody/policies/authorization/mediafile_download_policy.py,sha256=q5yib9EmXaWUcRMr-Kg2Fob7M7Mte6C8cmJ7K-rj_Go,2531
34
36
  elody/policies/authorization/multi_tenant_policy.py,sha256=SA9H7SBjzuh8mY3gYN7pDG8TV7hdI3GEUtNeiZeNL3M,3164
35
37
  elody/policies/authorization/tenant_request_policy.py,sha256=dEgblwRAqwWVcE-O7Jn8hVL3OnwDlQhDEOcPlcElBrk,1185
36
38
  tests/__init_.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -38,8 +40,8 @@ tests/data.py,sha256=Q3oxduf-E3m-Z5G_p3fcs8jVy6g10I7zXKL1m94UVMI,2906
38
40
  tests/unit/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
39
41
  tests/unit/test_csv.py,sha256=NQaOhehfQ4GuXku0Y1SA8DYjJeqqidbF50zEHAi8RZA,15923
40
42
  tests/unit/test_utils.py,sha256=g63szcEZyHhCOtrW4BnNbcgVca3oYPIOLjBdIzNwwN0,8784
41
- elody-0.0.157.dist-info/LICENSE,sha256=gXf5dRMhNSbfLPYYTY_5hsZ1r7UU1OaKQEAQUhuIBkM,18092
42
- elody-0.0.157.dist-info/METADATA,sha256=ZI5G47P1QM9FsrLjWu7b16W7htJ4B7wxFUptsUHNAy4,23283
43
- elody-0.0.157.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
44
- elody-0.0.157.dist-info/top_level.txt,sha256=E0mImupLj0KmtUUCXRYEoLDRaSkuiGaOIIseAa0oQ-M,21
45
- elody-0.0.157.dist-info/RECORD,,
43
+ elody-0.0.159.dist-info/LICENSE,sha256=gXf5dRMhNSbfLPYYTY_5hsZ1r7UU1OaKQEAQUhuIBkM,18092
44
+ elody-0.0.159.dist-info/METADATA,sha256=QGOws6FXVtyOTq8KLLM0QmOc6nElR0UwKgSozfJMDiM,23283
45
+ elody-0.0.159.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
46
+ elody-0.0.159.dist-info/top_level.txt,sha256=E0mImupLj0KmtUUCXRYEoLDRaSkuiGaOIIseAa0oQ-M,21
47
+ elody-0.0.159.dist-info/RECORD,,