async def get_current_user( auth_settings: AuthSettings = Depends(get_auth_settings), msal_app: msal.PublicClientApplication = Depends(get_public_app), authorization: Optional[str] = Header(None), x_ms_token_aad_id_token: Optional[str] = Header(None), ): tenant = urlparse(auth_settings.b2c_endpoint).path.lstrip('/') scopes = [ f'https://{tenant}/{auth_settings.b2c_client_id}/user.impersonate', ] if payload := msal_app.acquire_token_silent(scopes, None): return f'{payload["given_name"]} {payload["family_name"]}'
class InteractiveLoginTokenProvider(TokenProviderBase): """Acquire a token from MSAL with Device Login flow""" def __init__(self, kusto_uri: str, authority_uri: str, login_hint: Optional[str] = None, domain_hint: Optional[str] = None): super().__init__(kusto_uri) self._msal_client = None self._auth = authority_uri self._login_hint = login_hint self._domain_hint = domain_hint self._account = None @staticmethod def name() -> str: return "InteractiveLoginTokenProvider" def context(self) -> dict: return {"authority": self._auth, "client_id": self._cloud_info.kusto_client_app_id} def _init_impl(self): self._msal_client = PublicClientApplication(client_id=self._cloud_info.kusto_client_app_id, authority=self._auth) def _get_token_impl(self) -> dict: token = self._msal_client.acquire_token_interactive( scopes=self._scopes, prompt=TokenConstants.MSAL_INTERACTIVE_PROMPT, login_hint=self._login_hint, domain_hint=self._domain_hint ) return self._valid_token_or_throw(token) def _get_token_from_cache_impl(self) -> dict: account = None accounts = self._msal_client.get_accounts(self._login_hint) if len(accounts) > 0: account = accounts[0] token = self._msal_client.acquire_token_silent(scopes=self._scopes, account=account) return self._valid_token_or_none(token)
class UserPassTokenProvider(TokenProviderBase): """ Acquire a token from MSAL with username and password """ def __init__(self, kusto_uri: str, authority_uri: str, username: str, password: str): super().__init__(kusto_uri) self._msal_client = None self._auth = authority_uri self._user = username self._pass = password @staticmethod def name() -> str: return "UserPassTokenProvider" def context(self) -> dict: return {"authority": self._auth, "client_id": self._cloud_info.kusto_client_app_id, "username": self._user} def _init_impl(self): self._msal_client = PublicClientApplication(client_id=self._cloud_info.kusto_client_app_id, authority=self._auth) def _get_token_impl(self) -> dict: token = self._msal_client.acquire_token_by_username_password(username=self._user, password=self._pass, scopes=self._scopes) return self._valid_token_or_throw(token) def _get_token_from_cache_impl(self) -> dict: account = None if self._user is not None: accounts = self._msal_client.get_accounts(self._user) if len(accounts) > 0: account = accounts[0] token = self._msal_client.acquire_token_silent(scopes=self._scopes, account=account) return self._valid_token_or_none(token)
class DeviceLoginTokenProvider(CloudInfoTokenProvider): """Acquire a token from MSAL with Device Login flow""" def __init__(self, kusto_uri: str, authority_id: str, device_code_callback=None, is_async: bool = False): super().__init__(kusto_uri, is_async) self._msal_client = None self._auth = authority_id self._account = None self._device_code_callback = device_code_callback @staticmethod def name() -> str: return "DeviceLoginTokenProvider" def _context_impl(self) -> dict: return { "authority": self._cloud_info.authority_uri(self._auth), "client_id": self._cloud_info.kusto_client_app_id } def _init_impl(self): self._msal_client = PublicClientApplication( client_id=self._cloud_info.kusto_client_app_id, authority=self._cloud_info.authority_uri(self._auth), proxies=self._proxy_dict) def _get_token_impl(self) -> Optional[dict]: flow = self._msal_client.initiate_device_flow(scopes=self._scopes) try: if self._device_code_callback: self._device_code_callback( flow[TokenConstants.MSAL_DEVICE_MSG]) else: print(flow[TokenConstants.MSAL_DEVICE_MSG]) webbrowser.open(flow[TokenConstants.MSAL_DEVICE_URI]) except KeyError: raise KustoClientError("Failed to initiate device code flow") token = self._msal_client.acquire_token_by_device_flow(flow) # Keep the account for silent login if self._valid_token_or_none(token) is not None: accounts = self._msal_client.get_accounts() if len(accounts) == 1: self._account = accounts[0] return self._valid_token_or_throw(token) def _get_token_from_cache_impl(self) -> dict: token = self._msal_client.acquire_token_silent(scopes=self._scopes, account=self._account) return self._valid_token_or_none(token)
def access_account(token): if not token: return "" else: ''' tenantid and appid are Azure AD internal data, it is internal information, do not leave it exposed in the code, it is here to use as an example I advise to have it recorded in a safe.''' appid = "your_app_id" tenantid = "yout_tenant_id" ''' The username and password encoding was used base64 but you can use one of your own.''' auth_decode = base64.b64decode(token).decode() username, password = auth_decode.split(":") if appid and tenantid: ''' O scopo vocĂȘ define dentro do Azure AD. ''' scope = ["User.ReadBasic.All"] app = PublicClientApplication( appid, authority="https://login.microsoftonline.com/" + tenantid) result = None accounts = app.get_accounts(username=username) if accounts: logging.info( "Account(s) exists in cache, probably with token too. Let's try." ) result = app.acquire_token_silent(scope, account=accounts[0]) if not result: logging.info( "No suitable token exists in cache. Let's get a new one from AAD." ) result = app.acquire_token_by_username_password(username, password, scopes=scope) ''' If the validation is successful it returns the token, otherwise it prints the errors found. ''' if "access_token" in result: return result else: print(result.get("error")) print(result.get("error_description")) print(result.get("correlation_id")) if 65001 in result.get("error_codes", []): print("Visit this to consent:", app.get_authorization_request_url(scope)) return "" else: return ""
def get_token_from_cache(app: msal.PublicClientApplication, token_scope: List[str]) -> Union[str, None]: """Try to get an API access token from a local cache. If the access token is expired a refresh token will automatically be used to get a new access token. If the refresh token is expired then the user will have to reauthenticate.""" accounts = app.get_accounts() if accounts: result = app.acquire_token_silent(token_scope, account=accounts[0]) # Method returns None if no token can be acquired if result and "access_token" in result: return result["access_token"] else: return None
def get_access_token(self): '''Get access token from cache or request new''' cache = SerializableTokenCache() if os.path.exists('token_cache.bin'): cache.deserialize(open('token_cache.bin', 'r').read()) if cache.has_state_changed: atexit.register( lambda: open('token_cache.bin', 'w').write(cache.serialize())) app = PublicClientApplication(self.CLIENT_ID, authority=self.AUTHORITY, token_cache=cache) token_response = None accounts = app.get_accounts() if accounts: print("Pick the account you want to use to proceed:") for index, account in enumerate(accounts): print(index, account["username"]) account_nr = int(input("Type number: ")) chosen = accounts[account_nr] token_response = app.acquire_token_silent(["Notes.Read"], account=chosen) if not token_response: print('Trying to get token...') flow = app.initiate_device_flow(scopes=["Notes.Read"]) print(flow['message']) if 'enter the code ' in flow['message']: auth_code = flow['message'].split( 'enter the code ')[1].split()[0] pyperclip.copy(auth_code) print(f'Code {auth_code} has been copied to clipboard.') token_response = app.acquire_token_by_device_flow(flow) if "access_token" in token_response: return token_response["access_token"] else: print(token_response.get("error")) print(token_response.get("error_description")) print(token_response.get("correlation_id"))
def msal_connect(self): app = PublicClientApplication(self.client_id, authority=self.authority) result = None accounts = app.get_accounts() if accounts: # If so, you could then somehow display these accounts and let end user choose print("Pick the account you want to use to proceed:") for a in accounts: print(a["username"]) # Assuming the end user chose this one chosen = accounts[0] # Now let's try to find a token in cache for this account result = app.acquire_token_silent(self.scopes, account=chosen) if not result: # So no suitable token exists in cache. Let's get a new one from AAD. result = app.acquire_token_by_username_password(self.username, self.password, scopes=self.scopes) return result
def get_access_token(self): result = None app = PublicClientApplication( self.config['CLIENT_ID'], authority=self.config['AUTHORITY'], ) accounts = app.get_accounts() if accounts: chosen_account = accounts[0] result = app.acquire_token_silent(self.config['scopes'], account=chosen_account) if not result: result = app.acquire_token_by_username_password( self.config['USERNAME'], self.config['SECRET'], scopes=self.config['scopes'] ) print(result) return result['access_token']
def get_access_token(self): result = None app = PublicClientApplication( self.config['CLIENT_ID'], authority=self.config['AUTHORITY'], ) accounts = app.get_accounts() if accounts: chosen_account = accounts[0] result = app.acquire_token_silent(self.config['scopes'], account=chosen_account) if not result: result = app.acquire_token_by_username_password( self.config['USERNAME'], self.config['SECRET'], scopes=self.config['scopes']) print(result) return result['access_token']
client_secret = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' directory_id = 'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXXX' # Also known as: tenant authority = 'https://login.microsoftonline.com/' + directory_id username = '******' # Office365/Outlook Email account result = None graph_endpoint = 'https://graph.microsoft.com/v1.0{0}' app = PublicClientApplication(client_id, client_credential=None, authority=authority) # We now check the cache to see # whether we already have some accounts that the end user already used to sign in before. accounts = app.get_accounts() if accounts: result = app.acquire_token_silent(config["scope"], account=username) if not result: # So no suitable token exists in cache. Let's get a new one from AAD. flow = app.initiate_device_flow(scopes=["User.Read", "Calendars.Read"]) print(flow) result = app.acquire_token_by_device_flow(flow) if "access_token" in result: print(result["access_token"]) # Yay! else: print(result.get("error")) print(result.get("error_description")) print( result.get("correlation_id")) # You may need this when reporting a bug
class AADClient(object): """ This object uses the Microsoft Authentication Library for Python (MSAL) to log in and authenticate users using AAD. The only public method is get_access_token(), which returns an access token if one already exists in the cache, else it will fill the cache by prompting the user to log in. """ def __init__(self, url: str): self._base_url = url self.cache = BonsaiTokenCache() atexit.register(write_cache_to_file, self.cache) retry_count = 1 while True: try: self._app = PublicClientApplication(_AAD_CLIENT_ID, authority=_AAD_AUTHORITY, token_cache=self.cache) if self._app: break except ConnectionError as e: log.info('ConnectionError on attempt {} to ' 'create msal PublicClientApplication, ' 'retrying...'.format(retry_count)) if retry_count >= 5: raise e retry_count += 1 def _log_in_with_device_code(self) -> dict: """ Recommended login method. The user must open a browser to https://microsoft.com/devicelogin and enter a unique device code to begin authentication. """ flow = self._app.initiate_device_flow(_AAD_SCOPE) print(flow["message"]) sys.stdout.flush() # needed to print on Windows return self._app.acquire_token_by_device_flow(flow) def _log_in_with_password(self) -> dict: """ This login method is less secure and should be used for automation only. """ return self._app.acquire_token_by_username_password( os.environ['BONSAI_AAD_USER'], os.environ['BONSAI_AAD_PASSWORD'], _AAD_SCOPE) def _get_access_token_from_cache(self): """ This also does a token refresh if the access token has expired. """ result = None accounts = self._app.get_accounts() if accounts: """ Bonsai config only gives us the short username, and token cache stores accounts by email address (e.g. soc-auto vs [email protected]). So, if there are multiple accounts, assume the first one for now. """ chosen = accounts[0] result = self._app.acquire_token_silent(_AAD_SCOPE, account=chosen) return result def get_access_token(self): # attempt to get token from cache token = self._get_access_token_from_cache() if token: return 'Bearer {}'.format(token['access_token']) # no token found in cache, user must sign in and try again if (use_password_auth()): self._log_in_with_password() else: print('No access token found in cache, please sign in.') self._log_in_with_device_code() token = self._get_access_token_from_cache() if token: return "Bearer {}".format(token['access_token']) message = 'Error: could not fetch AAD access token after login.' raise AuthenticationError(message) def get_workspace(self): # make sure we have access token to request workspace auth_token = self.get_access_token() # get workspace, store to cache and return helper = AADRequestHelper(self._base_url, auth_token) self.workspace = helper.get_workspace() return self.workspace
class AADClient(object): """ This object uses the Microsoft Authentication Library for Python (MSAL) to log in and authenticate users using AAD. The only public method is get_access_token(), which returns an access token if one already exists in the cache, else it will fill the cache by prompting the user to log in. """ def __init__(self, tenant_id: Optional[str] = None): # cache file should be written to home directory home = os.path.expanduser('~') if home: self._cache_file = os.path.join(home, '.bonsaicache') else: raise RuntimeError('Unable to find home directory.') self.cache = TokenCache(self._cache_file) retry_count = 1 effective_tenant_id = tenant_id if tenant_id is not None and tenant_id != 'None' else 'organizations' _AAD_AUTHORITY = 'https://login.microsoftonline.com/' + effective_tenant_id while True: try: self._app = PublicClientApplication(_AAD_CLIENT_ID, authority=_AAD_AUTHORITY, token_cache=self.cache) if self._app: break except ConnectionError as e: log.info('ConnectionError on attempt {} to ' 'create msal PublicClientApplication, ' 'retrying...'.format(retry_count)) if retry_count >= 5: raise e retry_count += 1 def _log_in_with_device_code(self) -> Dict[str, str]: """ Recommended login method. The user must open a browser to https://microsoft.com/devicelogin and enter a unique device code to begin authentication. """ flow = self._app.initiate_device_flow(_AAD_SCOPE) print(flow["message"]) sys.stdout.flush() # needed to print on Windows return self._app.acquire_token_by_device_flow(flow) def _log_in_with_password(self) -> Dict[str, str]: """ This login method is less secure and should be used for automation only. """ return self._app.acquire_token_by_username_password( os.environ['BONSAI_AAD_USER'], os.environ['BONSAI_AAD_PASSWORD'], _AAD_SCOPE) def _get_access_token_from_cache(self): """ This also does a token refresh if the access token has expired. """ result = None accounts = self._app.get_accounts() if accounts: """ Bonsai config only gives us the short username, and token cache stores accounts by email address (e.g. soc-auto vs [email protected]). So, if there are multiple accounts, assume the first one for now. """ chosen = accounts[0] result = self._app.acquire_token_silent(_AAD_SCOPE, account=chosen) return result def get_access_token(self): # attempt to get token from cache token = self._get_access_token_from_cache() if token: return 'Bearer {}'.format(token['access_token']) # no token found in cache, user must sign in and try again if use_password_auth(): self._log_in_with_password() else: print('No access token found in cache, please sign in.') self._log_in_with_device_code() token = self._get_access_token_from_cache() if token: return "Bearer {}".format(token['access_token']) message = 'Error: could not fetch AAD access token after login.' raise AuthenticationError(message)
class AzCliTokenProvider(TokenProviderBase): """ AzCli Token Provider obtains a refresh token from the AzCli cache and uses it to authenticate with MSAL """ def __init__(self, kusto_uri: str): super().__init__(kusto_uri) self._msal_client = None self._client_id = None self._authority_uri = None self._username = None @staticmethod def name() -> str: return "AzCliTokenProvider" def context(self) -> dict: return {"authority:": self.name()} def _init_impl(self): pass def _get_token_impl(self) -> dict: # try and obtain the refresh token from AzCli refresh_token = None token = None stored_token = self._get_azure_cli_auth_token() if TokenConstants.AZ_REFRESH_TOKEN in stored_token and TokenConstants.AZ_CLIENT_ID in stored_token and TokenConstants.AZ_AUTHORITY in stored_token: refresh_token = stored_token[TokenConstants.AZ_REFRESH_TOKEN] self._client_id = stored_token[TokenConstants.AZ_CLIENT_ID] self._authority_uri = stored_token[TokenConstants.AZ_AUTHORITY] self._username = stored_token[TokenConstants.AZ_USER_ID] else: raise KustoClientError("Unable to obtain a refresh token from Az-Cli") if self._msal_client is None: self._msal_client = PublicClientApplication(client_id=self._client_id, authority=self._authority_uri) if refresh_token is not None: token = self._msal_client.acquire_token_by_refresh_token(refresh_token, self._scopes) return self._valid_token_or_throw(token) def _get_token_from_cache_impl(self) -> dict: token = None if self._msal_client is not None: account = None if self._username is not None: accounts = self._msal_client.get_accounts(self._username) if len(accounts) > 0: account = accounts[0] token = self._msal_client.acquire_token_silent(scopes=self._scopes, account=account, client_id=self._client_id, authority=self._authority_uri) return self._valid_token_or_none(token) @staticmethod def _get_azure_cli_auth_token() -> dict: """ Try to get the az cli authenticated token :return: refresh token """ import os try: # this makes it cleaner, but in case azure cli is not present on virtual env, # but cli exists on computer, we can try and manually get the token from the cache from azure.cli.core._profile import Profile from azure.cli.core._session import ACCOUNT from azure.cli.core._environment import get_config_dir azure_folder = get_config_dir() ACCOUNT.load(os.path.join(azure_folder, "azureProfile.json")) profile = Profile(storage=ACCOUNT) token_data = profile.get_raw_token()[0][2] return token_data except ModuleNotFoundError: try: import os import json folder = os.getenv("AZURE_CONFIG_DIR", None) or os.path.expanduser(os.path.join("~", ".azure")) token_path = os.path.join(folder, "accessTokens.json") with open(token_path) as f: data = json.load(f) # TODO: not sure I should take the first return data[0] except Exception as e: raise KustoClientError("Azure cli token was not found. Please run 'az login' to setup account.", e)
class MsalConnection(AppConnection): """ Implementation of the Connection class using msal """ def __init__(self, app_id: str, tenant_id: str, app_secret: str = None, username: str = None, password: str = None, resource: str = DEFAULT_RESOURCE, api_ver: str = DEFAULT_API_VER): super().__init__(app_id=app_id, app_secret=app_secret, tenant_id=tenant_id, resource=resource, api_ver=api_ver) self.username = username self.password = password self.scopes = DEFAULT_SCOPES self.app = None def getAccessToken(self) -> Optional[str]: if self.app_secret: return self.getConfidentialClientAccessToken() return self.getPublicAccessToken() def getConfidentialClientAccessToken(self) -> Optional[str]: # Initialise the app if not already exist if not self.app: logging.info("Initialise msal connection app") self.app = ConfidentialClientApplication( client_id=self.app_id, authority=self.authority, client_credential=self.app_secret) # try to get the token from cache if already exist result = self.app.acquire_token_silent(scopes=self.scopes, account=None) if not result: logging.info( "No suitable token exists in cache. Let's get a new one from AAD." ) result = self.app.acquire_token_for_client(scopes=self.scopes) if "access_token" in result: return result["access_token"] return None def getDeviceFlowAccessToken(self): if not self.app: # must have app initialised before calling that method print( "App must be initialised before calling getDeviceFlowAccessToken" ) return None flow = self.app.initiate_device_flow(scopes=self.scopes) print(flow["message"]) print(flow["verification_uri"]) print(flow["user_code"]) return self.app.acquire_token_by_device_flow(flow) def getUsernamePasswordAccessToken(self): if not self.app: # must have app initialised before calling that method print( "App must be initialised before calling getUsernamePasswordAccessToken" ) return None return self.app.acquire_token_by_username_password(self.username, self.password, scopes=self.scopes) def getPublicAccessToken(self) -> Optional[str]: # Initialise the app if not already exist if not self.app: print("Initialise msal connection app") self.app = PublicClientApplication(client_id=self.app_id, authority=self.authority) result = None accounts = self.app.get_accounts() if accounts: # TODO: need to pick the relevant account to proceed chosen = accounts[0] # try to get the token from cache if already exist result = self.app.acquire_token_silent(scopes=self.scopes, account=chosen) if not result: print( "No suitable token exists in cache. Let's get a new one from AAD." ) if self.username and self.password: result = self.getUsernamePasswordAccessToken() else: result = self.getDeviceFlowAccessToken() if "access_token" in result: return result["access_token"] return None