Ejemplo n.º 1
0
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)
Ejemplo n.º 2
0
 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.")
Ejemplo n.º 3
0
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()
Ejemplo n.º 4
0
    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"))
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


# Generic API Sending
def make_api_call(method, url, token, payload=None, parameters=None):
    # Send these headers with all API calls
Ejemplo n.º 6
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
Ejemplo n.º 7
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, 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)
Ejemplo n.º 8
0
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