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 _get_token(): auth_client = PublicClientApplication( client_id=current_app.config["AUTH_CLIENT_ID"], authority=current_app.config["AUTH_CLIENT_TENANCY"]) auth_flow = auth_client.initiate_device_flow( scopes=current_app.config["AUTH_CLIENT_SCOPES"]) click.pause( f"To sign-in, visit 'https://microsoft.com/devicelogin', enter this code '{auth_flow['user_code']}' and then press any key..." ) auth_payload = auth_client.acquire_token_by_device_flow(auth_flow) current_app.config["AUTH_TOKEN"] = auth_payload["access_token"] click.echo(current_app.config["AUTH_TOKEN"]) click.echo(f"Ok. Access token set.")
def get_token_by_device_flow(app: msal.PublicClientApplication, token_scope: List[str]) -> str: flow = app.initiate_device_flow(scopes=token_scope) if "user_code" not in flow: raise ValueError("Fail to create device flow. Err: %s" % json.dumps(flow, indent=4)) print(flow["message"]) print( "Program execution will continue automatically after authentication.") # This function polls every 5 seconds to see if the user has completed the authentication result = app.acquire_token_by_device_flow(flow) if "access_token" in result: return result["access_token"] else: print(result.get("error")) print(result.get("error_description")) raise ValueError()
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"))
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 # Generic API Sending def make_api_call(method, url, token, payload=None, parameters=None): # Send these headers with all API calls headers = { 'User-Agent': 'python_tutorial/1.0',
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 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