""" Create and delete Auth0 machine-to-machine applications (clients) ## Python SDK * https://github.com/auth0/auth0-python ## Auth0 management api docs * https://auth0.com/docs/api/management/v2 """ import argparse import copy import json import math import os import sys import time from typing import Any, Dict from auth0.authentication import GetToken from auth0.management import Clients as MgmtClientsAPI from environs import Env AUTH_MGMT_RATE_LIMIT_SLEEP_TIME = 1 # 1 SECOND ENV_FILE = "./.env" POST_CLIENT_BODY_TEMPLATE = { "is_token_endpoint_ip_header_trusted": False, "name": None, "is_first_party": True, "oidc_conformant": True, "sso_disabled": False, "cross_origin_auth": False, "description": "Test machine-to-machine client_credentials example", "logo_uri": "", "sso": False, "callbacks": [], "allowed_logout_urls": [], "allowed_clients": [], "allowed_origins": [], "jwt_configuration": { "alg": "RS256", "lifetime_in_seconds": 36000, "secret_encoded": False }, "token_endpoint_auth_method": "client_secret_post", "app_type": "non_interactive", "grant_types": [ "authorization_code", "implicit", "refresh_token", "client_credentials" ], "web_origins": [], "custom_login_page_on": True, "client_metadata": { "m2m_identity_uuid": None } } class Auth0Connector: """ Wrapper class for the auth-management-api client """ def __init__(self, auth0_mgmt_client_api: MgmtClientsAPI, m2m_json_filename: str): """Constructor.""" self.m2m_configs = self.load_m2m_json(m2m_json_filename) self.auth0_mgmt_client_api = auth0_mgmt_client_api def load_m2m_json(self, m2m_json_filename): """Load the JSON file.""" with open(m2m_json_filename) as fp: datums = json.load(fp) print(f"Found {len(datums)} entries in {m2m_json_filename}") return datums def to_client_dict_entry(self, clients_list): """Convert a /v2/clients/get-clients list to a dictionary keyed by name.""" out_di = {} for _client in clients_list: k = _client["name"] out_di[k] = _client return out_di def get_clients(self, page_size=100, debug=False): """ https://auth0.com/docs/api/management/v2/clients/get-clients#response-messages """ client_dict = {} requested_app_fields = ["name", "client_id", "client_metadata"] response = self.auth0_mgmt_client_api.all( fields=requested_app_fields, # NOTE: Could also add 'client_secret' page=0, per_page=page_size, extra_params={ "include_totals": 'true', "app_type": "non_interactive" } ) total = response['total'] total_pages = int(math.ceil(total / page_size)) if debug: print(f"total: {total}, total_pages: {total_pages}, page_size={page_size}") print(f"Page 0: {[c['name'] for c in response['clients']]}") client_dict.update( self.to_client_dict_entry(response["clients"]) ) for nextpage in range(1, total_pages): # Calling GET too frequently causes a 429 error. So sleeeeeeep. time.sleep(AUTH_MGMT_RATE_LIMIT_SLEEP_TIME) response = self.auth0_mgmt_client_api.all( fields=requested_app_fields, page=nextpage, per_page=page_size, extra_params={ "include_totals": 'true', "app_type": "non_interactive" } ) if debug: print(f"Page {nextpage}: {[c['name'] for c in response['clients']]}") client_dict.update( self.to_client_dict_entry(response["clients"]) ) return client_dict def create_client(self, m2mconfig: Dict[str, Any]) -> Any: """ https://auth0.com/docs/api/v2#!/Clients/post_clients """ _deployed_clients_dict = self.get_clients() name = m2mconfig["name"] client_metadata = m2mconfig["client_metadata"] # post-clients does not support a uniqueness constraint on application name. # Fetch the list of deployed clients and check if the name already exists. if name in _deployed_clients_dict: print(f"[create_client] Application with name already exists: {name}, {_deployed_clients_dict[name]}") return body = copy.deepcopy(POST_CLIENT_BODY_TEMPLATE) body["name"] = name body["client_metadata"] = client_metadata response = self.auth0_mgmt_client_api.create(body=body) return response def delete_client(self, m2mconfig: Dict[str, Any]) -> Any: """ https://auth0.com/docs/api/management/v2#!/Clients/delete_clients_by_id """ _deployed_clients_dict = self.get_clients() name = m2mconfig["name"] if name not in _deployed_clients_dict: print("*****") print(f"Application with name does not exist: {name}") return client_id = _deployed_clients_dict[name]['client_id'] print(f"Attempting to delete name: '{name}' id: '{client_id}'") response = self.auth0_mgmt_client_api.delete(id=client_id) print(f"Delete succeeded for: {name}") return response def create_clients(self): """ Create M2M Auth0 applications """ for _config in self.m2m_configs: _client = self.create_client(m2mconfig=_config) time.sleep(AUTH_MGMT_RATE_LIMIT_SLEEP_TIME) if _client: print(f"Created name={_client['name']}, client_id={_client['client_id']}, " f"metadata='{_client.get('client_metadata', {})}") def delete_clients(self) -> None: """ Delete M2M Auth0 applications """ for _config in self.m2m_configs: self.delete_client(m2mconfig=_config) time.sleep(AUTH_MGMT_RATE_LIMIT_SLEEP_TIME) def load_env() -> Env: env = Env() env.read_env(ENV_FILE, recurse=False) assert env('AUTH0_DOMAIN') assert env('AUTH0_CLI_MACHINE_CLIENT_ID') return env def get_auth0_mgmt_client_api(env: Env) -> MgmtClientsAPI: domain = env('AUTH0_DOMAIN') mgmt_api_token = _get_mgmt_token(env=env) return MgmtClientsAPI(domain, mgmt_api_token) def _get_mgmt_token(env: Env) -> str: """ /oauth/token endpoint to fetch the app credentials """ domain = env('AUTH0_DOMAIN') non_interactive_client_id = env('AUTH0_CLI_MACHINE_CLIENT_ID') non_interactive_client_secret = env('AUTH0_CLI_MACHINE_CLIENT_SECRET') get_token = GetToken(domain, non_interactive_client_id, client_secret=non_interactive_client_secret) token = get_token.client_credentials('https://{}/api/v2/'.format(domain)) return token['access_token'] def parse_args(): parser = argparse.ArgumentParser(description="POC for create M2M apps in Auth0 using the PySDK.") parser.add_argument("--mode", choices=('delete', 'create'), required=True, help="DELETE or CREATE entries in auth0") parser.add_argument("filename", help="m2m.json file path") return parser.parse_args() def main(): args = parse_args() if not os.path.isfile(ENV_FILE): print(f"File does not exist: '{os.path.abspath(ENV_FILE)}'") sys.exit(1) filename = os.path.abspath(args.filename) if not os.path.isfile(filename): print(f"File does not exist: '{filename}'") sys.exit(1) env = load_env() # Management API Client auth0_mgmt_client_api = get_auth0_mgmt_client_api(env=env) auth0_connector = Auth0Connector(auth0_mgmt_client_api=auth0_mgmt_client_api, m2m_json_filename=filename) # Create or Delete command if args.mode == "create": auth0_connector.create_clients() elif args.mode == "delete": auth0_connector.delete_clients() if __name__ == '__main__': main()