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.
- rucio/__init__.py +0 -1
- rucio/alembicrevision.py +1 -2
- rucio/client/__init__.py +0 -1
- rucio/client/accountclient.py +45 -25
- rucio/client/accountlimitclient.py +37 -9
- rucio/client/baseclient.py +199 -154
- rucio/client/client.py +2 -3
- rucio/client/configclient.py +19 -6
- rucio/client/credentialclient.py +9 -4
- rucio/client/didclient.py +238 -63
- rucio/client/diracclient.py +13 -5
- rucio/client/downloadclient.py +162 -51
- rucio/client/exportclient.py +4 -4
- rucio/client/fileclient.py +3 -4
- rucio/client/importclient.py +4 -4
- rucio/client/lifetimeclient.py +21 -5
- rucio/client/lockclient.py +18 -8
- rucio/client/{metaclient.py → metaconventionsclient.py} +18 -15
- rucio/client/pingclient.py +0 -1
- rucio/client/replicaclient.py +15 -5
- rucio/client/requestclient.py +35 -19
- rucio/client/rseclient.py +133 -51
- rucio/client/ruleclient.py +29 -22
- rucio/client/scopeclient.py +8 -6
- rucio/client/subscriptionclient.py +47 -35
- rucio/client/touchclient.py +8 -4
- rucio/client/uploadclient.py +166 -82
- rucio/common/__init__.py +0 -1
- rucio/common/cache.py +4 -4
- rucio/common/config.py +52 -47
- rucio/common/constants.py +69 -2
- rucio/common/constraints.py +0 -1
- rucio/common/didtype.py +24 -22
- rucio/common/exception.py +281 -222
- rucio/common/extra.py +0 -1
- rucio/common/logging.py +54 -38
- rucio/common/pcache.py +122 -101
- rucio/common/plugins.py +153 -0
- rucio/common/policy.py +4 -4
- rucio/common/schema/__init__.py +17 -10
- rucio/common/schema/atlas.py +7 -5
- rucio/common/schema/belleii.py +7 -5
- rucio/common/schema/domatpc.py +7 -5
- rucio/common/schema/escape.py +7 -5
- rucio/common/schema/generic.py +8 -6
- rucio/common/schema/generic_multi_vo.py +7 -5
- rucio/common/schema/icecube.py +7 -5
- rucio/common/stomp_utils.py +0 -1
- rucio/common/stopwatch.py +0 -1
- rucio/common/test_rucio_server.py +2 -2
- rucio/common/types.py +262 -17
- rucio/common/utils.py +743 -451
- rucio/rse/__init__.py +3 -4
- rucio/rse/protocols/__init__.py +0 -1
- rucio/rse/protocols/bittorrent.py +184 -0
- rucio/rse/protocols/cache.py +1 -2
- rucio/rse/protocols/dummy.py +1 -2
- rucio/rse/protocols/gfal.py +12 -10
- rucio/rse/protocols/globus.py +7 -7
- rucio/rse/protocols/gsiftp.py +2 -3
- rucio/rse/protocols/http_cache.py +1 -2
- rucio/rse/protocols/mock.py +1 -2
- rucio/rse/protocols/ngarc.py +1 -2
- rucio/rse/protocols/posix.py +12 -13
- rucio/rse/protocols/protocol.py +116 -52
- rucio/rse/protocols/rclone.py +6 -7
- rucio/rse/protocols/rfio.py +4 -5
- rucio/rse/protocols/srm.py +9 -10
- rucio/rse/protocols/ssh.py +8 -9
- rucio/rse/protocols/storm.py +2 -3
- rucio/rse/protocols/webdav.py +17 -14
- rucio/rse/protocols/xrootd.py +23 -17
- rucio/rse/rsemanager.py +19 -7
- rucio/vcsversion.py +4 -4
- rucio/version.py +5 -13
- rucio_clients-35.8.0.data/data/requirements.client.txt +15 -0
- {rucio_clients-32.8.6.data → rucio_clients-35.8.0.data}/data/rucio_client/merge_rucio_configs.py +2 -5
- {rucio_clients-32.8.6.data → rucio_clients-35.8.0.data}/scripts/rucio +87 -85
- {rucio_clients-32.8.6.data → rucio_clients-35.8.0.data}/scripts/rucio-admin +45 -32
- {rucio_clients-32.8.6.dist-info → rucio_clients-35.8.0.dist-info}/METADATA +13 -13
- rucio_clients-35.8.0.dist-info/RECORD +88 -0
- {rucio_clients-32.8.6.dist-info → rucio_clients-35.8.0.dist-info}/WHEEL +1 -1
- {rucio_clients-32.8.6.dist-info → rucio_clients-35.8.0.dist-info}/licenses/AUTHORS.rst +3 -0
- rucio/common/schema/cms.py +0 -478
- rucio/common/schema/lsst.py +0 -423
- rucio_clients-32.8.6.data/data/requirements.txt +0 -55
- rucio_clients-32.8.6.dist-info/RECORD +0 -88
- {rucio_clients-32.8.6.data → rucio_clients-35.8.0.data}/data/etc/rse-accounts.cfg.template +0 -0
- {rucio_clients-32.8.6.data → rucio_clients-35.8.0.data}/data/etc/rucio.cfg.atlas.client.template +0 -0
- {rucio_clients-32.8.6.data → rucio_clients-35.8.0.data}/data/etc/rucio.cfg.template +0 -0
- {rucio_clients-32.8.6.dist-info → rucio_clients-35.8.0.dist-info}/licenses/LICENSE +0 -0
- {rucio_clients-32.8.6.dist-info → rucio_clients-35.8.0.dist-info}/top_level.txt +0 -0
rucio/client/baseclient.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");
|
|
@@ -20,28 +19,30 @@
|
|
|
20
19
|
import errno
|
|
21
20
|
import getpass
|
|
22
21
|
import os
|
|
23
|
-
import
|
|
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
|
|
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
|
|
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
|
|
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,
|
|
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
|
|
71
|
+
return secrets.choice(hosts)
|
|
71
72
|
|
|
72
73
|
|
|
73
|
-
class BaseClient
|
|
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,
|
|
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.
|
|
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
|
|
118
|
+
if rucio_host is not None:
|
|
119
|
+
self.host = rucio_host
|
|
120
|
+
else:
|
|
111
121
|
self.host = config_get('client', 'rucio_host')
|
|
112
|
-
|
|
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.
|
|
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
|
-
|
|
140
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
272
|
-
|
|
273
|
-
|
|
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
|
-
|
|
225
|
+
token_path = self.TOKEN_PATH_PREFIX + getpass.getuser()
|
|
276
226
|
if self.vo != 'def':
|
|
277
|
-
|
|
278
|
-
self.token_file = self.token_path + '/' + self.TOKEN_PREFIX + token_filename_suffix
|
|
227
|
+
token_path += '@%s' % self.vo
|
|
279
228
|
|
|
280
|
-
|
|
229
|
+
token_file = token_path + '/' + self.TOKEN_PREFIX + token_filename_suffix
|
|
281
230
|
|
|
282
|
-
self.
|
|
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.
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
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
|
-
|
|
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
|
-
|
|
302
|
-
|
|
303
|
-
|
|
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
|
|
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
|
|
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("
|
|
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
|
-
|
|
863
|
-
|
|
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
|
|
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
|
|
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.
|
|
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
|
-
|
|
42
|
+
MetaConventionClient,
|
|
44
43
|
PingClient,
|
|
45
44
|
ReplicaClient,
|
|
46
45
|
RequestClient,
|