Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .azure-pipelines/templates/azdev_setup.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ steps:
chmod +x env/bin/activate
. env/bin/activate
python -m pip install -U pip
pip install azdev
pip install azdev==0.1.90
azdev --version

if [ -z "$CLI_EXT_REPO_PATH" ]; then
Expand Down
26 changes: 8 additions & 18 deletions azure-pipelines.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ trigger:
include:
- '*'
exclude:
- 'release'
- 'release*'

pr:
branches:
Expand Down Expand Up @@ -208,7 +208,7 @@ jobs:
artifactName: metadata

- task: PipAuthenticate@1
condition: eq(variables['Build.SourceBranch'], 'refs/heads/release')
condition: startsWith(variables['Build.SourceBranch'], 'refs/heads/release')
displayName: 'Pip Authenticate'
inputs:
artifactFeeds: $(AZURE_ARTIFACTS_FEEDS)
Expand Down Expand Up @@ -252,7 +252,7 @@ jobs:
artifactName: metadata

- task: PipAuthenticate@1
condition: eq(variables['Build.SourceBranch'], 'refs/heads/release')
condition: startsWith(variables['Build.SourceBranch'], 'refs/heads/release')
displayName: 'Pip Authenticate'
inputs:
artifactFeeds: $(AZURE_ARTIFACTS_FEEDS)
Expand Down Expand Up @@ -442,7 +442,7 @@ jobs:
versionSpec: 3.12

- task: PipAuthenticate@1
condition: eq(variables['Build.SourceBranch'], 'refs/heads/release')
condition: startsWith(variables['Build.SourceBranch'], 'refs/heads/release')
displayName: 'Pip Authenticate'
inputs:
artifactFeeds: $(AZURE_ARTIFACTS_FEEDS)
Expand Down Expand Up @@ -730,7 +730,7 @@ jobs:
displayName: Install Docker

- task: PipAuthenticate@1
condition: eq(variables['Build.SourceBranch'], 'refs/heads/release')
condition: startsWith(variables['Build.SourceBranch'], 'refs/heads/release')
displayName: 'Pip Authenticate'
inputs:
artifactFeeds: $(AZURE_ARTIFACTS_FEEDS)
Expand Down Expand Up @@ -826,7 +826,7 @@ jobs:
- bash: ./scripts/ci/install_docker.sh
displayName: Install Docker
- task: PipAuthenticate@1
condition: eq(variables['Build.SourceBranch'], 'refs/heads/release')
condition: startsWith(variables['Build.SourceBranch'], 'refs/heads/release')
displayName: 'Pip Authenticate'
inputs:
artifactFeeds: $(AZURE_ARTIFACTS_FEEDS)
Expand Down Expand Up @@ -911,12 +911,6 @@ jobs:
matrix:
${{ each arch in parameters.architectures }}:
# https://wiki.ubuntu.com/Releases
Focal ${{ arch.name }}:
# 20.04
deb_system: ubuntu
distro: focal
arch: ${{ arch.value }}
pool: ${{ arch.pool }}
Jammy ${{ arch.name }}:
# 22.04
deb_system: ubuntu
Expand Down Expand Up @@ -948,7 +942,7 @@ jobs:
- bash: ./scripts/ci/install_docker.sh
displayName: Install Docker
- task: PipAuthenticate@1
condition: eq(variables['Build.SourceBranch'], 'refs/heads/release')
condition: startsWith(variables['Build.SourceBranch'], 'refs/heads/release')
displayName: 'Pip Authenticate'
inputs:
artifactFeeds: $(AZURE_ARTIFACTS_FEEDS)
Expand Down Expand Up @@ -980,11 +974,6 @@ jobs:
strategy:
matrix:
${{ each arch in parameters.architectures }}:
Focal ${{ arch.name }}:
deb_system: ubuntu
distro: focal
arch: ${{ arch.value }}
pool: ${{ arch.pool }}
Jammy ${{ arch.name }}:
deb_system: ubuntu
distro: jammy
Expand Down Expand Up @@ -1054,6 +1043,7 @@ jobs:

- job: CheckHeaders
displayName: "Check License, History, and DocMap"
condition: and(not(contains(variables['Build.SourceBranch'], 'lts')), not(contains(variables['System.PullRequest.TargetBranch'], 'lts')))

pool:
name: ${{ variables.ubuntu_pool }}
Expand Down
2 changes: 1 addition & 1 deletion build_scripts/windows/scripts/build.cmd
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ if "%ARCH%"=="x86" (
echo Please set ARCH to "x86" or "x64"
goto ERROR
)
set PYTHON_VERSION=3.12.7
set PYTHON_VERSION=3.12.8

set WIX_DOWNLOAD_URL="https://azurecliprod.blob.core.windows.net/msi/wix310-binaries-mirror.zip"
set PYTHON_DOWNLOAD_URL="https://www.python.org/ftp/python/%PYTHON_VERSION%/python-%PYTHON_VERSION%-embed-%PYTHON_ARCH%.zip"
Expand Down
2 changes: 1 addition & 1 deletion scripts/release/debian/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ set -exv
ls -Rl /mnt/artifacts

WORKDIR=`cd $(dirname $0); cd ../../../; pwd`
PYTHON_VERSION="3.12.7"
PYTHON_VERSION="3.12.8"
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"

# Update APT packages
Expand Down
2 changes: 1 addition & 1 deletion scripts/release/pypi/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ pip list

script_dir=`cd $(dirname $BASH_SOURCE[0]); pwd`

if [[ "$branch" != "release" ]]; then
if [[ ! $branch =~ ^release ]]; then
. $script_dir/../../ci/version.sh post`date -u '+%Y%m%d%H%M%S'`
fi

Expand Down
2 changes: 1 addition & 1 deletion src/azure-cli-core/azure/cli/core/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
# --------------------------------------------------------------------------------------------
# pylint: disable=line-too-long

__version__ = "2.66.0"
__version__ = "2.66.1"

import os
import sys
Expand Down
22 changes: 5 additions & 17 deletions src/azure-cli-core/azure/cli/core/auth/identity.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,6 @@
"Select the account you want to log in with. "
"For more information on login with Azure CLI, see https://go.microsoft.com/fwlink/?linkid=2271136")

PASSWORD_CERTIFICATE_WARNING = (
"Passing the service principal certificate with `--password` is deprecated and will be removed "
"by version 2.74. Please use `--certificate` instead.")

logger = get_logger(__name__)


Expand Down Expand Up @@ -196,7 +192,7 @@ def login_with_service_principal(self, client_id, credential, scopes):
"""
sp_auth = ServicePrincipalAuth.build_from_credential(self.tenant_id, client_id, credential)
client_credential = sp_auth.get_msal_client_credential()
cca = ConfidentialClientApplication(client_id, client_credential, **self._msal_app_kwargs)
cca = ConfidentialClientApplication(client_id, client_credential=client_credential, **self._msal_app_kwargs)
result = cca.acquire_token_for_client(scopes)
check_result(result)

Expand Down Expand Up @@ -307,7 +303,7 @@ def build_from_credential(cls, tenant_id, client_id, credential):
return ServicePrincipalAuth(entry)

@classmethod
def build_credential(cls, secret_or_certificate=None,
def build_credential(cls, client_secret=None,
certificate=None, use_cert_sn_issuer=None,
client_assertion=None):
"""Build credential from user input. The credential looks like below, but only one key can exist.
Expand All @@ -318,20 +314,12 @@ def build_credential(cls, secret_or_certificate=None,
}
"""
entry = {}
if certificate:
if client_secret:
entry[_CLIENT_SECRET] = client_secret
elif certificate:
entry[_CERTIFICATE] = os.path.expanduser(certificate)
if use_cert_sn_issuer:
entry[_USE_CERT_SN_ISSUER] = use_cert_sn_issuer
elif secret_or_certificate:
# TODO: Make secret_or_certificate secret only
user_expanded = os.path.expanduser(secret_or_certificate)
if os.path.isfile(user_expanded):
logger.warning(PASSWORD_CERTIFICATE_WARNING)
entry[_CERTIFICATE] = user_expanded
if use_cert_sn_issuer:
entry[_USE_CERT_SN_ISSUER] = use_cert_sn_issuer
else:
entry[_CLIENT_SECRET] = secret_or_certificate
elif client_assertion:
entry[_CLIENT_ASSERTION] = client_assertion
return entry
Expand Down
16 changes: 3 additions & 13 deletions src/azure-cli-core/azure/cli/core/auth/tests/test_identity.py
Original file line number Diff line number Diff line change
Expand Up @@ -264,20 +264,10 @@ def test_service_principal_auth_client_assertion(self):
assert client_credential == {'client_assertion': 'test_jwt'}

def test_build_credential(self):
# secret
cred = ServicePrincipalAuth.build_credential(secret_or_certificate="test_secret")
# client_secret
cred = ServicePrincipalAuth.build_credential(client_secret="test_secret")
assert cred == {"client_secret": "test_secret"}

# secret with '~', which is preserved as-is
cred = ServicePrincipalAuth.build_credential(secret_or_certificate="~test_secret")
assert cred == {"client_secret": "~test_secret"}

# certificate as password (deprecated)
current_dir = os.path.dirname(os.path.realpath(__file__))
test_cert_file = os.path.join(current_dir, 'sp_cert.pem')
cred = ServicePrincipalAuth.build_credential(secret_or_certificate=test_cert_file)
assert cred == {'certificate': test_cert_file}

# certificate
current_dir = os.path.dirname(os.path.realpath(__file__))
test_cert_file = os.path.join(current_dir, 'sp_cert.pem')
Expand All @@ -297,7 +287,7 @@ def test_build_credential(self):
cred = ServicePrincipalAuth.build_credential(certificate=test_cert_file, use_cert_sn_issuer=True)
assert cred == {'certificate': test_cert_file, 'use_cert_sn_issuer': True}

# client assertion
# client_assertion
cred = ServicePrincipalAuth.build_credential(client_assertion="test_jwt")
assert cred == {"client_assertion": "test_jwt"}

Expand Down
24 changes: 17 additions & 7 deletions src/azure-cli-core/azure/cli/core/auth/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@
AccessToken = namedtuple("AccessToken", ["token", "expires_on"])


PASSWORD_CERTIFICATE_WARNING = (
"The error may be caused by passing a service principal certificate with --password. "
"Please note that --password no longer accepts a service principal certificate. "
"To pass a service principal certificate, use --certificate instead.")


def aad_error_handler(error, **kwargs):
""" Handle the error from AAD server returned by ADAL or MSAL. """

Expand All @@ -30,17 +36,21 @@ def aad_error_handler(error, **kwargs):
"below, please mention the hostname '%s'", socket.gethostname())

error_description = error.get('error_description')
error_codes = error.get('error_codes')

# Build recommendation message
login_command = _generate_login_command(**kwargs)
login_message = (
# Cloud Shell uses IMDS-like interface for implicit login. If getting token/cert failed,
# we let the user explicitly log in to AAD with MSAL.
"Please explicitly log in with:\n{}" if error.get('error') == 'broker_error'
else "Interactive authentication is needed. Please run:\n{}").format(login_command)
if error_codes and 7000215 in error_codes:
recommendation = PASSWORD_CERTIFICATE_WARNING
else:
login_command = _generate_login_command(**kwargs)
recommendation = (
# Cloud Shell uses IMDS-like interface for implicit login. If getting token/cert failed,
# we let the user explicitly log in to AAD with MSAL.
"Please explicitly log in with:\n{}" if error.get('error') == 'broker_error'
else "Interactive authentication is needed. Please run:\n{}").format(login_command)

from azure.cli.core.azclierror import AuthenticationError
raise AuthenticationError(error_description, msal_error=error, recommendation=login_message)
raise AuthenticationError(error_description, msal_error=error, recommendation=recommendation)


def _generate_login_command(scopes=None, claims=None):
Expand Down
12 changes: 6 additions & 6 deletions src/azure-cli-core/azure/cli/core/commands/arm.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from azure.cli.core.commands.client_factory import get_mgmt_service_client
from azure.cli.core.commands.events import EVENT_INVOKER_PRE_LOAD_ARGUMENTS
from azure.cli.core.commands.validators import IterateValue
from azure.cli.core.util import shell_safe_json_parse, get_command_type_kwarg
from azure.cli.core.util import shell_safe_json_parse, get_command_type_kwarg, getprop
from azure.cli.core.profiles import ResourceType, get_sdk

from knack.arguments import CLICommandArgument, ignore_type
Expand Down Expand Up @@ -600,7 +600,7 @@ def remove_properties(instance, argument_values):
def throw_and_show_options(instance, part, path):
from msrest.serialization import Model
options = instance.__dict__ if hasattr(instance, '__dict__') else instance
if isinstance(instance, Model) and isinstance(getattr(instance, 'additional_properties', None), dict):
if isinstance(instance, Model) and isinstance(getprop(instance, 'additional_properties', None), dict):
options.update(options.pop('additional_properties'))
parent = '.'.join(path[:-1]).replace('.[', '[')
error_message = "Couldn't find '{}' in '{}'.".format(part, parent)
Expand Down Expand Up @@ -673,15 +673,15 @@ def _update_instance(instance, part, path): # pylint: disable=too-many-return-s
matches.append(x)
elif not isinstance(x, dict):
snake_key = make_snake_case(key)
if hasattr(x, snake_key) and getattr(x, snake_key, None) == value:
if hasattr(x, snake_key) and getprop(x, snake_key, None) == value:
matches.append(x)

if len(matches) == 1:
return matches[0]
if len(matches) > 1:
raise CLIError("non-unique key '{}' found multiple matches on {}. Key must be unique."
.format(key, path[-2]))
if key in getattr(instance, 'additional_properties', {}):
if key in getprop(instance, 'additional_properties', {}):
instance.enable_additional_properties_sending()
return instance.additional_properties[key]
raise CLIError("item with value '{}' doesn\'t exist for key '{}' on {}".format(value, key, path[-2]))
Expand All @@ -697,8 +697,8 @@ def _update_instance(instance, part, path): # pylint: disable=too-many-return-s
return instance[part]

if hasattr(instance, make_snake_case(part)):
return getattr(instance, make_snake_case(part), None)
if part in getattr(instance, 'additional_properties', {}):
return getprop(instance, make_snake_case(part), None)
if part in getprop(instance, 'additional_properties', {}):
instance.enable_additional_properties_sending()
return instance.additional_properties[part]
raise AttributeError()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ class TestCredentialHelper(unittest.TestCase):
def _get_test_secret_masker(self):
from microsoft_security_utilities_secret_masker import SecretMasker, load_regex_pattern_from_json
email_address_json = {
"Pattern": r"(?<refine>[\w.%#+-]+)(%40|@)([a-z0-9.-]*.[a-z]{2,})",
"Pattern": r"(?P<refine>[\w.%#+-]+)(%40|@)([a-z0-9.-]*.[a-z]{2,})",
"Id": "001",
"Name": "EmailAddress",
"Signatures": [
Expand All @@ -22,7 +22,7 @@ def _get_test_secret_masker(self):
"DetectionMetadata": "HighConfidence"
}
guid_json = {
"Pattern": r"(?<refine>[0-9a-f]{8}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{12})",
"Pattern": r"(?P<refine>[0-9a-f]{8}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{12})",
"Id": "002",
"Name": "GUID",
"DetectionMetadata": "LowConfidence"
Expand Down
18 changes: 18 additions & 0 deletions src/azure-cli-core/azure/cli/core/tests/test_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,24 @@ def test_b64_to_hex_type(self):
self.assertIsInstance(b64_to_hex(self.base64), str)


class TestGetProperty(unittest.TestCase):

def test_getprop(self):
from azure.cli.core.util import getprop
with self.assertRaises(AttributeError):
getprop(self, '__class__')
with self.assertRaises(AttributeError):
getprop(self, '__init__')
with self.assertRaises(AttributeError):
getprop(self, 'assertRaises')
with self.assertRaises(AttributeError):
getprop(self, '_diffThreshold')
with self.assertRaises(AttributeError):
getprop(self, 'new_props')
self.assertEqual(getprop(self, 'maxDiff'), self.maxDiff)
self.assertEqual(getprop(self, 'new_props', "new_props"), "new_props")


class TestHandleException(unittest.TestCase):

@mock.patch('azure.cli.core.azclierror.logger.error', autospec=True)
Expand Down
14 changes: 14 additions & 0 deletions src/azure-cli-core/azure/cli/core/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -1392,3 +1392,17 @@ def run_az_cmd(args, out_file=None):
cli = get_default_cli()
cli.invoke(args, out_file=out_file)
return cli.result


def getprop(o, name, *default):
""" This function is used to get the property of the object.
It will raise an error if the property is a private property or a method.
"""
if name.startswith('_'):
# avoid to access the private properties or methods
raise AttributeError(name)
v = getattr(o, name, *default)
if callable(v):
# avoid to access the methods
raise AttributeError(name)
return v
Loading