rucio-clients 32.8.6__py3-none-any.whl → 35.8.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-clients might be problematic. Click here for more details.

Files changed (92) hide show
  1. rucio/__init__.py +0 -1
  2. rucio/alembicrevision.py +1 -2
  3. rucio/client/__init__.py +0 -1
  4. rucio/client/accountclient.py +45 -25
  5. rucio/client/accountlimitclient.py +37 -9
  6. rucio/client/baseclient.py +199 -154
  7. rucio/client/client.py +2 -3
  8. rucio/client/configclient.py +19 -6
  9. rucio/client/credentialclient.py +9 -4
  10. rucio/client/didclient.py +238 -63
  11. rucio/client/diracclient.py +13 -5
  12. rucio/client/downloadclient.py +162 -51
  13. rucio/client/exportclient.py +4 -4
  14. rucio/client/fileclient.py +3 -4
  15. rucio/client/importclient.py +4 -4
  16. rucio/client/lifetimeclient.py +21 -5
  17. rucio/client/lockclient.py +18 -8
  18. rucio/client/{metaclient.py → metaconventionsclient.py} +18 -15
  19. rucio/client/pingclient.py +0 -1
  20. rucio/client/replicaclient.py +15 -5
  21. rucio/client/requestclient.py +35 -19
  22. rucio/client/rseclient.py +133 -51
  23. rucio/client/ruleclient.py +29 -22
  24. rucio/client/scopeclient.py +8 -6
  25. rucio/client/subscriptionclient.py +47 -35
  26. rucio/client/touchclient.py +8 -4
  27. rucio/client/uploadclient.py +166 -82
  28. rucio/common/__init__.py +0 -1
  29. rucio/common/cache.py +4 -4
  30. rucio/common/config.py +52 -47
  31. rucio/common/constants.py +69 -2
  32. rucio/common/constraints.py +0 -1
  33. rucio/common/didtype.py +24 -22
  34. rucio/common/exception.py +281 -222
  35. rucio/common/extra.py +0 -1
  36. rucio/common/logging.py +54 -38
  37. rucio/common/pcache.py +122 -101
  38. rucio/common/plugins.py +153 -0
  39. rucio/common/policy.py +4 -4
  40. rucio/common/schema/__init__.py +17 -10
  41. rucio/common/schema/atlas.py +7 -5
  42. rucio/common/schema/belleii.py +7 -5
  43. rucio/common/schema/domatpc.py +7 -5
  44. rucio/common/schema/escape.py +7 -5
  45. rucio/common/schema/generic.py +8 -6
  46. rucio/common/schema/generic_multi_vo.py +7 -5
  47. rucio/common/schema/icecube.py +7 -5
  48. rucio/common/stomp_utils.py +0 -1
  49. rucio/common/stopwatch.py +0 -1
  50. rucio/common/test_rucio_server.py +2 -2
  51. rucio/common/types.py +262 -17
  52. rucio/common/utils.py +743 -451
  53. rucio/rse/__init__.py +3 -4
  54. rucio/rse/protocols/__init__.py +0 -1
  55. rucio/rse/protocols/bittorrent.py +184 -0
  56. rucio/rse/protocols/cache.py +1 -2
  57. rucio/rse/protocols/dummy.py +1 -2
  58. rucio/rse/protocols/gfal.py +12 -10
  59. rucio/rse/protocols/globus.py +7 -7
  60. rucio/rse/protocols/gsiftp.py +2 -3
  61. rucio/rse/protocols/http_cache.py +1 -2
  62. rucio/rse/protocols/mock.py +1 -2
  63. rucio/rse/protocols/ngarc.py +1 -2
  64. rucio/rse/protocols/posix.py +12 -13
  65. rucio/rse/protocols/protocol.py +116 -52
  66. rucio/rse/protocols/rclone.py +6 -7
  67. rucio/rse/protocols/rfio.py +4 -5
  68. rucio/rse/protocols/srm.py +9 -10
  69. rucio/rse/protocols/ssh.py +8 -9
  70. rucio/rse/protocols/storm.py +2 -3
  71. rucio/rse/protocols/webdav.py +17 -14
  72. rucio/rse/protocols/xrootd.py +23 -17
  73. rucio/rse/rsemanager.py +19 -7
  74. rucio/vcsversion.py +4 -4
  75. rucio/version.py +5 -13
  76. rucio_clients-35.8.0.data/data/requirements.client.txt +15 -0
  77. {rucio_clients-32.8.6.data → rucio_clients-35.8.0.data}/data/rucio_client/merge_rucio_configs.py +2 -5
  78. {rucio_clients-32.8.6.data → rucio_clients-35.8.0.data}/scripts/rucio +87 -85
  79. {rucio_clients-32.8.6.data → rucio_clients-35.8.0.data}/scripts/rucio-admin +45 -32
  80. {rucio_clients-32.8.6.dist-info → rucio_clients-35.8.0.dist-info}/METADATA +13 -13
  81. rucio_clients-35.8.0.dist-info/RECORD +88 -0
  82. {rucio_clients-32.8.6.dist-info → rucio_clients-35.8.0.dist-info}/WHEEL +1 -1
  83. {rucio_clients-32.8.6.dist-info → rucio_clients-35.8.0.dist-info}/licenses/AUTHORS.rst +3 -0
  84. rucio/common/schema/cms.py +0 -478
  85. rucio/common/schema/lsst.py +0 -423
  86. rucio_clients-32.8.6.data/data/requirements.txt +0 -55
  87. rucio_clients-32.8.6.dist-info/RECORD +0 -88
  88. {rucio_clients-32.8.6.data → rucio_clients-35.8.0.data}/data/etc/rse-accounts.cfg.template +0 -0
  89. {rucio_clients-32.8.6.data → rucio_clients-35.8.0.data}/data/etc/rucio.cfg.atlas.client.template +0 -0
  90. {rucio_clients-32.8.6.data → rucio_clients-35.8.0.data}/data/etc/rucio.cfg.template +0 -0
  91. {rucio_clients-32.8.6.dist-info → rucio_clients-35.8.0.dist-info}/licenses/LICENSE +0 -0
  92. {rucio_clients-32.8.6.dist-info → rucio_clients-35.8.0.dist-info}/top_level.txt +0 -0
@@ -1,4 +1,3 @@
1
- # -*- coding: utf-8 -*-
2
1
  # Copyright European Organization for Nuclear Research (CERN) since 2012
3
2
  #
4
3
  # Licensed under the Apache License, Version 2.0 (the "License");
@@ -20,28 +19,30 @@
20
19
  import errno
21
20
  import getpass
22
21
  import os
23
- import random
22
+ import secrets
24
23
  import sys
25
24
  import time
25
+ from collections.abc import Generator
26
26
  from configparser import NoOptionError, NoSectionError
27
- from os import environ, fdopen, path, makedirs, geteuid
27
+ from logging import Logger
28
+ from os import environ, fdopen, geteuid, makedirs, path
28
29
  from shutil import move
29
30
  from tempfile import mkstemp
31
+ from typing import Any, Optional
30
32
  from urllib.parse import urlparse
31
33
 
34
+ import requests
32
35
  from dogpile.cache import make_region
33
- from requests import Session, Response
36
+ from requests import Response, Session
34
37
  from requests.exceptions import ConnectionError
35
38
  from requests.status_codes import codes
36
39
 
37
40
  from rucio import version
38
41
  from rucio.common import exception
39
42
  from rucio.common.config import config_get, config_get_bool, config_get_int
40
- from rucio.common.exception import (CannotAuthenticate, ClientProtocolNotSupported,
41
- NoAuthInformation, MissingClientParameter,
42
- MissingModuleException, ServerConnectionException)
43
+ from rucio.common.exception import CannotAuthenticate, ClientProtocolNotSupported, ConfigNotFound, MissingClientParameter, MissingModuleException, NoAuthInformation, ServerConnectionException
43
44
  from rucio.common.extra import import_extras
44
- from rucio.common.utils import build_url, get_tmp_dir, my_key_generator, parse_response, ssh_sign, setup_logger
45
+ from rucio.common.utils import build_url, get_tmp_dir, my_key_generator, parse_response, setup_logger, ssh_sign
45
46
 
46
47
  EXTRA_MODULES = import_extras(['requests_kerberos'])
47
48
 
@@ -67,19 +68,29 @@ def choice(hosts):
67
68
  :param hosts: Lost of hosts
68
69
  :return: A randomly selected host.
69
70
  """
70
- return random.choice(hosts)
71
+ return secrets.choice(hosts)
71
72
 
72
73
 
73
- class BaseClient(object):
74
+ class BaseClient:
74
75
 
75
76
  """Main client class for accessing Rucio resources. Handles the authentication."""
76
77
 
77
78
  AUTH_RETRIES, REQUEST_RETRIES = 2, 3
78
79
  TOKEN_PATH_PREFIX = get_tmp_dir() + '/.rucio_'
79
- TOKEN_PREFIX = 'auth_token_'
80
- TOKEN_EXP_PREFIX = 'auth_token_exp_'
81
-
82
- def __init__(self, rucio_host=None, auth_host=None, account=None, ca_cert=None, auth_type=None, creds=None, timeout=600, user_agent='rucio-clients', vo=None, logger=None):
80
+ TOKEN_PREFIX = 'auth_token_' # noqa: S105
81
+ TOKEN_EXP_PREFIX = 'auth_token_exp_' # noqa: S105
82
+
83
+ def __init__(self,
84
+ rucio_host: Optional[str] = None,
85
+ auth_host: Optional[str] = None,
86
+ account: Optional[str] = None,
87
+ ca_cert: Optional[str] = None,
88
+ auth_type: Optional[str] = None,
89
+ creds: Optional[dict[str, Any]] = None,
90
+ timeout: Optional[int] = 600,
91
+ user_agent: Optional[str] = 'rucio-clients',
92
+ vo: Optional[str] = None,
93
+ logger: Logger = LOG) -> None:
83
94
  """
84
95
  Constructor of the BaseClient.
85
96
  :param rucio_host: The address of the rucio server, if None it is read from the config file.
@@ -96,10 +107,7 @@ class BaseClient(object):
96
107
  :param logger: Logger object to use. If None, use the default LOG created by the module
97
108
  """
98
109
 
99
- self.host = rucio_host
100
- self.list_hosts = []
101
- self.auth_host = auth_host
102
- self.logger = logger or LOG
110
+ self.logger = logger
103
111
  self.session = Session()
104
112
  self.user_agent = "%s/%s" % (user_agent, version.version_string()) # e.g. "rucio-clients/0.2.13"
105
113
  sys.argv[0] = sys.argv[0].split('/')[-1]
@@ -107,119 +115,42 @@ class BaseClient(object):
107
115
  if self.script_id == '': # Python interpreter used
108
116
  self.script_id = 'python'
109
117
  try:
110
- if self.host is None:
118
+ if rucio_host is not None:
119
+ self.host = rucio_host
120
+ else:
111
121
  self.host = config_get('client', 'rucio_host')
112
- if self.auth_host is None:
122
+ except (NoOptionError, NoSectionError) as error:
123
+ raise MissingClientParameter('Section client and Option \'%s\' cannot be found in config file' % error.args[0])
124
+
125
+ try:
126
+ if auth_host is not None:
127
+ self.auth_host = auth_host
128
+ else:
113
129
  self.auth_host = config_get('client', 'auth_host')
114
130
  except (NoOptionError, NoSectionError) as error:
115
131
  raise MissingClientParameter('Section client and Option \'%s\' cannot be found in config file' % error.args[0])
116
132
 
117
133
  try:
118
134
  self.trace_host = config_get('trace', 'trace_host')
119
- except (NoOptionError, NoSectionError):
135
+ except (NoOptionError, NoSectionError, ConfigNotFound):
120
136
  self.trace_host = self.host
121
137
  self.logger.debug('No trace_host passed. Using rucio_host instead')
122
138
 
139
+ self.list_hosts = [self.host]
123
140
  self.account = account
124
- self.vo = vo
125
141
  self.ca_cert = ca_cert
126
- self.auth_type = auth_type
127
- self.creds = creds
128
- self.auth_token = None
129
- self.auth_token_file_path = config_get('client', 'auth_token_file_path', False, None)
142
+ self.auth_token = ""
130
143
  self.headers = {}
131
144
  self.timeout = timeout
132
145
  self.request_retries = self.REQUEST_RETRIES
133
146
  self.token_exp_epoch = None
134
- self.token_exp_epoch_file = None
135
147
  self.auth_oidc_refresh_active = config_get_bool('client', 'auth_oidc_refresh_active', False, False)
148
+
136
149
  # defining how many minutes before token expires, oidc refresh (if active) should start
137
150
  self.auth_oidc_refresh_before_exp = config_get_int('client', 'auth_oidc_refresh_before_exp', False, 20)
138
151
 
139
- if auth_type is None:
140
- self.logger.debug('No auth_type passed. Trying to get it from the environment variable RUCIO_AUTH_TYPE and config file.')
141
- if 'RUCIO_AUTH_TYPE' in environ:
142
- if environ['RUCIO_AUTH_TYPE'] not in ['userpass', 'x509', 'x509_proxy', 'gss', 'ssh', 'saml', 'oidc']:
143
- raise MissingClientParameter('Possible RUCIO_AUTH_TYPE values: userpass, x509, x509_proxy, gss, ssh, saml, oidc, vs. ' + environ['RUCIO_AUTH_TYPE'])
144
- self.auth_type = environ['RUCIO_AUTH_TYPE']
145
- else:
146
- try:
147
- self.auth_type = config_get('client', 'auth_type')
148
- except (NoOptionError, NoSectionError) as error:
149
- raise MissingClientParameter('Option \'%s\' cannot be found in config file' % error.args[0])
150
-
151
- if self.auth_type == 'oidc':
152
- if not self.creds:
153
- self.creds = {}
154
- # if there are defautl values, check if rucio.cfg does not specify them, otherwise put default
155
- if 'oidc_refresh_lifetime' not in self.creds or self.creds['oidc_refresh_lifetime'] is None:
156
- self.creds['oidc_refresh_lifetime'] = config_get('client', 'oidc_refresh_lifetime', False, None)
157
- if 'oidc_issuer' not in self.creds or self.creds['oidc_issuer'] is None:
158
- self.creds['oidc_issuer'] = config_get('client', 'oidc_issuer', False, None)
159
- if 'oidc_audience' not in self.creds or self.creds['oidc_audience'] is None:
160
- self.creds['oidc_audience'] = config_get('client', 'oidc_audience', False, None)
161
- if 'oidc_auto' not in self.creds or self.creds['oidc_auto'] is False:
162
- self.creds['oidc_auto'] = config_get_bool('client', 'oidc_auto', False, False)
163
- if self.creds['oidc_auto']:
164
- if 'oidc_username' not in self.creds or self.creds['oidc_username'] is None:
165
- self.creds['oidc_username'] = config_get('client', 'oidc_username', False, None)
166
- if 'oidc_password' not in self.creds or self.creds['oidc_password'] is None:
167
- self.creds['oidc_password'] = config_get('client', 'oidc_password', False, None)
168
- if 'oidc_scope' not in self.creds or self.creds['oidc_scope'] == 'openid profile':
169
- self.creds['oidc_scope'] = config_get('client', 'oidc_scope', False, 'openid profile')
170
- if 'oidc_polling' not in self.creds or self.creds['oidc_polling'] is False:
171
- self.creds['oidc_polling'] = config_get_bool('client', 'oidc_polling', False, False)
172
-
173
- if not self.creds:
174
- self.logger.debug('No creds passed. Trying to get it from the config file.')
175
- self.creds = {}
176
- try:
177
- if self.auth_type in ['userpass', 'saml']:
178
- self.creds['username'] = config_get('client', 'username')
179
- self.creds['password'] = config_get('client', 'password')
180
- elif self.auth_type == 'x509':
181
- if "RUCIO_CLIENT_CERT" in environ:
182
- client_cert = environ["RUCIO_CLIENT_CERT"]
183
- else:
184
- client_cert = config_get('client', 'client_cert')
185
- self.creds['client_cert'] = path.abspath(path.expanduser(path.expandvars(client_cert)))
186
- if not path.exists(self.creds['client_cert']):
187
- raise MissingClientParameter('X.509 client certificate not found: %s' % self.creds['client_cert'])
188
-
189
- if "RUCIO_CLIENT_KEY" in environ:
190
- client_key = environ["RUCIO_CLIENT_KEY"]
191
- else:
192
- client_key = config_get('client', 'client_key')
193
- self.creds['client_key'] = path.abspath(path.expanduser(path.expandvars(client_key)))
194
- if not path.exists(self.creds['client_key']):
195
- raise MissingClientParameter('X.509 client key not found: %s' % self.creds['client_key'])
196
- else:
197
- perms = oct(os.stat(self.creds['client_key']).st_mode)[-3:]
198
- if perms not in ['400', '600']:
199
- raise CannotAuthenticate('X.509 authentication selected, but private key (%s) permissions are liberal (required: 400 or 600, found: %s)' % (self.creds['client_key'], perms))
200
-
201
- elif self.auth_type == 'x509_proxy':
202
- try:
203
- self.creds['client_proxy'] = path.abspath(path.expanduser(path.expandvars(config_get('client', 'client_x509_proxy'))))
204
- except NoOptionError:
205
- # Recreate the classic GSI logic for locating the proxy:
206
- # - $X509_USER_PROXY, if it is set.
207
- # - /tmp/x509up_u`id -u` otherwise.
208
- # If neither exists (at this point, we don't care if it exists but is invalid), then rethrow
209
- if 'X509_USER_PROXY' in environ:
210
- self.creds['client_proxy'] = environ['X509_USER_PROXY']
211
- else:
212
- fname = '/tmp/x509up_u%d' % geteuid()
213
- if path.exists(fname):
214
- self.creds['client_proxy'] = fname
215
- else:
216
- raise MissingClientParameter('Cannot find a valid X509 proxy; not in %s, $X509_USER_PROXY not set, and '
217
- '\'x509_proxy\' not set in the configuration file.' % fname)
218
- elif self.auth_type == 'ssh':
219
- self.creds['ssh_private_key'] = path.abspath(path.expanduser(path.expandvars(config_get('client', 'ssh_private_key'))))
220
- except (NoOptionError, NoSectionError) as error:
221
- if error.args[0] != 'client_key':
222
- raise MissingClientParameter('Option \'%s\' cannot be found in config file' % error.args[0])
152
+ self.auth_type = self._get_auth_type(auth_type)
153
+ self.creds = self._get_creds(creds)
223
154
 
224
155
  rucio_scheme = urlparse(self.host).scheme
225
156
  auth_scheme = urlparse(self.auth_host).scheme
@@ -240,8 +171,9 @@ class BaseClient(object):
240
171
  except (NoOptionError, NoSectionError):
241
172
  self.logger.debug('No ca_cert found in configuration. Falling back to Mozilla default CA bundle (certifi).')
242
173
  self.ca_cert = True
243
-
244
- self.list_hosts = [self.host]
174
+ except ConfigNotFound:
175
+ self.logger.debug('No configuration found. Falling back to Mozilla default CA bundle (certifi).')
176
+ self.ca_cert = True
245
177
 
246
178
  if account is None:
247
179
  self.logger.debug('No account passed. Trying to get it from the RUCIO_ACCOUNT environment variable or the config file.')
@@ -253,7 +185,9 @@ class BaseClient(object):
253
185
  except (NoOptionError, NoSectionError):
254
186
  pass
255
187
 
256
- if vo is None:
188
+ if vo is not None:
189
+ self.vo = vo
190
+ else:
257
191
  self.logger.debug('No VO passed. Trying to get it from environment variable RUCIO_VO.')
258
192
  try:
259
193
  self.vo = environ['RUCIO_VO']
@@ -264,31 +198,139 @@ class BaseClient(object):
264
198
  except (NoOptionError, NoSectionError):
265
199
  self.logger.debug('No VO found. Using default VO.')
266
200
  self.vo = 'def'
201
+ except ConfigNotFound:
202
+ self.logger.debug('No configuration found. Using default VO.')
203
+ self.vo = 'def'
267
204
 
268
- token_filename_suffix = "for_default_account" if self.account is None else "for_account_" + self.account
205
+ self.auth_token_file_path, self.token_exp_epoch_file, self.token_file, self.token_path = self._get_auth_tokens()
206
+ self.__authenticate()
207
+
208
+ try:
209
+ self.request_retries = config_get_int('client', 'request_retries')
210
+ except (NoOptionError, ConfigNotFound):
211
+ self.logger.debug('request_retries not specified in config file. Taking default.')
212
+ except ValueError:
213
+ self.logger.debug('request_retries must be an integer. Taking default.')
269
214
 
215
+ def _get_auth_tokens(self) -> tuple[Optional[str], str, str, str]:
270
216
  # if token file path is defined in the rucio.cfg file, use that file. Currently this prevents authenticating as another user or VO.
271
- if self.auth_token_file_path:
272
- self.token_file = self.auth_token_file_path
273
- self.token_path = '/'.join(self.token_file.split('/')[:-1])
217
+ auth_token_file_path = config_get('client', 'auth_token_file_path', False, None)
218
+ token_filename_suffix = "for_default_account" if self.account is None else "for_account_" + self.account
219
+
220
+ if auth_token_file_path:
221
+ token_file = auth_token_file_path
222
+ token_path = '/'.join(auth_token_file_path.split('/')[:-1])
223
+
274
224
  else:
275
- self.token_path = self.TOKEN_PATH_PREFIX + getpass.getuser()
225
+ token_path = self.TOKEN_PATH_PREFIX + getpass.getuser()
276
226
  if self.vo != 'def':
277
- self.token_path += '@%s' % self.vo
278
- self.token_file = self.token_path + '/' + self.TOKEN_PREFIX + token_filename_suffix
227
+ token_path += '@%s' % self.vo
279
228
 
280
- self.token_exp_epoch_file = self.token_path + '/' + self.TOKEN_EXP_PREFIX + token_filename_suffix
229
+ token_file = token_path + '/' + self.TOKEN_PREFIX + token_filename_suffix
281
230
 
282
- self.__authenticate()
231
+ token_exp_epoch_file = token_path + '/' + self.TOKEN_EXP_PREFIX + token_filename_suffix
232
+ return auth_token_file_path, token_exp_epoch_file, token_file, token_path
233
+
234
+ def _get_auth_type(self, auth_type: Optional[str]) -> str:
235
+ if auth_type is None:
236
+ self.logger.debug('No auth_type passed. Trying to get it from the environment variable RUCIO_AUTH_TYPE and config file.')
237
+ if 'RUCIO_AUTH_TYPE' in environ:
238
+ if environ['RUCIO_AUTH_TYPE'] not in ['userpass', 'x509', 'x509_proxy', 'gss', 'ssh', 'saml', 'oidc']:
239
+ raise MissingClientParameter('Possible RUCIO_AUTH_TYPE values: userpass, x509, x509_proxy, gss, ssh, saml, oidc, vs. ' + environ['RUCIO_AUTH_TYPE'])
240
+ auth_type = environ['RUCIO_AUTH_TYPE']
241
+ else:
242
+ try:
243
+ auth_type = config_get('client', 'auth_type')
244
+ except (NoOptionError, NoSectionError) as error:
245
+ raise MissingClientParameter('Option \'%s\' cannot be found in config file' % error.args[0])
246
+ return auth_type
247
+
248
+ def _get_creds(self, creds: Optional[dict[str, Any]]) -> dict[str, Any]:
249
+ if not creds:
250
+ self.logger.debug('No creds passed. Trying to get it from the config file.')
251
+ creds = {}
283
252
 
284
253
  try:
285
- self.request_retries = config_get_int('client', 'request_retries')
286
- except (NoOptionError, RuntimeError):
287
- LOG.debug('request_retries not specified in config file. Taking default.')
288
- except ValueError:
289
- self.logger.debug('request_retries must be an integer. Taking default.')
254
+ if self.auth_type == 'oidc':
255
+ # if there are default values, check if rucio.cfg does not specify them, otherwise put default
256
+ if 'oidc_refresh_lifetime' not in creds or creds['oidc_refresh_lifetime'] is None:
257
+ creds['oidc_refresh_lifetime'] = config_get('client', 'oidc_refresh_lifetime', False, None)
258
+ if 'oidc_issuer' not in creds or creds['oidc_issuer'] is None:
259
+ creds['oidc_issuer'] = config_get('client', 'oidc_issuer', False, None)
260
+ if 'oidc_audience' not in creds or creds['oidc_audience'] is None:
261
+ creds['oidc_audience'] = config_get('client', 'oidc_audience', False, None)
262
+ if 'oidc_auto' not in creds or creds['oidc_auto'] is False:
263
+ creds['oidc_auto'] = config_get_bool('client', 'oidc_auto', False, False)
264
+ if creds['oidc_auto']:
265
+ if 'oidc_username' not in creds or creds['oidc_username'] is None:
266
+ creds['oidc_username'] = config_get('client', 'oidc_username', False, None)
267
+ if 'oidc_password' not in creds or creds['oidc_password'] is None:
268
+ creds['oidc_password'] = config_get('client', 'oidc_password', False, None)
269
+ if 'oidc_scope' not in creds or creds['oidc_scope'] == 'openid profile':
270
+ creds['oidc_scope'] = config_get('client', 'oidc_scope', False, 'openid profile')
271
+ if 'oidc_polling' not in creds or creds['oidc_polling'] is False:
272
+ creds['oidc_polling'] = config_get_bool('client', 'oidc_polling', False, False)
273
+
274
+ elif self.auth_type in ['userpass', 'saml']:
275
+ if 'username' not in creds or creds['username'] is None:
276
+ creds['username'] = config_get('client', 'username')
277
+ if 'password' not in creds or creds['password'] is None:
278
+ creds['password'] = config_get('client', 'password')
279
+
280
+ elif self.auth_type == 'x509':
281
+ if 'client_cert' not in creds or creds['client_cert'] is None:
282
+ if "RUCIO_CLIENT_CERT" in environ:
283
+ creds['client_cert'] = environ["RUCIO_CLIENT_CERT"]
284
+ else:
285
+ creds['client_cert'] = config_get('client', 'client_cert')
286
+ creds['client_cert'] = path.abspath(path.expanduser(path.expandvars(creds['client_cert'])))
287
+ if not path.exists(creds['client_cert']):
288
+ raise MissingClientParameter('X.509 client certificate not found: %s' % creds['client_cert'])
290
289
 
291
- def _get_exception(self, headers, status_code=None, data=None):
290
+ if 'client_key' not in creds or creds['client_key'] is None:
291
+ if "RUCIO_CLIENT_KEY" in environ:
292
+ creds['client_key'] = environ["RUCIO_CLIENT_KEY"]
293
+ else:
294
+ creds['client_key'] = config_get('client', 'client_key')
295
+ creds['client_key'] = path.abspath(path.expanduser(path.expandvars(creds['client_key'])))
296
+ if not path.exists(creds['client_key']):
297
+ raise MissingClientParameter('X.509 client key not found: %s' % creds['client_key'])
298
+ else:
299
+ perms = oct(os.stat(creds['client_key']).st_mode)[-3:]
300
+ if perms not in ['400', '600']:
301
+ raise CannotAuthenticate('X.509 authentication selected, but private key (%s) permissions are liberal (required: 400 or 600, found: %s)' % (creds['client_key'], perms))
302
+
303
+ elif self.auth_type == 'x509_proxy':
304
+ if 'client_proxy' not in creds or creds['client_proxy'] is None:
305
+ try:
306
+ creds['client_proxy'] = path.abspath(path.expanduser(path.expandvars(config_get('client', 'client_x509_proxy'))))
307
+ except NoOptionError:
308
+ # Recreate the classic GSI logic for locating the proxy:
309
+ # - $X509_USER_PROXY, if it is set.
310
+ # - /tmp/x509up_u`id -u` otherwise.
311
+ # If neither exists (at this point, we don't care if it exists but is invalid), then rethrow
312
+ if 'X509_USER_PROXY' in environ:
313
+ creds['client_proxy'] = environ['X509_USER_PROXY']
314
+ else:
315
+ fname = '/tmp/x509up_u%d' % geteuid()
316
+ if path.exists(fname):
317
+ creds['client_proxy'] = fname
318
+ else:
319
+ raise MissingClientParameter(
320
+ 'Cannot find a valid X509 proxy; not in %s, $X509_USER_PROXY not set, and '
321
+ '\'x509_proxy\' not set in the configuration file.' % fname)
322
+
323
+ elif self.auth_type == 'ssh':
324
+ if 'ssh_private_key' not in creds or creds['ssh_private_key'] is None:
325
+ creds['ssh_private_key'] = path.abspath(path.expanduser(path.expandvars(config_get('client', 'ssh_private_key'))))
326
+
327
+ except (NoOptionError, NoSectionError) as error:
328
+ if error.args[0] != 'client_key':
329
+ raise MissingClientParameter('Option \'%s\' cannot be found in config file' % error.args[0])
330
+
331
+ return creds
332
+
333
+ def _get_exception(self, headers: dict[str, str], status_code: Optional[int] = None, data=None) -> tuple[type[exception.RucioException], str]:
292
334
  """
293
335
  Helper method to parse an error string send by the server and transform it into the corresponding rucio exception.
294
336
 
@@ -298,9 +340,12 @@ class BaseClient(object):
298
340
 
299
341
  :return: A rucio exception class and an error string.
300
342
  """
301
- try:
302
- data = parse_response(data)
303
- except ValueError:
343
+ if data is not None:
344
+ try:
345
+ data = parse_response(data)
346
+ except ValueError:
347
+ data = {}
348
+ else:
304
349
  data = {}
305
350
 
306
351
  exc_cls = 'RucioException'
@@ -319,7 +364,7 @@ class BaseClient(object):
319
364
  else:
320
365
  return exception.RucioException, "%s: %s" % (exc_cls, exc_msg)
321
366
 
322
- def _load_json_data(self, response):
367
+ def _load_json_data(self, response: requests.Response) -> Generator[Any, Any, Any]:
323
368
  """
324
369
  Helper method to correctly load json data based on the content type of the http response.
325
370
 
@@ -335,13 +380,13 @@ class BaseClient(object):
335
380
  if response.text:
336
381
  yield response.text
337
382
 
338
- def _reduce_data(self, data, maxlen=132):
383
+ def _reduce_data(self, data, maxlen: int = 132) -> str:
339
384
  text = data if isinstance(data, str) else data.decode("utf-8")
340
385
  if len(text) > maxlen:
341
386
  text = "%s ... %s" % (text[:maxlen - 15], text[-10:])
342
387
  return text
343
388
 
344
- def _back_off(self, retry_number, reason):
389
+ def _back_off(self, retry_number: int, reason: str) -> None:
345
390
  """
346
391
  Sleep a certain amount of time which increases with the retry count
347
392
  :param retry_number: the retry iteration
@@ -415,7 +460,7 @@ class BaseClient(object):
415
460
  if retry > self.request_retries:
416
461
  raise
417
462
  continue
418
- except IOError as error:
463
+ except OSError as error:
419
464
  # Handle Broken Pipe
420
465
  # While in python3 we can directly catch 'BrokenPipeError', in python2 it doesn't exist.
421
466
  if getattr(error, 'errno') != errno.EPIPE:
@@ -436,7 +481,7 @@ class BaseClient(object):
436
481
  raise ServerConnectionException
437
482
  return result
438
483
 
439
- def __get_token_userpass(self):
484
+ def __get_token_userpass(self) -> bool:
440
485
  """
441
486
  Sends a request to get an auth token from the server and stores it as a class attribute. Uses username/password.
442
487
 
@@ -472,7 +517,7 @@ class BaseClient(object):
472
517
  self.auth_token = result.headers['x-rucio-auth-token']
473
518
  return True
474
519
 
475
- def __refresh_token_OIDC(self):
520
+ def __refresh_token_OIDC(self) -> bool:
476
521
  """
477
522
  Checks if there is active refresh token and if so returns
478
523
  either active token with expiration timestamp or requests a new
@@ -526,7 +571,7 @@ class BaseClient(object):
526
571
  \nRucio Auth Server when attempting token refresh.")
527
572
  return False
528
573
 
529
- def __get_token_OIDC(self):
574
+ def __get_token_OIDC(self) -> bool:
530
575
  """
531
576
  First authenticates the user via a Identity Provider server
532
577
  (with user's username & password), by specifying oidc_scope,
@@ -596,7 +641,7 @@ class BaseClient(object):
596
641
 
597
642
  else:
598
643
  print("\nAccording to the OAuth2/OIDC standard you should NOT be sharing \n"
599
- + "your password with any 3rd party appplication, therefore, \n" # NOQA: W503
644
+ + "your password with any 3rd party application, therefore, \n" # NOQA: W503
600
645
  + "we strongly discourage you from following this --oidc-auto approach.") # NOQA: W503
601
646
  print("-------------------------------------------------------------------------")
602
647
  auth_res = self._send_request(auth_url, get_token=True)
@@ -639,7 +684,7 @@ class BaseClient(object):
639
684
 
640
685
  self.auth_token = result.headers['x-rucio-auth-token']
641
686
  if self.auth_oidc_refresh_active:
642
- self.logger.debug("Reseting the token expiration epoch file content.")
687
+ self.logger.debug("Resetting the token expiration epoch file content.")
643
688
  # reset the token expiration epoch file content
644
689
  # at new CLI OIDC authentication
645
690
  self.token_exp_epoch = None
@@ -650,7 +695,7 @@ class BaseClient(object):
650
695
  self.__refresh_token_OIDC()
651
696
  return True
652
697
 
653
- def __get_token_x509(self):
698
+ def __get_token_x509(self) -> bool:
654
699
  """
655
700
  Sends a request to get an auth token from the server and stores it as a class attribute. Uses x509 authentication.
656
701
 
@@ -667,7 +712,7 @@ class BaseClient(object):
667
712
  url = build_url(self.auth_host, path='auth/x509_proxy')
668
713
  client_cert = self.creds['client_proxy']
669
714
 
670
- if not path.exists(client_cert):
715
+ if (client_cert is not None) and not (path.exists(client_cert)):
671
716
  self.logger.error('given client cert (%s) doesn\'t exist' % client_cert)
672
717
  return False
673
718
  if client_key is not None and not path.exists(client_key):
@@ -695,7 +740,7 @@ class BaseClient(object):
695
740
  self.auth_token = result.headers['x-rucio-auth-token']
696
741
  return True
697
742
 
698
- def __get_token_ssh(self):
743
+ def __get_token_ssh(self) -> bool:
699
744
  """
700
745
  Sends a request to get an auth token from the server and stores it as a class attribute. Uses SSH key exchange authentication.
701
746
 
@@ -751,7 +796,7 @@ class BaseClient(object):
751
796
  self.auth_token = result.headers['x-rucio-auth-token']
752
797
  return True
753
798
 
754
- def __get_token_gss(self):
799
+ def __get_token_gss(self) -> bool:
755
800
  """
756
801
  Sends a request to get an auth token from the server and stores it as a class attribute. Uses Kerberos authentication.
757
802
 
@@ -777,7 +822,7 @@ class BaseClient(object):
777
822
  self.auth_token = result.headers['x-rucio-auth-token']
778
823
  return True
779
824
 
780
- def __get_token_saml(self):
825
+ def __get_token_saml(self) -> bool:
781
826
  """
782
827
  Sends a request to get an auth token from the server and stores it as a class attribute. Uses saml authentication.
783
828
 
@@ -807,7 +852,7 @@ class BaseClient(object):
807
852
  self.auth_token = result.headers['X-Rucio-Auth-Token']
808
853
  return True
809
854
 
810
- def __get_token(self):
855
+ def __get_token(self) -> None:
811
856
  """
812
857
  Calls the corresponding method to receive an auth token depending on the auth type. To be used if a 401 - Unauthorized error is received.
813
858
  """
@@ -849,7 +894,7 @@ class BaseClient(object):
849
894
  if self.auth_token is None:
850
895
  raise CannotAuthenticate('cannot get an auth token from server')
851
896
 
852
- def __read_token(self):
897
+ def __read_token(self) -> bool:
853
898
  """
854
899
  Checks if a local token file exists and reads the token from it.
855
900
 
@@ -859,10 +904,10 @@ class BaseClient(object):
859
904
  return False
860
905
 
861
906
  try:
862
- token_file_handler = open(self.token_file, 'r')
863
- self.auth_token = token_file_handler.readline()
907
+ with open(self.token_file, 'r') as token_file_handler:
908
+ self.auth_token = token_file_handler.readline()
864
909
  self.headers['X-Rucio-Auth-Token'] = self.auth_token
865
- except IOError as error:
910
+ except OSError as error:
866
911
  print("I/O error({0}): {1}".format(error.errno, error.strerror))
867
912
  except Exception:
868
913
  raise
@@ -871,7 +916,7 @@ class BaseClient(object):
871
916
  self.logger.debug('got token from file')
872
917
  return True
873
918
 
874
- def __write_token(self):
919
+ def __write_token(self) -> None:
875
920
  """
876
921
  Write the current auth_token to the local token file.
877
922
  """
@@ -893,12 +938,12 @@ class BaseClient(object):
893
938
  with fdopen(file_d, "w") as f_exp_epoch:
894
939
  f_exp_epoch.write(str(self.token_exp_epoch))
895
940
  move(file_n, self.token_exp_epoch_file)
896
- except IOError as error:
941
+ except OSError as error:
897
942
  print("I/O error({0}): {1}".format(error.errno, error.strerror))
898
943
  except Exception:
899
944
  raise
900
945
 
901
- def __authenticate(self):
946
+ def __authenticate(self) -> None:
902
947
  """
903
948
  Main method for authentication. It first tries to read a locally saved token. If not available it requests a new one.
904
949
  """
rucio/client/client.py CHANGED
@@ -1,4 +1,3 @@
1
- # -*- coding: utf-8 -*-
2
1
  # Copyright European Organization for Nuclear Research (CERN) since 2012
3
2
  #
4
3
  # Licensed under the Apache License, Version 2.0 (the "License");
@@ -27,7 +26,7 @@ from rucio.client.exportclient import ExportClient
27
26
  from rucio.client.importclient import ImportClient
28
27
  from rucio.client.lifetimeclient import LifetimeClient
29
28
  from rucio.client.lockclient import LockClient
30
- from rucio.client.metaclient import MetaClient
29
+ from rucio.client.metaconventionsclient import MetaConventionClient
31
30
  from rucio.client.pingclient import PingClient
32
31
  from rucio.client.replicaclient import ReplicaClient
33
32
  from rucio.client.requestclient import RequestClient
@@ -40,7 +39,7 @@ from rucio.client.touchclient import TouchClient
40
39
 
41
40
  class Client(AccountClient,
42
41
  AccountLimitClient,
43
- MetaClient,
42
+ MetaConventionClient,
44
43
  PingClient,
45
44
  ReplicaClient,
46
45
  RequestClient,