elody 0.0.158__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.
- elody/policies/authorization/filter_generic_objects_policy.py +1 -1
- elody/policies/authorization/filter_generic_objects_policy_v2.py +166 -0
- elody/policies/authorization/generic_object_detail_policy.py +1 -1
- elody/policies/authorization/generic_object_mediafiles_policy.py +1 -1
- elody/policies/authorization/generic_object_metadata_policy.py +1 -1
- elody/policies/authorization/generic_object_relations_policy.py +1 -1
- elody/policies/authorization/generic_object_request_policy.py +1 -1
- elody/policies/authorization/generic_object_request_policy_v2.py +133 -0
- elody/policies/authorization/mediafile_derivatives_policy.py +1 -1
- elody/policies/authorization/mediafile_download_policy.py +1 -1
- {elody-0.0.158.dist-info → elody-0.0.159.dist-info}/METADATA +1 -1
- {elody-0.0.158.dist-info → elody-0.0.159.dist-info}/RECORD +15 -13
- {elody-0.0.158.dist-info → elody-0.0.159.dist-info}/LICENSE +0 -0
- {elody-0.0.158.dist-info → elody-0.0.159.dist-info}/WHEEL +0 -0
- {elody-0.0.158.dist-info → elody-0.0.159.dist-info}/top_level.txt +0 -0
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
56
|
+
break
|
|
57
57
|
|
|
58
58
|
if policy_context.access_verdict:
|
|
59
59
|
return policy_context
|
|
@@ -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=
|
|
27
|
-
elody/policies/authorization/
|
|
28
|
-
elody/policies/authorization/
|
|
29
|
-
elody/policies/authorization/
|
|
30
|
-
elody/policies/authorization/
|
|
31
|
-
elody/policies/authorization/
|
|
32
|
-
elody/policies/authorization/
|
|
33
|
-
elody/policies/authorization/
|
|
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.
|
|
42
|
-
elody-0.0.
|
|
43
|
-
elody-0.0.
|
|
44
|
-
elody-0.0.
|
|
45
|
-
elody-0.0.
|
|
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,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|