From 72cbfdf89d36da8e41f97a14979a3bd9f5674499 Mon Sep 17 00:00:00 2001 From: ArturKamalov Date: Wed, 17 Apr 2024 17:45:44 +0200 Subject: [PATCH 1/7] Add back the SharedTokenCacheCredential to handle previously cached token --- .../azure/quantum/_authentication/_default.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/azure-quantum/azure/quantum/_authentication/_default.py b/azure-quantum/azure/quantum/_authentication/_default.py index 014754be6..ced1965e6 100644 --- a/azure-quantum/azure/quantum/_authentication/_default.py +++ b/azure-quantum/azure/quantum/_authentication/_default.py @@ -16,6 +16,8 @@ InteractiveBrowserCredential, DeviceCodeCredential, _internal as AzureIdentityInternals, + TokenCachePersistenceOptions, + SharedTokenCacheCredential, ) from ._chained import _ChainedTokenCredential from ._token import _TokenFileCredential @@ -97,7 +99,18 @@ def _initialize_credentials(self): credentials.append(VisualStudioCodeCredential(authority=self.authority, tenant_id=self.tenant_id)) credentials.append(AzureCliCredential(tenant_id=self.tenant_id)) credentials.append(AzurePowerShellCredential(tenant_id=self.tenant_id)) - credentials.append(InteractiveBrowserCredential(authority=self.authority, tenant_id=self.tenant_id)) + # Before trying other credential types, try to use already cached token. + credentials.append(SharedTokenCacheCredential(authority=self.authority)) + credentials.append( + InteractiveBrowserCredential( + authority=self.authority, + tenant_id=self.tenant_id, + cache_persistence_options=TokenCachePersistenceOptions( + # Not all linux systems has preinstalled libsecret, which is required for storage encryption. + allow_unencrypted_storage=True + ) + ) + ) if self.client_id: credentials.append(DeviceCodeCredential(authority=self.authority, client_id=self.client_id, tenant_id=self.tenant_id)) self.credentials = credentials From c27461294c16b0ef4e234d10ec7f62b6b9418f0c Mon Sep 17 00:00:00 2001 From: XField <58103249+vxfield@users.noreply.github.com> Date: Wed, 17 Apr 2024 17:27:14 -0700 Subject: [PATCH 2/7] Caching of InteractiveBrowserCredential --- .../azure/quantum/_authentication/_default.py | 66 +++++++++++++++---- 1 file changed, 52 insertions(+), 14 deletions(-) diff --git a/azure-quantum/azure/quantum/_authentication/_default.py b/azure-quantum/azure/quantum/_authentication/_default.py index ced1965e6..3588748cb 100644 --- a/azure-quantum/azure/quantum/_authentication/_default.py +++ b/azure-quantum/azure/quantum/_authentication/_default.py @@ -16,8 +16,9 @@ InteractiveBrowserCredential, DeviceCodeCredential, _internal as AzureIdentityInternals, - TokenCachePersistenceOptions, - SharedTokenCacheCredential, + TokenCachePersistenceOptions, + SharedTokenCacheCredential, + _persistent_cache as AzureIdentityPersistentCache ) from ._chained import _ChainedTokenCredential from ._token import _TokenFileCredential @@ -86,33 +87,70 @@ def _authority_or_default(self, authority: str, arm_endpoint: str): return ConnectionConstants.DOGFOOD_AUTHORITY return ConnectionConstants.AUTHORITY - def _initialize_credentials(self): + def _get_cache_options(self) -> Optional[TokenCachePersistenceOptions]: + """ + Returns a valid TokenCachePersistenceOptions + if the AzureIdentity Persistent Cache is accessible. + Returns None otherwise. + """ + cache_options = TokenCachePersistenceOptions( + allow_unencrypted_storage=True, + name="AzureQuantumSDK" + ) + try: + # pylint: disable=protected-access + cache = AzureIdentityPersistentCache._load_persistent_cache(cache_options) + try: + _LOGGER.error( + 'Using Azure.Identity Token Cache at %s. ', + cache._persistence.get_location() + ) + except: # pylint: disable=bare-except + pass + return cache_options + except Exception as ex: # pylint: disable=broad-except + _LOGGER.warning( + 'Error trying to access Azure.Identity Token Cache at %s. ' + 'Raised unexpected exception:\n%s', + self.__class__._get_cache_options.__qualname__, + ex, + exc_info=_LOGGER.isEnabledFor(logging.DEBUG), + ) + return None + + def _initialize_credentials(self) -> None: self._discover_tenant_id_( arm_endpoint=self.arm_endpoint, subscription_id=self.subscription_id) + cache_options = self._get_cache_options() credentials = [] credentials.append(_TokenFileCredential()) credentials.append(EnvironmentCredential()) if self.client_id: credentials.append(ManagedIdentityCredential(client_id=self.client_id)) if self.authority and self.tenant_id: - credentials.append(VisualStudioCodeCredential(authority=self.authority, tenant_id=self.tenant_id)) + credentials.append(VisualStudioCodeCredential( + authority=self.authority, + tenant_id=self.tenant_id)) credentials.append(AzureCliCredential(tenant_id=self.tenant_id)) credentials.append(AzurePowerShellCredential(tenant_id=self.tenant_id)) - # Before trying other credential types, try to use already cached token. - credentials.append(SharedTokenCacheCredential(authority=self.authority)) + # The SharedTokenCacheCredential is used when the token cache + # is available to attempt loading a token stored in the cache + # by the InteractiveBrowserCredential. + if cache_options: + credentials.append(SharedTokenCacheCredential( + authority=self.authority, + cache_persistence_options=cache_options)) credentials.append( InteractiveBrowserCredential( - authority=self.authority, + authority=self.authority, tenant_id=self.tenant_id, - cache_persistence_options=TokenCachePersistenceOptions( - # Not all linux systems has preinstalled libsecret, which is required for storage encryption. - allow_unencrypted_storage=True - ) - ) - ) + cache_persistence_options=cache_options)) if self.client_id: - credentials.append(DeviceCodeCredential(authority=self.authority, client_id=self.client_id, tenant_id=self.tenant_id)) + credentials.append(DeviceCodeCredential( + authority=self.authority, + client_id=self.client_id, + tenant_id=self.tenant_id)) self.credentials = credentials def get_token(self, *scopes: str, **kwargs) -> AccessToken: From 6b3c453b83b5e12ef53512e603e85b7c1caffcf7 Mon Sep 17 00:00:00 2001 From: XField <58103249+vxfield@users.noreply.github.com> Date: Wed, 17 Apr 2024 17:44:15 -0700 Subject: [PATCH 3/7] Do not allow unencrypted token cache --- azure-quantum/azure/quantum/_authentication/_default.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure-quantum/azure/quantum/_authentication/_default.py b/azure-quantum/azure/quantum/_authentication/_default.py index 3588748cb..71e63f36b 100644 --- a/azure-quantum/azure/quantum/_authentication/_default.py +++ b/azure-quantum/azure/quantum/_authentication/_default.py @@ -94,7 +94,7 @@ def _get_cache_options(self) -> Optional[TokenCachePersistenceOptions]: Returns None otherwise. """ cache_options = TokenCachePersistenceOptions( - allow_unencrypted_storage=True, + allow_unencrypted_storage=False, name="AzureQuantumSDK" ) try: From 0979be4d4641055f4b4717ae33cc446261197c07 Mon Sep 17 00:00:00 2001 From: XField <58103249+vxfield@users.noreply.github.com> Date: Wed, 17 Apr 2024 17:50:20 -0700 Subject: [PATCH 4/7] Add tenant_id to SharedTokenCacheCredential --- azure-quantum/azure/quantum/_authentication/_default.py | 1 + 1 file changed, 1 insertion(+) diff --git a/azure-quantum/azure/quantum/_authentication/_default.py b/azure-quantum/azure/quantum/_authentication/_default.py index 71e63f36b..c98563a62 100644 --- a/azure-quantum/azure/quantum/_authentication/_default.py +++ b/azure-quantum/azure/quantum/_authentication/_default.py @@ -140,6 +140,7 @@ def _initialize_credentials(self) -> None: if cache_options: credentials.append(SharedTokenCacheCredential( authority=self.authority, + tenant_id=self.tenant_id, cache_persistence_options=cache_options)) credentials.append( InteractiveBrowserCredential( From b68653a14302efcf92948bf08b2c30aedb305418 Mon Sep 17 00:00:00 2001 From: XField <58103249+vxfield@users.noreply.github.com> Date: Wed, 17 Apr 2024 21:25:54 -0700 Subject: [PATCH 5/7] Change log level to info --- azure-quantum/azure/quantum/_authentication/_default.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure-quantum/azure/quantum/_authentication/_default.py b/azure-quantum/azure/quantum/_authentication/_default.py index c98563a62..0218cb1e0 100644 --- a/azure-quantum/azure/quantum/_authentication/_default.py +++ b/azure-quantum/azure/quantum/_authentication/_default.py @@ -101,7 +101,7 @@ def _get_cache_options(self) -> Optional[TokenCachePersistenceOptions]: # pylint: disable=protected-access cache = AzureIdentityPersistentCache._load_persistent_cache(cache_options) try: - _LOGGER.error( + _LOGGER.info( 'Using Azure.Identity Token Cache at %s. ', cache._persistence.get_location() ) From 88d2ad5b289ee96bbe03a402ad426315250a1549 Mon Sep 17 00:00:00 2001 From: XField <58103249+vxfield@users.noreply.github.com> Date: Wed, 17 Apr 2024 22:07:28 -0700 Subject: [PATCH 6/7] Small improvements --- .../azure/quantum/_authentication/_default.py | 42 ++++++++++++------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/azure-quantum/azure/quantum/_authentication/_default.py b/azure-quantum/azure/quantum/_authentication/_default.py index 0218cb1e0..deee05738 100644 --- a/azure-quantum/azure/quantum/_authentication/_default.py +++ b/azure-quantum/azure/quantum/_authentication/_default.py @@ -20,9 +20,9 @@ SharedTokenCacheCredential, _persistent_cache as AzureIdentityPersistentCache ) +from azure.quantum._constants import ConnectionConstants from ._chained import _ChainedTokenCredential from ._token import _TokenFileCredential -from azure.quantum._constants import ConnectionConstants _LOGGER = logging.getLogger(__name__) WWW_AUTHENTICATE_REGEX = re.compile( @@ -64,7 +64,7 @@ def __init__( client_id: Optional[str] = None, tenant_id: Optional[str] = None, authority: Optional[str] = None, - ): + ) -> None: if arm_endpoint is None: raise ValueError("arm_endpoint is mandatory parameter") if subscription_id is None: @@ -101,22 +101,37 @@ def _get_cache_options(self) -> Optional[TokenCachePersistenceOptions]: # pylint: disable=protected-access cache = AzureIdentityPersistentCache._load_persistent_cache(cache_options) try: + # Try to get the location of the cache for + # tracing purpose. _LOGGER.info( - 'Using Azure.Identity Token Cache at %s. ', + "Using Azure.Identity Token Cache at %s.", cache._persistence.get_location() ) except: # pylint: disable=bare-except - pass + _LOGGER.info("Using Azure.Identity Token Cache.") return cache_options except Exception as ex: # pylint: disable=broad-except - _LOGGER.warning( - 'Error trying to access Azure.Identity Token Cache at %s. ' - 'Raised unexpected exception:\n%s', - self.__class__._get_cache_options.__qualname__, - ex, - exc_info=_LOGGER.isEnabledFor(logging.DEBUG), - ) - return None + if (isinstance(ex, ValueError) + and len(ex.args) > 0 + and "libsecret dependencies are not installed" in ex.args[0] + ): + _LOGGER.warning( + "Error trying to access Azure.Identity Token Cache. " + "libsecret dependencies are not installed or are unusable.\n" + "Please install the necessary dependencies as instructed in " + "https://github.com/AzureAD/microsoft-authentication-extensions-for-python/wiki/Encryption-on-Linux" # pylint: disable=line-too-long + "Exception:\n%s", + ex, + exc_info=_LOGGER.isEnabledFor(logging.DEBUG), + ) + else: + _LOGGER.warning( + 'Error trying to access Azure.Identity Token Cache. ' + "Raised unexpected exception:\n%s", + ex, + exc_info=_LOGGER.isEnabledFor(logging.DEBUG), + ) + return None def _initialize_credentials(self) -> None: self._discover_tenant_id_( @@ -197,8 +212,7 @@ def _discover_tenant_id_(self, arm_endpoint:str, subscription_id:str): match = re.search(WWW_AUTHENTICATE_REGEX, www_authenticate) if match: self.tenant_id = match.group("tenant_id") - # pylint: disable=broad-exception-caught - except Exception as ex: + except Exception as ex: # pylint: disable=broad-exception-caught _LOGGER.error(ex) # apply default values From 75b3d58b2bf691308ccb9b850399bf316e1c1484 Mon Sep 17 00:00:00 2001 From: XField <58103249+vxfield@users.noreply.github.com> Date: Wed, 17 Apr 2024 22:36:51 -0700 Subject: [PATCH 7/7] Better libsecret check --- .../azure/quantum/_authentication/_default.py | 47 ++++++++++--------- 1 file changed, 26 insertions(+), 21 deletions(-) diff --git a/azure-quantum/azure/quantum/_authentication/_default.py b/azure-quantum/azure/quantum/_authentication/_default.py index deee05738..32111de83 100644 --- a/azure-quantum/azure/quantum/_authentication/_default.py +++ b/azure-quantum/azure/quantum/_authentication/_default.py @@ -2,6 +2,7 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. # ------------------------------------ +import sys import logging import re from typing import Optional @@ -111,27 +112,31 @@ def _get_cache_options(self) -> Optional[TokenCachePersistenceOptions]: _LOGGER.info("Using Azure.Identity Token Cache.") return cache_options except Exception as ex: # pylint: disable=broad-except - if (isinstance(ex, ValueError) - and len(ex.args) > 0 - and "libsecret dependencies are not installed" in ex.args[0] - ): - _LOGGER.warning( - "Error trying to access Azure.Identity Token Cache. " - "libsecret dependencies are not installed or are unusable.\n" - "Please install the necessary dependencies as instructed in " - "https://github.com/AzureAD/microsoft-authentication-extensions-for-python/wiki/Encryption-on-Linux" # pylint: disable=line-too-long - "Exception:\n%s", - ex, - exc_info=_LOGGER.isEnabledFor(logging.DEBUG), - ) - else: - _LOGGER.warning( - 'Error trying to access Azure.Identity Token Cache. ' - "Raised unexpected exception:\n%s", - ex, - exc_info=_LOGGER.isEnabledFor(logging.DEBUG), - ) - return None + # Check if the cache issue on linux is due + # libsecret not functioning to provider better + # information to the user. + if sys.platform.startswith("linux"): + try: + # pylint: disable=import-outside-toplevel + from msal_extensions.libsecret import trial_run + trial_run() + except Exception as libsecret_ex: # pylint: disable=broad-except + _LOGGER.warning( + "libsecret dependencies are not installed or are unusable.\n" + "Please install the necessary dependencies as instructed in " + "https://github.com/AzureAD/microsoft-authentication-extensions-for-python/wiki/Encryption-on-Linux" # pylint: disable=line-too-long + "Exception:\n%s", + libsecret_ex, + exc_info=_LOGGER.isEnabledFor(logging.DEBUG), + ) + + _LOGGER.warning( + 'Error trying to access Azure.Identity Token Cache. ' + "Raised unexpected exception:\n%s", + ex, + exc_info=_LOGGER.isEnabledFor(logging.DEBUG), + ) + return None def _initialize_credentials(self) -> None: self._discover_tenant_id_(