rucio 37.1.0.post1__py3-none-any.whl → 37.3.0__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.

Potentially problematic release.


This version of rucio might be problematic. Click here for more details.

Files changed (106) hide show
  1. rucio/common/plugins.py +1 -1
  2. rucio/common/utils.py +17 -10
  3. rucio/core/did.py +32 -4
  4. rucio/core/did_meta_plugins/did_column_meta.py +1 -1
  5. rucio/core/replica.py +1 -1
  6. rucio/db/sqla/util.py +1 -1
  7. rucio/gateway/authentication.py +58 -88
  8. rucio/gateway/config.py +63 -75
  9. rucio/gateway/credential.py +11 -17
  10. rucio/gateway/did.py +245 -329
  11. rucio/gateway/dirac.py +33 -34
  12. rucio/gateway/exporter.py +27 -30
  13. rucio/gateway/heartbeat.py +15 -19
  14. rucio/gateway/importer.py +12 -14
  15. rucio/gateway/lifetime_exception.py +16 -24
  16. rucio/gateway/lock.py +27 -40
  17. rucio/gateway/meta_conventions.py +19 -28
  18. rucio/gateway/quarantined_replica.py +24 -27
  19. rucio/gateway/replica.py +223 -226
  20. rucio/gateway/rse.py +191 -218
  21. rucio/gateway/rule.py +115 -146
  22. rucio/gateway/scope.py +18 -25
  23. rucio/gateway/trace.py +48 -0
  24. rucio/gateway/vo.py +32 -37
  25. rucio/vcsversion.py +3 -3
  26. rucio/web/rest/flaskapi/v1/accounts.py +2 -2
  27. rucio/web/rest/flaskapi/v1/auth.py +15 -0
  28. rucio/web/rest/flaskapi/v1/common.py +20 -0
  29. rucio/web/rest/flaskapi/v1/config.py +7 -7
  30. rucio/web/rest/flaskapi/v1/credentials.py +20 -13
  31. rucio/web/rest/flaskapi/v1/dids.py +55 -55
  32. rucio/web/rest/flaskapi/v1/dirac.py +2 -2
  33. rucio/web/rest/flaskapi/v1/export.py +1 -1
  34. rucio/web/rest/flaskapi/v1/heartbeats.py +3 -3
  35. rucio/web/rest/flaskapi/v1/import.py +1 -1
  36. rucio/web/rest/flaskapi/v1/lifetime_exceptions.py +5 -5
  37. rucio/web/rest/flaskapi/v1/locks.py +4 -4
  38. rucio/web/rest/flaskapi/v1/main.py +17 -10
  39. rucio/web/rest/flaskapi/v1/meta_conventions.py +3 -3
  40. rucio/web/rest/flaskapi/v1/replicas.py +31 -30
  41. rucio/web/rest/flaskapi/v1/rses.py +37 -37
  42. rucio/web/rest/flaskapi/v1/rules.py +15 -15
  43. rucio/web/rest/flaskapi/v1/scopes.py +3 -3
  44. rucio/web/rest/flaskapi/v1/traces.py +75 -77
  45. rucio/web/rest/flaskapi/v1/vos.py +5 -7
  46. {rucio-37.1.0.post1.dist-info → rucio-37.3.0.dist-info}/METADATA +1 -2
  47. {rucio-37.1.0.post1.dist-info → rucio-37.3.0.dist-info}/RECORD +106 -105
  48. {rucio-37.1.0.post1.dist-info → rucio-37.3.0.dist-info}/WHEEL +1 -1
  49. {rucio-37.1.0.post1.data → rucio-37.3.0.data}/data/rucio/etc/alembic.ini.template +0 -0
  50. {rucio-37.1.0.post1.data → rucio-37.3.0.data}/data/rucio/etc/alembic_offline.ini.template +0 -0
  51. {rucio-37.1.0.post1.data → rucio-37.3.0.data}/data/rucio/etc/globus-config.yml.template +0 -0
  52. {rucio-37.1.0.post1.data → rucio-37.3.0.data}/data/rucio/etc/ldap.cfg.template +0 -0
  53. {rucio-37.1.0.post1.data → rucio-37.3.0.data}/data/rucio/etc/mail_templates/rule_approval_request.tmpl +0 -0
  54. {rucio-37.1.0.post1.data → rucio-37.3.0.data}/data/rucio/etc/mail_templates/rule_approved_admin.tmpl +0 -0
  55. {rucio-37.1.0.post1.data → rucio-37.3.0.data}/data/rucio/etc/mail_templates/rule_approved_user.tmpl +0 -0
  56. {rucio-37.1.0.post1.data → rucio-37.3.0.data}/data/rucio/etc/mail_templates/rule_denied_admin.tmpl +0 -0
  57. {rucio-37.1.0.post1.data → rucio-37.3.0.data}/data/rucio/etc/mail_templates/rule_denied_user.tmpl +0 -0
  58. {rucio-37.1.0.post1.data → rucio-37.3.0.data}/data/rucio/etc/mail_templates/rule_ok_notification.tmpl +0 -0
  59. {rucio-37.1.0.post1.data → rucio-37.3.0.data}/data/rucio/etc/rse-accounts.cfg.template +0 -0
  60. {rucio-37.1.0.post1.data → rucio-37.3.0.data}/data/rucio/etc/rucio.cfg.atlas.client.template +0 -0
  61. {rucio-37.1.0.post1.data → rucio-37.3.0.data}/data/rucio/etc/rucio.cfg.template +0 -0
  62. {rucio-37.1.0.post1.data → rucio-37.3.0.data}/data/rucio/etc/rucio_multi_vo.cfg.template +0 -0
  63. {rucio-37.1.0.post1.data → rucio-37.3.0.data}/data/rucio/requirements.server.txt +0 -0
  64. {rucio-37.1.0.post1.data → rucio-37.3.0.data}/data/rucio/tools/bootstrap.py +0 -0
  65. {rucio-37.1.0.post1.data → rucio-37.3.0.data}/data/rucio/tools/merge_rucio_configs.py +0 -0
  66. {rucio-37.1.0.post1.data → rucio-37.3.0.data}/data/rucio/tools/reset_database.py +0 -0
  67. {rucio-37.1.0.post1.data → rucio-37.3.0.data}/scripts/rucio +0 -0
  68. {rucio-37.1.0.post1.data → rucio-37.3.0.data}/scripts/rucio-abacus-account +0 -0
  69. {rucio-37.1.0.post1.data → rucio-37.3.0.data}/scripts/rucio-abacus-collection-replica +0 -0
  70. {rucio-37.1.0.post1.data → rucio-37.3.0.data}/scripts/rucio-abacus-rse +0 -0
  71. {rucio-37.1.0.post1.data → rucio-37.3.0.data}/scripts/rucio-admin +0 -0
  72. {rucio-37.1.0.post1.data → rucio-37.3.0.data}/scripts/rucio-atropos +0 -0
  73. {rucio-37.1.0.post1.data → rucio-37.3.0.data}/scripts/rucio-auditor +0 -0
  74. {rucio-37.1.0.post1.data → rucio-37.3.0.data}/scripts/rucio-automatix +0 -0
  75. {rucio-37.1.0.post1.data → rucio-37.3.0.data}/scripts/rucio-bb8 +0 -0
  76. {rucio-37.1.0.post1.data → rucio-37.3.0.data}/scripts/rucio-cache-client +0 -0
  77. {rucio-37.1.0.post1.data → rucio-37.3.0.data}/scripts/rucio-cache-consumer +0 -0
  78. {rucio-37.1.0.post1.data → rucio-37.3.0.data}/scripts/rucio-conveyor-finisher +0 -0
  79. {rucio-37.1.0.post1.data → rucio-37.3.0.data}/scripts/rucio-conveyor-poller +0 -0
  80. {rucio-37.1.0.post1.data → rucio-37.3.0.data}/scripts/rucio-conveyor-preparer +0 -0
  81. {rucio-37.1.0.post1.data → rucio-37.3.0.data}/scripts/rucio-conveyor-receiver +0 -0
  82. {rucio-37.1.0.post1.data → rucio-37.3.0.data}/scripts/rucio-conveyor-stager +0 -0
  83. {rucio-37.1.0.post1.data → rucio-37.3.0.data}/scripts/rucio-conveyor-submitter +0 -0
  84. {rucio-37.1.0.post1.data → rucio-37.3.0.data}/scripts/rucio-conveyor-throttler +0 -0
  85. {rucio-37.1.0.post1.data → rucio-37.3.0.data}/scripts/rucio-dark-reaper +0 -0
  86. {rucio-37.1.0.post1.data → rucio-37.3.0.data}/scripts/rucio-dumper +0 -0
  87. {rucio-37.1.0.post1.data → rucio-37.3.0.data}/scripts/rucio-follower +0 -0
  88. {rucio-37.1.0.post1.data → rucio-37.3.0.data}/scripts/rucio-hermes +0 -0
  89. {rucio-37.1.0.post1.data → rucio-37.3.0.data}/scripts/rucio-judge-cleaner +0 -0
  90. {rucio-37.1.0.post1.data → rucio-37.3.0.data}/scripts/rucio-judge-evaluator +0 -0
  91. {rucio-37.1.0.post1.data → rucio-37.3.0.data}/scripts/rucio-judge-injector +0 -0
  92. {rucio-37.1.0.post1.data → rucio-37.3.0.data}/scripts/rucio-judge-repairer +0 -0
  93. {rucio-37.1.0.post1.data → rucio-37.3.0.data}/scripts/rucio-kronos +0 -0
  94. {rucio-37.1.0.post1.data → rucio-37.3.0.data}/scripts/rucio-minos +0 -0
  95. {rucio-37.1.0.post1.data → rucio-37.3.0.data}/scripts/rucio-minos-temporary-expiration +0 -0
  96. {rucio-37.1.0.post1.data → rucio-37.3.0.data}/scripts/rucio-necromancer +0 -0
  97. {rucio-37.1.0.post1.data → rucio-37.3.0.data}/scripts/rucio-oauth-manager +0 -0
  98. {rucio-37.1.0.post1.data → rucio-37.3.0.data}/scripts/rucio-reaper +0 -0
  99. {rucio-37.1.0.post1.data → rucio-37.3.0.data}/scripts/rucio-replica-recoverer +0 -0
  100. {rucio-37.1.0.post1.data → rucio-37.3.0.data}/scripts/rucio-rse-decommissioner +0 -0
  101. {rucio-37.1.0.post1.data → rucio-37.3.0.data}/scripts/rucio-storage-consistency-actions +0 -0
  102. {rucio-37.1.0.post1.data → rucio-37.3.0.data}/scripts/rucio-transmogrifier +0 -0
  103. {rucio-37.1.0.post1.data → rucio-37.3.0.data}/scripts/rucio-undertaker +0 -0
  104. {rucio-37.1.0.post1.dist-info → rucio-37.3.0.dist-info}/licenses/AUTHORS.rst +0 -0
  105. {rucio-37.1.0.post1.dist-info → rucio-37.3.0.dist-info}/licenses/LICENSE +0 -0
  106. {rucio-37.1.0.post1.dist-info → rucio-37.3.0.dist-info}/top_level.txt +0 -0
rucio/gateway/vo.py CHANGED
@@ -12,23 +12,19 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
- from typing import TYPE_CHECKING, Any, Optional
15
+ from typing import Any, Optional
16
16
 
17
17
  from rucio.common import exception
18
18
  from rucio.common.schema import validate_schema
19
19
  from rucio.common.types import InternalAccount
20
20
  from rucio.core import identity
21
21
  from rucio.core import vo as vo_core
22
- from rucio.db.sqla.constants import IdentityType
23
- from rucio.db.sqla.session import read_session, transactional_session
22
+ from rucio.db.sqla.constants import DatabaseOperationType, IdentityType
23
+ from rucio.db.sqla.session import db_session
24
24
  from rucio.gateway.permission import has_permission
25
25
 
26
- if TYPE_CHECKING:
27
- from sqlalchemy.orm import Session
28
26
 
29
-
30
- @transactional_session
31
- def add_vo(new_vo: str, issuer: str, description: Optional[str] = None, email: Optional[str] = None, vo: str = 'def', *, session: "Session") -> None:
27
+ def add_vo(new_vo: str, issuer: str, description: Optional[str] = None, email: Optional[str] = None, vo: str = 'def') -> None:
32
28
  '''
33
29
  Add a new VO.
34
30
 
@@ -37,38 +33,38 @@ def add_vo(new_vo: str, issuer: str, description: Optional[str] = None, email: O
37
33
  :param email: A contact for the VO.
38
34
  :param issuer: The user issuing the command.
39
35
  :param vo: The vo of the user issuing the command.
40
- :param session: The database session in use.
41
36
  '''
42
37
 
43
38
  new_vo = vo_core.map_vo(new_vo)
44
39
  validate_schema('vo', new_vo, vo=vo)
45
40
 
46
41
  kwargs = {}
47
- auth_result = has_permission(issuer=issuer, action='add_vo', kwargs=kwargs, vo=vo, session=session)
48
- if not auth_result.allowed:
49
- raise exception.AccessDenied('Account {} cannot add a VO. {}'.format(issuer, auth_result.message))
50
42
 
51
- vo_core.add_vo(vo=new_vo, description=description, email=email, session=session)
43
+ with db_session(DatabaseOperationType.WRITE) as session:
44
+ auth_result = has_permission(issuer=issuer, action='add_vo', kwargs=kwargs, vo=vo, session=session)
45
+ if not auth_result.allowed:
46
+ raise exception.AccessDenied('Account {} cannot add a VO. {}'.format(issuer, auth_result.message))
47
+
48
+ vo_core.add_vo(vo=new_vo, description=description, email=email, session=session)
52
49
 
53
50
 
54
- @read_session
55
- def list_vos(issuer: str, vo: str = 'def', *, session: "Session") -> list[dict[str, Any]]:
51
+ def list_vos(issuer: str, vo: str = 'def') -> list[dict[str, Any]]:
56
52
  '''
57
53
  List the VOs.
58
54
 
59
55
  :param issuer: The user issuing the command.
60
56
  :param vo: The vo of the user issuing the command.
61
- :param session: The database session in use.
62
57
  '''
63
58
  kwargs = {}
64
- auth_result = has_permission(issuer=issuer, action='list_vos', kwargs=kwargs, vo=vo, session=session)
65
- if not auth_result.allowed:
66
- raise exception.AccessDenied('Account {} cannot list VOs. {}'.format(issuer, auth_result.message))
67
59
 
68
- return vo_core.list_vos(session=session)
60
+ with db_session(DatabaseOperationType.READ) as session:
61
+ auth_result = has_permission(issuer=issuer, action='list_vos', kwargs=kwargs, vo=vo, session=session)
62
+ if not auth_result.allowed:
63
+ raise exception.AccessDenied('Account {} cannot list VOs. {}'.format(issuer, auth_result.message))
64
+
65
+ return vo_core.list_vos(session=session)
69
66
 
70
67
 
71
- @transactional_session
72
68
  def recover_vo_root_identity(
73
69
  root_vo: str,
74
70
  identity_key: str,
@@ -78,8 +74,6 @@ def recover_vo_root_identity(
78
74
  default: bool = False,
79
75
  password: Optional[str] = None,
80
76
  vo: str = 'def',
81
- *,
82
- session: "Session"
83
77
  ) -> None:
84
78
  """
85
79
  Adds a membership association between identity and the root account for given VO.
@@ -92,22 +86,22 @@ def recover_vo_root_identity(
92
86
  :param default: If True, the account should be used by default with the provided identity.
93
87
  :param password: Password if id_type is userpass.
94
88
  :param vo: the VO to act on.
95
- :param session: The database session in use.
96
89
  """
97
90
  kwargs = {}
98
91
  root_vo = vo_core.map_vo(root_vo)
99
- auth_result = has_permission(issuer=issuer, vo=vo, action='recover_vo_root_identity', kwargs=kwargs, session=session)
100
- if not auth_result.allowed:
101
- raise exception.AccessDenied('Account %s can not recover root identity. %s' % (issuer, auth_result.message))
102
92
 
103
- account = InternalAccount('root', vo=root_vo)
93
+ with db_session(DatabaseOperationType.WRITE) as session:
94
+ auth_result = has_permission(issuer=issuer, vo=vo, action='recover_vo_root_identity', kwargs=kwargs, session=session)
95
+ if not auth_result.allowed:
96
+ raise exception.AccessDenied('Account %s can not recover root identity. %s' % (issuer, auth_result.message))
104
97
 
105
- return identity.add_account_identity(identity=identity_key, type_=IdentityType[id_type.upper()], default=default,
106
- email=email, account=account, password=password, session=session)
98
+ account = InternalAccount('root', vo=root_vo)
107
99
 
100
+ return identity.add_account_identity(identity=identity_key, type_=IdentityType[id_type.upper()], default=default,
101
+ email=email, account=account, password=password, session=session)
108
102
 
109
- @transactional_session
110
- def update_vo(updated_vo: str, parameters: dict[str, Any], issuer: str, vo: str = 'def', *, session: "Session") -> None:
103
+
104
+ def update_vo(updated_vo: str, parameters: dict[str, Any], issuer: str, vo: str = 'def') -> None:
111
105
  """
112
106
  Update VO properties (email, description).
113
107
 
@@ -115,12 +109,13 @@ def update_vo(updated_vo: str, parameters: dict[str, Any], issuer: str, vo: str
115
109
  :param parameters: A dictionary with the new properties.
116
110
  :param issuer: The user issuing the command.
117
111
  :param vo: The VO of the user issusing the command.
118
- :param session: The database session in use.
119
112
  """
120
113
  kwargs = {}
121
114
  updated_vo = vo_core.map_vo(updated_vo)
122
- auth_result = has_permission(issuer=issuer, action='update_vo', kwargs=kwargs, vo=vo, session=session)
123
- if not auth_result.allowed:
124
- raise exception.AccessDenied('Account {} cannot update VO. {}'.format(issuer, auth_result.message))
125
115
 
126
- return vo_core.update_vo(vo=updated_vo, parameters=parameters, session=session)
116
+ with db_session(DatabaseOperationType.WRITE) as session:
117
+ auth_result = has_permission(issuer=issuer, action='update_vo', kwargs=kwargs, vo=vo, session=session)
118
+ if not auth_result.allowed:
119
+ raise exception.AccessDenied('Account {} cannot update VO. {}'.format(issuer, auth_result.message))
120
+
121
+ return vo_core.update_vo(vo=updated_vo, parameters=parameters, session=session)
rucio/vcsversion.py CHANGED
@@ -4,8 +4,8 @@ This file is automatically generated; Do not edit it. :)
4
4
  '''
5
5
  VERSION_INFO = {
6
6
  'final': True,
7
- 'version': '37.1.0.post1',
7
+ 'version': '37.3.0',
8
8
  'branch_nick': 'release-37',
9
- 'revision_id': '420fea7843ab9f55e21b71eb93709f7621db8873',
10
- 'revno': 13642
9
+ 'revision_id': '874ee3e820d3ff64ab941ccfe8a02906b5540066',
10
+ 'revno': 13716
11
11
  }
@@ -217,7 +217,7 @@ class Scopes(ErrorHandlingMethodView):
217
217
  description: Not acceptable
218
218
  """
219
219
  try:
220
- scopes = get_scopes(account, vo=request.environ.get('vo'))
220
+ scopes = get_scopes(account, vo=request.environ['vo'])
221
221
  except AccountNotFound as error:
222
222
  return generate_http_error_flask(404, error)
223
223
 
@@ -264,7 +264,7 @@ class Scopes(ErrorHandlingMethodView):
264
264
  description: Scope already exists.
265
265
  """
266
266
  try:
267
- add_scope(scope, account, issuer=request.environ.get('issuer'), vo=request.environ.get('vo'))
267
+ add_scope(scope, account, issuer=request.environ['issuer'], vo=request.environ['vo'])
268
268
  except InvalidObject as error:
269
269
  return generate_http_error_flask(400, error)
270
270
  except AccessDenied as error:
@@ -916,6 +916,9 @@ class GSS(ErrorHandlingMethodView):
916
916
  appid = request.headers.get('X-Rucio-AppID', default='unknown')
917
917
  ip = request.headers.get('X-Forwarded-For', default=request.remote_addr)
918
918
 
919
+ if not account or not gsscred:
920
+ return generate_http_error_flask(400, ValueError.__name__, 'Account and REMOTE_USER must be set.')
921
+
919
922
  try:
920
923
  result = get_auth_token_gss(account, gsscred, appid, ip, vo=vo)
921
924
  except AccessDenied:
@@ -1212,6 +1215,9 @@ class SSH(ErrorHandlingMethodView):
1212
1215
  appid = request.headers.get('X-Rucio-AppID', default='unknown')
1213
1216
  ip = request.headers.get('X-Forwarded-For', default=request.remote_addr)
1214
1217
 
1218
+ if not account or not signature:
1219
+ return generate_http_error_flask(400, ValueError.__name__, 'Account and SSH signature must be set.')
1220
+
1215
1221
  try:
1216
1222
  result = get_auth_token_ssh(account, signature, appid, ip, vo=vo)
1217
1223
  except AccessDenied:
@@ -1332,6 +1338,9 @@ class SSHChallengeToken(ErrorHandlingMethodView):
1332
1338
  appid = request.headers.get('X-Rucio-AppID', default='unknown')
1333
1339
  ip = request.headers.get('X-Forwarded-For', default=request.remote_addr)
1334
1340
 
1341
+ if not account:
1342
+ return generate_http_error_flask(400, ValueError.__name__, 'Account must be set.')
1343
+
1335
1344
  result = get_ssh_challenge_token(account, appid, ip, vo=vo)
1336
1345
 
1337
1346
  if not result:
@@ -1452,6 +1461,9 @@ class SAML(ErrorHandlingMethodView):
1452
1461
  appid = request.headers.get('X-Rucio-AppID', default='unknown')
1453
1462
  ip = request.headers.get('X-Forwarded-For', default=request.remote_addr)
1454
1463
 
1464
+ if not account:
1465
+ return generate_http_error_flask(400, ValueError.__name__, 'Account must be set.')
1466
+
1455
1467
  if saml_nameid:
1456
1468
  try:
1457
1469
  result = get_auth_token_saml(account, saml_nameid, appid, ip, vo=vo)
@@ -1592,6 +1604,9 @@ class Validate(ErrorHandlingMethodView):
1592
1604
 
1593
1605
  token = request.headers.get('X-Rucio-Auth-Token', default=None)
1594
1606
 
1607
+ if not token:
1608
+ return generate_http_error_flask(400, ValueError.__name__, 'Token must be set.')
1609
+
1595
1610
  result = validate_auth_token(token)
1596
1611
  if not result:
1597
1612
  return generate_http_error_flask(
@@ -15,11 +15,13 @@
15
15
  import itertools
16
16
  import json
17
17
  import logging
18
+ import os
18
19
  import re
19
20
  from configparser import NoOptionError, NoSectionError
20
21
  from functools import wraps
21
22
  from time import time
22
23
  from typing import TYPE_CHECKING, Any, Literal, Optional, TypeVar, Union
24
+ from urllib.parse import unquote_plus
23
25
 
24
26
  import flask
25
27
  from flask.views import MethodView
@@ -46,6 +48,9 @@ if TYPE_CHECKING:
46
48
 
47
49
  ResponseTypeVar = TypeVar('ResponseTypeVar', bound=flask.wrappers.Response)
48
50
 
51
+ RUCIO_HTTPD_ENCODED_SLASHES_NO_DECODE = os.environ.get('RUCIO_HTTPD_ENCODED_SLASHES_NO_DECODE',
52
+ 'false').lower() == 'true'
53
+
49
54
 
50
55
  class CORSMiddleware:
51
56
  """
@@ -143,6 +148,9 @@ def request_auth_env() -> Optional['ResponseReturnValue']:
143
148
 
144
149
  auth_token = flask.request.headers.get('X-Rucio-Auth-Token', default=None)
145
150
 
151
+ if not auth_token:
152
+ return generate_http_error_flask(400, ValueError.__name__, 'Token must be set.')
153
+
146
154
  try:
147
155
  auth = validate_auth_token(auth_token)
148
156
  except CannotAuthenticate:
@@ -211,6 +219,18 @@ def parse_scope_name(scope_name: str, vo: Optional[str]) -> tuple[str, ...]:
211
219
  :returns: a (scope, name) tuple.
212
220
  """
213
221
 
222
+ if RUCIO_HTTPD_ENCODED_SLASHES_NO_DECODE:
223
+ if scope_name.count('/') != 1:
224
+ # scope and name are always separated by a single slash ('/', unencoded) in the request.
225
+ # If the server is configured with the 'NoDecode' option, other slashes will be encoded.
226
+ # This is just a sanity check that should never happen.
227
+ raise ValueError(f"Could not parse '{scope_name}' ({scope_name=}) with encoded '/' into scope and name.")
228
+
229
+ scope, name = scope_name.split('/', 1)
230
+ name = unquote_plus(name)
231
+
232
+ return scope, name
233
+
214
234
  if not vo:
215
235
  vo = 'def'
216
236
 
@@ -46,9 +46,9 @@ class Config(ErrorHandlingMethodView):
46
46
  description: Not acceptable
47
47
  """
48
48
  res = {}
49
- for section in config.sections(issuer=request.environ.get('issuer'), vo=request.environ.get('vo')):
49
+ for section in config.sections(issuer=request.environ['issuer'], vo=request.environ['vo']):
50
50
  res[section] = {}
51
- for item in config.items(section, issuer=request.environ.get('issuer'), vo=request.environ.get('vo')):
51
+ for item in config.items(section, issuer=request.environ['issuer'], vo=request.environ['vo']):
52
52
  res[section][item[0]] = item[1]
53
53
 
54
54
  return jsonify(res), 200
@@ -87,7 +87,7 @@ class Config(ErrorHandlingMethodView):
87
87
  return generate_http_error_flask(400, ValueError.__name__, '')
88
88
  for option, value in section_config.items():
89
89
  try:
90
- config.set(section=section, option=option, value=value, issuer=request.environ.get('issuer'), vo=request.environ.get('vo'))
90
+ config.set(section=section, option=option, value=value, issuer=request.environ['issuer'], vo=request.environ['vo'])
91
91
  except ConfigurationError:
92
92
  return generate_http_error_flask(400, 'ConfigurationError', f"Could not set value '{value}' for section '{section}' option '{option}'")
93
93
  return 'Created', 201
@@ -137,7 +137,7 @@ class Section(ErrorHandlingMethodView):
137
137
  description: Not acceptable
138
138
  """
139
139
  res = {}
140
- for item in config.items(section, issuer=request.environ.get('issuer'), vo=request.environ.get('vo')):
140
+ for item in config.items(section, issuer=request.environ['issuer'], vo=request.environ['vo']):
141
141
  res[item[0]] = item[1]
142
142
 
143
143
  if res == {}:
@@ -190,7 +190,7 @@ class OptionGetDel(ErrorHandlingMethodView):
190
190
  description: Not acceptable
191
191
  """
192
192
  try:
193
- result = config.get(section=section, option=option, issuer=request.environ.get('issuer'), vo=request.environ.get('vo'))
193
+ result = config.get(section=section, option=option, issuer=request.environ['issuer'], vo=request.environ['vo'])
194
194
  return jsonify(result), 200
195
195
  except AccessDenied as error:
196
196
  return generate_http_error_flask(401, error, f"Access to '{section}' option '{option}' denied")
@@ -223,7 +223,7 @@ class OptionGetDel(ErrorHandlingMethodView):
223
223
  401:
224
224
  description: Invalid Auth Token
225
225
  """
226
- config.remove_option(section=section, option=option, issuer=request.environ.get('issuer'), vo=request.environ.get('vo'))
226
+ config.remove_option(section=section, option=option, issuer=request.environ['issuer'], vo=request.environ['vo'])
227
227
  return '', 200
228
228
 
229
229
 
@@ -275,7 +275,7 @@ class OptionSet(ErrorHandlingMethodView):
275
275
  enum: ['Could not set value {} for section {} option {}']
276
276
  """
277
277
  try:
278
- config.set(section=section, option=option, value=value, issuer=request.environ.get('issuer'), vo=request.environ.get('vo'))
278
+ config.set(section=section, option=option, value=value, issuer=request.environ['issuer'], vo=request.environ['vo'])
279
279
  return 'Created', 201
280
280
  except ConfigurationError as error:
281
281
  return generate_http_error_flask(500, error, f"Could not set value '{value}' for section '{section}' option '{option}'")
@@ -12,12 +12,12 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
- from typing import TYPE_CHECKING
15
+ from typing import TYPE_CHECKING, cast
16
16
 
17
17
  from flask import Flask, request
18
18
  from werkzeug.datastructures import Headers
19
19
 
20
- from rucio.common.constants import RSE_BASE_SUPPORTED_PROTOCOL_OPERATIONS, SUPPORTED_SIGN_URL_SERVICES
20
+ from rucio.common.constants import RSE_BASE_SUPPORTED_PROTOCOL_OPERATIONS, RSE_BASE_SUPPORTED_PROTOCOL_OPERATIONS_LITERAL, SUPPORTED_SIGN_URL_SERVICES, SUPPORTED_SIGN_URL_SERVICES_LITERAL
21
21
  from rucio.common.exception import CannotAuthenticate
22
22
  from rucio.gateway.credential import get_signed_url
23
23
  from rucio.web.rest.flaskapi.authenticated_bp import AuthenticatedBlueprint
@@ -163,28 +163,35 @@ class SignURL(ErrorHandlingMethodView):
163
163
  headers = self.get_headers()
164
164
  vo = extract_vo(request.headers)
165
165
  account = request.headers.get('X-Rucio-Account', default=None)
166
- appid = request.headers.get('X-Rucio-AppID', default='unknown')
167
- ip = request.headers.get('X-Forwarded-For', default=request.remote_addr)
168
166
 
169
- if 'rse' not in request.args:
170
- return generate_http_error_flask(400, ValueError.__name__, 'Parameter "rse" not found', headers=headers)
167
+ if account is None:
168
+ return generate_http_error_flask(400, ValueError.__name__, 'Parameter "account" not found.', headers=headers)
169
+
171
170
  rse = request.args.get('rse')
172
171
 
172
+ if rse is None:
173
+ return generate_http_error_flask(400, ValueError.__name__, 'Parameter "rse" not found', headers=headers)
174
+
175
+ url = request.args.get('url')
176
+
177
+ if url is None:
178
+ return generate_http_error_flask(400, ValueError.__name__, 'Parameter "url" not found', headers=headers)
179
+
173
180
  lifetime = request.args.get('lifetime', type=int, default=600)
174
181
  service = request.args.get('svc', default='gcs')
175
182
  operation = request.args.get('op', default='read')
176
183
 
177
- if 'url' not in request.args:
178
- return generate_http_error_flask(400, ValueError.__name__, 'Parameter "url" not found', headers=headers)
179
- url = request.args.get('url')
180
-
181
184
  if service not in SUPPORTED_SIGN_URL_SERVICES:
182
- return generate_http_error_flask(400, ValueError.__name__, 'Parameter "svc" must be either empty(=gcs), gcs, s3 or swift', headers=headers)
185
+ return generate_http_error_flask(400, ValueError.__name__, 'Parameter "svc" must be either empty (which defaults to "gcs"), "gcs", "s3" or "swift"', headers=headers)
186
+
187
+ service = cast("SUPPORTED_SIGN_URL_SERVICES_LITERAL", service)
183
188
 
184
189
  if operation not in RSE_BASE_SUPPORTED_PROTOCOL_OPERATIONS:
185
- return generate_http_error_flask(400, ValueError.__name__, 'Parameter "op" must be either empty(=read), read, write, or delete.', headers=headers)
190
+ return generate_http_error_flask(400, ValueError.__name__, 'Parameter "op" must be either empty (which defaults to "read"), "read", "write", or "delete".', headers=headers)
191
+
192
+ operation = cast("RSE_BASE_SUPPORTED_PROTOCOL_OPERATIONS_LITERAL", service)
186
193
 
187
- result = get_signed_url(account, appid, ip, rse=rse, service=service, operation=operation, url=url, lifetime=lifetime, vo=vo)
194
+ result = get_signed_url(account, rse=rse, service=service, operation=operation, url=url, lifetime=lifetime, vo=vo)
188
195
 
189
196
  if not result:
190
197
  return generate_http_error_flask(401, CannotAuthenticate.__name__, f'Cannot generate signed URL for account {account}', headers=headers)