import abc import jwt import uuid import requests from urllib.parse import urlencode from authing import AuthenticationClient class InvalidCode(Exception): def __init__(self, code): super(InvalidCode, self).__init__() self.code = code def __str__(self): return f'invalid code: {self.code}' class InvalidToken(Exception): def __init__(self, token): super(InvalidToken, self).__init__() self.token = token def __str__(self): return f'invalid token: {self.token}' class InvalidCredentials(Exception): def __init__(self, access_key: str, access_secret: str): super(InvalidCredentials, self).__init__() self.access_key = access_key self.access_secret = access_secret def __str__(self): return f'invalid credentials, access_key: {self.access_key}' class BaseAuthenticationClient(abc.ABC): @abc.abstractmethod def build_authorize_url(self, scope: str, state: str) -> str: ... @abc.abstractmethod def get_token_by_code(self, code: str) -> str: ... @abc.abstractmethod def get_token_by_credentials(self, scope: str, access_key: str, secret_key: str) -> str: ... @abc.abstractmethod def get_user_identity_by_token(self, token: str) -> str: ... class PingCAPAuthenticationClient(BaseAuthenticationClient): def __init__(self, auth_app_id, auth_app_host, auth_app_secret, redirect_uri=None): super(PingCAPAuthenticationClient, self).__init__() self.client = AuthenticationClient( auth_app_id, auth_app_host, auth_app_secret, redirect_uri=redirect_uri, ) def build_authorize_url(self, scope: str, state: str) -> str: return self.client.build_authorize_url(scope=scope, state=state) def get_token_by_code(self, code: str) -> str: r = self.client.get_access_token_by_code(code) if 'access_token' not in r: raise InvalidCode(code) return jwt.encode({ 'access_token': r['access_token'] }, '') def get_token_by_credentials(self, scope: str, access_key: str, secret_key: str) -> str: r = self.client.get_access_token_by_client_credentials(scope, access_key, secret_key) if 'access_token' not in r: raise InvalidCredentials(access_key, secret_key) # the access_token is not useful in this case return jwt.encode({ 'access_key': access_key, }, '') def get_user_identity_by_token(self, token: str) -> str: payload = jwt.decode(token, options={"verify_signature": False}) if 'access_key' in payload: return payload['access_key'] if 'access_token' in payload: user_info = self.client.get_user_info_by_access_token(payload['access_token']) return user_info['profile'] raise InvalidToken(token) class CommunityAuthenticationClient(BaseAuthenticationClient): def __init__(self, auth_app_id, auth_app_host, auth_app_secret, redirect_uri=None): super(CommunityAuthenticationClient, self).__init__() self.auth_app_id = auth_app_id self.auth_app_host = auth_app_host self.auth_app_secret = auth_app_secret self.redirect_uri = redirect_uri def build_authorize_url(self, scope: str, state: str) -> str: query = urlencode({ 'client_id': self.auth_app_id, 'redirect_uri': self.redirect_uri, 'scope': scope, 'state': state, 'nonce': str(uuid.uuid4()), 'response_type': 'code', }) return f'{self.auth_app_host}/o/auth?{query}' def get_token_by_code(self, code: str) -> str: payload = { 'grant_type': 'authorization_code', 'client_id': self.auth_app_id, 'client_secret': self.auth_app_secret, 'redirect_uri': self.redirect_uri, 'code': code, } response = requests.post(f'{self.auth_app_host}/o/token', data=payload).json() if 'id_token' not in response: raise InvalidCode(code) return response['id_token'] def get_token_by_credentials(self, scope: str, access_key: str, secret_key: str) -> str: raise NotImplemented('get_token_by_credentials is not supported by CommunityAuthenticationClient') def get_user_identity_by_token(self, token: str) -> str: payload = jwt.decode(token, options={"verify_signature": False}) return payload['email']