volcengine-python-sdk 5.0.32__py2.py3-none-any.whl → 5.0.33__py2.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.
- {volcengine_python_sdk-5.0.32.dist-info → volcengine_python_sdk-5.0.33.dist-info}/METADATA +1 -1
- {volcengine_python_sdk-5.0.32.dist-info → volcengine_python_sdk-5.0.33.dist-info}/RECORD +11 -11
- volcenginesdkcore/api_client.py +1 -1
- volcenginesdkcore/auth/providers/cli_config_provider.py +558 -86
- volcenginesdkcore/configuration.py +1 -1
- volcenginesdkgtm/models/statistic_for_update_load_input.py +9 -18
- volcenginesdklivesaas/models/list_activity_media_api_request.py +27 -1
- {volcengine_python_sdk-5.0.32.dist-info → volcengine_python_sdk-5.0.33.dist-info}/WHEEL +0 -0
- {volcengine_python_sdk-5.0.32.dist-info → volcengine_python_sdk-5.0.33.dist-info}/licenses/LICENSE.txt +0 -0
- {volcengine_python_sdk-5.0.32.dist-info → volcengine_python_sdk-5.0.33.dist-info}/licenses/NOTICE.md +0 -0
- {volcengine_python_sdk-5.0.32.dist-info → volcengine_python_sdk-5.0.33.dist-info}/top_level.txt +0 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
volcengine_python_sdk-5.0.
|
|
2
|
-
volcengine_python_sdk-5.0.
|
|
1
|
+
volcengine_python_sdk-5.0.33.dist-info/licenses/LICENSE.txt,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
|
|
2
|
+
volcengine_python_sdk-5.0.33.dist-info/licenses/NOTICE.md,sha256=dqWX0O4-gFqGLdHJsXAiF6Q8JHlu_3nFaQSmrMHzujM,254
|
|
3
3
|
volcenginesdkacep/__init__.py,sha256=GZCeL2Dsp5v1XblcgInhNJzKW7X37XxrpMIE2Q3VL-c,31200
|
|
4
4
|
volcenginesdkacep/api/__init__.py,sha256=MCzGEBg4XwY5aDIDy8nitUz4vu_2xmRRY83V6lqt460,138
|
|
5
5
|
volcenginesdkacep/api/acep_api.py,sha256=ollQvh5qkrmNbENVBBh2JRha9aGDMUwtO7V6Q1A3-Js,410621
|
|
@@ -5381,8 +5381,8 @@ volcenginesdkconfig/models/update_rule_response.py,sha256=f16EgPddqWulPRT1Yw225x
|
|
|
5381
5381
|
volcenginesdkconfig/models/update_rule_template_request.py,sha256=k4JyMyOEhL8Zvg9N6siac_6MlcB38ZoDeKfx5uVV4xo,10129
|
|
5382
5382
|
volcenginesdkconfig/models/update_rule_template_response.py,sha256=eiATOg0bsvElLzs-K0Jt8x8k4JaLxlXUcsg3toMteCM,2814
|
|
5383
5383
|
volcenginesdkcore/__init__.py,sha256=WkgQAEhL6zg4ofQI82LD-3K-ij28yT-lpMiDoKmppwM,318
|
|
5384
|
-
volcenginesdkcore/api_client.py,sha256=
|
|
5385
|
-
volcenginesdkcore/configuration.py,sha256=
|
|
5384
|
+
volcenginesdkcore/api_client.py,sha256=xk2TgRm7PGtgDkAOvUwUA_z5E2Akb2Iy4IfRgViMKzc,29339
|
|
5385
|
+
volcenginesdkcore/configuration.py,sha256=uBLeXYDr1tkLYWERwK0oKph7z5pdJHH_yGipo-bYSWI,12082
|
|
5386
5386
|
volcenginesdkcore/flatten.py,sha256=g3r61JS_AO7WV6ClRDkXgtnVXcw3c7tbZjeLAJxkSLc,3811
|
|
5387
5387
|
volcenginesdkcore/metadata.py,sha256=lHsOz9JcwdM7oqBb5dhSl6z3qF-M3TnTFbCzxRV89f4,4529
|
|
5388
5388
|
volcenginesdkcore/model.py,sha256=f3cvQ6QiQfecClucu-_-l5xVI3W5_vF-EKkjwZu7Vjc,177
|
|
@@ -5392,7 +5392,7 @@ volcenginesdkcore/universal.py,sha256=j1p9XAktEqIvVsWXm_sCI29jxf30YjqFEWBZAlkUEd
|
|
|
5392
5392
|
volcenginesdkcore/auth/__init__.py,sha256=3g1cdl8E5ZJrjF_kijTELAIWTT74fNEYOPieE88DBBg,132
|
|
5393
5393
|
volcenginesdkcore/auth/credential.py,sha256=adOo43AZi4BNL9sSYGe5v9YMn8vm2rL0vBU1iAe0jes,377
|
|
5394
5394
|
volcenginesdkcore/auth/providers/__init__.py,sha256=Os4sb88zqpdSncVAiKTs8KPGarCViJeXpdUZ1ItnZSM,454
|
|
5395
|
-
volcenginesdkcore/auth/providers/cli_config_provider.py,sha256=
|
|
5395
|
+
volcenginesdkcore/auth/providers/cli_config_provider.py,sha256=tmpyc4YpyJ5CRSCrR9IAsisMxsL6Xb4FWEfEIgsZuDo,44251
|
|
5396
5396
|
volcenginesdkcore/auth/providers/default_provider.py,sha256=R-OxpYSlIfk_YcaAW23EcsoMCuMRAFJQ035baE_3NfQ,4234
|
|
5397
5397
|
volcenginesdkcore/auth/providers/ecs_role_provider.py,sha256=lWllVPA01NpPaaqXdP9vCMwbxBgEQW1URfxMl9uOTyc,7529
|
|
5398
5398
|
volcenginesdkcore/auth/providers/env_provider.py,sha256=LUrPFemUs0i8X-hLqXDEuz5CHgOsQG6XbxaROC0tpxA,1255
|
|
@@ -9395,7 +9395,7 @@ volcenginesdkgtm/models/rule_for_update_rule_input.py,sha256=Ldb6pTVTfOLVbTG98bd
|
|
|
9395
9395
|
volcenginesdkgtm/models/source_flow_for_get_policy_output.py,sha256=tyXwIrVjy8Kwjqa16Y_UMFjtxIMJL7OSb2VKtWlfA1I,2821
|
|
9396
9396
|
volcenginesdkgtm/models/start_routing_request.py,sha256=KDNWMAiV23QsfPbuZA3om2SmQE2j5tDCg5Q1tNWdrag,3552
|
|
9397
9397
|
volcenginesdkgtm/models/start_routing_response.py,sha256=atUcMwhhXJzt0kYQpH35XNEw8zYWm2vXqK0TXeARRxM,2781
|
|
9398
|
-
volcenginesdkgtm/models/statistic_for_update_load_input.py,sha256=
|
|
9398
|
+
volcenginesdkgtm/models/statistic_for_update_load_input.py,sha256=TaSErSmDxX28BRhBHPlKZtPtHlch93VT_Fk0wKeGiVg,6615
|
|
9399
9399
|
volcenginesdkgtm/models/statistics_for_get_policy_output.py,sha256=hi9vXAgJiYcqq0a91sX0s8_bmd9rP_dfLr14eGKLiFI,4421
|
|
9400
9400
|
volcenginesdkgtm/models/stop_routing_request.py,sha256=oz24AtXhG8gn-NpB667E-x8a6I_iK73E07T_zTUoHsM,3543
|
|
9401
9401
|
volcenginesdkgtm/models/stop_routing_response.py,sha256=9BuGoyBMymk2e1xT5OQYlT-2KJq9Ae7ipboxei7K65U,2776
|
|
@@ -11575,7 +11575,7 @@ volcenginesdklivesaas/models/list_activity_api_request.py,sha256=QuB4k1j8_TaMQFH
|
|
|
11575
11575
|
volcenginesdklivesaas/models/list_activity_api_response.py,sha256=AYSOPD3SXOmdpIYYO1xVDJiPN8iFbgjODNIqa6pWn4w,6025
|
|
11576
11576
|
volcenginesdklivesaas/models/list_activity_by_cache_api_request.py,sha256=KiHpTJRpsXNIfbbcWvQspBYcouC84jsAumRW7MYby4U,14895
|
|
11577
11577
|
volcenginesdklivesaas/models/list_activity_by_cache_api_response.py,sha256=MtnemSo41lO9XscXKzZ-NIITIVc5sdcCGRLZ3u8Ttq8,6193
|
|
11578
|
-
volcenginesdklivesaas/models/list_activity_media_api_request.py,sha256=
|
|
11578
|
+
volcenginesdklivesaas/models/list_activity_media_api_request.py,sha256=NQUYJccpSJTWoYkYbyYohnyipRdRPO8DKMps0QrR04c,10444
|
|
11579
11579
|
volcenginesdklivesaas/models/list_activity_media_api_response.py,sha256=ThfoI96iUIrR8Al7dHIGxFCl7B--clFjejWVEjbFEM0,4444
|
|
11580
11580
|
volcenginesdklivesaas/models/list_an_activity_start_and_end_time_api_request.py,sha256=KPp00NCSYE6oZeAodgVfEv1XRmBzXZtMSPZw8h7Alt4,3842
|
|
11581
11581
|
volcenginesdklivesaas/models/list_an_activity_start_and_end_time_api_response.py,sha256=NxY8CB9rz6YiuPsi9WTMkthgaij7tyBaOOmbD3L-OZU,4029
|
|
@@ -22510,7 +22510,7 @@ volcenginesdkwafruntime/api/__init__.py,sha256=di_9IIKQ0HCuzkGrTyZ6h38iP7y1xelH8
|
|
|
22510
22510
|
volcenginesdkwafruntime/api/waf_runtime_api.py,sha256=eWgdzFcwflEkhkNrPzZL1mqSid7O1OWiwHGUyWJvZNA,4035
|
|
22511
22511
|
volcenginesdkwafruntime/models/__init__.py,sha256=di_9IIKQ0HCuzkGrTyZ6h38iP7y1xelH8_IJbKi4aWA,71
|
|
22512
22512
|
volcenginesdkwafruntime/models/llm_stream_session.py,sha256=U9cig3fuZRBT4Ov8_cUsWDKXcMLNjBDxKt-7IoewoUw,1589
|
|
22513
|
-
volcengine_python_sdk-5.0.
|
|
22514
|
-
volcengine_python_sdk-5.0.
|
|
22515
|
-
volcengine_python_sdk-5.0.
|
|
22516
|
-
volcengine_python_sdk-5.0.
|
|
22513
|
+
volcengine_python_sdk-5.0.33.dist-info/METADATA,sha256=_J8YoRxCLPMoJbWfAgQPHGM7yBAaDQ-ZA8eVNekkx7U,8435
|
|
22514
|
+
volcengine_python_sdk-5.0.33.dist-info/WHEEL,sha256=Mk1ST5gDzEO5il5kYREiBnzzM469m5sI8ESPl7TRhJY,110
|
|
22515
|
+
volcengine_python_sdk-5.0.33.dist-info/top_level.txt,sha256=EWPYkwb-yVEO1fKOGHmIp8xZ5FoKVJmQ8njYtOV7XwA,2820
|
|
22516
|
+
volcengine_python_sdk-5.0.33.dist-info/RECORD,,
|
volcenginesdkcore/api_client.py
CHANGED
|
@@ -69,7 +69,7 @@ class ApiClient(object):
|
|
|
69
69
|
self.default_headers[header_name] = header_value
|
|
70
70
|
self.cookie = cookie
|
|
71
71
|
# Set default User-Agent.
|
|
72
|
-
self.user_agent = 'volcstack-python-sdk/5.0.
|
|
72
|
+
self.user_agent = 'volcstack-python-sdk/5.0.33'
|
|
73
73
|
self.client_side_validation = configuration.client_side_validation
|
|
74
74
|
|
|
75
75
|
self.interceptor_chain = InterceptorChain()
|
|
@@ -3,11 +3,11 @@ import calendar
|
|
|
3
3
|
import hashlib
|
|
4
4
|
import json
|
|
5
5
|
import os
|
|
6
|
-
import tempfile
|
|
7
6
|
import threading
|
|
8
7
|
import time
|
|
9
8
|
|
|
10
|
-
|
|
9
|
+
import dateutil.parser
|
|
10
|
+
import six
|
|
11
11
|
|
|
12
12
|
from .provider import Provider, CredentialValue
|
|
13
13
|
|
|
@@ -18,6 +18,9 @@ _PORTAL_ACCESS_TOKEN_HEADER = "x-bd-cloudidentity-bearer-token"
|
|
|
18
18
|
_HTTP_TIMEOUT = 30
|
|
19
19
|
_HTTP_MAX_RETRIES = 3
|
|
20
20
|
_HTTP_RETRY_INTERVAL = 1
|
|
21
|
+
_LOGIN_CACHE_DIRECTORY_ENV = "VOLCENGINE_LOGIN_CACHE_DIRECTORY"
|
|
22
|
+
_DEFAULT_CONSOLE_LOGIN_ENDPOINT = "https://signin.volcengine.com"
|
|
23
|
+
_CONSOLE_LOGIN_TOKEN_PATH = "/authorize/oauth/token"
|
|
21
24
|
|
|
22
25
|
|
|
23
26
|
class CLIConfigCredentialProvider(Provider):
|
|
@@ -34,7 +37,10 @@ class CLIConfigCredentialProvider(Provider):
|
|
|
34
37
|
- "RamRoleArn": delegate to StsCredentialProvider
|
|
35
38
|
- "OIDC": delegate to StsOidcCredentialProvider
|
|
36
39
|
- "EcsRole": delegate to EcsRoleCredentialProvider
|
|
37
|
-
- "sso": delegate to SsoCredentialProvider
|
|
40
|
+
- "sso": delegate to SsoCredentialProvider (SDK manages OAuth refresh in-memory,
|
|
41
|
+
never writes the cache file; ve sso login is the sole writer)
|
|
42
|
+
- "console-login": read CLI console-login cache; the SDK manages OAuth refresh
|
|
43
|
+
in-memory (never writes the cache file). ve login is the sole writer.
|
|
38
44
|
- Other modes: raise RuntimeError (unsupported)
|
|
39
45
|
"""
|
|
40
46
|
|
|
@@ -50,6 +56,9 @@ class CLIConfigCredentialProvider(Provider):
|
|
|
50
56
|
self._resolved_config_path = None
|
|
51
57
|
|
|
52
58
|
def _init_delegate(self):
|
|
59
|
+
self._delegate = None
|
|
60
|
+
self._static_cred = None
|
|
61
|
+
|
|
53
62
|
profile, profile_name, config = self._load_profile()
|
|
54
63
|
raw_mode = profile.get("mode", "") or ""
|
|
55
64
|
mode = raw_mode.lower().strip()
|
|
@@ -68,6 +77,9 @@ class CLIConfigCredentialProvider(Provider):
|
|
|
68
77
|
elif mode == "sso":
|
|
69
78
|
self._static_cred = None
|
|
70
79
|
self._delegate = self._create_sso_delegate(profile, profile_name, config)
|
|
80
|
+
elif mode == "console-login":
|
|
81
|
+
self._static_cred = None
|
|
82
|
+
self._delegate = self._create_console_login_delegate(profile, profile_name)
|
|
71
83
|
else:
|
|
72
84
|
raise RuntimeError(
|
|
73
85
|
"{}: unsupported mode: {}".format(self.PROVIDER_NAME, mode)
|
|
@@ -294,6 +306,29 @@ class CLIConfigCredentialProvider(Provider):
|
|
|
294
306
|
cache_dir=cache_dir,
|
|
295
307
|
)
|
|
296
308
|
|
|
309
|
+
def _create_console_login_delegate(self, profile, profile_name):
|
|
310
|
+
login_session = (profile.get("login-session") or "").strip()
|
|
311
|
+
if not login_session:
|
|
312
|
+
raise RuntimeError(
|
|
313
|
+
"{}: profile '{}' mode is console-login but login-session is not set; run 've login' first.".format(
|
|
314
|
+
self.PROVIDER_NAME, profile_name
|
|
315
|
+
)
|
|
316
|
+
)
|
|
317
|
+
|
|
318
|
+
config_path = self._resolved_config_path or (
|
|
319
|
+
self._config_path
|
|
320
|
+
or os.environ.get("VOLCENGINE_CLI_CONFIG_FILE")
|
|
321
|
+
or os.path.expanduser("~/.volcengine/config.json")
|
|
322
|
+
)
|
|
323
|
+
cache_dir = (
|
|
324
|
+
os.environ.get(_LOGIN_CACHE_DIRECTORY_ENV)
|
|
325
|
+
or os.path.join(os.path.dirname(config_path), "login", "cache")
|
|
326
|
+
)
|
|
327
|
+
return ConsoleLoginCredentialProvider(
|
|
328
|
+
login_session=login_session,
|
|
329
|
+
cache_dir=cache_dir,
|
|
330
|
+
)
|
|
331
|
+
|
|
297
332
|
|
|
298
333
|
def _unix_timestamp_to_epoch(ts):
|
|
299
334
|
"""Convert a Unix timestamp (seconds, milliseconds, microseconds, or
|
|
@@ -316,26 +351,24 @@ def _token_cache_filename(start_url, session_name):
|
|
|
316
351
|
return "{}.json".format(digest)
|
|
317
352
|
|
|
318
353
|
|
|
354
|
+
def _login_cache_filename(login_session):
|
|
355
|
+
return "{}.json".format(hashlib.sha1(login_session.encode("utf-8")).hexdigest())
|
|
356
|
+
|
|
357
|
+
|
|
319
358
|
def _parse_rfc3339(value):
|
|
320
|
-
"""Parse an RFC 3339 timestamp string into a datetime
|
|
359
|
+
"""Parse an RFC 3339 timestamp string into a datetime.
|
|
360
|
+
|
|
361
|
+
Uses python-dateutil (already a project dependency) for Py2/Py3
|
|
362
|
+
compatibility; handles trailing 'Z' and explicit timezone offsets
|
|
363
|
+
(e.g. '+08:00') uniformly.
|
|
364
|
+
"""
|
|
321
365
|
value = value.strip()
|
|
322
366
|
if not value:
|
|
323
367
|
raise ValueError("expires_at is empty")
|
|
324
|
-
# Python 3.7+ datetime.fromisoformat doesn't handle trailing Z
|
|
325
|
-
if value.endswith("Z"):
|
|
326
|
-
value = value[:-1] + "+00:00"
|
|
327
368
|
try:
|
|
328
|
-
return
|
|
329
|
-
except (ValueError,
|
|
330
|
-
|
|
331
|
-
# Fallback for Python < 3.7 or unusual formats
|
|
332
|
-
for fmt in ("%Y-%m-%dT%H:%M:%S%z", "%Y-%m-%dT%H:%M:%S.%f%z",
|
|
333
|
-
"%Y-%m-%dT%H:%M:%S", "%Y-%m-%dT%H:%M:%S.%f"):
|
|
334
|
-
try:
|
|
335
|
-
return datetime.strptime(value, fmt)
|
|
336
|
-
except ValueError:
|
|
337
|
-
continue
|
|
338
|
-
raise ValueError("cannot parse expires_at: {}".format(value))
|
|
369
|
+
return dateutil.parser.parse(value)
|
|
370
|
+
except (ValueError, OverflowError, TypeError) as e:
|
|
371
|
+
raise ValueError("cannot parse expires_at: {}: {}".format(value, e))
|
|
339
372
|
|
|
340
373
|
|
|
341
374
|
def _rfc3339_to_epoch(value):
|
|
@@ -346,21 +379,396 @@ def _rfc3339_to_epoch(value):
|
|
|
346
379
|
return calendar.timegm(exp_dt.timetuple())
|
|
347
380
|
|
|
348
381
|
|
|
349
|
-
def
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
382
|
+
def _console_login_cache_expiration(token_cache, cache_path, provider_name):
|
|
383
|
+
_raw_ia = token_cache.get("issued_at")
|
|
384
|
+
issued_at = _raw_ia.strip() if isinstance(_raw_ia, six.string_types) else ""
|
|
385
|
+
if not issued_at:
|
|
386
|
+
raise RuntimeError(
|
|
387
|
+
"{}: console-login token cache '{}' does not contain issued_at.".format(
|
|
388
|
+
provider_name, cache_path
|
|
389
|
+
)
|
|
390
|
+
)
|
|
391
|
+
expires_in = token_cache.get("expires_in", 0)
|
|
392
|
+
try:
|
|
393
|
+
expires_in = int(expires_in)
|
|
394
|
+
except (TypeError, ValueError):
|
|
395
|
+
expires_in = 0
|
|
396
|
+
if expires_in <= 0:
|
|
397
|
+
raise RuntimeError(
|
|
398
|
+
"{}: console-login token cache '{}' does not contain valid expires_in.".format(
|
|
399
|
+
provider_name, cache_path
|
|
400
|
+
)
|
|
401
|
+
)
|
|
353
402
|
try:
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
403
|
+
issued_at_epoch = _rfc3339_to_epoch(issued_at)
|
|
404
|
+
except ValueError as e:
|
|
405
|
+
raise RuntimeError(
|
|
406
|
+
"{}: failed to parse console-login issued_at in '{}': {}".format(
|
|
407
|
+
provider_name, cache_path, e
|
|
408
|
+
)
|
|
409
|
+
)
|
|
410
|
+
return issued_at_epoch + expires_in
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
def _parse_console_login_access_token(access_token, cache_path, provider_name):
|
|
414
|
+
if isinstance(access_token, dict):
|
|
415
|
+
sts_creds = access_token
|
|
416
|
+
elif isinstance(access_token, six.string_types):
|
|
417
|
+
try:
|
|
418
|
+
sts_creds = json.loads(access_token)
|
|
419
|
+
except ValueError as e:
|
|
420
|
+
raise RuntimeError(
|
|
421
|
+
"{}: failed to parse console-login access_token in '{}': {}".format(
|
|
422
|
+
provider_name, cache_path, e
|
|
423
|
+
)
|
|
424
|
+
)
|
|
425
|
+
else:
|
|
426
|
+
raise RuntimeError(
|
|
427
|
+
"{}: console-login token cache '{}' does not contain valid access_token.".format(
|
|
428
|
+
provider_name, cache_path
|
|
429
|
+
)
|
|
430
|
+
)
|
|
431
|
+
|
|
432
|
+
if not isinstance(sts_creds, dict):
|
|
433
|
+
raise RuntimeError(
|
|
434
|
+
"{}: console-login access_token in '{}' is not an object.".format(
|
|
435
|
+
provider_name, cache_path
|
|
436
|
+
)
|
|
437
|
+
)
|
|
438
|
+
|
|
439
|
+
_raw_ak = sts_creds.get("access_key_id")
|
|
440
|
+
ak = _raw_ak.strip() if isinstance(_raw_ak, six.string_types) else ""
|
|
441
|
+
_raw_sk = sts_creds.get("secret_access_key")
|
|
442
|
+
sk = _raw_sk.strip() if isinstance(_raw_sk, six.string_types) else ""
|
|
443
|
+
_raw_tok = sts_creds.get("session_token")
|
|
444
|
+
token = _raw_tok.strip() if isinstance(_raw_tok, six.string_types) else ""
|
|
445
|
+
if not ak or not sk or not token:
|
|
446
|
+
raise RuntimeError(
|
|
447
|
+
"{}: console-login access_token in '{}' is missing STS credential fields.".format(
|
|
448
|
+
provider_name, cache_path
|
|
449
|
+
)
|
|
450
|
+
)
|
|
451
|
+
|
|
452
|
+
return {
|
|
453
|
+
"access_key_id": ak,
|
|
454
|
+
"secret_access_key": sk,
|
|
455
|
+
"session_token": token,
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
|
|
459
|
+
|
|
460
|
+
class ConsoleLoginCredentialProvider(Provider):
|
|
461
|
+
"""Reads and refreshes STS credentials for the CLI 've login' flow.
|
|
462
|
+
|
|
463
|
+
Contract:
|
|
464
|
+
- Disk reads happen on bootstrap and on the invalid_grant fallback only;
|
|
465
|
+
the provider never writes the cache file. ve cli remains the sole
|
|
466
|
+
writer.
|
|
467
|
+
- When the in-memory access_token expires, the provider exchanges the
|
|
468
|
+
cached refresh_token at signin.volcengine.com/authorize/oauth/token and
|
|
469
|
+
updates the in-memory cache only.
|
|
470
|
+
- If the signin service rejects the refresh_token with invalid_grant,
|
|
471
|
+
the provider re-reads the disk cache once. If the disk holds a newer
|
|
472
|
+
refresh_token (i.e. ve cli refreshed it under us), it retries; if the
|
|
473
|
+
disk RT is the same, the user must run `ve login` again.
|
|
474
|
+
"""
|
|
475
|
+
|
|
476
|
+
PROVIDER_NAME = "ConsoleLoginCredentialProvider"
|
|
477
|
+
|
|
478
|
+
def __init__(self, login_session, cache_dir):
|
|
479
|
+
self._login_session = login_session
|
|
480
|
+
self._cache_dir = cache_dir
|
|
481
|
+
|
|
482
|
+
self._credentials = None
|
|
483
|
+
self._expiration = None # epoch seconds
|
|
484
|
+
# _cache holds the most recently observed login cache dict, including
|
|
485
|
+
# refresh_token / client_id / scope / endpoint_url, so that the SDK can
|
|
486
|
+
# silently refresh access_token without re-reading the file on every
|
|
487
|
+
# call.
|
|
488
|
+
self._cache = None
|
|
489
|
+
self._lock = threading.Lock()
|
|
490
|
+
|
|
491
|
+
def retrieve(self):
|
|
492
|
+
return self.get_credentials()
|
|
493
|
+
|
|
494
|
+
def is_expired(self):
|
|
495
|
+
if self._credentials is None:
|
|
496
|
+
return True
|
|
497
|
+
if self._expiration is not None:
|
|
498
|
+
return time.time() >= self._expiration - 60
|
|
499
|
+
return False
|
|
500
|
+
|
|
501
|
+
def refresh(self):
|
|
502
|
+
with self._lock:
|
|
503
|
+
if self.is_expired():
|
|
504
|
+
self._do_refresh()
|
|
505
|
+
|
|
506
|
+
def get_credentials(self):
|
|
507
|
+
self.refresh()
|
|
508
|
+
return self._credentials
|
|
509
|
+
|
|
510
|
+
def _do_refresh(self):
|
|
511
|
+
# Bootstrap from disk only when the in-memory cache is empty; on every
|
|
512
|
+
# subsequent expiry we try the in-memory refresh_token first and only
|
|
513
|
+
# re-read the disk file as a fallback when the server rejects the RT.
|
|
514
|
+
if self._cache is None:
|
|
515
|
+
self._cache = self._load_cache_from_disk()
|
|
516
|
+
|
|
517
|
+
cache_path = os.path.join(
|
|
518
|
+
self._cache_dir, _login_cache_filename(self._login_session)
|
|
519
|
+
)
|
|
520
|
+
|
|
521
|
+
# Fast path: the cached access_token is still within its TTL.
|
|
522
|
+
applied = self._try_apply_from_cache(self._cache, cache_path)
|
|
523
|
+
if applied:
|
|
524
|
+
return
|
|
525
|
+
|
|
526
|
+
# Slow path: refresh in-memory using the cached refresh_token.
|
|
527
|
+
try:
|
|
528
|
+
self._refresh_with_oauth(self._cache, cache_path)
|
|
529
|
+
return
|
|
530
|
+
except _ConsoleLoginInvalidGrantError as exc:
|
|
531
|
+
invalid_grant_exc = exc
|
|
532
|
+
# Fallback: re-read the disk cache once. If ve login ran concurrently
|
|
533
|
+
# and wrote a new refresh_token, retrying with the disk RT may succeed.
|
|
534
|
+
# Any I/O or parse error surfaces with the actionable ve login hint.
|
|
535
|
+
try:
|
|
536
|
+
disk_cache = self._load_cache_from_disk()
|
|
537
|
+
except (RuntimeError, IOError, OSError) as e:
|
|
538
|
+
raise RuntimeError(
|
|
539
|
+
"{}: failed to reload console-login cache from disk; "
|
|
540
|
+
"please run 've login' to re-authenticate. "
|
|
541
|
+
"underlying error: {}".format(self.PROVIDER_NAME, e)
|
|
542
|
+
)
|
|
543
|
+
_disk_rt = disk_cache.get("refresh_token")
|
|
544
|
+
if not (isinstance(_disk_rt, six.string_types) and _disk_rt.strip()):
|
|
545
|
+
raise RuntimeError(
|
|
546
|
+
"{}: console-login refresh token rejected and disk cache lacks "
|
|
547
|
+
"refresh_token; please run 've login' to re-authenticate.".format(
|
|
548
|
+
self.PROVIDER_NAME
|
|
549
|
+
)
|
|
550
|
+
)
|
|
551
|
+
if disk_cache.get("refresh_token") == self._cache.get("refresh_token"):
|
|
552
|
+
raise RuntimeError(
|
|
553
|
+
"{}: console-login refresh token rejected by signin service "
|
|
554
|
+
"(disk cache has the same RT); please run 've login' to "
|
|
555
|
+
"re-authenticate. underlying error: {}".format(
|
|
556
|
+
self.PROVIDER_NAME, invalid_grant_exc
|
|
557
|
+
)
|
|
558
|
+
)
|
|
559
|
+
self._cache = disk_cache
|
|
560
|
+
# If the disk cache holds a fresh access_token (e.g. ve login ran
|
|
561
|
+
# concurrently), apply it directly without another OAuth round-trip.
|
|
562
|
+
# Otherwise exchange the disk refresh_token via OAuth once.
|
|
563
|
+
if self._try_apply_from_cache(self._cache, cache_path):
|
|
564
|
+
return
|
|
565
|
+
try:
|
|
566
|
+
self._refresh_with_oauth(self._cache, cache_path)
|
|
567
|
+
except _ConsoleLoginInvalidGrantError as exc2:
|
|
568
|
+
raise RuntimeError(
|
|
569
|
+
"{}: console-login refresh token rejected; reloaded disk cache "
|
|
570
|
+
"but new RT also failed; please run 've login'. underlying error: {}".format(
|
|
571
|
+
self.PROVIDER_NAME, exc2
|
|
572
|
+
)
|
|
573
|
+
)
|
|
574
|
+
|
|
575
|
+
|
|
576
|
+
|
|
577
|
+
def _load_cache_from_disk(self):
|
|
578
|
+
cache_path = os.path.join(
|
|
579
|
+
self._cache_dir, _login_cache_filename(self._login_session)
|
|
580
|
+
)
|
|
581
|
+
if not os.path.isfile(cache_path):
|
|
582
|
+
raise RuntimeError(
|
|
583
|
+
"{}: console-login token cache file '{}' does not exist; "
|
|
584
|
+
"please run 've login' to re-authenticate.".format(
|
|
585
|
+
self.PROVIDER_NAME, cache_path
|
|
586
|
+
)
|
|
587
|
+
)
|
|
588
|
+
try:
|
|
589
|
+
with open(cache_path, 'r') as f:
|
|
590
|
+
try:
|
|
591
|
+
return json.load(f)
|
|
592
|
+
except ValueError as e:
|
|
593
|
+
raise RuntimeError(
|
|
594
|
+
"{}: failed to parse console-login token cache '{}': {}; "
|
|
595
|
+
"please run 've login' to re-authenticate.".format(
|
|
596
|
+
self.PROVIDER_NAME, cache_path, e
|
|
597
|
+
)
|
|
598
|
+
)
|
|
599
|
+
except (IOError, OSError) as e:
|
|
600
|
+
raise RuntimeError(
|
|
601
|
+
"{}: failed to read console-login token cache '{}': {}; "
|
|
602
|
+
"please run 've login' to re-authenticate.".format(
|
|
603
|
+
self.PROVIDER_NAME, cache_path, e
|
|
604
|
+
)
|
|
605
|
+
)
|
|
606
|
+
|
|
607
|
+
def _try_apply_from_cache(self, cache, cache_path):
|
|
608
|
+
"""Try to apply cache.access_token as live STS without calling OAuth.
|
|
609
|
+
|
|
610
|
+
Returns True iff the cache contains a non-expired access_token that
|
|
611
|
+
parses into STS credentials. Returns False on any expiry/parse miss
|
|
612
|
+
so the caller can fall through to OAuth refresh."""
|
|
613
|
+
try:
|
|
614
|
+
exp_epoch = _console_login_cache_expiration(
|
|
615
|
+
cache, cache_path, self.PROVIDER_NAME
|
|
616
|
+
)
|
|
617
|
+
except RuntimeError:
|
|
618
|
+
return False
|
|
619
|
+
if time.time() >= exp_epoch - 60:
|
|
620
|
+
return False
|
|
621
|
+
try:
|
|
622
|
+
sts_creds = _parse_console_login_access_token(
|
|
623
|
+
cache.get("access_token"), cache_path, self.PROVIDER_NAME
|
|
624
|
+
)
|
|
625
|
+
except RuntimeError:
|
|
626
|
+
return False
|
|
627
|
+
self._credentials = CredentialValue(
|
|
628
|
+
ak=sts_creds["access_key_id"],
|
|
629
|
+
sk=sts_creds["secret_access_key"],
|
|
630
|
+
session_token=sts_creds["session_token"],
|
|
631
|
+
provider_name=self.PROVIDER_NAME,
|
|
632
|
+
)
|
|
633
|
+
self._expiration = exp_epoch
|
|
634
|
+
return True
|
|
635
|
+
|
|
636
|
+
def _refresh_with_oauth(self, cache, cache_path):
|
|
637
|
+
"""Refresh access_token via OAuth refresh_token grant; in-memory only.
|
|
638
|
+
|
|
639
|
+
On HTTP 400 invalid_grant the server has declared the refresh_token
|
|
640
|
+
unusable; raise _ConsoleLoginInvalidGrantError so the caller can run
|
|
641
|
+
the disk-reload fallback. All other failures raise RuntimeError."""
|
|
642
|
+
_raw_rt = cache.get("refresh_token")
|
|
643
|
+
refresh_token = _raw_rt.strip() if isinstance(_raw_rt, six.string_types) else ""
|
|
644
|
+
if not refresh_token:
|
|
645
|
+
raise RuntimeError(
|
|
646
|
+
"{}: console-login cache lacks refresh_token; please run 've login' first.".format(
|
|
647
|
+
self.PROVIDER_NAME
|
|
648
|
+
)
|
|
649
|
+
)
|
|
650
|
+
_raw_cid = cache.get("client_id")
|
|
651
|
+
client_id = _raw_cid.strip() if isinstance(_raw_cid, six.string_types) else ""
|
|
652
|
+
if not client_id:
|
|
653
|
+
raise RuntimeError(
|
|
654
|
+
"{}: console-login cache lacks client_id; please run 've login' to regenerate.".format(
|
|
655
|
+
self.PROVIDER_NAME
|
|
656
|
+
)
|
|
657
|
+
)
|
|
658
|
+
_raw_ep = cache.get("endpoint_url")
|
|
659
|
+
endpoint = (_raw_ep.strip() if isinstance(_raw_ep, six.string_types) else "") or _DEFAULT_CONSOLE_LOGIN_ENDPOINT
|
|
660
|
+
_raw_scope = cache.get("scope")
|
|
661
|
+
scope = _raw_scope.strip() if isinstance(_raw_scope, six.string_types) else ""
|
|
662
|
+
url = "{}{}".format(endpoint.rstrip("/"), _CONSOLE_LOGIN_TOKEN_PATH)
|
|
663
|
+
body = {
|
|
664
|
+
"grant_type": "refresh_token",
|
|
665
|
+
"client_id": client_id,
|
|
666
|
+
"refresh_token": refresh_token,
|
|
667
|
+
}
|
|
668
|
+
if scope:
|
|
669
|
+
body["scope"] = scope
|
|
670
|
+
try:
|
|
671
|
+
from urllib.parse import urlencode
|
|
672
|
+
except ImportError:
|
|
673
|
+
from urllib import urlencode
|
|
674
|
+
encoded = urlencode(body)
|
|
675
|
+
encoded_bytes = encoded.encode("utf-8")
|
|
676
|
+
# Py3: urllib.request + urllib.error; Py2: urllib2 exposes everything.
|
|
677
|
+
try:
|
|
678
|
+
import urllib.request as _urlreq
|
|
679
|
+
import urllib.error as _urlerr
|
|
680
|
+
except ImportError:
|
|
681
|
+
import urllib2 as _urlreq # noqa: F401
|
|
682
|
+
_urlerr = _urlreq
|
|
683
|
+
# Providing data= implies POST on both Py2 and Py3 without needing
|
|
684
|
+
# the method= kwarg (which urllib2.Request does not accept).
|
|
685
|
+
req = _urlreq.Request(
|
|
686
|
+
url, encoded_bytes,
|
|
687
|
+
{"Content-Type": "application/x-www-form-urlencoded"},
|
|
688
|
+
)
|
|
689
|
+
try:
|
|
690
|
+
resp = _urlreq.urlopen(req, timeout=_HTTP_TIMEOUT)
|
|
691
|
+
try:
|
|
692
|
+
resp_bytes = resp.read()
|
|
693
|
+
finally:
|
|
694
|
+
resp.close()
|
|
695
|
+
except _urlerr.HTTPError as e:
|
|
696
|
+
err_body = e.read().decode("utf-8", "replace") if hasattr(e, "read") else ""
|
|
697
|
+
err_code = ""
|
|
698
|
+
try:
|
|
699
|
+
err_code = (json.loads(err_body or "{}").get("error") or "")
|
|
700
|
+
except ValueError:
|
|
701
|
+
pass
|
|
702
|
+
if e.code == 400 and err_code == "invalid_grant":
|
|
703
|
+
raise _ConsoleLoginInvalidGrantError(
|
|
704
|
+
"console-login refresh_token rejected (invalid_grant): {}".format(err_body)
|
|
705
|
+
)
|
|
706
|
+
raise RuntimeError(
|
|
707
|
+
"{}: console-login refresh failed with HTTP {}: {}".format(
|
|
708
|
+
self.PROVIDER_NAME, e.code, err_body
|
|
709
|
+
)
|
|
710
|
+
)
|
|
711
|
+
except Exception as e:
|
|
712
|
+
raise RuntimeError(
|
|
713
|
+
"{}: console-login refresh request failed: {}".format(self.PROVIDER_NAME, e)
|
|
714
|
+
)
|
|
715
|
+
|
|
716
|
+
try:
|
|
717
|
+
payload = json.loads(resp_bytes)
|
|
718
|
+
except ValueError as e:
|
|
719
|
+
raise RuntimeError(
|
|
720
|
+
"{}: console-login refresh response not JSON: {}".format(self.PROVIDER_NAME, e)
|
|
721
|
+
)
|
|
722
|
+
|
|
723
|
+
if not isinstance(payload, dict):
|
|
724
|
+
raise RuntimeError(
|
|
725
|
+
"{}: console-login refresh response is not a JSON object.".format(
|
|
726
|
+
self.PROVIDER_NAME
|
|
727
|
+
)
|
|
728
|
+
)
|
|
729
|
+
|
|
730
|
+
_raw_access = payload.get("access_token")
|
|
731
|
+
new_access = _raw_access.strip() if isinstance(_raw_access, six.string_types) else ""
|
|
359
732
|
try:
|
|
360
|
-
|
|
361
|
-
except
|
|
362
|
-
|
|
363
|
-
|
|
733
|
+
new_expires = int(payload.get("expires_in", 0))
|
|
734
|
+
except (TypeError, ValueError) as e:
|
|
735
|
+
raise RuntimeError(
|
|
736
|
+
"{}: console-login refresh response has invalid expires_in: {}".format(
|
|
737
|
+
self.PROVIDER_NAME, e
|
|
738
|
+
)
|
|
739
|
+
)
|
|
740
|
+
if not new_access or new_expires <= 0:
|
|
741
|
+
raise RuntimeError(
|
|
742
|
+
"{}: console-login refresh response missing access_token or expires_in".format(
|
|
743
|
+
self.PROVIDER_NAME
|
|
744
|
+
)
|
|
745
|
+
)
|
|
746
|
+
cache["access_token"] = new_access
|
|
747
|
+
_raw_rt = payload.get("refresh_token")
|
|
748
|
+
new_rt = _raw_rt.strip() if isinstance(_raw_rt, six.string_types) else ""
|
|
749
|
+
if new_rt:
|
|
750
|
+
cache["refresh_token"] = new_rt
|
|
751
|
+
_raw_id = payload.get("id_token")
|
|
752
|
+
new_id = _raw_id.strip() if isinstance(_raw_id, six.string_types) else ""
|
|
753
|
+
if new_id:
|
|
754
|
+
cache["id_token"] = new_id
|
|
755
|
+
_raw_tt = payload.get("token_type")
|
|
756
|
+
if isinstance(_raw_tt, six.string_types) and _raw_tt.strip():
|
|
757
|
+
cache["token_type"] = _raw_tt
|
|
758
|
+
cache["issued_at"] = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
|
|
759
|
+
cache["expires_in"] = new_expires
|
|
760
|
+
|
|
761
|
+
# Now the in-memory cache is fresh; apply it (or surface a malformed-
|
|
762
|
+
# STS error from _try_apply_from_cache).
|
|
763
|
+
if not self._try_apply_from_cache(cache, cache_path):
|
|
764
|
+
raise RuntimeError(
|
|
765
|
+
"{}: console-login refresh succeeded but the new access_token "
|
|
766
|
+
"could not be parsed into STS credentials".format(self.PROVIDER_NAME)
|
|
767
|
+
)
|
|
768
|
+
|
|
769
|
+
|
|
770
|
+
class _ConsoleLoginInvalidGrantError(Exception):
|
|
771
|
+
"""Sentinel raised when the signin OAuth endpoint returns invalid_grant."""
|
|
364
772
|
|
|
365
773
|
|
|
366
774
|
class SsoCredentialProvider(Provider):
|
|
@@ -384,6 +792,10 @@ class SsoCredentialProvider(Provider):
|
|
|
384
792
|
|
|
385
793
|
self._credentials = None
|
|
386
794
|
self._expiration = None # epoch seconds
|
|
795
|
+
# _cache holds the most recently observed SSO token cache dict, including
|
|
796
|
+
# refresh_token / client_id / client_secret, so that the SDK can silently
|
|
797
|
+
# refresh the access_token without re-reading the file on every call.
|
|
798
|
+
self._cache = None
|
|
387
799
|
self._lock = threading.Lock()
|
|
388
800
|
|
|
389
801
|
def retrieve(self):
|
|
@@ -412,38 +824,53 @@ class SsoCredentialProvider(Provider):
|
|
|
412
824
|
self._credentials = cred
|
|
413
825
|
return
|
|
414
826
|
|
|
415
|
-
# Load SSO token cache
|
|
416
827
|
token_path = os.path.join(
|
|
417
828
|
self._cache_dir,
|
|
418
829
|
_token_cache_filename(self._start_url, self._session_name),
|
|
419
830
|
)
|
|
420
|
-
if not os.path.isfile(token_path):
|
|
421
|
-
raise RuntimeError(
|
|
422
|
-
"{}: SSO token cache file not found at '{}'.".format(
|
|
423
|
-
self.PROVIDER_NAME, token_path
|
|
424
|
-
)
|
|
425
|
-
)
|
|
426
831
|
|
|
427
|
-
|
|
832
|
+
# Bootstrap from disk only when the in-memory cache is empty; on every
|
|
833
|
+
# subsequent expiry we use the in-memory cache (which already has any
|
|
834
|
+
# rotated refresh_token from a previous cycle).
|
|
835
|
+
if self._cache is None:
|
|
836
|
+
if not os.path.isfile(token_path):
|
|
837
|
+
raise RuntimeError(
|
|
838
|
+
"{}: SSO token cache file not found at '{}'; "
|
|
839
|
+
"please run 've sso login' to re-authenticate.".format(
|
|
840
|
+
self.PROVIDER_NAME, token_path
|
|
841
|
+
)
|
|
842
|
+
)
|
|
428
843
|
try:
|
|
429
|
-
|
|
844
|
+
with open(token_path, 'r') as f:
|
|
845
|
+
self._cache = json.load(f)
|
|
430
846
|
except ValueError as e:
|
|
431
847
|
raise RuntimeError(
|
|
432
|
-
"{}: failed to parse SSO token cache '{}': {}"
|
|
848
|
+
"{}: failed to parse SSO token cache '{}': {}; "
|
|
849
|
+
"please run 've sso login' to re-authenticate.".format(
|
|
850
|
+
self.PROVIDER_NAME, token_path, e
|
|
851
|
+
)
|
|
852
|
+
)
|
|
853
|
+
except (IOError, OSError) as e:
|
|
854
|
+
raise RuntimeError(
|
|
855
|
+
"{}: failed to read SSO token cache '{}': {}; "
|
|
856
|
+
"please run 've sso login' to re-authenticate.".format(
|
|
433
857
|
self.PROVIDER_NAME, token_path, e
|
|
434
858
|
)
|
|
435
859
|
)
|
|
436
860
|
|
|
437
|
-
|
|
861
|
+
_raw_at = self._cache.get("access_token")
|
|
862
|
+
access_token = _raw_at.strip() if isinstance(_raw_at, six.string_types) else ""
|
|
438
863
|
if not access_token:
|
|
439
864
|
raise RuntimeError(
|
|
440
|
-
"{}: SSO token cache '{}' does not contain access_token
|
|
865
|
+
"{}: SSO token cache '{}' does not contain access_token; "
|
|
866
|
+
"please run 've sso login' to re-authenticate.".format(
|
|
441
867
|
self.PROVIDER_NAME, token_path
|
|
442
868
|
)
|
|
443
869
|
)
|
|
444
870
|
|
|
445
871
|
# Check if access token is expired
|
|
446
|
-
|
|
872
|
+
_raw_ea = self._cache.get("expires_at")
|
|
873
|
+
expires_at = _raw_ea.strip() if isinstance(_raw_ea, six.string_types) else ""
|
|
447
874
|
token_expired = False
|
|
448
875
|
if expires_at:
|
|
449
876
|
try:
|
|
@@ -451,13 +878,14 @@ class SsoCredentialProvider(Provider):
|
|
|
451
878
|
token_expired = time.time() > exp_epoch
|
|
452
879
|
except ValueError as e:
|
|
453
880
|
raise RuntimeError(
|
|
454
|
-
"{}: failed to parse expires_at in '{}': {}"
|
|
881
|
+
"{}: failed to parse expires_at in '{}': {}; "
|
|
882
|
+
"please run 've sso login' to re-authenticate.".format(
|
|
455
883
|
self.PROVIDER_NAME, token_path, e
|
|
456
884
|
)
|
|
457
885
|
)
|
|
458
886
|
|
|
459
887
|
if token_expired:
|
|
460
|
-
access_token = self._refresh_access_token(
|
|
888
|
+
access_token = self._refresh_access_token(token_path)
|
|
461
889
|
|
|
462
890
|
# Get role credentials from portal
|
|
463
891
|
self._fetch_role_credentials(access_token)
|
|
@@ -487,12 +915,19 @@ class SsoCredentialProvider(Provider):
|
|
|
487
915
|
provider_name=self.PROVIDER_NAME,
|
|
488
916
|
)
|
|
489
917
|
|
|
490
|
-
def _refresh_access_token(self,
|
|
491
|
-
"""Refresh the SSO access token using the OAuth token endpoint.
|
|
492
|
-
|
|
918
|
+
def _refresh_access_token(self, token_path):
|
|
919
|
+
"""Refresh the SSO access token using the OAuth token endpoint.
|
|
920
|
+
|
|
921
|
+
Operates on self._cache so that any rotated refresh_token is preserved
|
|
922
|
+
across subsequent expiry cycles (in-memory refresh, never writes disk).
|
|
923
|
+
"""
|
|
924
|
+
token_cache = self._cache
|
|
925
|
+
_raw_sso_rt = token_cache.get("refresh_token")
|
|
926
|
+
refresh_token = _raw_sso_rt.strip() if isinstance(_raw_sso_rt, six.string_types) else ""
|
|
493
927
|
if not refresh_token:
|
|
494
928
|
raise RuntimeError(
|
|
495
|
-
"{}: SSO token cache '{}' does not contain refresh_token
|
|
929
|
+
"{}: SSO token cache '{}' does not contain refresh_token; "
|
|
930
|
+
"please run 've sso login' to re-authenticate.".format(
|
|
496
931
|
self.PROVIDER_NAME, token_path
|
|
497
932
|
)
|
|
498
933
|
)
|
|
@@ -503,16 +938,20 @@ class SsoCredentialProvider(Provider):
|
|
|
503
938
|
exp_epoch = _unix_timestamp_to_epoch(client_secret_expires_at)
|
|
504
939
|
if time.time() >= exp_epoch:
|
|
505
940
|
raise RuntimeError(
|
|
506
|
-
"{}: refresh token in '{}' has expired
|
|
941
|
+
"{}: refresh token in '{}' has expired; "
|
|
942
|
+
"please run 've sso login' to re-authenticate.".format(
|
|
507
943
|
self.PROVIDER_NAME, token_path
|
|
508
944
|
)
|
|
509
945
|
)
|
|
510
946
|
|
|
511
|
-
|
|
512
|
-
|
|
947
|
+
_raw_cid = token_cache.get("client_id")
|
|
948
|
+
client_id = _raw_cid.strip() if isinstance(_raw_cid, six.string_types) else ""
|
|
949
|
+
_raw_cs = token_cache.get("client_secret")
|
|
950
|
+
client_secret = _raw_cs.strip() if isinstance(_raw_cs, six.string_types) else ""
|
|
513
951
|
if not client_id or not client_secret:
|
|
514
952
|
raise RuntimeError(
|
|
515
|
-
"{}: SSO token cache '{}' does not contain client_id/client_secret
|
|
953
|
+
"{}: SSO token cache '{}' does not contain client_id/client_secret; "
|
|
954
|
+
"please run 've sso login' to re-authenticate.".format(
|
|
516
955
|
self.PROVIDER_NAME, token_path
|
|
517
956
|
)
|
|
518
957
|
)
|
|
@@ -527,63 +966,93 @@ class SsoCredentialProvider(Provider):
|
|
|
527
966
|
# Pass a dict body; RESTClient auto-serializes with Content-Type:
|
|
528
967
|
# application/json (see volcenginesdkcore/rest.py). Do NOT json.dumps
|
|
529
968
|
# here or it will be double-encoded.
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
969
|
+
try:
|
|
970
|
+
resp_body = ApiClient(Configuration())._do_http_request(
|
|
971
|
+
oauth_url,
|
|
972
|
+
method="POST",
|
|
973
|
+
data={
|
|
974
|
+
"grant_type": "refresh_token",
|
|
975
|
+
"client_id": client_id,
|
|
976
|
+
"client_secret": client_secret,
|
|
977
|
+
"refresh_token": refresh_token,
|
|
978
|
+
},
|
|
979
|
+
headers={"Content-Type": "application/json"},
|
|
980
|
+
timeout=_HTTP_TIMEOUT,
|
|
981
|
+
max_retries=_HTTP_MAX_RETRIES,
|
|
982
|
+
retry_interval=_HTTP_RETRY_INTERVAL,
|
|
983
|
+
request_name="OAuth token refresh",
|
|
984
|
+
# OAuth refresh_token grants may rotate the refresh token on use;
|
|
985
|
+
# replaying a successful-but-response-lost POST would invalidate
|
|
986
|
+
# the local refresh_token. Fail fast on 5xx instead.
|
|
987
|
+
retry_on_5xx=False,
|
|
988
|
+
provider_name=self.PROVIDER_NAME,
|
|
989
|
+
)
|
|
990
|
+
except RuntimeError as e:
|
|
991
|
+
raise RuntimeError(
|
|
992
|
+
"{}: SSO OAuth token refresh failed; "
|
|
993
|
+
"please run 've sso login' to re-authenticate. "
|
|
994
|
+
"underlying error: {}".format(self.PROVIDER_NAME, e)
|
|
995
|
+
)
|
|
550
996
|
|
|
551
997
|
try:
|
|
552
998
|
resp_data = json.loads(resp_body)
|
|
553
999
|
except ValueError as e:
|
|
554
1000
|
raise RuntimeError(
|
|
555
|
-
"{}: failed to parse OAuth token response: {}"
|
|
1001
|
+
"{}: failed to parse OAuth token response: {}; "
|
|
1002
|
+
"please run 've sso login' to re-authenticate.".format(
|
|
556
1003
|
self.PROVIDER_NAME, e
|
|
557
1004
|
)
|
|
558
1005
|
)
|
|
559
1006
|
|
|
560
|
-
|
|
1007
|
+
if not isinstance(resp_data, dict):
|
|
1008
|
+
raise RuntimeError(
|
|
1009
|
+
"{}: OAuth token response is not a JSON object; "
|
|
1010
|
+
"please run 've sso login' to re-authenticate.".format(
|
|
1011
|
+
self.PROVIDER_NAME
|
|
1012
|
+
)
|
|
1013
|
+
)
|
|
1014
|
+
|
|
1015
|
+
_raw_access_token = resp_data.get("access_token")
|
|
1016
|
+
new_access_token = _raw_access_token.strip() if isinstance(_raw_access_token, six.string_types) else ""
|
|
561
1017
|
if not new_access_token:
|
|
562
1018
|
raise RuntimeError(
|
|
563
|
-
"{}: OAuth token response did not contain access_token
|
|
1019
|
+
"{}: OAuth token response did not contain access_token; "
|
|
1020
|
+
"please run 've sso login' to re-authenticate.".format(
|
|
564
1021
|
self.PROVIDER_NAME
|
|
565
1022
|
)
|
|
566
1023
|
)
|
|
567
1024
|
|
|
568
|
-
|
|
1025
|
+
try:
|
|
1026
|
+
expires_in = int(resp_data.get("expires_in", 0))
|
|
1027
|
+
except (TypeError, ValueError) as e:
|
|
1028
|
+
raise RuntimeError(
|
|
1029
|
+
"{}: OAuth token response has invalid expires_in: {}; "
|
|
1030
|
+
"please run 've sso login' to re-authenticate.".format(
|
|
1031
|
+
self.PROVIDER_NAME, e
|
|
1032
|
+
)
|
|
1033
|
+
)
|
|
569
1034
|
if expires_in <= 0:
|
|
570
1035
|
raise RuntimeError(
|
|
571
|
-
"{}: OAuth token response did not contain valid expires_in
|
|
1036
|
+
"{}: OAuth token response did not contain valid expires_in; "
|
|
1037
|
+
"please run 've sso login' to re-authenticate.".format(
|
|
572
1038
|
self.PROVIDER_NAME
|
|
573
1039
|
)
|
|
574
1040
|
)
|
|
575
1041
|
|
|
576
|
-
#
|
|
1042
|
+
# Write back into self._cache so any rotated refresh_token survives
|
|
1043
|
+
# the next expiry cycle (in-memory only; disk is never written).
|
|
577
1044
|
token_cache["access_token"] = new_access_token
|
|
578
|
-
|
|
1045
|
+
_raw_refresh = resp_data.get("refresh_token")
|
|
1046
|
+
new_refresh = _raw_refresh.strip() if isinstance(_raw_refresh, six.string_types) else ""
|
|
579
1047
|
if new_refresh:
|
|
580
1048
|
token_cache["refresh_token"] = new_refresh
|
|
581
1049
|
token_cache["expires_at"] = time.strftime(
|
|
582
1050
|
"%Y-%m-%dT%H:%M:%SZ", time.gmtime(time.time() + expires_in)
|
|
583
1051
|
)
|
|
584
1052
|
|
|
585
|
-
|
|
586
|
-
|
|
1053
|
+
# SDK never writes the sso cache file: ve cli is the single writer; the
|
|
1054
|
+
# SDK only refreshes the in-memory self._cache to avoid concurrent
|
|
1055
|
+
# write races with cli.
|
|
587
1056
|
return new_access_token
|
|
588
1057
|
|
|
589
1058
|
def _fetch_role_credentials(self, access_token):
|
|
@@ -627,9 +1096,12 @@ class SsoCredentialProvider(Provider):
|
|
|
627
1096
|
result = resp_data.get("Result") or resp_data.get("result") or {}
|
|
628
1097
|
role_creds = result.get("RoleCredentials") or result.get("roleCredentials") or {}
|
|
629
1098
|
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
1099
|
+
_raw_ak = role_creds.get("AccessKeyId")
|
|
1100
|
+
ak = _raw_ak.strip() if isinstance(_raw_ak, six.string_types) else ""
|
|
1101
|
+
_raw_sk = role_creds.get("SecretAccessKey")
|
|
1102
|
+
sk = _raw_sk.strip() if isinstance(_raw_sk, six.string_types) else ""
|
|
1103
|
+
_raw_tok = role_creds.get("sessionToken") or role_creds.get("SessionToken")
|
|
1104
|
+
token = _raw_tok.strip() if isinstance(_raw_tok, six.string_types) else ""
|
|
633
1105
|
|
|
634
1106
|
if not ak or not sk:
|
|
635
1107
|
# Check ResponseMetadata for error
|
|
@@ -291,7 +291,7 @@ class Configuration(six.with_metaclass(TypeWithDefault, object)):
|
|
|
291
291
|
"OS: {env}\n" \
|
|
292
292
|
"Python Version: {pyversion}\n" \
|
|
293
293
|
"Version of the API: 0.1.0\n" \
|
|
294
|
-
"SDK Package Version: 5.0.
|
|
294
|
+
"SDK Package Version: 5.0.33".\
|
|
295
295
|
format(env=sys.platform, pyversion=sys.version)
|
|
296
296
|
|
|
297
297
|
@property
|
|
@@ -34,10 +34,10 @@ class StatisticForUpdateLoadInput(object):
|
|
|
34
34
|
"""
|
|
35
35
|
swagger_types = {
|
|
36
36
|
'addr_value': 'str',
|
|
37
|
-
'capacity': '
|
|
38
|
-
'current_load': '
|
|
37
|
+
'capacity': 'float',
|
|
38
|
+
'current_load': 'float',
|
|
39
39
|
'pool_name': 'str',
|
|
40
|
-
'target_load': '
|
|
40
|
+
'target_load': 'float'
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
attribute_map = {
|
|
@@ -99,7 +99,7 @@ class StatisticForUpdateLoadInput(object):
|
|
|
99
99
|
|
|
100
100
|
|
|
101
101
|
:return: The capacity of this StatisticForUpdateLoadInput. # noqa: E501
|
|
102
|
-
:rtype:
|
|
102
|
+
:rtype: float
|
|
103
103
|
"""
|
|
104
104
|
return self._capacity
|
|
105
105
|
|
|
@@ -109,11 +109,8 @@ class StatisticForUpdateLoadInput(object):
|
|
|
109
109
|
|
|
110
110
|
|
|
111
111
|
:param capacity: The capacity of this StatisticForUpdateLoadInput. # noqa: E501
|
|
112
|
-
:type:
|
|
112
|
+
:type: float
|
|
113
113
|
"""
|
|
114
|
-
if (self._configuration.client_side_validation and
|
|
115
|
-
capacity is not None and capacity < 0): # noqa: E501
|
|
116
|
-
raise ValueError("Invalid value for `capacity`, must be a value greater than or equal to `0`") # noqa: E501
|
|
117
114
|
|
|
118
115
|
self._capacity = capacity
|
|
119
116
|
|
|
@@ -123,7 +120,7 @@ class StatisticForUpdateLoadInput(object):
|
|
|
123
120
|
|
|
124
121
|
|
|
125
122
|
:return: The current_load of this StatisticForUpdateLoadInput. # noqa: E501
|
|
126
|
-
:rtype:
|
|
123
|
+
:rtype: float
|
|
127
124
|
"""
|
|
128
125
|
return self._current_load
|
|
129
126
|
|
|
@@ -133,11 +130,8 @@ class StatisticForUpdateLoadInput(object):
|
|
|
133
130
|
|
|
134
131
|
|
|
135
132
|
:param current_load: The current_load of this StatisticForUpdateLoadInput. # noqa: E501
|
|
136
|
-
:type:
|
|
133
|
+
:type: float
|
|
137
134
|
"""
|
|
138
|
-
if (self._configuration.client_side_validation and
|
|
139
|
-
current_load is not None and current_load < 0): # noqa: E501
|
|
140
|
-
raise ValueError("Invalid value for `current_load`, must be a value greater than or equal to `0`") # noqa: E501
|
|
141
135
|
|
|
142
136
|
self._current_load = current_load
|
|
143
137
|
|
|
@@ -168,7 +162,7 @@ class StatisticForUpdateLoadInput(object):
|
|
|
168
162
|
|
|
169
163
|
|
|
170
164
|
:return: The target_load of this StatisticForUpdateLoadInput. # noqa: E501
|
|
171
|
-
:rtype:
|
|
165
|
+
:rtype: float
|
|
172
166
|
"""
|
|
173
167
|
return self._target_load
|
|
174
168
|
|
|
@@ -178,11 +172,8 @@ class StatisticForUpdateLoadInput(object):
|
|
|
178
172
|
|
|
179
173
|
|
|
180
174
|
:param target_load: The target_load of this StatisticForUpdateLoadInput. # noqa: E501
|
|
181
|
-
:type:
|
|
175
|
+
:type: float
|
|
182
176
|
"""
|
|
183
|
-
if (self._configuration.client_side_validation and
|
|
184
|
-
target_load is not None and target_load < 0): # noqa: E501
|
|
185
|
-
raise ValueError("Invalid value for `target_load`, must be a value greater than or equal to `0`") # noqa: E501
|
|
186
177
|
|
|
187
178
|
self._target_load = target_load
|
|
188
179
|
|
|
@@ -35,6 +35,7 @@ class ListActivityMediaAPIRequest(object):
|
|
|
35
35
|
swagger_types = {
|
|
36
36
|
'folder_id': 'int',
|
|
37
37
|
'include_sub_folder': 'bool',
|
|
38
|
+
'is_close_dated': 'bool',
|
|
38
39
|
'name': 'str',
|
|
39
40
|
'order_key': 'str',
|
|
40
41
|
'page_num': 'int',
|
|
@@ -47,6 +48,7 @@ class ListActivityMediaAPIRequest(object):
|
|
|
47
48
|
attribute_map = {
|
|
48
49
|
'folder_id': 'FolderId',
|
|
49
50
|
'include_sub_folder': 'IncludeSubFolder',
|
|
51
|
+
'is_close_dated': 'IsCloseDated',
|
|
50
52
|
'name': 'Name',
|
|
51
53
|
'order_key': 'OrderKey',
|
|
52
54
|
'page_num': 'PageNum',
|
|
@@ -56,7 +58,7 @@ class ListActivityMediaAPIRequest(object):
|
|
|
56
58
|
'vid': 'Vid'
|
|
57
59
|
}
|
|
58
60
|
|
|
59
|
-
def __init__(self, folder_id=None, include_sub_folder=None, name=None, order_key=None, page_num=None, page_size=None, search_type=None, source_type=None, vid=None, _configuration=None): # noqa: E501
|
|
61
|
+
def __init__(self, folder_id=None, include_sub_folder=None, is_close_dated=None, name=None, order_key=None, page_num=None, page_size=None, search_type=None, source_type=None, vid=None, _configuration=None): # noqa: E501
|
|
60
62
|
"""ListActivityMediaAPIRequest - a model defined in Swagger""" # noqa: E501
|
|
61
63
|
if _configuration is None:
|
|
62
64
|
_configuration = Configuration()
|
|
@@ -64,6 +66,7 @@ class ListActivityMediaAPIRequest(object):
|
|
|
64
66
|
|
|
65
67
|
self._folder_id = None
|
|
66
68
|
self._include_sub_folder = None
|
|
69
|
+
self._is_close_dated = None
|
|
67
70
|
self._name = None
|
|
68
71
|
self._order_key = None
|
|
69
72
|
self._page_num = None
|
|
@@ -77,6 +80,8 @@ class ListActivityMediaAPIRequest(object):
|
|
|
77
80
|
self.folder_id = folder_id
|
|
78
81
|
if include_sub_folder is not None:
|
|
79
82
|
self.include_sub_folder = include_sub_folder
|
|
83
|
+
if is_close_dated is not None:
|
|
84
|
+
self.is_close_dated = is_close_dated
|
|
80
85
|
if name is not None:
|
|
81
86
|
self.name = name
|
|
82
87
|
if order_key is not None:
|
|
@@ -133,6 +138,27 @@ class ListActivityMediaAPIRequest(object):
|
|
|
133
138
|
|
|
134
139
|
self._include_sub_folder = include_sub_folder
|
|
135
140
|
|
|
141
|
+
@property
|
|
142
|
+
def is_close_dated(self):
|
|
143
|
+
"""Gets the is_close_dated of this ListActivityMediaAPIRequest. # noqa: E501
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
:return: The is_close_dated of this ListActivityMediaAPIRequest. # noqa: E501
|
|
147
|
+
:rtype: bool
|
|
148
|
+
"""
|
|
149
|
+
return self._is_close_dated
|
|
150
|
+
|
|
151
|
+
@is_close_dated.setter
|
|
152
|
+
def is_close_dated(self, is_close_dated):
|
|
153
|
+
"""Sets the is_close_dated of this ListActivityMediaAPIRequest.
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
:param is_close_dated: The is_close_dated of this ListActivityMediaAPIRequest. # noqa: E501
|
|
157
|
+
:type: bool
|
|
158
|
+
"""
|
|
159
|
+
|
|
160
|
+
self._is_close_dated = is_close_dated
|
|
161
|
+
|
|
136
162
|
@property
|
|
137
163
|
def name(self):
|
|
138
164
|
"""Gets the name of this ListActivityMediaAPIRequest. # noqa: E501
|
|
File without changes
|
|
File without changes
|
{volcengine_python_sdk-5.0.32.dist-info → volcengine_python_sdk-5.0.33.dist-info}/licenses/NOTICE.md
RENAMED
|
File without changes
|
{volcengine_python_sdk-5.0.32.dist-info → volcengine_python_sdk-5.0.33.dist-info}/top_level.txt
RENAMED
|
File without changes
|