pilot-platform-common 2.0.0__tar.gz → 2.3.0__tar.gz

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 (57) hide show
  1. {pilot-platform-common-2.0.0 → pilot_platform_common-2.3.0}/PKG-INFO +13 -1
  2. {pilot-platform-common-2.0.0 → pilot_platform_common-2.3.0}/common/logging/logging.py +17 -1
  3. {pilot-platform-common-2.0.0 → pilot_platform_common-2.3.0}/common/permissions/permissions.py +55 -6
  4. {pilot-platform-common-2.0.0 → pilot_platform_common-2.3.0}/common/project/project_client.py +3 -0
  5. {pilot-platform-common-2.0.0 → pilot_platform_common-2.3.0}/pilot_platform_common.egg-info/PKG-INFO +13 -1
  6. {pilot-platform-common-2.0.0 → pilot_platform_common-2.3.0}/pyproject.toml +3 -1
  7. {pilot-platform-common-2.0.0 → pilot_platform_common-2.3.0}/setup.py +1 -1
  8. {pilot-platform-common-2.0.0 → pilot_platform_common-2.3.0}/tests/conftest.py +7 -0
  9. {pilot-platform-common-2.0.0 → pilot_platform_common-2.3.0}/tests/permissions/test_permissions.py +187 -105
  10. {pilot-platform-common-2.0.0 → pilot_platform_common-2.3.0}/tests/test_logging.py +36 -13
  11. {pilot-platform-common-2.0.0 → pilot_platform_common-2.3.0}/README.md +0 -0
  12. {pilot-platform-common-2.0.0 → pilot_platform_common-2.3.0}/common/__init__.py +0 -0
  13. {pilot-platform-common-2.0.0 → pilot_platform_common-2.3.0}/common/geid/__init__.py +0 -0
  14. {pilot-platform-common-2.0.0 → pilot_platform_common-2.3.0}/common/geid/geid_client.py +0 -0
  15. {pilot-platform-common-2.0.0 → pilot_platform_common-2.3.0}/common/jwt_handler/JWTHandler.py +0 -0
  16. {pilot-platform-common-2.0.0 → pilot_platform_common-2.3.0}/common/jwt_handler/__init__.py +0 -0
  17. {pilot-platform-common-2.0.0 → pilot_platform_common-2.3.0}/common/jwt_handler/jwt_handler_exception.py +0 -0
  18. {pilot-platform-common-2.0.0 → pilot_platform_common-2.3.0}/common/jwt_handler/models.py +0 -0
  19. {pilot-platform-common-2.0.0 → pilot_platform_common-2.3.0}/common/logging/__init__.py +0 -0
  20. {pilot-platform-common-2.0.0 → pilot_platform_common-2.3.0}/common/logging/formatter.py +0 -0
  21. {pilot-platform-common-2.0.0 → pilot_platform_common-2.3.0}/common/logging/logger.py +0 -0
  22. {pilot-platform-common-2.0.0 → pilot_platform_common-2.3.0}/common/models/__init__.py +0 -0
  23. {pilot-platform-common-2.0.0 → pilot_platform_common-2.3.0}/common/models/config_center_policy.py +0 -0
  24. {pilot-platform-common-2.0.0 → pilot_platform_common-2.3.0}/common/models/service_id_generator.py +0 -0
  25. {pilot-platform-common-2.0.0 → pilot_platform_common-2.3.0}/common/object_storage_adaptor/__init__.py +0 -0
  26. {pilot-platform-common-2.0.0 → pilot_platform_common-2.3.0}/common/object_storage_adaptor/base_client.py +0 -0
  27. {pilot-platform-common-2.0.0 → pilot_platform_common-2.3.0}/common/object_storage_adaptor/boto3_admin_client.py +0 -0
  28. {pilot-platform-common-2.0.0 → pilot_platform_common-2.3.0}/common/object_storage_adaptor/boto3_client.py +0 -0
  29. {pilot-platform-common-2.0.0 → pilot_platform_common-2.3.0}/common/object_storage_adaptor/minio_policy_client.py +0 -0
  30. {pilot-platform-common-2.0.0 → pilot_platform_common-2.3.0}/common/permissions/__init__.py +0 -0
  31. {pilot-platform-common-2.0.0 → pilot_platform_common-2.3.0}/common/permissions/exceptions.py +0 -0
  32. {pilot-platform-common-2.0.0 → pilot_platform_common-2.3.0}/common/permissions/permissions_manager.py +0 -0
  33. {pilot-platform-common-2.0.0 → pilot_platform_common-2.3.0}/common/permissions/schemas.py +0 -0
  34. {pilot-platform-common-2.0.0 → pilot_platform_common-2.3.0}/common/project/__init__.py +0 -0
  35. {pilot-platform-common-2.0.0 → pilot_platform_common-2.3.0}/common/project/project_exceptions.py +0 -0
  36. {pilot-platform-common-2.0.0 → pilot_platform_common-2.3.0}/common/services/__init__.py +0 -0
  37. {pilot-platform-common-2.0.0 → pilot_platform_common-2.3.0}/common/services/auth_client.py +0 -0
  38. {pilot-platform-common-2.0.0 → pilot_platform_common-2.3.0}/common/vault/__init__.py +0 -0
  39. {pilot-platform-common-2.0.0 → pilot_platform_common-2.3.0}/common/vault/vault_client.py +0 -0
  40. {pilot-platform-common-2.0.0 → pilot_platform_common-2.3.0}/common/vault/vault_exception.py +0 -0
  41. {pilot-platform-common-2.0.0 → pilot_platform_common-2.3.0}/pilot_platform_common.egg-info/SOURCES.txt +0 -0
  42. {pilot-platform-common-2.0.0 → pilot_platform_common-2.3.0}/pilot_platform_common.egg-info/dependency_links.txt +0 -0
  43. {pilot-platform-common-2.0.0 → pilot_platform_common-2.3.0}/pilot_platform_common.egg-info/requires.txt +0 -0
  44. {pilot-platform-common-2.0.0 → pilot_platform_common-2.3.0}/pilot_platform_common.egg-info/top_level.txt +0 -0
  45. {pilot-platform-common-2.0.0 → pilot_platform_common-2.3.0}/setup.cfg +0 -0
  46. {pilot-platform-common-2.0.0 → pilot_platform_common-2.3.0}/tests/__init__.py +0 -0
  47. {pilot-platform-common-2.0.0 → pilot_platform_common-2.3.0}/tests/fixtures/__init__.py +0 -0
  48. {pilot-platform-common-2.0.0 → pilot_platform_common-2.3.0}/tests/fixtures/fake.py +0 -0
  49. {pilot-platform-common-2.0.0 → pilot_platform_common-2.3.0}/tests/fixtures/metadata.py +0 -0
  50. {pilot-platform-common-2.0.0 → pilot_platform_common-2.3.0}/tests/permissions/__init__.py +0 -0
  51. {pilot-platform-common-2.0.0 → pilot_platform_common-2.3.0}/tests/permissions/test_auth_client.py +0 -0
  52. {pilot-platform-common-2.0.0 → pilot_platform_common-2.3.0}/tests/permissions/test_permissions_manager.py +0 -0
  53. {pilot-platform-common-2.0.0 → pilot_platform_common-2.3.0}/tests/test_formatter.py +0 -0
  54. {pilot-platform-common-2.0.0 → pilot_platform_common-2.3.0}/tests/test_geid_client.py +0 -0
  55. {pilot-platform-common-2.0.0 → pilot_platform_common-2.3.0}/tests/test_jwt_handler.py +0 -0
  56. {pilot-platform-common-2.0.0 → pilot_platform_common-2.3.0}/tests/test_project_client.py +0 -0
  57. {pilot-platform-common-2.0.0 → pilot_platform_common-2.3.0}/tests/test_vault_client.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pilot-platform-common
3
- Version: 2.0.0
3
+ Version: 2.3.0
4
4
  Summary: Generates entity ID and connects with Vault (secret engine) to retrieve credentials
5
5
  Author: Indoc Systems
6
6
  Author-email: etaylor@indocresearch.org
@@ -8,6 +8,18 @@ Classifier: Programming Language :: Python :: 3
8
8
  Classifier: Operating System :: OS Independent
9
9
  Requires-Python: >=3.10
10
10
  Description-Content-Type: text/markdown
11
+ Requires-Dist: python-dotenv>=0.19.1
12
+ Requires-Dist: httpx<0.27.0,>=0.23.0
13
+ Requires-Dist: redis<5.0.0,>=4.5.0
14
+ Requires-Dist: aioboto3==9.6.0
15
+ Requires-Dist: xmltodict==0.13.0
16
+ Requires-Dist: minio==7.1.8
17
+ Requires-Dist: python-json-logger==2.0.2
18
+ Requires-Dist: pyjwt==2.6.0
19
+ Requires-Dist: starlette<0.38.0,>=0.37.2
20
+ Requires-Dist: requests<2.32.0,>=2.26.0
21
+ Requires-Dist: cryptography==39.0.0
22
+ Requires-Dist: pydantic<3.0.0,>=2.7.1
11
23
 
12
24
  # common
13
25
 
@@ -10,6 +10,8 @@ from collections.abc import Mapping
10
10
  from typing import Any
11
11
  from typing import Union
12
12
 
13
+ from opentelemetry import trace
14
+
13
15
  AUDIT = logging.INFO + 5
14
16
 
15
17
 
@@ -19,12 +21,22 @@ class Logger(logging.Logger):
19
21
 
20
22
 
21
23
  class JsonFormatter(logging.Formatter):
22
- """Convert LogRecord to json string."""
24
+ """Convert LogRecord to JSON string with trace and span IDs."""
23
25
 
24
26
  def format(self, record: logging.LogRecord) -> str:
27
+ # Get the current span
28
+ span = trace.get_current_span()
29
+
30
+ # Extract trace ID and span ID
31
+ span_context = span.get_span_context()
32
+ trace_id = format(span_context.trace_id, '032x') if span_context.is_valid else None
33
+ span_id = format(span_context.span_id, '016x') if span_context.is_valid else None
34
+ trace_enabled = span_context.trace_flags.sampled if span_context.trace_flags is not None else False
35
+
25
36
  asctime = dt.datetime.fromtimestamp(record.created, tz=dt.timezone.utc).isoformat()
26
37
  level = record.levelname
27
38
  logger = record.name
39
+ location = f'{record.filename}:{record.lineno}'
28
40
  message = record.getMessage()
29
41
  exc_info = None
30
42
  if record.exc_info:
@@ -38,9 +50,13 @@ class JsonFormatter(logging.Formatter):
38
50
  'asctime': asctime,
39
51
  'level': level,
40
52
  'logger': logger,
53
+ 'location': location,
41
54
  'message': message,
42
55
  'exc_info': exc_info,
43
56
  'details': details,
57
+ 'trace_id': trace_id,
58
+ 'span_id': span_id,
59
+ 'trace_enabled': trace_enabled,
44
60
  }
45
61
  )
46
62
 
@@ -2,6 +2,7 @@
2
2
  #
3
3
  # Contact Indoc Systems for any questions regarding the use of this source code.
4
4
 
5
+ import os
5
6
  from typing import Any
6
7
  from typing import Optional
7
8
 
@@ -10,11 +11,19 @@ from httpx import AsyncClient
10
11
  from common.logging import logger
11
12
  from common.permissions.exceptions import ProjectRoleNotFound
12
13
  from common.permissions.permissions_manager import PermissionsManager
14
+ from common.project.project_client import ProjectClient
13
15
  from common.services.auth_client import AuthClient
14
16
 
15
17
 
16
18
  async def has_permission(
17
- auth_url: str, project_code: str, resource: str, zone: str, operation: str, current_identity: dict[str, Any]
19
+ auth_url: str,
20
+ project_url: str,
21
+ redis_url: str,
22
+ project_code: str,
23
+ resource: str,
24
+ zone: str,
25
+ operation: str,
26
+ current_identity: dict[str, Any],
18
27
  ) -> bool:
19
28
  if current_identity['role'] == 'admin':
20
29
  role = 'platform_admin'
@@ -27,6 +36,13 @@ async def has_permission(
27
36
  logger.info('Unable to get project role in permissions check, user might not belong to project')
28
37
  return False
29
38
 
39
+ enable_state_check = os.environ.get('ENABLE_PROJECT_STATE_CHECK', 'true') == 'true'
40
+ if enable_state_check:
41
+ project_client = ProjectClient(project_url, redis_url)
42
+ project = await project_client.get(project_code)
43
+ if project.state == 'trashed':
44
+ return False
45
+
30
46
  try:
31
47
  payload = {
32
48
  'role': role,
@@ -82,7 +98,13 @@ async def get_project_folder_entity(metadata_url: str, name: str, _type: str, zo
82
98
 
83
99
 
84
100
  async def has_file_permission(
85
- auth_url: str, metadata_url: str, file_entity: dict, operation: str, current_identity: dict
101
+ auth_url: str,
102
+ metadata_url: str,
103
+ project_url: str,
104
+ redis_url: str,
105
+ file_entity: dict,
106
+ operation: str,
107
+ current_identity: dict,
86
108
  ) -> bool:
87
109
  if file_entity['container_type'] != 'project':
88
110
  logger.info('Unsupport container type, permission denied')
@@ -111,18 +133,34 @@ async def has_file_permission(
111
133
  project_folder_id = project_folder['id']
112
134
 
113
135
  if not await has_permission(
114
- auth_url, project_code, f'file_shared_{project_folder_id}', zone, operation, current_identity
136
+ auth_url,
137
+ project_url,
138
+ redis_url,
139
+ project_code,
140
+ f'file_shared_{project_folder_id}',
141
+ zone,
142
+ operation,
143
+ current_identity,
115
144
  ):
116
145
  return False
117
146
  elif root_folder == 'users':
118
147
  name_folder_name = get_name_or_path(file_entity, folder_path_split)
119
148
  # If the user has file_any permission return True, if the file is in the users namefolder and the
120
149
  # user has file own permissions return True else False
121
- if not await has_permission(auth_url, project_code, 'file_any', zone, operation, current_identity):
150
+ if not await has_permission(
151
+ auth_url, project_url, redis_url, project_code, 'file_any', zone, operation, current_identity
152
+ ):
122
153
  if name_folder_name != username:
123
154
  return False
124
155
  if not await has_permission(
125
- auth_url, project_code, 'file_in_own_namefolder', zone, operation, current_identity
156
+ auth_url,
157
+ project_url,
158
+ redis_url,
159
+ project_code,
160
+ 'file_in_own_namefolder',
161
+ zone,
162
+ operation,
163
+ current_identity,
126
164
  ):
127
165
  return False
128
166
  else:
@@ -154,9 +192,20 @@ def get_name_or_path(file_entity: dict[str, str], folder_path_split: list[str])
154
192
 
155
193
 
156
194
  async def has_permissions(
157
- auth_url: str, project_code: str, permissions: list[dict[str, str]], current_identity: dict[str, Any]
195
+ auth_url: str,
196
+ project_url: str,
197
+ redis_url: str,
198
+ project_code: str,
199
+ permissions: list[dict[str, str]],
200
+ current_identity: dict[str, Any],
158
201
  ) -> dict[str, bool]:
159
202
  """Performs bulk permission assertions for a project."""
203
+ enable_state_check = os.environ.get('ENABLE_PROJECT_STATE_CHECK', 'true') == 'true'
204
+ if enable_state_check:
205
+ project_client = ProjectClient(project_url, redis_url)
206
+ project = await project_client.get(project_code)
207
+ if project.state == 'trashed':
208
+ return False
160
209
 
161
210
  permission_manager = PermissionsManager(project_code)
162
211
  role = permission_manager.retrieve_project_role(current_identity)
@@ -226,6 +226,9 @@ class ProjectClient:
226
226
  raise ProjectException(status_code=response.status_code, error_msg=response.json())
227
227
  return self.project_object(response.json(), self)
228
228
 
229
+ async def clear_cache(self, code: str):
230
+ self.redis.delete(CACHE_PREFIX + code)
231
+
229
232
 
230
233
  class ProjectObjectSync(ProjectObject):
231
234
  def __init__(self, *args, **kwargs):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pilot-platform-common
3
- Version: 2.0.0
3
+ Version: 2.3.0
4
4
  Summary: Generates entity ID and connects with Vault (secret engine) to retrieve credentials
5
5
  Author: Indoc Systems
6
6
  Author-email: etaylor@indocresearch.org
@@ -8,6 +8,18 @@ Classifier: Programming Language :: Python :: 3
8
8
  Classifier: Operating System :: OS Independent
9
9
  Requires-Python: >=3.10
10
10
  Description-Content-Type: text/markdown
11
+ Requires-Dist: python-dotenv>=0.19.1
12
+ Requires-Dist: httpx<0.27.0,>=0.23.0
13
+ Requires-Dist: redis<5.0.0,>=4.5.0
14
+ Requires-Dist: aioboto3==9.6.0
15
+ Requires-Dist: xmltodict==0.13.0
16
+ Requires-Dist: minio==7.1.8
17
+ Requires-Dist: python-json-logger==2.0.2
18
+ Requires-Dist: pyjwt==2.6.0
19
+ Requires-Dist: starlette<0.38.0,>=0.37.2
20
+ Requires-Dist: requests<2.32.0,>=2.26.0
21
+ Requires-Dist: cryptography==39.0.0
22
+ Requires-Dist: pydantic<3.0.0,>=2.7.1
11
23
 
12
24
  # common
13
25
 
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "common"
3
- version = "2.0.0"
3
+ version = "2.3.0"
4
4
  description = ""
5
5
  authors = ["Indoc Systems"]
6
6
 
@@ -18,6 +18,8 @@ starlette = ">=0.37.2,<0.38.0"
18
18
  cryptography = "39.0.0"
19
19
  pydantic = "^2.7.1"
20
20
  requests = ">=2.26.0,<2.32.0"
21
+ opentelemetry-instrumentation-fastapi = "^0.39b0"
22
+ opentelemetry-sdk = "1.18.0"
21
23
 
22
24
  [tool.poetry.dev-dependencies]
23
25
  pytest = "7.2.1"
@@ -11,7 +11,7 @@ long_description = (this_directory / 'README.md').read_text()
11
11
 
12
12
  setuptools.setup(
13
13
  name='pilot-platform-common',
14
- version='2.0.0',
14
+ version='2.3.0',
15
15
  author='Indoc Systems',
16
16
  author_email='etaylor@indocresearch.org',
17
17
  description='Generates entity ID and connects with Vault (secret engine) to retrieve credentials',
@@ -3,6 +3,7 @@
3
3
  # Contact Indoc Systems for any questions regarding the use of this source code.
4
4
 
5
5
  import asyncio
6
+ import re
6
7
  import time
7
8
  from uuid import uuid4
8
9
 
@@ -37,6 +38,12 @@ PROJECT_CREDENTIALS = {
37
38
  }
38
39
 
39
40
 
41
+ @pytest.fixture
42
+ def project_mock(httpx_mock):
43
+ url = re.compile('^http://project/v1/projects/.*$')
44
+ httpx_mock.add_response(method='GET', json={'state': 'active'}, url=url)
45
+
46
+
40
47
  @pytest_asyncio.fixture(scope='session')
41
48
  def event_loop(request):
42
49
  """Create an instance of the default event loop for each test case."""
@@ -16,275 +16,325 @@ from common.permissions.exceptions import ProjectRoleNotFound
16
16
 
17
17
  class TestPermissions:
18
18
  @pytest.mark.asyncio
19
- async def test_has_permission_true(self, httpx_mock):
19
+ async def test_has_permission_true(self, httpx_mock, redis, project_mock):
20
+ project_code = str(uuid4())
20
21
  current_identity = {
21
22
  'username': 'test',
22
23
  'role': 'member',
23
- 'realm_roles': ['test_project-admin'],
24
+ 'realm_roles': [f'{project_code}-admin'],
24
25
  }
25
- url = 'http://auth/authorize?role=admin&resource=project&zone=core&operation=view&project_code=test_project'
26
+ url = f'http://auth/authorize?role=admin&resource=project&zone=core&operation=view&project_code={project_code}'
26
27
  httpx_mock.add_response(method='GET', json={'result': {'has_permission': True}}, url=url)
27
- result = await has_permission('http://auth/', 'test_project', 'project', 'core', 'view', current_identity)
28
+
29
+ result = await has_permission(
30
+ 'http://auth/', 'http://project', redis.url, project_code, 'project', 'core', 'view', current_identity
31
+ )
28
32
  assert result is True
29
33
 
30
34
  @pytest.mark.asyncio
31
- async def test_has_permission_false(self, httpx_mock):
35
+ async def test_has_permission_false(self, httpx_mock, redis, project_mock):
36
+ project_code = str(uuid4())
32
37
  current_identity = {
33
38
  'username': 'test',
34
39
  'role': 'member',
35
- 'realm_roles': ['test_project-admin'],
40
+ 'realm_roles': [f'{project_code}-admin'],
36
41
  }
37
- url = 'http://auth/authorize?role=admin&resource=project&zone=core&operation=view&project_code=test_project'
42
+ url = f'http://auth/authorize?role=admin&resource=project&zone=core&operation=view&project_code={project_code}'
38
43
  httpx_mock.add_response(method='GET', json={'result': {'has_permission': False}}, url=url)
39
- result = await has_permission('http://auth/', 'test_project', 'project', 'core', 'view', current_identity)
44
+ result = await has_permission(
45
+ 'http://auth/', 'http://project', redis.url, project_code, 'project', 'core', 'view', current_identity
46
+ )
40
47
  assert result is False
41
48
 
42
49
  @pytest.mark.asyncio
43
- async def test_has_permission_platform_admin_true(self, httpx_mock):
50
+ async def test_has_permission_platform_admin_true(self, httpx_mock, redis, project_mock):
51
+ project_code = str(uuid4())
44
52
  current_identity = {
45
53
  'username': 'test',
46
54
  'role': 'admin',
47
55
  }
48
56
  url = (
49
57
  'http://auth/authorize?role=platform_admin&resource=project&zone=core&operation=view'
50
- '&project_code=test_project'
58
+ f'&project_code={project_code}'
51
59
  )
52
60
  httpx_mock.add_response(method='GET', json={'result': {'has_permission': True}}, url=url)
53
- result = await has_permission('http://auth/', 'test_project', 'project', 'core', 'view', current_identity)
61
+ result = await has_permission(
62
+ 'http://auth/', 'http://project', redis.url, project_code, 'project', 'core', 'view', current_identity
63
+ )
54
64
  assert result is True
55
65
 
56
66
  @pytest.mark.asyncio
57
- async def test_has_permission_no_project_code(self, httpx_mock):
67
+ async def test_has_permission_no_project_code(self, httpx_mock, redis):
68
+ project_code = str(uuid4())
58
69
  current_identity = {
59
70
  'username': 'test',
60
71
  'role': 'member',
61
- 'realm_roles': ['test_project-admin'],
72
+ 'realm_roles': [f'{project_code}-admin'],
62
73
  }
63
- result = await has_permission('http://auth/', '', 'project', 'core', 'view', current_identity)
74
+ result = await has_permission(
75
+ 'http://auth/', 'http://project', redis.url, '', 'project', 'core', 'view', current_identity
76
+ )
64
77
  assert result is False
65
78
 
66
79
  @pytest.mark.asyncio
67
- async def test_has_permission_service_error(self, httpx_mock):
80
+ async def test_has_permission_service_error(self, httpx_mock, redis, project_mock):
81
+ project_code = str(uuid4())
68
82
  current_identity = {
69
83
  'username': 'test',
70
84
  'role': 'member',
71
- 'realm_roles': ['test_project-admin'],
85
+ 'realm_roles': [f'{project_code}-admin'],
72
86
  }
73
- url = 'http://auth/authorize?role=admin&resource=project&zone=core&operation=view&project_code=test_project'
87
+ url = f'http://auth/authorize?role=admin&resource=project&zone=core&operation=view&project_code={project_code}'
74
88
  httpx_mock.add_response(method='GET', json={'result': {'has_permission': True}}, url=url, status_code=500)
75
89
  with pytest.raises(Exception): # noqa: B017
76
- await has_permission('http://auth/', 'test_project', 'project', 'core', 'view', current_identity)
90
+ await has_permission(
91
+ 'http://auth/', 'http://project', redis.url, project_code, 'project', 'core', 'view', current_identity
92
+ )
77
93
 
78
94
 
79
95
  class TestFilePermissions:
80
96
  @pytest.mark.asyncio
81
- async def test_has_file_permission_in_namefolder_true(self, httpx_mock):
97
+ async def test_has_file_permission_in_namefolder_true(self, httpx_mock, redis, project_mock):
98
+ project_code = str(uuid4())
82
99
  file_data = {
83
100
  'parent_path': 'users/test',
84
101
  'zone': 'core',
85
- 'container_code': 'test_project',
102
+ 'container_code': project_code,
86
103
  'container_type': 'project',
87
104
  }
88
105
  current_identity = {
89
106
  'username': 'test',
90
107
  'role': 'member',
91
- 'realm_roles': ['test_project-admin'],
108
+ 'realm_roles': [f'{project_code}-admin'],
92
109
  }
93
- url = 'http://auth/authorize?role=admin&resource=file_any&zone=core&operation=view' '&project_code=test_project'
110
+ url = f'http://auth/authorize?role=admin&resource=file_any&zone=core&operation=view&project_code={project_code}'
94
111
  httpx_mock.add_response(method='GET', json={'result': {'has_permission': False}}, url=url)
95
112
  url = (
96
113
  'http://auth/authorize?role=admin&resource=file_in_own_namefolder&zone=core&operation=view'
97
- '&project_code=test_project'
114
+ f'&project_code={project_code}'
98
115
  )
99
116
  httpx_mock.add_response(method='GET', json={'result': {'has_permission': True}}, url=url)
100
- result = await has_file_permission('http://auth/', 'http://metadata/v1/', file_data, 'view', current_identity)
117
+ result = await has_file_permission(
118
+ 'http://auth/', 'http://metadata/v1/', 'http://project', redis.url, file_data, 'view', current_identity
119
+ )
101
120
  assert result is True
102
121
 
103
122
  @pytest.mark.asyncio
104
- async def test_has_file_permission_in_other_namefolder_true(self, httpx_mock):
123
+ async def test_has_file_permission_in_other_namefolder_true(self, httpx_mock, redis, project_mock):
124
+ project_code = str(uuid4())
105
125
  file_data = {
106
126
  'parent_path': 'users/another',
107
127
  'zone': 'core',
108
- 'container_code': 'test_project',
128
+ 'container_code': project_code,
109
129
  'container_type': 'project',
110
130
  }
111
131
  current_identity = {
112
132
  'username': 'test',
113
133
  'role': 'member',
114
- 'realm_roles': ['test_project-admin'],
134
+ 'realm_roles': [f'{project_code}-admin'],
115
135
  }
116
- url = 'http://auth/authorize?role=admin&resource=file_any&zone=core&operation=view' '&project_code=test_project'
136
+ url = f'http://auth/authorize?role=admin&resource=file_any&zone=core&operation=view&project_code={project_code}'
117
137
  httpx_mock.add_response(method='GET', json={'result': {'has_permission': False}}, url=url)
118
- result = await has_file_permission('http://auth/', 'http://metadata/v1/', file_data, 'view', current_identity)
138
+ result = await has_file_permission(
139
+ 'http://auth/', 'http://metadata/v1/', 'http://project', redis.url, file_data, 'view', current_identity
140
+ )
119
141
  assert result is False
120
142
 
121
143
  @pytest.mark.asyncio
122
- async def test_has_file_permission_false(self, httpx_mock):
144
+ async def test_has_file_permission_false(self, httpx_mock, redis, project_mock):
145
+ project_code = str(uuid4())
123
146
  file_data = {
124
147
  'parent_path': 'users/test',
125
148
  'zone': 'core',
126
- 'container_code': 'test_project',
149
+ 'container_code': project_code,
127
150
  'container_type': 'project',
128
151
  }
129
152
  current_identity = {
130
153
  'username': 'test',
131
154
  'role': 'member',
132
- 'realm_roles': ['test_project-admin'],
155
+ 'realm_roles': [f'{project_code}-admin'],
133
156
  }
134
- url = 'http://auth/authorize?role=admin&resource=file_any&zone=core&operation=view' '&project_code=test_project'
157
+ url = f'http://auth/authorize?role=admin&resource=file_any&zone=core&operation=view&project_code={project_code}'
135
158
  httpx_mock.add_response(method='GET', json={'result': {'has_permission': False}}, url=url)
136
159
  url = (
137
160
  'http://auth/authorize?role=admin&resource=file_in_own_namefolder&zone=core&operation=view'
138
- '&project_code=test_project'
161
+ f'&project_code={project_code}'
139
162
  )
140
163
  httpx_mock.add_response(method='GET', json={'result': {'has_permission': False}}, url=url)
141
- result = await has_file_permission('http://auth/', 'http://metadata/v1/', file_data, 'view', current_identity)
164
+ result = await has_file_permission(
165
+ 'http://auth/', 'http://metadata/v1/', 'http://project', redis.url, file_data, 'view', current_identity
166
+ )
142
167
  assert result is False
143
168
 
144
169
  @pytest.mark.asyncio
145
- async def test_has_file_permission_outside_of_namefolder_false(self, httpx_mock):
170
+ async def test_has_file_permission_outside_of_namefolder_false(self, httpx_mock, redis, project_mock):
171
+ project_code = str(uuid4())
146
172
  file_data = {
147
173
  'parent_path': 'users/test',
148
174
  'zone': 'core',
149
- 'container_code': 'test_project',
175
+ 'container_code': project_code,
150
176
  'container_type': 'project',
151
177
  }
152
178
  current_identity = {
153
179
  'username': 'test',
154
180
  'role': 'member',
155
- 'realm_roles': ['test_project-admin'],
181
+ 'realm_roles': [f'{project_code}-admin'],
156
182
  }
157
- url = 'http://auth/authorize?role=admin&resource=file_any&zone=core&operation=view' '&project_code=test_project'
183
+ url = f'http://auth/authorize?role=admin&resource=file_any&zone=core&operation=view&project_code={project_code}'
158
184
  httpx_mock.add_response(method='GET', json={'result': {'has_permission': False}}, url=url)
159
185
  url = (
160
186
  'http://auth/authorize?role=admin&resource=file_in_own_namefolder&zone=core&operation=view'
161
- '&project_code=test_project'
187
+ f'&project_code={project_code}'
162
188
  )
163
189
  httpx_mock.add_response(method='GET', json={'result': {'has_permission': False}}, url=url)
164
- result = await has_file_permission('http://auth/', 'http://metadata/v1/', file_data, 'view', current_identity)
190
+ result = await has_file_permission(
191
+ 'http://auth/', 'http://metadata/v1/', 'http://project', redis.url, file_data, 'view', current_identity
192
+ )
165
193
  assert result is False
166
194
 
167
195
  @pytest.mark.asyncio
168
- async def test_has_file_permission_wrong_project(self, httpx_mock):
196
+ async def test_has_file_permission_wrong_project(self, httpx_mock, redis):
197
+ project_code = str(uuid4())
169
198
  file_data = {
170
199
  'parent_path': 'users/test',
171
200
  'zone': 'core',
172
- 'container_code': 'test_project2',
201
+ 'container_code': project_code,
173
202
  'container_type': 'project',
174
203
  }
175
204
  current_identity = {
176
205
  'username': 'test',
177
206
  'role': 'member',
178
- 'realm_roles': ['test_project-admin'],
207
+ 'realm_roles': ['wrongproject-admin'],
179
208
  }
180
- result = await has_file_permission('http://auth/', 'http://metadata/v1/', file_data, 'view', current_identity)
209
+ result = await has_file_permission(
210
+ 'http://auth/', 'http://metadata/v1/', 'http://project', redis.url, file_data, 'view', current_identity
211
+ )
181
212
  assert result is False
182
213
 
183
214
  @pytest.mark.asyncio
184
- async def test_has_file_permission_wrong_container_type(self, httpx_mock):
215
+ async def test_has_file_permission_wrong_container_type(self, httpx_mock, redis):
216
+ project_code = str(uuid4())
185
217
  file_data = {
186
218
  'parent_path': 'users/test',
187
219
  'zone': 'core',
188
- 'container_code': 'test_project',
220
+ 'container_code': project_code,
189
221
  'container_type': 'dataset',
190
222
  }
191
223
  current_identity = {
192
224
  'username': 'test',
193
225
  'role': 'member',
194
- 'realm_roles': ['test_project-admin'],
226
+ 'realm_roles': [f'{project_code}-admin'],
195
227
  }
196
- result = await has_file_permission('http://auth/', 'http://metadata/v1/', file_data, 'view', current_identity)
228
+ result = await has_file_permission(
229
+ 'http://auth/', 'http://metadata/v1/', 'http://project', redis.url, file_data, 'view', current_identity
230
+ )
197
231
  assert result is False
198
232
 
199
233
  @pytest.mark.asyncio
200
- async def test_has_file_permission_trashed(self, httpx_mock):
234
+ async def test_has_file_permission_trashed(self, httpx_mock, redis, project_mock):
235
+ project_code = str(uuid4())
201
236
  file_data = {
202
237
  'status': 'TRASHED',
203
238
  'restore_path': 'users/test',
204
239
  'zone': 'core',
205
- 'container_code': 'test_project',
240
+ 'container_code': project_code,
206
241
  'container_type': 'project',
207
242
  }
208
243
  current_identity = {
209
244
  'username': 'test',
210
245
  'role': 'member',
211
- 'realm_roles': ['test_project-admin'],
246
+ 'realm_roles': [f'{project_code}-admin'],
212
247
  }
213
- url = 'http://auth/authorize?role=admin&resource=file_any&zone=core&operation=view' '&project_code=test_project'
248
+ url = f'http://auth/authorize?role=admin&resource=file_any&zone=core&operation=view&project_code={project_code}'
214
249
  httpx_mock.add_response(method='GET', json={'result': {'has_permission': False}}, url=url)
215
250
  url = (
216
251
  'http://auth/authorize?role=admin&resource=file_in_own_namefolder&zone=core&operation=view'
217
- '&project_code=test_project'
252
+ f'&project_code={project_code}'
218
253
  )
219
254
  httpx_mock.add_response(method='GET', json={'result': {'has_permission': True}}, url=url)
220
- result = await has_file_permission('http://auth/', 'http://metadata/v1/', file_data, 'view', current_identity)
255
+ result = await has_file_permission(
256
+ 'http://auth/', 'http://metadata/v1/', 'http://project', redis.url, file_data, 'view', current_identity
257
+ )
221
258
  assert result is True
222
259
 
223
260
  @pytest.mark.asyncio
224
- async def test_has_file_permission_namefolder(self, httpx_mock):
261
+ async def test_has_file_permission_namefolder(self, httpx_mock, redis, project_mock):
262
+ project_code = str(uuid4())
225
263
  file_data = {
226
264
  'parent_path': 'users',
227
265
  'type': 'name_folder',
228
266
  'name': 'test',
229
267
  'zone': 'core',
230
- 'container_code': 'test_project',
268
+ 'container_code': project_code,
231
269
  'container_type': 'project',
232
270
  }
233
271
  current_identity = {
234
272
  'username': 'test',
235
273
  'role': 'member',
236
- 'realm_roles': ['test_project-admin'],
274
+ 'realm_roles': [f'{project_code}-admin'],
237
275
  }
238
- url = 'http://auth/authorize?role=admin&resource=file_any&zone=core&operation=view' '&project_code=test_project'
276
+ url = f'http://auth/authorize?role=admin&resource=file_any&zone=core&operation=view&project_code={project_code}'
239
277
  httpx_mock.add_response(method='GET', json={'result': {'has_permission': False}}, url=url)
240
278
  url = (
241
279
  'http://auth/authorize?role=admin&resource=file_in_own_namefolder&zone=core&operation=view'
242
- '&project_code=test_project'
280
+ f'&project_code={project_code}'
243
281
  )
244
282
  httpx_mock.add_response(method='GET', json={'result': {'has_permission': True}}, url=url)
245
- result = await has_file_permission('http://auth/', 'http://metadata/v1/', file_data, 'view', current_identity)
283
+ result = await has_file_permission(
284
+ 'http://auth/', 'http://metadata/v1/', 'http://project', redis.url, file_data, 'view', current_identity
285
+ )
246
286
  assert result is True
247
287
 
248
288
  @pytest.mark.asyncio
249
289
  @pytest.mark.parametrize('is_authorized', [True, False])
250
- async def test_has_file_permission_file_in_shared_folder(self, httpx_mock, get_project_folder, is_authorized):
290
+ async def test_has_file_permission_file_in_shared_folder(
291
+ self, httpx_mock, get_project_folder, is_authorized, redis, project_mock
292
+ ):
293
+ project_code = str(uuid4())
251
294
  file_data = {
252
295
  'type': 'file',
253
296
  'name': 'test.txt',
254
297
  'zone': 'core',
255
- 'container_code': 'test_project',
298
+ 'container_code': project_code,
256
299
  'container_type': 'project',
257
300
  'parent_path': 'shared/user',
258
301
  }
259
302
  current_identity = {
260
303
  'username': 'test',
261
304
  'role': 'member',
262
- 'realm_roles': ['test_project-admin'],
305
+ 'realm_roles': [f'{project_code}-admin'],
263
306
  }
264
307
  project_folder = get_project_folder
265
308
  resource = 'file_shared_' + project_folder['id']
266
309
 
267
- url = f'http://auth/authorize?role=admin&resource={resource}&zone=core&operation=view&project_code=test_project'
310
+ url = (
311
+ f'http://auth/authorize?role=admin&resource={resource}&zone=core&operation=view&project_code={project_code}'
312
+ )
268
313
  httpx_mock.add_response(method='GET', json={'result': {'has_permission': is_authorized}}, url=url)
269
314
 
270
- result = await has_file_permission('http://auth/', 'http://metadata/v1/', file_data, 'view', current_identity)
315
+ result = await has_file_permission(
316
+ 'http://auth/', 'http://metadata/v1/', 'http://project', redis.url, file_data, 'view', current_identity
317
+ )
271
318
  assert result is is_authorized
272
319
 
273
320
  @pytest.mark.asyncio
274
321
  @pytest.mark.parametrize('is_authorized', [True, False])
275
- async def test_has_file_permission_file_in_shared_folder_trashed_sourced(self, httpx_mock, is_authorized):
322
+ async def test_has_file_permission_file_in_shared_folder_trashed_sourced(
323
+ self, httpx_mock, is_authorized, redis, project_mock
324
+ ):
325
+ project_code = str(uuid4())
276
326
  file_data = {
277
327
  'type': 'file',
278
328
  'name': 'test.txt',
279
329
  'zone': 'core',
280
- 'container_code': 'test_project',
330
+ 'container_code': project_code,
281
331
  'container_type': 'project',
282
332
  'parent_path': 'shared/user',
283
333
  }
284
334
  current_identity = {
285
335
  'username': 'test',
286
336
  'role': 'member',
287
- 'realm_roles': ['test_project-admin'],
337
+ 'realm_roles': [f'{project_code}-admin'],
288
338
  }
289
339
  PROJECT_FOLDER_DATA = {
290
340
  'type': 'project_folder',
@@ -293,33 +343,40 @@ class TestFilePermissions:
293
343
  }
294
344
 
295
345
  url = (
296
- 'http://metadata/v1/item/?name=user&container_code=test_project&container_type=project&zone=1'
346
+ f'http://metadata/v1/item/?name=user&container_code={project_code}&container_type=project&zone=1'
297
347
  '&type=project_folder&status=ACTIVE'
298
348
  )
299
349
  httpx_mock.add_response(status_code=404, method='GET', json={}, url=url)
300
350
 
301
351
  url = (
302
- 'http://metadata/v1/item/?name=user&container_code=test_project&container_type=project&zone=1'
352
+ f'http://metadata/v1/item/?name=user&container_code={project_code}&container_type=project&zone=1'
303
353
  '&type=project_folder&status=TRASHED'
304
354
  )
305
355
  httpx_mock.add_response(method='GET', json={'result': PROJECT_FOLDER_DATA}, url=url)
306
356
  project_folder = PROJECT_FOLDER_DATA
307
357
 
308
358
  resource = 'file_shared_' + project_folder['id']
309
- url = f'http://auth/authorize?role=admin&resource={resource}&zone=core&operation=view&project_code=test_project'
359
+ url = (
360
+ f'http://auth/authorize?role=admin&resource={resource}&zone=core&operation=view&project_code={project_code}'
361
+ )
310
362
  httpx_mock.add_response(method='GET', json={'result': {'has_permission': is_authorized}}, url=url)
311
363
 
312
- result = await has_file_permission('http://auth/', 'http://metadata/v1/', file_data, 'view', current_identity)
364
+ result = await has_file_permission(
365
+ 'http://auth/', 'http://metadata/v1/', 'http://project', redis.url, file_data, 'view', current_identity
366
+ )
313
367
  assert result is is_authorized
314
368
 
315
369
  @pytest.mark.asyncio
316
370
  @pytest.mark.parametrize('is_authorized', [True, False])
317
- async def test_has_file_permission_file_in_shared_folder_trash(self, httpx_mock, is_authorized):
371
+ async def test_has_file_permission_file_in_shared_folder_trash(
372
+ self, httpx_mock, is_authorized, redis, project_mock
373
+ ):
374
+ project_code = str(uuid4())
318
375
  file_data = {
319
376
  'type': 'file',
320
377
  'name': 'test.txt',
321
378
  'zone': 'core',
322
- 'container_code': 'test_project',
379
+ 'container_code': project_code,
323
380
  'container_type': 'project',
324
381
  'restore_path': 'shared',
325
382
  'status': 'TRASHED',
@@ -327,7 +384,7 @@ class TestFilePermissions:
327
384
  current_identity = {
328
385
  'username': 'test',
329
386
  'role': 'member',
330
- 'realm_roles': ['test_project-admin'],
387
+ 'realm_roles': [f'{project_code}-admin'],
331
388
  }
332
389
  PROJECT_FOLDER_DATA = {
333
390
  'type': 'project_folder',
@@ -340,44 +397,56 @@ class TestFilePermissions:
340
397
 
341
398
  resource = 'file_shared_' + PROJECT_FOLDER_DATA['id']
342
399
 
343
- url = f'http://auth/authorize?role=admin&resource={resource}&zone=core&operation=view&project_code=test_project'
400
+ url = (
401
+ f'http://auth/authorize?role=admin&resource={resource}&zone=core&operation=view&project_code={project_code}'
402
+ )
344
403
  httpx_mock.add_response(method='GET', json={'result': {'has_permission': is_authorized}}, url=url)
345
404
 
346
- result = await has_file_permission('http://auth/', 'http://metadata/v1/', file_data, 'view', current_identity)
405
+ result = await has_file_permission(
406
+ 'http://auth/', 'http://metadata/v1/', 'http://project', redis.url, file_data, 'view', current_identity
407
+ )
347
408
  assert result is is_authorized
348
409
 
349
410
  @pytest.mark.asyncio
350
411
  @pytest.mark.parametrize('is_authorized', [True, False])
351
- async def test_has_file_permission_shared_folder(self, httpx_mock, get_project_folder, is_authorized):
412
+ async def test_has_file_permission_shared_folder(
413
+ self, httpx_mock, get_project_folder, is_authorized, redis, project_mock
414
+ ):
415
+ project_code = str(uuid4())
352
416
  file_data = {
353
417
  'type': 'project_folder',
354
418
  'name': 'user',
355
419
  'zone': 'core',
356
- 'container_code': 'test_project',
420
+ 'container_code': project_code,
357
421
  'container_type': 'project',
358
422
  'parent_path': 'shared',
359
423
  }
360
424
  current_identity = {
361
425
  'username': 'test',
362
426
  'role': 'member',
363
- 'realm_roles': ['test_project-admin'],
427
+ 'realm_roles': [f'{project_code}-admin'],
364
428
  }
365
429
  project_folder = get_project_folder
366
430
  resource = 'file_shared_' + project_folder['id']
367
431
 
368
- url = f'http://auth/authorize?role=admin&resource={resource}&zone=core&operation=view&project_code=test_project'
432
+ url = (
433
+ f'http://auth/authorize?role=admin&resource={resource}&zone=core&operation=view&project_code={project_code}'
434
+ )
369
435
  httpx_mock.add_response(method='GET', json={'result': {'has_permission': is_authorized}}, url=url)
370
436
 
371
- result = await has_file_permission('http://auth/', 'http://metadata/v1/', file_data, 'view', current_identity)
437
+ result = await has_file_permission(
438
+ 'http://auth/', 'http://metadata/v1/', 'http://project', redis.url, file_data, 'view', current_identity
439
+ )
372
440
  assert result is is_authorized
373
441
 
374
442
  @pytest.mark.asyncio
375
- async def test_has_file_permission_root_level_users(self, httpx_mock):
443
+ async def test_has_file_permission_root_level_users(self, httpx_mock, redis):
444
+ project_code = str(uuid4())
376
445
  file_data = {
377
446
  'type': 'root_folder',
378
447
  'name': 'users',
379
448
  'zone': 'core',
380
- 'container_code': 'test_project',
449
+ 'container_code': project_code,
381
450
  'container_type': 'project',
382
451
  'parent_path': None,
383
452
  'parent': None,
@@ -385,18 +454,21 @@ class TestFilePermissions:
385
454
  current_identity = {
386
455
  'username': 'test',
387
456
  'role': 'member',
388
- 'realm_roles': ['test_project-admin'],
457
+ 'realm_roles': [f'{project_code}-admin'],
389
458
  }
390
- result = await has_file_permission('http://auth/', 'http://metadata/v1/', file_data, 'view', current_identity)
459
+ result = await has_file_permission(
460
+ 'http://auth/', 'http://metadata/v1/', 'http://project', redis.url, file_data, 'view', current_identity
461
+ )
391
462
  assert result is True
392
463
 
393
464
  @pytest.mark.asyncio
394
- async def test_has_file_permission_root_level_shared(self, httpx_mock):
465
+ async def test_has_file_permission_root_level_shared(self, httpx_mock, redis):
466
+ project_code = str(uuid4())
395
467
  file_data = {
396
468
  'type': 'root_folder',
397
469
  'name': 'shared',
398
470
  'zone': 'core',
399
- 'container_code': 'test_project',
471
+ 'container_code': project_code,
400
472
  'container_type': 'project',
401
473
  'parent_path': None,
402
474
  'parent': None,
@@ -404,40 +476,45 @@ class TestFilePermissions:
404
476
  current_identity = {
405
477
  'username': 'test',
406
478
  'role': 'member',
407
- 'realm_roles': ['test_project-admin'],
479
+ 'realm_roles': [f'{project_code}-admin'],
408
480
  }
409
- result = await has_file_permission('http://auth/', 'http://metadata/v1/', file_data, 'view', current_identity)
481
+ result = await has_file_permission(
482
+ 'http://auth/', 'http://metadata/v1/', 'http://project', redis.url, file_data, 'view', current_identity
483
+ )
410
484
  assert result is True
411
485
 
412
486
 
413
487
  class TestGetCurrentRole:
414
488
  @pytest.mark.asyncio
415
489
  async def test_get_current_role_admin(self, httpx_mock):
490
+ project_code = str(uuid4())
416
491
  current_identity = {
417
492
  'username': 'test',
418
493
  'role': 'admin',
419
- 'realm_roles': ['test_project-admin'],
494
+ 'realm_roles': [f'{project_code}-admin'],
420
495
  }
421
- result = await get_project_role('test_project', current_identity)
496
+ result = await get_project_role(project_code, current_identity)
422
497
  assert result == 'platform_admin'
423
498
 
424
499
  @pytest.mark.asyncio
425
500
  async def test_get_current_role_member(self, httpx_mock):
501
+ project_code = str(uuid4())
426
502
  current_identity = {
427
503
  'username': 'test',
428
504
  'role': 'member',
429
- 'realm_roles': ['test_project-admin'],
505
+ 'realm_roles': [f'{project_code}-admin'],
430
506
  }
431
- result = await get_project_role('test_project', current_identity)
507
+ result = await get_project_role(project_code, current_identity)
432
508
  assert result == 'admin'
433
509
 
434
510
  async def test_get_current_role_non_default(self, httpx_mock):
511
+ project_code = str(uuid4())
435
512
  current_identity = {
436
513
  'username': 'test',
437
514
  'role': 'member',
438
- 'realm_roles': ['test_project-customrole'],
515
+ 'realm_roles': [f'{project_code}-customrole'],
439
516
  }
440
- result = await get_project_role('test_project', current_identity)
517
+ result = await get_project_role(project_code, current_identity)
441
518
  assert result == 'customrole'
442
519
 
443
520
 
@@ -447,26 +524,31 @@ class TestBulkPermissions:
447
524
  {'role': 'admin', 'zone': 'greenroom', 'resource': 'file_any', 'operation': 'copy'},
448
525
  {'role': 'admin', 'zone': 'greenroom', 'resource': 'file_any', 'operation': 'view'},
449
526
  ]
450
- current_identity = {'role': 'collaborator', 'realm_roles': ['test_project-collaborator']}
451
527
 
452
- async def test_has_permissions_return_mapped_assertions(self, httpx_mock):
528
+ async def test_has_permissions_return_mapped_assertions(self, httpx_mock, redis, project_mock):
529
+ project_code = str(uuid4())
530
+ current_identity = {'role': 'collaborator', 'realm_roles': [f'{project_code}-collaborator']}
453
531
  granted = {'has_permission': True}
454
532
  mapped_result = {'greenroom_file_any_copy': True, 'greenroom_file_any_view': True}
455
533
 
456
534
  httpx_mock.add_response(
457
535
  method='POST',
458
- url=f'{self.auth_service_url}authorize?project_code=test_project',
536
+ url=f'{self.auth_service_url}authorize?project_code={project_code}',
459
537
  status_code=200,
460
538
  json=granted,
461
539
  )
462
540
  mapped = await has_permissions(
463
- self.auth_service_url, 'test_project', self.permission_request, self.current_identity
541
+ self.auth_service_url, 'http://project', redis.url, project_code, self.permission_request, current_identity
464
542
  )
465
543
 
466
544
  assert mapped == mapped_result
467
545
 
468
- async def test_has_permissions_raise_project_role_not_found_exception_for_invalid_role(self, httpx_mock):
469
- identity = self.current_identity
470
- identity['realm_roles'] = ['invalidproject-collaborator']
546
+ async def test_has_permissions_raise_project_role_not_found_exception_for_invalid_role(
547
+ self, httpx_mock, redis, project_mock
548
+ ):
549
+ project_code = str(uuid4())
550
+ identity = {'role': 'collaborator', 'realm_roles': ['invalid-collaborator']}
471
551
  with pytest.raises(ProjectRoleNotFound):
472
- await has_permissions(self.auth_service_url, 'test_project', self.permission_request, identity)
552
+ await has_permissions(
553
+ self.auth_service_url, 'http://project', redis.url, project_code, self.permission_request, identity
554
+ )
@@ -8,6 +8,11 @@ import logging
8
8
  from logging import LogRecord
9
9
  from logging import getLevelName
10
10
 
11
+ from opentelemetry import trace
12
+ from opentelemetry.sdk.trace import TracerProvider
13
+ from opentelemetry.sdk.trace.export import BatchSpanProcessor
14
+ from opentelemetry.sdk.trace.export import ConsoleSpanExporter
15
+
11
16
  from common.logging.logging import AUDIT
12
17
  from common.logging.logging import JsonFormatter
13
18
  from common.logging.logging import Logger
@@ -17,6 +22,12 @@ from common.logging.logging import extend_logger_class
17
22
 
18
23
  class TestJsonFormatter:
19
24
  def test_format_converts_log_record_into_expected_json_string(self, fake):
25
+ provider = TracerProvider()
26
+ processor = BatchSpanProcessor(ConsoleSpanExporter())
27
+ provider.add_span_processor(processor)
28
+ trace.set_tracer_provider(provider)
29
+ tracer = trace.get_tracer('test.format.logging')
30
+
20
31
  logger = fake.word()
21
32
  level = fake.pyint(0, 5) * 10
22
33
  message = fake.word()
@@ -24,20 +35,32 @@ class TestJsonFormatter:
24
35
  exception = Exception(exception_message)
25
36
  exc_info = (type(exception), exception, exception.__traceback__)
26
37
  details = fake.pydict(allowed_types=(str, int, float, bool))
27
- record = LogRecord(logger, level, '', 0, message, details, exc_info)
38
+ record = LogRecord(logger, level, 'test.py', 0, message, details, exc_info)
28
39
  asctime = dt.datetime.fromtimestamp(record.created, tz=dt.timezone.utc).isoformat()
29
- expected_string = json.dumps(
30
- {
31
- 'asctime': asctime,
32
- 'level': getLevelName(level),
33
- 'logger': logger,
34
- 'message': message,
35
- 'exc_info': f'Exception: {exception_message}',
36
- 'details': details,
37
- }
38
- )
39
-
40
- received_string = JsonFormatter().format(record)
40
+ with tracer.start_as_current_span('test-log-record-format') as span:
41
+ span_context = span.get_span_context()
42
+ trace_id = format(span_context.trace_id, '032x') if span_context.is_valid else None
43
+ span_id = format(span_context.span_id, '016x') if span_context.is_valid else None
44
+ trace_enabled = span_context.trace_flags.sampled if span_context.trace_flags is not None else False
45
+ expected_string = json.dumps(
46
+ {
47
+ 'asctime': asctime,
48
+ 'level': getLevelName(level),
49
+ 'logger': logger,
50
+ 'location': 'test.py:0',
51
+ 'message': message,
52
+ 'exc_info': f'Exception: {exception_message}',
53
+ 'details': details,
54
+ 'trace_id': trace_id,
55
+ 'span_id': span_id,
56
+ 'trace_enabled': trace_enabled,
57
+ }
58
+ )
59
+ received_string = JsonFormatter().format(record)
60
+ trace.get_tracer_provider().shutdown()
61
+ # Normalize newlines by replacing them with `\\n` in both strings for comparison
62
+ expected_string = expected_string.replace('\n', '\\n')
63
+ received_string = received_string.replace('\n', '\\n')
41
64
 
42
65
  assert received_string == expected_string
43
66