Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package azure-cli-core for openSUSE:Factory checked in at 2021-12-08 22:08:55 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/azure-cli-core (Old) and /work/SRC/openSUSE:Factory/.azure-cli-core.new.31177 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "azure-cli-core" Wed Dec 8 22:08:55 2021 rev:32 rq:936287 version:2.31.0 Changes: -------- --- /work/SRC/openSUSE:Factory/azure-cli-core/azure-cli-core.changes 2021-11-20 02:39:31.636703517 +0100 +++ /work/SRC/openSUSE:Factory/.azure-cli-core.new.31177/azure-cli-core.changes 2021-12-08 22:09:45.222885043 +0100 @@ -1,0 +2,10 @@ +Tue Dec 7 08:28:16 UTC 2021 - John Paul Adrian Glaubitz <adrian.glaub...@suse.com> + +- New upstream release + + Version 2.31.0 + + For detailed information about changes see the + HISTORY.rst file provided with this package +- Remove bogus python-PyYAML dependency from Requires (bsc#1193394) +- Update Requires from setup.py + +------------------------------------------------------------------- Old: ---- azure-cli-core-2.30.0.tar.gz New: ---- azure-cli-core-2.31.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ azure-cli-core.spec ++++++ --- /var/tmp/diff_new_pack.qL6lxR/_old 2021-12-08 22:09:45.698885266 +0100 +++ /var/tmp/diff_new_pack.qL6lxR/_new 2021-12-08 22:09:45.702885268 +0100 @@ -17,7 +17,7 @@ Name: azure-cli-core -Version: 2.30.0 +Version: 2.31.0 Release: 0 Summary: Microsoft Azure CLI Core Module License: MIT @@ -35,19 +35,17 @@ Requires: azure-cli-nspkg Requires: azure-cli-telemetry >= 1.0.6 Requires: python3-PyJWT >= 2.1.0 -Requires: python3-PyYAML < 6.0 -Requires: python3-PyYAML >= 5.2 Requires: python3-argcomplete < 2.0 Requires: python3-argcomplete >= 1.8 Requires: python3-azure-mgmt-core < 2.0.0 Requires: python3-azure-mgmt-core >= 1.2.0 Requires: python3-azure-nspkg >= 3.0.0 Requires: python3-cryptography -Requires: python3-humanfriendly < 10.0 -Requires: python3-humanfriendly >= 4.7 +Requires: python3-humanfriendly < 11.0 +Requires: python3-humanfriendly >= 10.0 Requires: python3-jmespath Requires: python3-knack < 1.0.0 -Requires: python3-knack >= 0.8.2 +Requires: python3-knack >= 0.9.0 Requires: python3-msal < 2.0.0 Requires: python3-msal >= 1.15.0 Requires: python3-msal-extensions < 1.0.0 @@ -59,7 +57,6 @@ Requires: python3-pyOpenSSL >= 17.1.0 Requires: python3-requests < 3.0.0 Requires: python3-requests >= 2.25.1 -Requires: python3-urllib3 >= 1.26.5 Requires: python3-wheel >= 0.30.0 %if %{python3_version_nodots} < 34 Requires: python-enum34 >= 1.0.4 ++++++ azure-cli-core-2.30.0.tar.gz -> azure-cli-core-2.31.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/azure-cli-core-2.30.0/HISTORY.rst new/azure-cli-core-2.31.0/HISTORY.rst --- old/azure-cli-core-2.30.0/HISTORY.rst 2021-10-29 12:07:43.000000000 +0200 +++ new/azure-cli-core-2.31.0/HISTORY.rst 2021-12-03 06:02:53.000000000 +0100 @@ -3,6 +3,10 @@ Release History =============== +2.31.0 +++++++ +* Use MSAL HTTP cache (#20234) + 2.30.0 ++++++ * [BREAKING CHANGE] ADAL to MSAL migration (#19853) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/azure-cli-core-2.30.0/PKG-INFO new/azure-cli-core-2.31.0/PKG-INFO --- old/azure-cli-core-2.30.0/PKG-INFO 2021-10-29 12:07:54.091770000 +0200 +++ new/azure-cli-core-2.31.0/PKG-INFO 2021-12-03 06:03:07.647528200 +0100 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: azure-cli-core -Version: 2.30.0 +Version: 2.31.0 Summary: Microsoft Azure Command-Line Tools Core Module Home-page: https://github.com/Azure/azure-cli Author: Microsoft Corporation @@ -16,6 +16,7 @@ Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 Classifier: License :: OSI Approved :: MIT License Requires-Python: >=3.6.0 License-File: LICENSE.txt diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/azure-cli-core-2.30.0/azure/cli/core/__init__.py new/azure-cli-core-2.31.0/azure/cli/core/__init__.py --- old/azure-cli-core-2.30.0/azure/cli/core/__init__.py 2021-10-29 12:07:43.000000000 +0200 +++ new/azure-cli-core-2.31.0/azure/cli/core/__init__.py 2021-12-03 06:02:53.000000000 +0100 @@ -4,7 +4,7 @@ # -------------------------------------------------------------------------------------------- # pylint: disable=line-too-long -__version__ = "2.30.0" +__version__ = "2.31.0" import os import sys @@ -107,7 +107,7 @@ self.data['headers']['x-ms-client-request-id'] = str(uuid.uuid1()) def get_progress_controller(self, det=False, spinner=None): - import azure.cli.core.commands.progress as progress + from azure.cli.core.commands import progress if not self.progress_controller: self.progress_controller = progress.ProgressHook() @@ -279,7 +279,7 @@ except Exception as ex: # pylint: disable=broad-except # Changing this error message requires updating CI script that checks for failed # module loading. - import azure.cli.core.telemetry as telemetry + from azure.cli.core import telemetry logger.error("Error loading command module '%s': %s", mod, ex) telemetry.set_exception(exception=ex, fault_type='module-load-error-' + mod, summary='Error loading module: {}'.format(mod)) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/azure-cli-core-2.30.0/azure/cli/core/_help.py new/azure-cli-core-2.31.0/azure/cli/core/_help.py --- old/azure-cli-core-2.30.0/azure/cli/core/_help.py 2021-10-29 12:07:43.000000000 +0200 +++ new/azure-cli-core-2.31.0/azure/cli/core/_help.py 2021-12-03 06:02:53.000000000 +0100 @@ -62,7 +62,7 @@ def _print_detailed_help(self, cli_name, help_file): CLIPrintMixin._print_extensions_msg(help_file) super(CLIPrintMixin, self)._print_detailed_help(cli_name, help_file) - self._print_az_find_message(help_file.command, self.cli_ctx.enable_color) + self._print_az_find_message(help_file.command) @staticmethod def _get_choices_defaults_sources_str(p): @@ -88,12 +88,9 @@ print('') @staticmethod - def _print_az_find_message(command, enable_color): - from colorama import Style + def _print_az_find_message(command): indent = 0 message = 'To search AI knowledge base for examples, use: az find "az {}"'.format(command) - if enable_color: - message = Style.BRIGHT + message + Style.RESET_ALL _print_indent(message + '\n', indent) @staticmethod diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/azure-cli-core-2.30.0/azure/cli/core/_profile.py new/azure-cli-core-2.31.0/azure/cli/core/_profile.py --- old/azure-cli-core-2.30.0/azure/cli/core/_profile.py 2021-10-29 12:07:43.000000000 +0200 +++ new/azure-cli-core-2.31.0/azure/cli/core/_profile.py 2021-12-03 06:02:53.000000000 +0100 @@ -361,37 +361,42 @@ raise CLIError("Please specify only one of subscription and tenant, not both") account = self.get_subscription(subscription) - resource = resource or self.cli_ctx.cloud.endpoints.active_directory_resource_id identity_type, identity_id = Profile._try_parse_msi_account_name(account) if identity_type: - # MSI + # managed identity if tenant: - raise CLIError("Tenant shouldn't be specified for MSI account") - msi_creds = MsiAccountTypes.msi_auth_factory(identity_type, identity_id, resource) - msi_creds.set_token() - token_entry = msi_creds.token - creds = (token_entry['token_type'], token_entry['access_token'], token_entry) + raise CLIError("Tenant shouldn't be specified for managed identity account") + from .auth.util import scopes_to_resource + msi_creds = MsiAccountTypes.msi_auth_factory(identity_type, identity_id, + scopes_to_resource(scopes)) + sdk_token = msi_creds.get_token(*scopes) elif in_cloud_console() and account[_USER_ENTITY].get(_CLOUD_SHELL_ID): - # Cloud Shell + # Cloud Shell, which is just a system-assigned managed identity. if tenant: raise CLIError("Tenant shouldn't be specified for Cloud Shell account") - creds = self._get_token_from_cloud_shell(resource) + from .auth.util import scopes_to_resource + msi_creds = MsiAccountTypes.msi_auth_factory(MsiAccountTypes.system_assigned, identity_id, + scopes_to_resource(scopes)) + sdk_token = msi_creds.get_token(*scopes) else: credential = self._create_credential(account, tenant) - token = credential.get_token(*scopes) + sdk_token = credential.get_token(*scopes) - import datetime - expiresOn = datetime.datetime.fromtimestamp(token.expires_on).strftime("%Y-%m-%d %H:%M:%S.%f") + # Convert epoch int 'expires_on' to datetime string 'expiresOn' for backward compatibility + # WARNING: expiresOn is deprecated and will be removed in future release. + import datetime + expiresOn = datetime.datetime.fromtimestamp(sdk_token.expires_on).strftime("%Y-%m-%d %H:%M:%S.%f") + + token_entry = { + 'accessToken': sdk_token.token, + 'expires_on': sdk_token.expires_on, # epoch int, like 1605238724 + 'expiresOn': expiresOn # datetime string, like "2020-11-12 13:50:47.114324" + } - token_entry = { - 'accessToken': token.token, - 'expires_on': token.expires_on, - 'expiresOn': expiresOn - } + # (tokenType, accessToken, tokenEntry) + creds = 'Bearer', sdk_token.token, token_entry - # (tokenType, accessToken, tokenEntry) - creds = 'Bearer', token.token, token_entry # (cred, subscription, tenant) return (creds, None if tenant else str(account[_SUBSCRIPTION_ID]), @@ -628,7 +633,7 @@ if not subscriptions: continue - consolidated = self._normalize_properties(subscription_finder.user_id, + consolidated = self._normalize_properties(user_name, subscriptions, is_service_principal) result += consolidated @@ -695,13 +700,6 @@ self._storage[_INSTALLATION_ID] = installation_id return installation_id - def _get_token_from_cloud_shell(self, resource): # pylint: disable=no-self-use - from azure.cli.core.auth.adal_authentication import MSIAuthenticationWrapper - auth = MSIAuthenticationWrapper(resource=resource) - auth.set_token() - token_entry = auth.token - return (token_entry['token_type'], token_entry['access_token'], token_entry) - class MsiAccountTypes: # pylint: disable=no-method-argument,no-self-argument @@ -733,8 +731,6 @@ # An ARM client. It finds subscriptions for a user or service principal. It shouldn't do any # authentication work, but only find subscriptions def __init__(self, cli_ctx): - - self.user_id = None # will figure out after log user in self.cli_ctx = cli_ctx self.secret = None self._arm_resource_id = cli_ctx.cloud.endpoints.active_directory_resource_id @@ -866,6 +862,7 @@ # Only enable encryption for Windows (for now). fallback = sys.platform.startswith('win32') - encrypt = cli_ctx.config.getboolean('core', 'token_encryption', fallback=fallback) + # encrypt_token_cache affects both MSAL token cache and service principal entries. + encrypt = cli_ctx.config.getboolean('core', 'encrypt_token_cache', fallback=fallback) return Identity(*args, encrypt=encrypt, **kwargs) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/azure-cli-core-2.30.0/azure/cli/core/auth/adal_authentication.py new/azure-cli-core-2.31.0/azure/cli/core/auth/adal_authentication.py --- old/azure-cli-core-2.30.0/azure/cli/core/auth/adal_authentication.py 2021-10-29 12:07:43.000000000 +0200 +++ new/azure-cli-core-2.31.0/azure/cli/core/auth/adal_authentication.py 2021-12-03 06:02:53.000000000 +0100 @@ -4,17 +4,17 @@ # -------------------------------------------------------------------------------------------- import requests -from azure.core.credentials import AccessToken from knack.log import get_logger from msrestazure.azure_active_directory import MSIAuthentication -from .util import _normalize_scopes, scopes_to_resource +from .util import _normalize_scopes, scopes_to_resource, AccessToken logger = get_logger(__name__) class MSIAuthenticationWrapper(MSIAuthentication): # This method is exposed for Azure Core. Add *scopes, **kwargs to fit azure.core requirement + # pylint: disable=line-too-long def get_token(self, *scopes, **kwargs): # pylint:disable=unused-argument logger.debug("MSIAuthenticationWrapper.get_token invoked by Track 2 SDK with scopes=%s", scopes) @@ -27,7 +27,8 @@ # If available, use resource provided by SDK self.resource = resource self.set_token() - # Managed Identity token entry sample: + # VM managed identity endpoint 2018-02-01 token entry sample: + # curl "http://169.254.169.254:80/metadata/identity/oauth2/token?resource=https://management.core.windows.net/&api-version=2018-02-01" -H "Metadata: true" # { # "access_token": "eyJ0eXAiOiJKV...", # "client_id": "da95e381-d7ab-4fdc-8047-2457909c723b", @@ -35,10 +36,20 @@ # "expires_on": "1605238724", # "ext_expires_in": "86399", # "not_before": "1605152024", - # "resource": "https://management.azure.com/", + # "resource": "https://management.core.windows.net/", # "token_type": "Bearer" # } - return AccessToken(self.token['access_token'], int(self.token['expires_on'])) + + # App Service managed identity endpoint 2017-09-01 token entry sample: + # curl "${MSI_ENDPOINT}?resource=https://management.core.windows.net/&api-version=2017-09-01" -H "secret: ${MSI_SECRET}" + # { + # "access_token": "eyJ0eXAiOiJKV...", + # "expires_on":"11/05/2021 15:18:31 +00:00", + # "resource":"https://management.core.windows.net/", + # "token_type":"Bearer", + # "client_id":"df45d93a-de31-47ca-acef-081ca60d1a83" + # } + return AccessToken(self.token['access_token'], _normalize_expires_on(self.token['expires_on'])) def set_token(self): import traceback @@ -69,3 +80,20 @@ def signed_session(self, session=None): logger.debug("MSIAuthenticationWrapper.signed_session invoked by Track 1 SDK") super().signed_session(session) + + +def _normalize_expires_on(expires_on): + """ + The expires_on field returned by managed identity differs on Azure VM (epoch str) and App Service (datetime str). + Normalize to epoch int. + """ + try: + # Treat as epoch string "1605238724" + expires_on_epoch_int = int(expires_on) + except ValueError: + import datetime + # Treat as datetime string "11/05/2021 15:18:31 +00:00" + expires_on_epoch_int = int(datetime.datetime.strptime(expires_on, '%m/%d/%Y %H:%M:%S %z').timestamp()) + + logger.debug("Normalize expires_on: %r -> %r", expires_on, expires_on_epoch_int) + return expires_on_epoch_int diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/azure-cli-core-2.30.0/azure/cli/core/auth/identity.py new/azure-cli-core-2.31.0/azure/cli/core/auth/identity.py --- old/azure-cli-core-2.30.0/azure/cli/core/auth/identity.py 2021-10-29 12:07:43.000000000 +0200 +++ new/azure-cli-core-2.31.0/azure/cli/core/auth/identity.py 2021-12-03 06:02:53.000000000 +0100 @@ -16,7 +16,7 @@ from .msal_authentication import _CLIENT_ID, _TENANT, _CLIENT_SECRET, _CERTIFICATE, _CLIENT_ASSERTION, \ _USE_CERT_SN_ISSUER from .msal_authentication import UserCredential, ServicePrincipalCredential -from .persistence import load_persisted_token_cache, file_extensions +from .persistence import load_persisted_token_cache, file_extensions, load_secret_store from .util import check_result AZURE_CLI_CLIENT_ID = '04b07795-8ddb-461a-bbee-02f9e1bf7b46' @@ -31,10 +31,19 @@ - service principal - TODO: managed identity """ - # HTTP cache for MSAL's tenant discovery, retry-after error cache, etc. - # It must follow singleton pattern. Otherwise, a new dbm.dumb http_cache can read out-of-sync dat and dir. + + # MSAL token cache. + # It follows singleton pattern so that all MSAL app instances share the same token cache. + _msal_token_cache = None + + # MSAL HTTP cache for MSAL's tenant discovery, retry-after error cache, etc. + # It *must* follow singleton pattern so that all MSAL app instances share the same HTTP cache. # https://github.com/AzureAD/microsoft-authentication-library-for-python/pull/407 - http_cache = None + _msal_http_cache = None + + # Instance of ServicePrincipalStore. + # It follows singleton pattern so that _secret_file is read only once. + _service_principal_store_instance = None def __init__(self, authority, tenant_id=None, client_id=None, encrypt=False): """ @@ -44,7 +53,7 @@ :param tenant_id: Tenant GUID, like 00000000-0000-0000-0000-000000000000. If unspecified, default to 'organizations'. :param client_id: Client ID of the CLI application. - :param encrypt: Whether to encrypt token cache and service principal entries. + :param encrypt: Whether to encrypt MSAL token cache and service principal entries. """ self.authority = authority self.tenant_id = tenant_id @@ -57,35 +66,53 @@ config_dir = get_config_dir() self._token_cache_file = os.path.join(config_dir, "msal_token_cache") self._secret_file = os.path.join(config_dir, "service_principal_entries") - self._http_cache_file = os.path.join(config_dir, "msal_http_cache") - - # Prepare HTTP cache. - # https://github.com/AzureAD/microsoft-authentication-library-for-python/pull/407 - # if not Identity.http_cache: - # Identity.http_cache = self._load_http_cache() + self._http_cache_file = os.path.join(config_dir, "msal_http_cache.bin") + # We make _msal_app_instance an instance attribute, instead of a class attribute, + # because MSAL apps can have different tenant IDs. self._msal_app_instance = None - # Store for Service principal credential persistence - self._msal_secret_store = ServicePrincipalStore(self._secret_file, self.encrypt) - self._msal_app_kwargs = { + + @property + def _msal_app_kwargs(self): + """kwargs for creating UserCredential or ServicePrincipalCredential. + MSAL token cache and HTTP cache are lazily created. + """ + if not Identity._msal_token_cache: + Identity._msal_token_cache = self._load_msal_token_cache() + + if not Identity._msal_http_cache: + Identity._msal_http_cache = self._load_msal_http_cache() + + return { "authority": self._msal_authority, - "token_cache": self._load_msal_cache() - # "http_cache": Identity.http_cache + "token_cache": Identity._msal_token_cache, + "http_cache": Identity._msal_http_cache } - def _load_msal_cache(self): + @property + def _msal_app(self): + """A PublicClientApplication instance for user login/logout. + The instance is lazily created. + """ + if not self._msal_app_instance: + self._msal_app_instance = PublicClientApplication(self.client_id, **self._msal_app_kwargs) + return self._msal_app_instance + + def _load_msal_token_cache(self): # Store for user token persistence cache = load_persisted_token_cache(self._token_cache_file, self.encrypt) return cache - def _load_http_cache(self): + def _load_msal_http_cache(self): import atexit import pickle + logger.debug("_load_msal_http_cache: %s", self._http_cache_file) try: with open(self._http_cache_file, 'rb') as f: - persisted_http_cache = pickle.load(f) # Take a snapshot - except: # pylint: disable=bare-except + persisted_http_cache = pickle.load(f) + except (pickle.UnpicklingError, FileNotFoundError) as ex: + logger.debug("Failed to load MSAL HTTP cache: %s", ex) persisted_http_cache = {} # Ignore a non-exist or corrupted http_cache atexit.register(lambda: pickle.dump( # When exit, flush it back to the file. @@ -95,45 +122,44 @@ return persisted_http_cache - def _build_persistent_msal_app(self): - # Initialize _msal_app for login and logout - msal_app = PublicClientApplication(self.client_id, **self._msal_app_kwargs) - return msal_app - @property - def msal_app(self): - if not self._msal_app_instance: - self._msal_app_instance = self._build_persistent_msal_app() - return self._msal_app_instance + def _service_principal_store(self): + """A ServicePrincipalStore instance for service principal entries persistence. + The instance is lazily created. + """ + if not Identity._service_principal_store_instance: + store = load_secret_store(self._secret_file, self.encrypt) + Identity._service_principal_store_instance = ServicePrincipalStore(store) + return Identity._service_principal_store_instance def login_with_auth_code(self, scopes, **kwargs): # Emit a warning to inform that a browser is opened. # Only show the path part of the URL and hide the query string. logger.warning("The default web browser has been opened at %s. Please continue the login in the web browser. " "If no web browser is available or if the web browser fails to open, use device code flow " - "with `az login --use-device-code`.", self.msal_app.authority.authorization_endpoint) + "with `az login --use-device-code`.", self._msal_app.authority.authorization_endpoint) from .util import read_response_templates success_template, error_template = read_response_templates() # For AAD, use port 0 to let the system choose arbitrary unused ephemeral port to avoid port collision # on port 8400 from the old design. However, ADFS only allows port 8400. - result = self.msal_app.acquire_token_interactive( + result = self._msal_app.acquire_token_interactive( scopes, prompt='select_account', port=8400 if self._is_adfs else None, success_template=success_template, error_template=error_template, **kwargs) return check_result(result) def login_with_device_code(self, scopes, **kwargs): - flow = self.msal_app.initiate_device_flow(scopes, **kwargs) + flow = self._msal_app.initiate_device_flow(scopes, **kwargs) if "user_code" not in flow: raise ValueError( "Fail to create device flow. Err: %s" % json.dumps(flow, indent=4)) logger.warning(flow["message"]) - result = self.msal_app.acquire_token_by_device_flow(flow, **kwargs) # By default it will block + result = self._msal_app.acquire_token_by_device_flow(flow, **kwargs) # By default it will block return check_result(result) def login_with_username_password(self, username, password, scopes, **kwargs): - result = self.msal_app.acquire_token_by_username_password(username, password, scopes, **kwargs) + result = self._msal_app.acquire_token_by_username_password(username, password, scopes, **kwargs) return check_result(result) def login_with_service_principal(self, client_id, credential, scopes): @@ -149,7 +175,7 @@ # Only persist the service principal after a successful login entry = sp_auth.get_entry_to_persist() - self._msal_secret_store.save_entry(entry) + self._service_principal_store.save_entry(entry) def login_with_managed_identity(self, scopes, identity_id=None): # pylint: disable=too-many-statements raise NotImplementedError @@ -158,9 +184,9 @@ raise NotImplementedError def logout_user(self, user): - accounts = self.msal_app.get_accounts(user) + accounts = self._msal_app.get_accounts(user) for account in accounts: - self.msal_app.remove_account(account) + self._msal_app.remove_account(account) def logout_all_users(self): for e in file_extensions.values(): @@ -168,27 +194,28 @@ def logout_service_principal(self, sp): # remove service principal secrets - self._msal_secret_store.remove_entry(sp) + self._service_principal_store.remove_entry(sp) def logout_all_service_principal(self): # remove service principal secrets - self._msal_secret_store.remove_all_entries() + for e in file_extensions.values(): + _try_remove(self._secret_file + e) def get_user(self, user=None): - accounts = self.msal_app.get_accounts(user) if user else self.msal_app.get_accounts() + accounts = self._msal_app.get_accounts(user) if user else self._msal_app.get_accounts() return accounts def get_user_credential(self, username): return UserCredential(self.client_id, username, **self._msal_app_kwargs) def get_service_principal_credential(self, client_id): - entry = self._msal_secret_store.load_entry(client_id, self.tenant_id) + entry = self._service_principal_store.load_entry(client_id, self.tenant_id) sp_auth = ServicePrincipalAuth(entry) return ServicePrincipalCredential(sp_auth, **self._msal_app_kwargs) def get_service_principal_entry(self, client_id): """This method is only used by --sdk-auth. DO NOT use it elsewhere.""" - return self._msal_secret_store.load_entry(client_id, self.tenant_id) + return self._service_principal_store.load_entry(client_id, self.tenant_id) def get_managed_identity_credential(self, client_id=None): raise NotImplementedError @@ -255,10 +282,8 @@ """Save secrets in MSAL custom secret store for Service Principal authentication. """ - def __init__(self, secret_file, encrypt): - from .persistence import load_secret_store - self._secret_store = load_secret_store(secret_file, encrypt) - self._secret_file = secret_file + def __init__(self, secret_store): + self._secret_store = secret_store self._entries = [] def load_entry(self, sp_id, tenant): @@ -305,10 +330,6 @@ if state_changed: self._save_persistence() - def remove_all_entries(self): - for e in file_extensions.values(): - _try_remove(self._secret_file + e) - def _save_persistence(self): self._secret_store.save(self._entries) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/azure-cli-core-2.30.0/azure/cli/core/auth/msal_authentication.py new/azure-cli-core-2.31.0/azure/cli/core/auth/msal_authentication.py --- old/azure-cli-core-2.30.0/azure/cli/core/auth/msal_authentication.py 2021-10-29 12:07:43.000000000 +0200 +++ new/azure-cli-core-2.31.0/azure/cli/core/auth/msal_authentication.py 2021-12-03 06:02:53.000000000 +0100 @@ -14,7 +14,7 @@ from knack.util import CLIError from msal import PublicClientApplication, ConfidentialClientApplication -from .util import check_result +from .util import check_result, AccessToken # OAuth 2.0 client credentials flow parameter # https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-client-creds-grant-flow @@ -38,17 +38,20 @@ """ super().__init__(client_id, **kwargs) - accounts = self.get_accounts(username) + # Make sure username is specified, otherwise MSAL returns all accounts + assert username, "username must be specified, got {!r}".format(username) - if not accounts: - raise CLIError("User {} does not exist in MSAL token cache. Run `az login`.".format(username)) + accounts = self.get_accounts(username) + # Usernames are usually unique. We are collecting corner cases to better understand its behavior. if len(accounts) > 1: - raise CLIError("Found multiple accounts with the same username. Please report to us via Github: " - "https://github.com/Azure/azure-cli/issues/new") + raise CLIError(f"Found multiple accounts with the same username '{username}': {accounts}\n" + "Please report to us via Github: https://github.com/Azure/azure-cli/issues/20168") - account = accounts[0] - self._account = account + if not accounts: + raise CLIError("User '{}' does not exist in MSAL token cache. Run `az login`.".format(username)) + + self._account = accounts[0] def get_token(self, *scopes, **kwargs): # scopes = ['https://pas.windows.net/CheckMyAccess/Linux/.default'] @@ -134,9 +137,14 @@ import time request_time = int(time.time()) + # MSAL token entry sample: + # { + # 'access_token': 'eyJ0eXAiOiJKV...', + # 'token_type': 'Bearer', + # 'expires_in': 1618 + # } + # Importing azure.core.credentials.AccessToken is expensive. # This can slow down commands that doesn't need azure.core, like `az account get-access-token`. # So We define our own AccessToken. - from collections import namedtuple - AccessToken = namedtuple("AccessToken", ["token", "expires_on"]) return AccessToken(token_entry["access_token"], request_time + token_entry["expires_in"]) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/azure-cli-core-2.30.0/azure/cli/core/auth/persistence.py new/azure-cli-core-2.31.0/azure/cli/core/auth/persistence.py --- old/azure-cli-core-2.30.0/azure/cli/core/auth/persistence.py 2021-10-29 12:07:43.000000000 +0200 +++ new/azure-cli-core-2.31.0/azure/cli/core/auth/persistence.py 2021-12-03 06:02:53.000000000 +0100 @@ -35,6 +35,7 @@ def build_persistence(location, encrypt): """Build a suitable persistence instance based your current OS""" location += file_extensions[encrypt] + logger.debug("build_persistence: location=%r, encrypt=%r", location, encrypt) if encrypt: if sys.platform.startswith('win'): return FilePersistenceWithDataProtection(location) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/azure-cli-core-2.30.0/azure/cli/core/auth/util.py new/azure-cli-core-2.31.0/azure/cli/core/auth/util.py --- old/azure-cli-core-2.30.0/azure/cli/core/auth/util.py 2021-10-29 12:07:43.000000000 +0200 +++ new/azure-cli-core-2.31.0/azure/cli/core/auth/util.py 2021-12-03 06:02:53.000000000 +0100 @@ -4,11 +4,16 @@ # -------------------------------------------------------------------------------------------- import os +from collections import namedtuple + from knack.log import get_logger logger = get_logger(__name__) +AccessToken = namedtuple("AccessToken", ["token", "expires_on"]) + + def aad_error_handler(error, **kwargs): """ Handle the error from AAD server returned by ADAL or MSAL. """ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/azure-cli-core-2.30.0/azure/cli/core/azclierror.py new/azure-cli-core-2.31.0/azure/cli/core/azclierror.py --- old/azure-cli-core-2.30.0/azure/cli/core/azclierror.py 2021-10-29 12:07:43.000000000 +0200 +++ new/azure-cli-core-2.31.0/azure/cli/core/azclierror.py 2021-12-03 06:02:53.000000000 +0100 @@ -5,7 +5,7 @@ import sys -import azure.cli.core.telemetry as telemetry +from azure.cli.core import telemetry from knack.util import CLIError from knack.log import get_logger diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/azure-cli-core-2.30.0/azure/cli/core/command_recommender.py new/azure-cli-core-2.31.0/azure/cli/core/command_recommender.py --- old/azure-cli-core-2.30.0/azure/cli/core/command_recommender.py 2021-10-29 12:07:43.000000000 +0200 +++ new/azure-cli-core-2.31.0/azure/cli/core/command_recommender.py 2021-12-03 06:02:53.000000000 +0100 @@ -5,7 +5,7 @@ from enum import Enum -import azure.cli.core.telemetry as telemetry +from azure.cli.core import telemetry from knack.log import get_logger diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/azure-cli-core-2.30.0/azure/cli/core/commands/__init__.py new/azure-cli-core-2.31.0/azure/cli/core/commands/__init__.py --- old/azure-cli-core-2.30.0/azure/cli/core/commands/__init__.py 2021-10-29 12:07:43.000000000 +0200 +++ new/azure-cli-core-2.31.0/azure/cli/core/commands/__init__.py 2021-12-03 06:02:53.000000000 +0100 @@ -26,7 +26,7 @@ from azure.cli.core.util import ( get_command_type_kwarg, read_file_content, get_arg_list, poller_classes) from azure.cli.core.local_context import LocalContextAction -import azure.cli.core.telemetry as telemetry +from azure.cli.core import telemetry from azure.cli.core.commands.progress import IndeterminateProgressBar from knack.arguments import CLICommandArgument @@ -35,7 +35,7 @@ from knack.invocation import CommandInvoker from knack.preview import ImplicitPreviewItem, PreviewItem, resolve_preview_info from knack.experimental import ImplicitExperimentalItem, ExperimentalItem, resolve_experimental_info -from knack.log import get_logger +from knack.log import get_logger, CLILogging from knack.util import CLIError, CommandResultItem, todict from knack.events import EVENT_INVOKER_TRANSFORM_RESULT from knack.validators import DefaultStr @@ -562,7 +562,8 @@ self.cli_ctx.raise_event(EVENT_INVOKER_CMD_TBL_LOADED, cmd_tbl=self.commands_loader.command_table, parser=self.parser) - arg_check = [a for a in args if a not in ['--debug', '--verbose']] + arg_check = [a for a in args if a not in + (CLILogging.DEBUG_FLAG, CLILogging.VERBOSE_FLAG, CLILogging.ONLY_SHOW_ERRORS_FLAG)] if not arg_check: self.parser.enable_autocomplete() subparser = self.parser.subparsers[tuple()] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/azure-cli-core-2.30.0/azure/cli/core/commands/arm.py new/azure-cli-core-2.31.0/azure/cli/core/commands/arm.py --- old/azure-cli-core-2.30.0/azure/cli/core/commands/arm.py 2021-10-29 12:07:43.000000000 +0200 +++ new/azure-cli-core-2.31.0/azure/cli/core/commands/arm.py 2021-12-03 06:02:53.000000000 +0100 @@ -115,7 +115,7 @@ def handle_long_running_operation_exception(ex): - import azure.cli.core.telemetry as telemetry + from azure.cli.core import telemetry telemetry.set_exception( ex, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/azure-cli-core-2.30.0/azure/cli/core/commands/client_factory.py new/azure-cli-core-2.31.0/azure/cli/core/commands/client_factory.py --- old/azure-cli-core-2.30.0/azure/cli/core/commands/client_factory.py 2021-10-29 12:07:43.000000000 +0200 +++ new/azure-cli-core-2.31.0/azure/cli/core/commands/client_factory.py 2021-12-03 06:02:53.000000000 +0100 @@ -3,7 +3,7 @@ # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- -import azure.cli.core._debug as _debug +from azure.cli.core import _debug from azure.cli.core.auth.util import resource_to_scopes from azure.cli.core.extension import EXTENSIONS_MOD_PREFIX from azure.cli.core.profiles import ResourceType, CustomResourceType, get_api_version, get_sdk diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/azure-cli-core-2.30.0/azure/cli/core/commands/transform.py new/azure-cli-core-2.31.0/azure/cli/core/commands/transform.py --- old/azure-cli-core-2.30.0/azure/cli/core/commands/transform.py 2021-10-29 12:07:43.000000000 +0200 +++ new/azure-cli-core-2.31.0/azure/cli/core/commands/transform.py 2021-12-03 06:02:53.000000000 +0100 @@ -7,7 +7,7 @@ from azure.cli.core.util import b64_to_hex -import knack.events as events +from knack import events def register_global_transforms(cli_ctx): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/azure-cli-core-2.30.0/azure/cli/core/extension/dynamic_install.py new/azure-cli-core-2.31.0/azure/cli/core/extension/dynamic_install.py --- old/azure-cli-core-2.30.0/azure/cli/core/extension/dynamic_install.py 2021-10-29 12:07:43.000000000 +0200 +++ new/azure-cli-core-2.31.0/azure/cli/core/extension/dynamic_install.py 2021-12-03 06:02:53.000000000 +0100 @@ -3,7 +3,7 @@ # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- -import azure.cli.core.telemetry as telemetry +from azure.cli.core import telemetry from azure.cli.core.commands import AzCliCommandInvoker from azure.cli.core.azclierror import CommandNotFoundError from knack.log import get_logger diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/azure-cli-core-2.30.0/azure/cli/core/parser.py new/azure-cli-core-2.31.0/azure/cli/core/parser.py --- old/azure-cli-core-2.30.0/azure/cli/core/parser.py 2021-10-29 12:07:43.000000000 +0200 +++ new/azure-cli-core-2.31.0/azure/cli/core/parser.py 2021-12-03 06:02:53.000000000 +0100 @@ -8,7 +8,7 @@ import argparse import argcomplete -import azure.cli.core.telemetry as telemetry +from azure.cli.core import telemetry from azure.cli.core.extension import get_extension from azure.cli.core.commands import ExtensionCommandSource from azure.cli.core.commands import AzCliCommandInvoker diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/azure-cli-core-2.30.0/azure/cli/core/profiles/_shared.py new/azure-cli-core-2.31.0/azure/cli/core/profiles/_shared.py --- old/azure-cli-core-2.30.0/azure/cli/core/profiles/_shared.py 2021-10-29 12:07:43.000000000 +0200 +++ new/azure-cli-core-2.31.0/azure/cli/core/profiles/_shared.py 2021-12-03 06:02:53.000000000 +0100 @@ -143,10 +143,10 @@ AZURE_API_PROFILES = { 'latest': { ResourceType.MGMT_STORAGE: '2021-06-01', - ResourceType.MGMT_NETWORK: '2021-03-01', + ResourceType.MGMT_NETWORK: '2021-05-01', ResourceType.MGMT_COMPUTE: SDKProfile('2021-07-01', { 'resource_skus': '2019-04-01', - 'disks': '2020-12-01', + 'disks': '2021-04-01', 'disk_encryption_sets': '2020-12-01', 'disk_accesses': '2020-05-01', 'snapshots': '2021-04-01', @@ -159,7 +159,7 @@ ResourceType.MGMT_RESOURCE_FEATURES: '2021-07-01', ResourceType.MGMT_RESOURCE_LINKS: '2016-09-01', ResourceType.MGMT_RESOURCE_LOCKS: '2016-09-01', - ResourceType.MGMT_RESOURCE_POLICY: '2020-09-01', + ResourceType.MGMT_RESOURCE_POLICY: '2021-06-01', ResourceType.MGMT_RESOURCE_RESOURCES: '2021-04-01', ResourceType.MGMT_RESOURCE_SUBSCRIPTIONS: '2019-11-01', ResourceType.MGMT_RESOURCE_DEPLOYMENTSCRIPTS: '2020-10-01', @@ -187,7 +187,7 @@ ResourceType.DATA_KEYVAULT_ADMINISTRATION_BACKUP: '7.2-preview', ResourceType.DATA_KEYVAULT_ADMINISTRATION_ACCESS_CONTROL: '7.2-preview', ResourceType.DATA_STORAGE: '2018-11-09', - ResourceType.DATA_STORAGE_BLOB: '2020-06-12', + ResourceType.DATA_STORAGE_BLOB: '2020-10-02', ResourceType.DATA_STORAGE_FILEDATALAKE: '2020-02-10', ResourceType.DATA_STORAGE_FILESHARE: '2019-07-07', ResourceType.DATA_STORAGE_QUEUE: '2018-03-28', diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/azure-cli-core-2.30.0/azure/cli/core/telemetry.py new/azure-cli-core-2.31.0/azure/cli/core/telemetry.py --- old/azure-cli-core-2.30.0/azure/cli/core/telemetry.py 2021-10-29 12:07:43.000000000 +0200 +++ new/azure-cli-core-2.31.0/azure/cli/core/telemetry.py 2021-12-03 06:02:53.000000000 +0100 @@ -15,7 +15,7 @@ from collections import defaultdict from functools import wraps from knack.util import CLIError -import azure.cli.core.decorators as decorators +from azure.cli.core import decorators PRODUCT_NAME = 'azurecli' TELEMETRY_VERSION = '0.0.1.4' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/azure-cli-core-2.30.0/azure/cli/core/util.py new/azure-cli-core-2.31.0/azure/cli/core/util.py --- old/azure-cli-core-2.30.0/azure/cli/core/util.py 2021-10-29 12:07:43.000000000 +0200 +++ new/azure-cli-core-2.31.0/azure/cli/core/util.py 2021-12-03 06:02:53.000000000 +0100 @@ -60,7 +60,7 @@ from azure.common import AzureException from azure.core.exceptions import AzureError from requests.exceptions import SSLError, HTTPError - import azure.cli.core.azclierror as azclierror + from azure.cli.core import azclierror import traceback logger.debug("azure.cli.core.util.handle_exception is called with an exception:") @@ -185,8 +185,8 @@ def get_error_type_by_azure_error(ex): - import azure.core.exceptions as exceptions - import azure.cli.core.azclierror as azclierror + from azure.core import exceptions + from azure.cli.core import azclierror if isinstance(ex, exceptions.HttpResponseError): status_code = str(ex.status_code) @@ -205,7 +205,7 @@ # pylint: disable=too-many-return-statements def get_error_type_by_status_code(status_code): - import azure.cli.core.azclierror as azclierror + from azure.cli.core import azclierror if status_code == '400': return azclierror.BadRequestError diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/azure-cli-core-2.30.0/azure_cli_core.egg-info/PKG-INFO new/azure-cli-core-2.31.0/azure_cli_core.egg-info/PKG-INFO --- old/azure-cli-core-2.30.0/azure_cli_core.egg-info/PKG-INFO 2021-10-29 12:07:54.000000000 +0200 +++ new/azure-cli-core-2.31.0/azure_cli_core.egg-info/PKG-INFO 2021-12-03 06:03:07.000000000 +0100 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: azure-cli-core -Version: 2.30.0 +Version: 2.31.0 Summary: Microsoft Azure Command-Line Tools Core Module Home-page: https://github.com/Azure/azure-cli Author: Microsoft Corporation @@ -16,6 +16,7 @@ Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 Classifier: License :: OSI Approved :: MIT License Requires-Python: >=3.6.0 License-File: LICENSE.txt diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/azure-cli-core-2.30.0/azure_cli_core.egg-info/requires.txt new/azure-cli-core-2.31.0/azure_cli_core.egg-info/requires.txt --- old/azure-cli-core-2.30.0/azure_cli_core.egg-info/requires.txt 2021-10-29 12:07:54.000000000 +0200 +++ new/azure-cli-core-2.31.0/azure_cli_core.egg-info/requires.txt 2021-12-03 06:03:07.000000000 +0100 @@ -2,15 +2,14 @@ azure-cli-telemetry==1.0.6.* azure-mgmt-core<2,>=1.2.0 cryptography -humanfriendly<10.0,>=4.7 +humanfriendly~=10.0 jmespath -knack~=0.8.2 +knack~=0.9.0 msal-extensions<0.4,>=0.3.0 -msal<2.0.0,>=1.15.0 +msal<2.0.0,>=1.16.0 paramiko<3.0.0,>=2.0.8 pkginfo>=1.5.0.1 PyJWT>=2.1.0 pyopenssl>=17.1.0 -requests[socks]~=2.25.1 -urllib3[secure]>=1.26.5 +requests[socks] psutil~=5.8 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/azure-cli-core-2.30.0/setup.py new/azure-cli-core-2.31.0/setup.py --- old/azure-cli-core-2.30.0/setup.py 2021-10-29 12:07:43.000000000 +0200 +++ new/azure-cli-core-2.31.0/setup.py 2021-12-03 06:02:53.000000000 +0100 @@ -8,7 +8,7 @@ from codecs import open from setuptools import setup, find_packages -VERSION = "2.30.0" +VERSION = "2.31.0" # If we have source, validate that our version numbers match # This should prevent uploading releases with mismatched versions. @@ -39,6 +39,7 @@ 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', 'License :: OSI Approved :: MIT License', ] @@ -47,17 +48,16 @@ 'azure-cli-telemetry==1.0.6.*', 'azure-mgmt-core>=1.2.0,<2', 'cryptography', - 'humanfriendly>=4.7,<10.0', + 'humanfriendly~=10.0', 'jmespath', - 'knack~=0.8.2', + 'knack~=0.9.0', 'msal-extensions>=0.3.0,<0.4', - 'msal>=1.15.0,<2.0.0', + 'msal>=1.16.0,<2.0.0', 'paramiko>=2.0.8,<3.0.0', 'pkginfo>=1.5.0.1', 'PyJWT>=2.1.0', 'pyopenssl>=17.1.0', # https://github.com/pyca/pyopenssl/pull/612 - 'requests[socks]~=2.25.1', - 'urllib3[secure]>=1.26.5' + 'requests[socks]' ] # dependencies for specific OSes