huaweicloud-sdk-python-v3/huaweicloud-sdk-core/huaweicloudsdkcore/auth/credentials.py

297 lines
11 KiB
Python

# coding: utf-8
"""
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache LICENSE, Version 2.0 (the
"LICENSE"); you may not use this file except in compliance
with the LICENSE. You may obtain a copy of the LICENSE at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the LICENSE is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the LICENSE for the
specific language governing permissions and limitations
under the LICENSE.
"""
import os
import re
from abc import ABCMeta, abstractmethod
from concurrent.futures import ThreadPoolExecutor
from huaweicloudsdkcore.auth.iam_service import get_keystone_list_projects_request, keystone_list_projects, \
get_keystone_list_auth_domains_request, keystone_list_auth_domains, DEFAULT_IAM_ENDPOINT
from huaweicloudsdkcore.exceptions.exceptions import ApiValueError, ServiceResponseException
from huaweicloudsdkcore.signer.signer import Signer, DerivationAKSKSigner
from huaweicloudsdkcore.auth.cache import AuthCache
class Credentials(object):
def __init__(self, ak=None, sk=None):
self.ak = ak
self.sk = sk
self.iam_endpoint = None
self.security_token = None
def with_iam_endpoint(self, endpoint):
self.iam_endpoint = endpoint
return self
def with_security_token(self, token):
self.security_token = token
return self
def get_update_path_params(self):
pass
def process_auth_params(self, http_client, region_id):
pass
def process_auth_request(self, request, http_client):
executor = ThreadPoolExecutor(max_workers=8)
future = executor.submit(self.sign_request, request)
return future
def sign_request(self, request):
pass
class DerivedCredentials:
__metaclass__ = ABCMeta
GLOBAL_REGION_ID = "globe"
_DEFAULT_ENDPOINT_REG = "^[a-z][a-z0-9-]+(\\.[a-z]{2,}-[a-z]+-\\d{1,2})?\\.(my)?(huaweicloud|myhwclouds).(com|cn)"
@abstractmethod
def _process_derived_auth_params(self, derived_auth_service_name, region_id):
pass
@abstractmethod
def with_derived_predicate(self, derived_predicate):
pass
@abstractmethod
def _is_derived_auth(self, request):
pass
@classmethod
def get_default_derived_predicate(cls):
return lambda request: False if re.match(cls._DEFAULT_ENDPOINT_REG, request.host) else True
class BasicCredentials(Credentials, DerivedCredentials):
def __init__(self, ak, sk, project_id=None):
if not ak:
raise ApiValueError("AK can not be null.")
if not sk:
raise ApiValueError("SK can not be null.")
super(BasicCredentials, self).__init__(ak, sk)
self.project_id = project_id
self._region_id = None
self._derived_auth_service_name = None
self._derived_predicate = None
def get_update_path_params(self):
path_params = {}
if self.project_id:
path_params["project_id"] = self.project_id
return path_params
def _process_derived_auth_params(self, derived_auth_service_name, region_id):
if not self._derived_auth_service_name:
self._derived_auth_service_name = derived_auth_service_name
if not self._region_id:
self._region_id = region_id
def _is_derived_auth(self, request):
if not self._derived_predicate:
return False
return self._derived_predicate(request)
def with_derived_predicate(self, derived_predicate):
self._derived_predicate = derived_predicate
return self
def process_auth_params(self, http_client, region_id):
if self.project_id:
return self
ak_with_name = self.ak + region_id
project_id = AuthCache.get_auth(ak_with_name)
if project_id:
self.project_id = project_id
return self
derived_predicate = self._derived_predicate
self._derived_predicate = None
if self.iam_endpoint is None:
self.iam_endpoint = DEFAULT_IAM_ENDPOINT
future_request = self.process_auth_request(
get_keystone_list_projects_request(self.iam_endpoint, region_id=region_id), http_client)
request = future_request.result()
try:
self.project_id = keystone_list_projects(http_client, request)
AuthCache.put_auth(ak_with_name, self.project_id)
except ServiceResponseException as e:
err_msg = e.error_msg if hasattr(e, "error_msg") else "unknown exception."
raise ApiValueError("Failed to get project id, " + err_msg)
self._derived_predicate = derived_predicate
return self
def process_auth_request(self, request, http_client):
return super(BasicCredentials, self).process_auth_request(request, http_client)
def sign_request(self, request):
if self.project_id:
request.header_params["X-Project-Id"] = self.project_id
if self.security_token is not None:
request.header_params["X-Security-Token"] = self.security_token
if "Content-Type" in request.header_params and not request.header_params["Content-Type"].startswith(
"application/json"):
request.header_params["X-Sdk-Content-Sha256"] = "UNSIGNED-PAYLOAD"
return DerivationAKSKSigner(self).sign(request, self._derived_auth_service_name, self._region_id) \
if self._is_derived_auth(request) else Signer(self).sign(request)
class GlobalCredentials(Credentials, DerivedCredentials):
def __init__(self, ak, sk, domain_id=None):
if not ak:
raise ApiValueError("AK can not be null.")
if not sk:
raise ApiValueError("SK can not be null.")
super(GlobalCredentials, self).__init__(ak, sk)
self.domain_id = domain_id
self._region_id = None
self._derived_auth_service_name = None
self._derived_predicate = None
def get_update_path_params(self):
path_params = {}
if self.domain_id:
path_params["domain_id"] = self.domain_id
return path_params
def process_auth_params(self, http_client, region_id):
if self.domain_id:
return self
domain_id = AuthCache.get_auth(self.ak)
if domain_id:
self.domain_id = domain_id
return self
derived_predicate = self._derived_predicate
self._derived_predicate = None
if self.iam_endpoint is None:
self.iam_endpoint = DEFAULT_IAM_ENDPOINT
future_request = self.process_auth_request(get_keystone_list_auth_domains_request(self.iam_endpoint),
http_client)
request = future_request.result()
try:
self.domain_id = keystone_list_auth_domains(http_client, request)
AuthCache.put_auth(self.ak, self.domain_id)
except ServiceResponseException as e:
err_msg = e.error_msg if hasattr(e, "error_msg") else "unknown exception."
raise ApiValueError("Failed to get domain id, " + err_msg)
self._derived_predicate = derived_predicate
return self
def process_auth_request(self, request, http_client):
return super(GlobalCredentials, self).process_auth_request(request, http_client)
def sign_request(self, request):
if self.domain_id:
request.header_params["X-Domain-Id"] = self.domain_id
if self.security_token is not None:
request.header_params["X-Security-Token"] = self.security_token
if "Content-Type" in request.header_params and not request.header_params["Content-Type"].startswith(
"application/json"):
request.header_params["X-Sdk-Content-Sha256"] = "UNSIGNED-PAYLOAD"
return DerivationAKSKSigner(self).sign(request, self._derived_auth_service_name, self._region_id) \
if self._is_derived_auth(request) else Signer(self).sign(request)
def _process_derived_auth_params(self, derived_auth_service_name, region_id):
if not self._derived_auth_service_name:
self._derived_auth_service_name = derived_auth_service_name
if not self._region_id:
self._region_id = self.GLOBAL_REGION_ID
def _is_derived_auth(self, request):
if not self._derived_predicate:
return False
return self._derived_predicate(request)
def with_derived_predicate(self, derived_predicate):
self._derived_predicate = derived_predicate
return self
class EnvCredentialHelper(object):
def __init__(self):
pass
AK_ENV_NAME = "HUAWEICLOUD_SDK_AK"
SK_ENV_NAME = "HUAWEICLOUD_SDK_SK"
PROJECT_ID_ENV_NAME = "HUAWEICLOUD_SDK_PROJECT_ID"
DOMAIN_ID_ENV_NAME = "HUAWEICLOUD_SDK_DOMAIN_ID"
IAM_ENDPOINT_ENV_NAME = "HUAWEICLOUD_SDK_IAM_ENDPOINT"
REGION_ID_ENV_NAME = "HUAWEICLOUD_SDK_REGION_ID"
DERIVED_AUTH_SERVICE_NAME_ENV_NAME = "HUAWEICLOUD_SDK_DERIVED_AUTH_SERVICE_NAME"
DERIVED_PREDICATE_ENV_NAME = "HUAWEICLOUD_SDK_DERIVED_PREDICATE"
DEFAULT_DERIVED_PREDICATE = "DEFAULT_DERIVED_PREDICATE"
BASIC_CREDENTIAL_TYPE = "BasicCredentials"
GLOBAL_CREDENTIAL_TYPE = "GlobalCredentials"
@classmethod
def load_credential_from_env(cls, default_type):
credential = None
ak = os.environ.get(cls.AK_ENV_NAME)
sk = os.environ.get(cls.SK_ENV_NAME)
iam_endpoint = cls.get_iam_endpoint_env_name()
region_id = os.environ.get(cls.REGION_ID_ENV_NAME)
derived_auth_service_name = os.environ.get(cls.DERIVED_AUTH_SERVICE_NAME_ENV_NAME)
derived_predicate = os.environ.get(cls.DERIVED_PREDICATE_ENV_NAME)
if derived_predicate and derived_predicate == cls.DEFAULT_DERIVED_PREDICATE:
derived_predicate = DerivedCredentials.get_default_derived_predicate()
if default_type == cls.BASIC_CREDENTIAL_TYPE:
project_id = os.environ.get(cls.PROJECT_ID_ENV_NAME)
credential = BasicCredentials(ak, sk, project_id).with_derived_predicate(derived_predicate)
credential._process_derived_auth_params(derived_auth_service_name, region_id)
credential.with_iam_endpoint(iam_endpoint)
elif default_type == cls.GLOBAL_CREDENTIAL_TYPE:
domain_id = os.environ.get(cls.DOMAIN_ID_ENV_NAME)
credential = GlobalCredentials(ak, sk, domain_id).with_derived_predicate(derived_predicate)
credential._process_derived_auth_params(derived_auth_service_name, region_id)
credential.with_iam_endpoint(iam_endpoint)
return credential
@classmethod
def get_iam_endpoint_env_name(cls):
return os.environ.get(cls.IAM_ENDPOINT_ENV_NAME)