cloud_sso.py 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. import calendar
  2. import json
  3. import time
  4. from urllib.parse import urlparse
  5. from alibabacloud_credentials.provider.refreshable import Credentials, RefreshResult, RefreshCachedSupplier
  6. from alibabacloud_credentials.http import HttpOptions
  7. from Tea.core import TeaCore
  8. from alibabacloud_credentials_api import ICredentialsProvider
  9. from alibabacloud_credentials.utils import parameter_helper as ph
  10. from alibabacloud_credentials.exceptions import CredentialException
  11. def _get_stale_time(expiration: int) -> int:
  12. if expiration < 0:
  13. return int(time.mktime(time.localtime())) + 60 * 60
  14. return expiration - 15 * 60
  15. class CloudSSOCredentialsProvider(ICredentialsProvider):
  16. DEFAULT_CONNECT_TIMEOUT = 5000
  17. DEFAULT_READ_TIMEOUT = 10000
  18. def __init__(self, *,
  19. sign_in_url: str = None,
  20. account_id: str = None,
  21. access_config: str = None,
  22. access_token: str = None,
  23. access_token_expire: int = 0,
  24. http_options: HttpOptions = None):
  25. self._sign_in_url = sign_in_url
  26. self._account_id = account_id
  27. self._access_config = access_config
  28. self._access_token = access_token
  29. self._access_token_expire = access_token_expire
  30. if self._access_token is None or self._access_token_expire == 0 or self._access_token_expire - int(
  31. time.mktime(time.localtime())) <= 0:
  32. raise ValueError(
  33. 'CloudSSO access token is empty or expired, please re-login with cli')
  34. if self._sign_in_url is None or self._account_id is None or self._access_config is None:
  35. raise ValueError(
  36. 'CloudSSO sign in url or account id or access config is empty')
  37. self._http_options = http_options if http_options is not None else HttpOptions()
  38. self._runtime_options = {
  39. 'connectTimeout': self._http_options.connect_timeout if self._http_options.connect_timeout is not None else CloudSSOCredentialsProvider.DEFAULT_CONNECT_TIMEOUT,
  40. 'readTimeout': self._http_options.read_timeout if self._http_options.read_timeout is not None else CloudSSOCredentialsProvider.DEFAULT_READ_TIMEOUT,
  41. 'httpsProxy': self._http_options.proxy
  42. }
  43. self._credentials_cache = RefreshCachedSupplier(
  44. refresh_callable=self._refresh_credentials,
  45. refresh_callable_async=self._refresh_credentials_async,
  46. )
  47. def get_credentials(self) -> Credentials:
  48. return self._credentials_cache._sync_call()
  49. async def get_credentials_async(self) -> Credentials:
  50. return await self._credentials_cache._async_call()
  51. def _refresh_credentials(self) -> RefreshResult[Credentials]:
  52. r = urlparse(self._sign_in_url)
  53. tea_request = ph.get_new_request()
  54. tea_request.headers['host'] = r.hostname
  55. tea_request.port = r.port
  56. tea_request.protocol = r.scheme
  57. tea_request.method = 'POST'
  58. tea_request.pathname = '/cloud-credentials'
  59. tea_request.body = json.dumps({
  60. 'AccountId': self._account_id,
  61. 'AccessConfigurationId': self._access_config,
  62. })
  63. tea_request.headers['Accept'] = 'application/json'
  64. tea_request.headers['Content-Type'] = 'application/json'
  65. tea_request.headers['Authorization'] = f'Bearer {self._access_token}'
  66. response = TeaCore.do_action(tea_request, self._runtime_options)
  67. if response.status_code != 200:
  68. raise CredentialException(
  69. f'error refreshing credentials from sso, http_code: {response.status_code}, result: {response.body.decode("utf-8")}')
  70. dic = json.loads(response.body.decode('utf-8'))
  71. if 'CloudCredential' not in dic:
  72. raise CredentialException(
  73. f'error retrieving credentials from sso result: {response.body.decode("utf-8")}')
  74. cre = dic.get('CloudCredential')
  75. if 'AccessKeyId' not in cre or 'AccessKeySecret' not in cre or 'SecurityToken' not in cre:
  76. raise CredentialException(
  77. f'error retrieving credentials from sso result: {response.body.decode("utf-8")}')
  78. # 先转换为时间数组
  79. time_array = time.strptime(cre.get('Expiration'), '%Y-%m-%dT%H:%M:%SZ')
  80. # 转换为时间戳
  81. expiration = calendar.timegm(time_array)
  82. credentials = Credentials(
  83. access_key_id=cre.get('AccessKeyId'),
  84. access_key_secret=cre.get('AccessKeySecret'),
  85. security_token=cre.get('SecurityToken'),
  86. expiration=expiration,
  87. provider_name=self.get_provider_name()
  88. )
  89. return RefreshResult(value=credentials,
  90. stale_time=_get_stale_time(expiration))
  91. async def _refresh_credentials_async(self) -> RefreshResult[Credentials]:
  92. r = urlparse(self._sign_in_url)
  93. tea_request = ph.get_new_request()
  94. tea_request.headers['host'] = r.hostname
  95. tea_request.port = r.port
  96. tea_request.protocol = r.scheme
  97. tea_request.method = 'POST'
  98. tea_request.pathname = '/cloud-credentials'
  99. tea_request.body = json.dumps({
  100. 'AccountId': self._account_id,
  101. 'AccessConfigurationId': self._access_config,
  102. })
  103. tea_request.headers['Accept'] = 'application/json'
  104. tea_request.headers['Content-Type'] = 'application/json'
  105. tea_request.headers['Authorization'] = f'Bearer {self._access_token}'
  106. response = await TeaCore.async_do_action(tea_request, self._runtime_options)
  107. if response.status_code != 200:
  108. raise CredentialException(
  109. f'error refreshing credentials from sso, http_code: {response.status_code}, result: {response.body.decode("utf-8")}')
  110. dic = json.loads(response.body.decode('utf-8'))
  111. if 'CloudCredential' not in dic:
  112. raise CredentialException(
  113. f'error retrieving credentials from sso result: {response.body.decode("utf-8")}')
  114. cre = dic.get('CloudCredential')
  115. if 'AccessKeyId' not in cre or 'AccessKeySecret' not in cre or 'SecurityToken' not in cre:
  116. raise CredentialException(
  117. f'error retrieving credentials from sso result: {response.body.decode("utf-8")}')
  118. # 先转换为时间数组
  119. time_array = time.strptime(cre.get('Expiration'), '%Y-%m-%dT%H:%M:%SZ')
  120. # 转换为时间戳
  121. expiration = calendar.timegm(time_array)
  122. credentials = Credentials(
  123. access_key_id=cre.get('AccessKeyId'),
  124. access_key_secret=cre.get('AccessKeySecret'),
  125. security_token=cre.get('SecurityToken'),
  126. expiration=expiration,
  127. provider_name=self.get_provider_name()
  128. )
  129. return RefreshResult(value=credentials,
  130. stale_time=_get_stale_time(expiration))
  131. def get_provider_name(self) -> str:
  132. return 'cloud_sso'