elody 0.0.193__py3-none-any.whl → 0.0.195__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.
@@ -109,6 +109,8 @@ class BaseObjectConfiguration(ABC):
109
109
  for element in value
110
110
  if element
111
111
  ]
112
+ if all(isinstance(element, str) for element in sanitized_document[key]):
113
+ sanitized_document[key] = list(set(sanitized_document[key]))
112
114
  elif isinstance(value, str):
113
115
  lines = value.splitlines()
114
116
  value = "\n".join(line.strip() for line in lines).strip()
@@ -3,123 +3,130 @@ import re as regex
3
3
  from abc import ABC, abstractmethod
4
4
  from configuration import get_object_configuration_mapper # pyright: ignore
5
5
  from copy import deepcopy
6
- from elody.util import get_raw_id
7
6
  from inuits_policy_based_auth.contexts.user_context import ( # pyright: ignore
8
7
  UserContext,
9
8
  )
10
9
  from inuits_policy_based_auth.helpers.tenant import Tenant # pyright: ignore
11
- from storage.storagemanager import StorageManager # pyright: ignore
12
10
  from werkzeug.exceptions import Forbidden # pyright: ignore
13
11
 
14
12
 
15
13
  class BaseUserTenantValidationPolicy(ABC):
16
- def __init__(self) -> None:
17
- self.storage = StorageManager().get_db_engine()
18
- self.super_tenant_id = "tenant:super"
19
- self.user = {}
20
-
21
14
  @abstractmethod
22
- def get_user(self, id: str, user_context: UserContext) -> dict:
15
+ def get_user(
16
+ self,
17
+ id: str,
18
+ user_context: UserContext,
19
+ storage,
20
+ *,
21
+ user_metadata_key_for_global_roles="roles",
22
+ user_tenant_relation_type="hasTenant",
23
+ ) -> dict:
24
+ config = get_object_configuration_mapper().get("user")
25
+ collection = config.crud()["collection"]
26
+ serialize = config.serialization(config.SCHEMA_TYPE, "elody")
27
+
28
+ user = storage.get_item_from_collection_by_id(collection, id) or {}
29
+ self.user = serialize(user)
23
30
  user_context.bag["roles_from_idp"] = deepcopy(user_context.x_tenant.roles)
24
-
25
- @abstractmethod
26
- def build_user_context_for_authenticated_user(
27
- self, request, user_context: UserContext, user: dict
28
- ):
29
- self.user = user
30
- user_context.x_tenant = Tenant()
31
- user_context.x_tenant.id = self._determine_tenant_id(request, user)
32
- user_context.x_tenant.roles = self.__get_tenant_roles(
33
- user_context.x_tenant.id, request
31
+ user_context.bag["user_metadata_key_for_global_roles"] = (
32
+ user_metadata_key_for_global_roles
34
33
  )
35
- user_context.x_tenant.raw = self.__get_x_tenant_raw(user_context.x_tenant.id)
36
- user_context.tenants = [user_context.x_tenant]
37
- user_context.bag["x_tenant_id"] = user_context.x_tenant.id
38
- user_context.bag["tenant_defining_entity_id"] = user_context.x_tenant.id
39
- user_context.bag["tenant_relation_type"] = "isIn"
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
34
+ user_context.bag["user_tenant_relation_type"] = user_tenant_relation_type
35
+
36
+ return self.user
44
37
 
45
38
  @abstractmethod
46
39
  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)
40
+ self, request, user_context: UserContext
41
+ ) -> UserContext:
42
+ user_context = self.__build_user_context(request, user_context)
43
+ user_context.id = "anonymous"
51
44
  user_context.x_tenant = Tenant()
52
- user_context.x_tenant.id = self.super_tenant_id
45
+ user_context.x_tenant.id = ""
53
46
  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"]
47
+ return user_context
48
+
49
+ @abstractmethod
50
+ def build_user_context_for_authenticated_user(
51
+ self, request, user_context: UserContext, user: dict
52
+ ) -> UserContext:
53
+ user_context = self.__build_user_context(request, user_context)
54
+ user_context.x_tenant = Tenant()
55
+ user_context.x_tenant.id = self._determine_tenant_id(request, user_context)
56
+ user_context.x_tenant.roles = self.__get_tenant_roles(request, user_context)
57
+ return user_context
60
58
 
61
59
  @abstractmethod
62
- def _determine_tenant_id(self, request, user) -> str:
60
+ def _determine_tenant_id(self, request, user_context: UserContext) -> str:
63
61
  pass
64
62
 
65
- def __get_tenant_roles(self, x_tenant_id: str, request) -> list[str]:
66
- roles = self.__get_user_tenant_relation(self.super_tenant_id).get("roles", [])
67
- if x_tenant_id != self.super_tenant_id:
68
- try:
69
- user_tenant_relation = self.__get_user_tenant_relation(x_tenant_id)
70
- except Forbidden as error:
71
- user_tenant_relation = {}
72
- if len(roles) == 0:
73
- raise Forbidden(error.description)
74
- roles.extend(user_tenant_relation.get("roles", []))
63
+ def __build_user_context(self, request, user_context: UserContext):
64
+ user_context.bag["http_method"] = request.method
65
+ user_context.bag["requested_endpoint"] = request.endpoint
66
+ user_context.bag["full_path"] = request.full_path
67
+ user_context.bag["collection_resolver"] = (
68
+ self._resolve_collections # pyright: ignore
69
+ )
70
+ return user_context
71
+
72
+ def __get_tenant_roles(self, request, user_context: UserContext) -> list[str]:
73
+ """
74
+ Gathering multiple tenants are supported, but there are some important notes:
75
+ - all roles are stored in a combined way in user_context.x_tenant.roles
76
+ - those combination of different tenant roles will work, as long as:
77
+ - the roles are the same
78
+ - the object restrictions allow all tenants that are linked with the user
79
+ => to make it fully work, see #143185
80
+ - with combined roles, when combaring rol_a vs rol_b
81
+ - if rol_a allow and role_b does not allow access: the role allowing access will always win
82
+ - if both roles allow, but just have different restrictions: the first allowing role will win
83
+ - this is completely random
84
+ => currently no logic in policies to determine which allowing role to apply
85
+ """
86
+
87
+ roles = []
88
+ for metadata in self.user.get("metadata", []):
89
+ if (
90
+ metadata["key"]
91
+ == user_context.bag["user_metadata_key_for_global_roles"]
92
+ ):
93
+ roles.extend(metadata["value"])
94
+
95
+ if user_context.x_tenant.id:
96
+ tenant_ids = user_context.x_tenant.id.split(",")
97
+ for tenant_id in tenant_ids:
98
+ try:
99
+ user_tenant_relation = self.__get_user_tenant_relation(
100
+ tenant_id, user_context.bag["user_tenant_relation_type"]
101
+ )
102
+ except Forbidden as error:
103
+ user_tenant_relation = {}
104
+ if len(roles) == 0:
105
+ raise Forbidden(error.description)
106
+ roles.extend(user_tenant_relation.get("roles", []))
75
107
 
76
108
  if len(roles) == 0 and not regex.match(
77
109
  "(/[^/]+/v[0-9]+)?/tenants$", request.path
78
110
  ):
79
111
  raise Forbidden("User has no global roles, switch to a specific tenant.")
80
- return roles
81
112
 
82
- def __get_user_tenant_relation(self, x_tenant_id: str) -> dict:
113
+ return list(set(roles))
114
+
115
+ def __get_user_tenant_relation(
116
+ self, tenant_id: str, user_tenant_relation_type: str
117
+ ) -> dict:
83
118
  user_tenant_relation = None
84
119
  for relation in self.user.get("relations", []):
85
- if relation["key"] == x_tenant_id and relation["type"] == "hasTenant":
120
+ if (
121
+ relation["key"] == tenant_id
122
+ and relation["type"] == user_tenant_relation_type
123
+ ):
86
124
  user_tenant_relation = relation
87
125
  break
88
126
 
89
127
  if not user_tenant_relation:
90
- if x_tenant_id != self.super_tenant_id:
91
- raise Forbidden(f"User is not a member of tenant {x_tenant_id}.")
92
- else:
93
- return {}
128
+ if tenant_id:
129
+ raise Forbidden(f"User is not a member of tenant {tenant_id}.")
130
+ return {}
94
131
 
95
132
  return user_tenant_relation
96
-
97
- def __get_x_tenant_raw(self, x_tenant_id: str) -> dict:
98
- collection = (
99
- get_object_configuration_mapper().get("tenant").crud()["collection"]
100
- )
101
- x_tenant_raw = (
102
- self.storage.get_item_from_collection_by_id(collection, x_tenant_id) or {}
103
- )
104
- if x_tenant_raw.get("type") != "tenant":
105
- raise Forbidden(f"No tenant {x_tenant_id} exists.")
106
-
107
- return x_tenant_raw
108
-
109
- def _get_tenant_defining_entity_id(
110
- self, x_tenant_id: str, x_tenant_raw: dict
111
- ) -> str:
112
- if x_tenant_id == self.super_tenant_id:
113
- return x_tenant_id.removeprefix("tenant:")
114
-
115
- tenant_defining_entity_id = None
116
- for relation in x_tenant_raw.get("relations", []):
117
- if relation["type"] == "definedBy":
118
- tenant_defining_entity_id = relation["key"]
119
- break
120
- if not tenant_defining_entity_id:
121
- raise Forbidden(
122
- f"{x_tenant_raw['_id']} has no relation with a tenant defining entity."
123
- )
124
-
125
- return tenant_defining_entity_id
@@ -5,7 +5,7 @@ from elody.policies.permission_handler import (
5
5
  get_permissions,
6
6
  mask_protected_content_post_request_hook,
7
7
  )
8
- from flask import Request # pyright: ignore
8
+ from flask import g, Request # pyright: ignore
9
9
  from inuits_policy_based_auth import BaseAuthorizationPolicy # pyright: ignore
10
10
  from inuits_policy_based_auth.contexts.policy_context import ( # pyright: ignore
11
11
  PolicyContext,
@@ -26,7 +26,7 @@ class FilterGenericObjectsPolicyV2(BaseAuthorizationPolicy):
26
26
  if not isinstance(user_context.access_restrictions.filters, list):
27
27
  user_context.access_restrictions.filters = []
28
28
  type_filter, filters = self.__split_type_filter(
29
- user_context, deepcopy(request.json or [])
29
+ user_context, deepcopy(g.get("content") or request.json or [])
30
30
  )
31
31
  if not type_filter:
32
32
  policy_context.access_verdict = True
@@ -1,12 +1,12 @@
1
1
  import re as regex
2
2
 
3
- from elody.error_codes import ErrorCode, get_error_code, get_read, get_write
3
+ from configuration import get_object_configuration_mapper # pyright: ignore
4
+ from elody.policies.helpers import get_content, get_item
4
5
  from elody.policies.permission_handler import (
5
6
  get_permissions,
6
7
  handle_single_item_request,
7
8
  )
8
- from flask import Request # pyright: ignore
9
- from flask_restful import abort # pyright: ignore
9
+ from flask import g, Request # pyright: ignore
10
10
  from inuits_policy_based_auth import BaseAuthorizationPolicy # pyright: ignore
11
11
  from inuits_policy_based_auth.contexts.policy_context import ( # pyright: ignore
12
12
  PolicyContext,
@@ -22,32 +22,18 @@ class MediafileDerivativesPolicy(BaseAuthorizationPolicy):
22
22
  self, policy_context: PolicyContext, user_context: UserContext, request_context
23
23
  ):
24
24
  request: Request = request_context.http_request
25
- if not regex.match(r"^/mediafiles/(.+)/derivatives$", request.path):
25
+ if not regex.match(
26
+ "^(/elody/v[0-9]+)?/mediafiles/[^/]+/derivatives$", request.path
27
+ ):
26
28
  return policy_context
27
29
 
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
-
30
+ item = get_item(StorageManager(), user_context.bag, request.view_args)
42
31
  for role in user_context.x_tenant.roles:
43
32
  permissions = get_permissions(role, user_context)
44
33
  if not permissions:
45
34
  continue
46
35
 
47
- rules = [
48
- PostRequestRules,
49
- GetRequestRules,
50
- ]
36
+ rules = [PostRequestRules, GetRequestRules]
51
37
  access_verdict = None
52
38
  for rule in rules:
53
39
  access_verdict = rule().apply(item, user_context, request, permissions)
@@ -69,7 +55,13 @@ class PostRequestRules:
69
55
  if request.method != "POST":
70
56
  return None
71
57
 
72
- return handle_single_item_request(user_context, item, permissions, "create")
58
+ content = g.get("content") or request.json
59
+ content = get_content(content, request, content)
60
+ schema_type = get_object_configuration_mapper().get(content["type"]).SCHEMA_TYPE
61
+ item = {**content, "schema": {"type": schema_type}}
62
+ return handle_single_item_request(
63
+ user_context, item, permissions, "create", content
64
+ )
73
65
 
74
66
 
75
67
  class GetRequestRules:
@@ -80,13 +72,3 @@ class GetRequestRules:
80
72
  return None
81
73
 
82
74
  return handle_single_item_request(user_context, item, permissions, "read")
83
-
84
-
85
- class DeleteRequestRules:
86
- def apply(
87
- self, item, user_context: UserContext, request: Request, permissions
88
- ) -> bool | None:
89
- if request.method != "DELETE":
90
- return None
91
-
92
- return handle_single_item_request(user_context, item, permissions, "delete")
@@ -1,12 +1,11 @@
1
1
  import re as regex
2
2
 
3
- from elody.error_codes import ErrorCode, get_error_code, get_read
3
+ from elody.policies.helpers import get_item
4
4
  from elody.policies.permission_handler import (
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,31 +21,18 @@ class MediafileDownloadPolicy(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 regex.match(r"^/mediafiles/(.+)/download$", request.path):
24
+ if not regex.match(
25
+ "^(/elody/v[0-9]+)?/mediafiles/[^/]+/download$", request.path
26
+ ):
26
27
  return policy_context
27
28
 
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
-
29
+ item = get_item(StorageManager(), user_context.bag, request.view_args)
42
30
  for role in user_context.x_tenant.roles:
43
31
  permissions = get_permissions(role, user_context)
44
32
  if not permissions:
45
33
  continue
46
34
 
47
- rules = [
48
- GetRequestRules,
49
- ]
35
+ rules = [GetRequestRules]
50
36
  access_verdict = None
51
37
  for rule in rules:
52
38
  access_verdict = rule().apply(item, user_context, request, permissions)
elody/policies/helpers.py CHANGED
@@ -1,7 +1,7 @@
1
- from elody.error_codes import ErrorCode, get_error_code, get_read
2
1
  from configuration import get_object_configuration_mapper # pyright: ignore
3
- from flask_restful import abort # pyright: ignore
2
+ from elody.error_codes import ErrorCode, get_error_code, get_read
4
3
  from serialization.serialize import serialize # pyright: ignore
4
+ from werkzeug.exceptions import NotFound
5
5
 
6
6
 
7
7
  def get_content(item, request, content):
@@ -15,23 +15,17 @@ def get_content(item, request, content):
15
15
  )
16
16
 
17
17
 
18
- def get_item(storage_manager, user_context_bag, view_args):
18
+ def get_item(storage_manager, user_context_bag, view_args) -> dict:
19
19
  view_args = view_args or {}
20
- id = view_args.get("id")
21
- resolve_collections = user_context_bag.get("collection_resolver")
22
- if not resolve_collections:
23
- abort(
24
- 403,
25
- message=f"{get_error_code(ErrorCode.UNDEFINED_COLLECTION_RESOLVER, get_read())} - Collection resolver not defined for user.",
26
- )
27
- collections = resolve_collections(collection=view_args.get("collection"), id=id)
28
- for collection in collections:
29
- if item := storage_manager.get_db_engine().get_item_from_collection_by_id(
30
- collection, id
31
- ):
32
- return item
33
- else:
34
- abort(
35
- 404,
36
- message=f"{get_error_code(ErrorCode.ITEM_NOT_FOUND, get_read())} | id:{id} - Item with id {id} does not exist.",
37
- )
20
+ if id := view_args.get("id"):
21
+ resolve_collections = user_context_bag.get("collection_resolver")
22
+ collections = resolve_collections(collection=view_args.get("collection"), id=id)
23
+ for collection in collections:
24
+ if item := storage_manager.get_db_engine().get_item_from_collection_by_id(
25
+ collection, id
26
+ ):
27
+ return item
28
+
29
+ raise NotFound(
30
+ f"{get_error_code(ErrorCode.ITEM_NOT_FOUND, get_read())} | id:{id} - Item with id {id} does not exist."
31
+ )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: elody
3
- Version: 0.0.193
3
+ Version: 0.0.195
4
4
  Summary: elody SDK for Python
5
5
  Author-email: Inuits <developers@inuits.eu>
6
6
  License: GNU GENERAL PUBLIC LICENSE
@@ -12,36 +12,35 @@ 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=Jn39KS-GesMIf-rVd5S-Zpjezv1YUo7KUkuScecudd8,7223
15
+ elody/object_configurations/base_object_configuration.py,sha256=8wyUq_zqRkGb4Mp198pyxOaGdz2WMZzVOO65s1SDCRw,7393
16
16
  elody/object_configurations/elody_configuration.py,sha256=H3iqNXPhYX6scR7C57L_hCWQH2MnWKwnH3vegLg-UGw,7645
17
17
  elody/object_configurations/job_configuration.py,sha256=HMDxaRUyfqhIy0q3yQDDMH9uW5iCd7VCmqknQofXNt0,2039
18
18
  elody/policies/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
19
- elody/policies/helpers.py,sha256=AV3wtvthJafW6ueEYGxggB5kk5knWTWzh3zq29Y1-ws,1434
19
+ elody/policies/helpers.py,sha256=FFpCRNITO-Y9yzTBJEV70FeFwx6xTG93ddYHL5QAl10,1215
20
20
  elody/policies/permission_handler.py,sha256=C5wwgAplxmrkrseA-oNjX_Ff1PfVGP5NIvpx0F5PYho,9869
21
- elody/policies/tenant_id_resolver.py,sha256=BIl6lr9AH6Q_aZSsxF24B7ramkIZH-R-8bpLrTvYLtM,13796
22
21
  elody/policies/authentication/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
23
- elody/policies/authentication/base_user_tenant_validation_policy.py,sha256=fxvrB4iG6fehBh6b4XS8AWewtNsCABgSooma5ljjZk4,5144
22
+ elody/policies/authentication/base_user_tenant_validation_policy.py,sha256=yyGiYy-MM63itQYwUc99FPeXt6TBW5B95FeLMfjMEok,5368
24
23
  elody/policies/authentication/multi_tenant_policy.py,sha256=g4ZYUQMmCjgLg09wj0-0lGKsJsRt7h4ppI25o1VdZHw,4039
25
24
  elody/policies/authorization/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
26
25
  elody/policies/authorization/filter_generic_objects_policy.py,sha256=mF32moh8hRetBgG8vQW-rz4xjoRQD2yOxdI740SFSUo,6522
27
- elody/policies/authorization/filter_generic_objects_policy_v2.py,sha256=AUnnJpevVP7_XT7jbpt0KDJx5X96H12JKrWOkG3C-uE,6451
26
+ elody/policies/authorization/filter_generic_objects_policy_v2.py,sha256=RrGFARQJnIQUvUSN8i9PvhNxzEVpNInkLGf5mK1Ch0Y,6474
28
27
  elody/policies/authorization/generic_object_detail_policy.py,sha256=y6g1i3vdKMKY4xS4H2m0e1DRztrivMEomx6NkDqA0Pk,3672
29
28
  elody/policies/authorization/generic_object_mediafiles_policy.py,sha256=1-DMsV-FDkcrQCE4KL-SGlVHjTZSMPwYq1bWln2nXE4,2887
30
29
  elody/policies/authorization/generic_object_metadata_policy.py,sha256=xwtOVYmiCKgf75CydfWnV7DLI9X1yVfXPQ4-Ux0Htqk,2787
31
30
  elody/policies/authorization/generic_object_relations_policy.py,sha256=hRLeA5gXB44ufiVVaxWtAuwnXBRY1U4bLWgIadzKtmw,3712
32
31
  elody/policies/authorization/generic_object_request_policy.py,sha256=kuLczjnK5omMF2Gw5ViY_Z9MNPx_w6bNwexiMzvLiUU,4867
33
32
  elody/policies/authorization/generic_object_request_policy_v2.py,sha256=zaoCdV7KVAcMYNNXHYTi8Vtjkw_E8_w8aMqxJ62QqBE,5099
34
- elody/policies/authorization/mediafile_derivatives_policy.py,sha256=0JHg0gsj7pdHsGyS5ksIPYatowBjNEgyiqCU2MtV6TU,3165
35
- elody/policies/authorization/mediafile_download_policy.py,sha256=I9j-w_CrfC1vJ9AqJetKQW9ebs0zur-Wfshrb_cN8vU,2535
33
+ elody/policies/authorization/mediafile_derivatives_policy.py,sha256=OwNpbS8i7-LzcQDPddMWTrXk_Y4wqZxrB12Lw1dhkr0,2671
34
+ elody/policies/authorization/mediafile_download_policy.py,sha256=XMsKavBucmTh4W1kWOzpFWxJ_ZXgHVK1RS7JB4HjtQo,1979
36
35
  elody/policies/authorization/multi_tenant_policy.py,sha256=SA9H7SBjzuh8mY3gYN7pDG8TV7hdI3GEUtNeiZeNL3M,3164
37
36
  elody/policies/authorization/tenant_request_policy.py,sha256=dEgblwRAqwWVcE-O7Jn8hVL3OnwDlQhDEOcPlcElBrk,1185
38
- elody-0.0.193.dist-info/licenses/LICENSE,sha256=gXf5dRMhNSbfLPYYTY_5hsZ1r7UU1OaKQEAQUhuIBkM,18092
37
+ elody-0.0.195.dist-info/licenses/LICENSE,sha256=gXf5dRMhNSbfLPYYTY_5hsZ1r7UU1OaKQEAQUhuIBkM,18092
39
38
  tests/__init_.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
40
39
  tests/data.py,sha256=Q3oxduf-E3m-Z5G_p3fcs8jVy6g10I7zXKL1m94UVMI,2906
41
40
  tests/unit/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
42
41
  tests/unit/test_csv.py,sha256=NQaOhehfQ4GuXku0Y1SA8DYjJeqqidbF50zEHAi8RZA,15923
43
42
  tests/unit/test_utils.py,sha256=g63szcEZyHhCOtrW4BnNbcgVca3oYPIOLjBdIzNwwN0,8784
44
- elody-0.0.193.dist-info/METADATA,sha256=dDSZz4Mfpv3ciUEJWpSUeZ0EGplX7D10MoywzaIdN6w,23358
45
- elody-0.0.193.dist-info/WHEEL,sha256=zaaOINJESkSfm_4HQVc5ssNzHCPXhJm0kEUakpsEHaU,91
46
- elody-0.0.193.dist-info/top_level.txt,sha256=E0mImupLj0KmtUUCXRYEoLDRaSkuiGaOIIseAa0oQ-M,21
47
- elody-0.0.193.dist-info/RECORD,,
43
+ elody-0.0.195.dist-info/METADATA,sha256=ryafZPjxZIEOt-tgv23aB6yqYkXrP1MtBztr4AxsrVI,23358
44
+ elody-0.0.195.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
45
+ elody-0.0.195.dist-info/top_level.txt,sha256=E0mImupLj0KmtUUCXRYEoLDRaSkuiGaOIIseAa0oQ-M,21
46
+ elody-0.0.195.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.8.0)
2
+ Generator: setuptools (80.9.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,375 +0,0 @@
1
- import re as regex
2
-
3
- from elody.error_codes import ErrorCode, get_error_code, get_read, get_write
4
- from flask import Request
5
- from flask_restful import abort
6
- from storage.storagemanager import StorageManager # pyright: ignore
7
- from elody.util import get_item_metadata_value
8
- from elody.exceptions import NotFoundException, NoTenantException
9
-
10
-
11
- class TenantIdResolver:
12
- def resolve(self, request, user):
13
- endpoints = [
14
- EntityGetRequest,
15
- EntityPostRequest,
16
- EntityDetailGetRequest,
17
- EntityDetailUpdateRequest,
18
- EntityDetailDeleteRequest,
19
- EntityDetailGetRelationsRequest,
20
- EntityDetailUpdateRelationsRequest,
21
- EntityDetailCreateRelationsRequest,
22
- EntityDetailDeleteRelationsRequest,
23
- EntityDetailGetMetadataRequest,
24
- EntityDetailUpdateMetadataRequest,
25
- EntityDetailCreateMetadataRequest,
26
- EntityDetailGetMediafilesRequest,
27
- EntityDetailCreateMediafilesRequest,
28
- MediafileGetRequest,
29
- MediafilePostRequest,
30
- MediafileDetailGetRequest,
31
- MediafileDetailUpdateRequest,
32
- MediafileDetailDeleteRequest,
33
- MediafileDetailGetDerivativesRequest,
34
- MediafileDetailCreateDerivativesRequest,
35
- MediafileDetailDeleteDerivativesRequest,
36
- ]
37
-
38
- for endpoint in endpoints:
39
- tenant_id = endpoint().get_tenant_id(request)
40
- if tenant_id != None:
41
- relations = user.get("relations", [])
42
- if len(relations) < 1:
43
- raise NoTenantException(f"User is not attached to a tenant")
44
- has_tenant_relation = any(
45
- relation.get("type") == "hasTenant"
46
- and relation.get("key") == tenant_id
47
- for relation in relations
48
- )
49
- if has_tenant_relation:
50
- return tenant_id
51
- elif len(relations) == 1 and relations[0].get("key") == "tenant:super":
52
- return "tenant:super"
53
- else:
54
- return relations[0].get("key")
55
- return "tenant:super"
56
-
57
-
58
- class BaseRequest:
59
- def __init__(self) -> None:
60
- self.storage = StorageManager().get_db_engine()
61
- self.super_tenant_id = "tenant:super"
62
- # TODO refactor this in a more generic way
63
- self.global_types = [
64
- "language",
65
- "type",
66
- "collectionForm",
67
- "institution",
68
- "tag",
69
- "triple",
70
- "person",
71
- "externalRecord",
72
- "verzameling",
73
- "arches_record",
74
- "manufacturer",
75
- "photographer",
76
- "creator",
77
- "assetPart",
78
- "set",
79
- "download",
80
- "license",
81
- "share_link"
82
- # TODO Mediafile should have a link to an asset
83
- "mediafile",
84
- "savedSearch",
85
- "original_data",
86
- "user",
87
- ]
88
-
89
- def _get_tenant_id_from_entity(self, entity_id):
90
- entity_relations = self.storage.get_collection_item_relations(
91
- "entities", entity_id
92
- )
93
- entity = self.storage.get_item_from_collection_by_id("entities", entity_id)
94
- if not entity:
95
- abort(
96
- 404,
97
- message=f"{get_error_code(ErrorCode.ITEM_NOT_FOUND, get_read())} | id:{entity_id} - Item with id {entity_id} doesn't exist",
98
- )
99
- type = entity.get("type")
100
- if type in self.global_types:
101
- return "tenant:super"
102
- if entity_relations:
103
- for entity_relation in entity_relations:
104
- if entity_relation.get("type") == "isIn":
105
- tenant = self.storage.get_item_from_collection_by_id(
106
- "entities", entity_relation.get("key")
107
- )
108
- return tenant["_id"]
109
- if entity_relation.get("type") == "hasInstitution":
110
- instution_relations = self.storage.get_collection_item_relations(
111
- "entities", entity_relation.get("key")
112
- )
113
- for institution_relation in instution_relations:
114
- if institution_relation.get("type") == "defines":
115
- return institution_relation.get("key")
116
-
117
- if entity and get_item_metadata_value(entity, "institution"):
118
- return f"tenant:{get_item_metadata_value(entity, 'institution')}"
119
- abort(
120
- 400,
121
- message=f"{get_error_code(ErrorCode.ENTITY_HAS_NO_TENANT, get_read())} - Entity has no tenant, and is suppose to have one.",
122
- )
123
-
124
- def _get_tenant_id_from_mediafile(self, mediafile_id):
125
- mediafile_relations = self.storage.get_collection_item_relations(
126
- "mediafiles", mediafile_id
127
- )
128
-
129
- for relation in mediafile_relations:
130
- if relation.get("type") == "belongsTo":
131
- return self._get_tenant_id_from_entity(relation.get("key"))
132
-
133
- # TODO Mediafile should have a link to an asset
134
- def _get_tenant_id_from_body(self, item, soft_call=False):
135
- if soft_call:
136
- return "tenant:super"
137
- if item.get("type") in self.global_types or item.get("type") == "institution":
138
- return "tenant:super"
139
- if item.get("type") == "asset":
140
- for relation in item.get("relations", []):
141
- if relation.get("type") in [
142
- "hasArchesLink",
143
- "hasAdlibLink",
144
- "hasBrocadeLink",
145
- ]:
146
- return "tenant:super"
147
- institution_id = get_item_metadata_value(item, "institution")
148
- if not institution_id:
149
- for relation in item.get("relations", []):
150
- if relation.get("type") == "hasInstitution":
151
- institution_id = relation.get("key")
152
- if institution_id:
153
- return f"tenant:{institution_id}"
154
- abort(
155
- 400,
156
- message=f"{get_error_code(ErrorCode.ENTITY_HAS_NO_TENANT, get_read())} - Item in body doesn't have an institution.",
157
- )
158
-
159
-
160
- class EntityGetRequest(BaseRequest):
161
- def get_tenant_id(self, request: Request) -> str | None:
162
- if (
163
- regex.match(r"^/entities(?:\?(.*))?$", request.path)
164
- and request.method == "GET"
165
- ):
166
- return self.super_tenant_id
167
- return None
168
-
169
-
170
- class EntityPostRequest(BaseRequest):
171
- def get_tenant_id(self, request: Request) -> str | None:
172
- if (
173
- regex.match(r"^/entities(?:\?(.*))?$", request.path)
174
- and request.method == "POST"
175
- ):
176
- is_soft_call = request.args.get("soft") is not None
177
- return self._get_tenant_id_from_body(request.json, soft_call=is_soft_call)
178
- return None
179
-
180
-
181
- class EntityDetailGetRequest(BaseRequest):
182
- def get_tenant_id(self, request: Request) -> str | None:
183
- if regex.match(r"/entities/(.+)$", request.path) and request.method == "GET":
184
- return self._get_tenant_id_from_entity(request.view_args.get("id"))
185
- return None
186
-
187
-
188
- class EntityDetailUpdateRequest(BaseRequest):
189
- def get_tenant_id(self, request: Request) -> str | None:
190
- if regex.match(r"^/entities/(.+)$", request.path) and request.method in [
191
- "PUT",
192
- "PATCH",
193
- ]:
194
- return self._get_tenant_id_from_entity(request.view_args.get("id"))
195
- return None
196
-
197
-
198
- class EntityDetailDeleteRequest(BaseRequest):
199
- def get_tenant_id(self, request: Request) -> str | None:
200
- if (
201
- regex.match(r"^/entities/([^/]+)$", request.path)
202
- and request.method == "DELETE"
203
- ):
204
- return self._get_tenant_id_from_entity(request.view_args.get("id"))
205
- return None
206
-
207
-
208
- class EntityDetailGetRelationsRequest(BaseRequest):
209
- def get_tenant_id(self, request: Request) -> str | None:
210
- if (
211
- regex.match(r"^/entities/(.+)/relations$", request.path)
212
- and request.method == "GET"
213
- ):
214
- return self._get_tenant_id_from_entity(request.view_args.get("id"))
215
- return None
216
-
217
-
218
- class EntityDetailUpdateRelationsRequest(BaseRequest):
219
- def get_tenant_id(self, request: Request) -> str | None:
220
- if regex.match(
221
- r"^/entities/(.+)/relations$", request.path
222
- ) and request.method in ["PUT", "PATCH"]:
223
- return self._get_tenant_id_from_entity(request.view_args.get("id"))
224
- return None
225
-
226
-
227
- class EntityDetailCreateRelationsRequest(BaseRequest):
228
- def get_tenant_id(self, request: Request) -> str | None:
229
- if (
230
- regex.match(r"^/entities/(.+)/relations$", request.path)
231
- and request.method == "POST"
232
- ):
233
- return self._get_tenant_id_from_entity(request.view_args.get("id"))
234
- return None
235
-
236
-
237
- class EntityDetailDeleteRelationsRequest(BaseRequest):
238
- def get_tenant_id(self, request: Request) -> str | None:
239
- if (
240
- regex.match(r"^/entities/(.+)/relations$", request.path)
241
- and request.method == "DELETE"
242
- ):
243
- return self._get_tenant_id_from_entity(request.view_args.get("id"))
244
- return None
245
-
246
-
247
- class EntityDetailGetMetadataRequest(BaseRequest):
248
- def get_tenant_id(self, request: Request) -> str | None:
249
- if (
250
- regex.match(r"^/entities/(.+)/metadata$", request.path)
251
- and request.method == "GET"
252
- ):
253
- return self._get_tenant_id_from_entity(request.view_args.get("id"))
254
- return None
255
-
256
-
257
- class EntityDetailUpdateMetadataRequest(BaseRequest):
258
- def get_tenant_id(self, request: Request) -> str | None:
259
- if regex.match(
260
- r"^/entities/(.+)/metadata$", request.path
261
- ) and request.method in ["PUT", "PATCH"]:
262
- return self._get_tenant_id_from_entity(request.view_args.get("id"))
263
- return None
264
-
265
-
266
- class EntityDetailCreateMetadataRequest(BaseRequest):
267
- def get_tenant_id(self, request: Request) -> str | None:
268
- if (
269
- regex.match(r"^/entities/(.+)/metadata$", request.path)
270
- and request.method == "POST"
271
- ):
272
- return self._get_tenant_id_from_entity(request.view_args.get("id"))
273
- return None
274
-
275
-
276
- class EntityDetailGetMediafilesRequest(BaseRequest):
277
- def get_tenant_id(self, request: Request) -> str | None:
278
- if (
279
- regex.match(r"^/entities/(.+)/mediafiles$", request.path)
280
- and request.method == "GET"
281
- ):
282
- return self._get_tenant_id_from_entity(request.view_args.get("id"))
283
- return None
284
-
285
-
286
- class EntityDetailCreateMediafilesRequest(BaseRequest):
287
- def get_tenant_id(self, request: Request) -> str | None:
288
- if (
289
- regex.match(r"^/entities/(.+)/mediafiles$", request.path)
290
- and request.method == "POST"
291
- ):
292
- return self._get_tenant_id_from_entity(request.view_args.get("id"))
293
- return None
294
-
295
-
296
- # Mediafiles
297
- class MediafileGetRequest(BaseRequest):
298
- def get_tenant_id(self, request: Request) -> str | None:
299
- if (
300
- regex.match(r"^/mediafiles(?:\?(.*))?$", request.path)
301
- and request.method == "GET"
302
- ):
303
- return self.super_tenant_id
304
- return None
305
-
306
-
307
- class MediafilePostRequest(BaseRequest):
308
- def get_tenant_id(self, request: Request) -> str | None:
309
- if (
310
- regex.match(r"^/mediafiles(?:\?(.*))?$", request.path)
311
- and request.method == "POST"
312
- ):
313
- is_soft_call = request.args.get("soft") is not None
314
- return self._get_tenant_id_from_body(request.json, soft_call=is_soft_call)
315
- return None
316
-
317
-
318
- class MediafileDetailGetRequest(BaseRequest):
319
- def get_tenant_id(self, request: Request) -> str | None:
320
- if (
321
- regex.match(r"^/mediafiles/([^/]+)$", request.path)
322
- and request.method == "GET"
323
- ):
324
- return self.super_tenant_id
325
- return None
326
-
327
-
328
- class MediafileDetailUpdateRequest(BaseRequest):
329
- def get_tenant_id(self, request: Request) -> str | None:
330
- if regex.match(r"^/mediafiles/(.+)$", request.path) and request.method in [
331
- "PUT",
332
- "PATCH",
333
- ]:
334
- return self._get_tenant_id_from_mediafile(request.view_args.get("id"))
335
- return None
336
-
337
-
338
- class MediafileDetailDeleteRequest(BaseRequest):
339
- def get_tenant_id(self, request: Request) -> str | None:
340
- if (
341
- regex.match(r"^/mediafiles/(.+)$", request.path)
342
- and request.method == "DELETE"
343
- ):
344
- return self._get_tenant_id_from_mediafile(request.view_args.get("id"))
345
- return None
346
-
347
-
348
- class MediafileDetailGetDerivativesRequest(BaseRequest):
349
- def get_tenant_id(self, request: Request) -> str | None:
350
- if (
351
- regex.match(r"^/mediafiles/(.+)/derivatives$", request.path)
352
- and request.method == "GET"
353
- ):
354
- return self.super_tenant_id
355
- return None
356
-
357
-
358
- class MediafileDetailCreateDerivativesRequest(BaseRequest):
359
- def get_tenant_id(self, request: Request) -> str | None:
360
- if (
361
- regex.match(r"^/mediafiles/(.+)/derivatives$", request.path)
362
- and request.method == "POST"
363
- ):
364
- return self._get_tenant_id_from_mediafile(request.view_args.get("id"))
365
- return None
366
-
367
-
368
- class MediafileDetailDeleteDerivativesRequest(BaseRequest):
369
- def get_tenant_id(self, request: Request) -> str | None:
370
- if (
371
- regex.match(r"^/mediafiles/(.+)/derivatives$", request.path)
372
- and request.method == "DELETE"
373
- ):
374
- return self._get_tenant_id_from_mediafile(request.view_args.get("id"))
375
- return None