import string import uuid from urllib.parse import urlencode, urlunsplit import backoff import requests from flask import url_for from atlas_um.helpers.either import Left, Right from atlas_um.logs import logger class USMClient: RANDOM_CHARS = string.ascii_letters + string.digits def __init__( self, app=None, hostname=None, client_id=None, client_secret=None, authorize_path=None, token_path=None, logout_path=None, graph_path=None, external_user_path=None, util_path=None, ): self.app = app if app is not None: self.init_app(app) self.hostname = hostname self.client_id = client_id self.client_secret = client_secret self.authorize_path = authorize_path self.token_path = token_path self.logout_path = logout_path self.graph_path = graph_path self.external_user_path = external_user_path self.util_path = util_path def init_app(self, app): self.hostname = self.hostname or app.config.get("USM_HOSTNAME") self.client_id = self.client_id or app.config.get("USM_CLIENT_ID") self.client_secret = self.client_secret or app.config.get( "USM_CLIENT_SECRET" ) self.authorize_path = self.authorize_path or app.config.get( "USM_AUTHORIZE_PATH" ) self.token_path = self.token_path or app.config.get("USM_TOKEN_PATH") self.logout_path = self.logout_path or app.config.get( "USM_LOGOUT_PATH" ) self.graph_path = self.graph_path or app.config.get("USM_GRAPH_PATH") self.external_user_path = self.external_user_path or app.config.get( "USM_EXTERNAL_USER_PATH" ) self.util_path = self.util_path or app.config.get("USM_UTIL_PATH") app.extensions["usm"] = self def generate_state(self, length=8): return uuid.uuid4().hex[:length] def generate_nonce(self, length=8): return uuid.uuid4().hex[:length] def authentication_url(self, state, nonce, prompt=None): query = { "response_type": "code", "scope": "openid email", "client_id": self.client_id, "state": state, "redirect_uri": url_for( "usm_login.get_login_callback", _external=True, _scheme="https" ), "nonce": nonce, } if prompt: query["prompt"] = prompt url = urlunsplit( ( "https", self.hostname, self.authorize_path, urlencode(query), None, ) ) return url def logout_url(self, usm_id_token, state): query = { "id_token_hint": usm_id_token, "state": state, "post_logout_redirect_uri": url_for( "usm_logout.get_logout_callback", _external=True, _scheme="https", ), } url = urlunsplit( ("https", self.hostname, self.logout_path, urlencode(query), None) ) return url def fetch_access_token(self, code): url = urlunsplit(("https", self.hostname, self.token_path, None, None)) accept = { "Accept": "application/json", } payload = { "grant_type": "authorization_code", "code": code, "redirect_uri": url_for( "usm_login.get_login_callback", _external=True, _scheme="https" ), "client_id": self.client_id, "client_secret": self.client_secret, } resp = requests.post(url, headers=accept, data=payload) if resp.ok: logger.info("Access token granted by USM") return Right(resp.json()) else: logger.bind(resp=resp.text, headers=resp.headers).warning( "Access token denied by USM" ) return Left(resp.text) def search_users(self, search_term): endpoint = f"{self.graph_path.rstrip('/')}/searchUsers" url = urlunsplit(("https", self.hostname, endpoint, None, None)) accept = { "Accept": "application/json", } payload = { "client_id": self.client_id, "client_secret": self.client_secret, "search_text": search_term, } resp = requests.post(url, headers=accept, data=payload) try: content = resp.json() except ValueError: content = {} if not resp.ok or not content.get("success", False): logger.bind(endpoint=endpoint, resp=resp.text).error( "Error response from the endpoint" ) return Left(resp.text) return Right(content.get("data", [])) @backoff.on_exception( backoff.expo, requests.exceptions.RequestException, max_tries=3 ) def get_user(self, email): """Endpoint to get internal users info.""" endpoint = f"{self.graph_path.rstrip('/')}/getUser" url = urlunsplit(("https", self.hostname, endpoint, None, None)) accept = { "Accept": "application/json", } payload = { "client_id": self.client_id, "client_secret": self.client_secret, "email": email, } resp = requests.post(url, headers=accept, data=payload) try: content = resp.json() except ValueError: content = {} if not resp.ok or not content.get("success", False): logger.warning( f"Error response from the endpoint {endpoint}: {resp.text}" ) return Left(resp.text) return Right(content.get("data", {})) def get_internal_domains(self): endpoint = f"{self.util_path.rstrip('/')}/findAllSonyDomains" url = urlunsplit(("https", self.hostname, endpoint, None, None)) accept = { "Accept": "application/json", } payload = { "client_id": self.client_id, "client_secret": self.client_secret, } resp = requests.post(url, headers=accept, data=payload) try: content = resp.json() except ValueError: content = {} if not resp.ok or not content.get("success", False): logger.warning( f"Error response from the endpoint {endpoint}: {resp.text}" ) return Left(resp.text) return Right(content.get("data", {})) @backoff.on_exception( backoff.expo, requests.exceptions.RequestException, max_tries=3 ) def lookup_user(self, email): """Endpoint to get external user info.""" endpoint = f"{self.external_user_path.rstrip('/')}/lookup" url = urlunsplit(("https", self.hostname, endpoint, None, None)) accept = { "Accept": "application/json", } payload = { "client_id": self.client_id, "client_secret": self.client_secret, "email": email, } resp = requests.post(url, headers=accept, data=payload) try: content = resp.json() except ValueError: content = {} if not resp.ok or not content.get("success", False): logger.warning( f"Error response from the endpoint {endpoint}: {resp.text}" ) return Left(resp.text) return Right(content.get("data", {})) def save_user(self, email, first_name, last_name, admin_email): endpoint = f"{self.external_user_path.rstrip('/')}/save" url = urlunsplit(("https", self.hostname, endpoint, None, None)) accept = { "Accept": "application/json", } payload = { "client_id": self.client_id, "client_secret": self.client_secret, "email": email, "client_admin_user_email": admin_email, "firstName": first_name, "lastName": last_name, } resp = requests.post(url, headers=accept, data=payload) try: content = resp.json() except ValueError: content = {} if not resp.ok or not content.get("success", False): logger.warning( f"Error response from the endpoint {endpoint}: {resp.text}" ) return Left(resp.text) return Right(content.get("data", {})) def reset_password(self, email, admin_email): endpoint = f"{self.external_user_path.rstrip('/')}/password-reset" url = urlunsplit(("https", self.hostname, endpoint, None, None)) accept = { "Accept": "application/json", } payload = { "client_id": self.client_id, "client_secret": self.client_secret, "email": email, "client_admin_user_email": admin_email, } resp = requests.post(url, headers=accept, data=payload) try: content = resp.json() except ValueError: content = {} if not resp.ok or not content.get("success", False): logger.warning( f"Error response from the endpoint {endpoint}: {resp.text}" ) return Left(resp.text) return Right(content.get("data", {}).get("link"))