diff --git a/src/azure-cli-core/azure/cli/core/_profile.py b/src/azure-cli-core/azure/cli/core/_profile.py index 4c49377d842..2ea2131efce 100644 --- a/src/azure-cli-core/azure/cli/core/_profile.py +++ b/src/azure-cli-core/azure/cli/core/_profile.py @@ -421,7 +421,7 @@ def get_login_credentials(self, resource=None, subscription_id=None, aux_subscri str(account[_SUBSCRIPTION_ID]), str(account[_TENANT_ID])) - def get_raw_token(self, resource=None, scopes=None, subscription=None, tenant=None): + def get_raw_token(self, resource=None, scopes=None, subscription=None, tenant=None, client_id=None): # Convert resource to scopes if resource and not scopes: from .auth.util import resource_to_scopes @@ -458,7 +458,7 @@ def get_raw_token(self, resource=None, scopes=None, subscription=None, tenant=No scopes_to_resource(scopes)) else: - cred = self._create_credential(account, tenant_id=tenant) + cred = self._create_credential(account, tenant_id=tenant, client_id=client_id) sdk_token = cred.get_token(*scopes) # Convert epoch int 'expires_on' to datetime string 'expiresOn' for backward compatibility diff --git a/src/azure-cli-core/azure/cli/core/auth/identity.py b/src/azure-cli-core/azure/cli/core/auth/identity.py index 69f853a4a36..a183de81e99 100644 --- a/src/azure-cli-core/azure/cli/core/auth/identity.py +++ b/src/azure-cli-core/azure/cli/core/auth/identity.py @@ -73,6 +73,7 @@ def __init__(self, authority, tenant_id=None, client_id=None, encrypt=False, use """ self.authority = authority self.tenant_id = tenant_id + # This client ID is only used for PublicClientApplication, not ConfidentialClientApplication self.client_id = client_id or AZURE_CLI_CLIENT_ID self._encrypt = encrypt self._use_msal_http_cache = use_msal_http_cache diff --git a/src/azure-cli/azure/cli/command_modules/profile/__init__.py b/src/azure-cli/azure/cli/command_modules/profile/__init__.py index 4df9e6d23a6..745e3412121 100644 --- a/src/azure-cli/azure/cli/command_modules/profile/__init__.py +++ b/src/azure-cli/azure/cli/command_modules/profile/__init__.py @@ -101,6 +101,8 @@ def load_arguments(self, command): c.argument('tenant', options_list=['--tenant', '-t'], help='Tenant ID for which the token is acquired. Only available for user and service principal ' 'account, not for managed identity or Cloud Shell account') + c.argument('client_id', + help='A first-party app ID that can do single sign-on with Azure CLI.') COMMAND_LOADER_CLS = ProfileCommandsLoader diff --git a/src/azure-cli/azure/cli/command_modules/profile/custom.py b/src/azure-cli/azure/cli/command_modules/profile/custom.py index 89416732a73..b2036943d70 100644 --- a/src/azure-cli/azure/cli/command_modules/profile/custom.py +++ b/src/azure-cli/azure/cli/command_modules/profile/custom.py @@ -70,7 +70,8 @@ def show_subscription(cmd, subscription=None): return profile.get_subscription(subscription) -def get_access_token(cmd, subscription=None, resource=None, scopes=None, resource_type=None, tenant=None): +def get_access_token(cmd, subscription=None, resource=None, scopes=None, resource_type=None, tenant=None, + client_id=None): """ get AAD token to access to a specified resource. Use 'az cloud show' command for other Azure resources @@ -80,8 +81,8 @@ def get_access_token(cmd, subscription=None, resource=None, scopes=None, resourc resource = getattr(cmd.cli_ctx.cloud.endpoints, endpoints_attr_name) profile = Profile(cli_ctx=cmd.cli_ctx) - creds, subscription, tenant = profile.get_raw_token(subscription=subscription, resource=resource, scopes=scopes, - tenant=tenant) + creds, subscription, tenant = profile.get_raw_token( + subscription=subscription, resource=resource, scopes=scopes, tenant=tenant, client_id=client_id) result = { 'tokenType': creds[0], diff --git a/src/azure-cli/azure/cli/command_modules/profile/tests/latest/test_profile_custom.py b/src/azure-cli/azure/cli/command_modules/profile/tests/latest/test_profile_custom.py index 8388ff70463..a850a109d61 100644 --- a/src/azure-cli/azure/cli/command_modules/profile/tests/latest/test_profile_custom.py +++ b/src/azure-cli/azure/cli/command_modules/profile/tests/latest/test_profile_custom.py @@ -50,7 +50,8 @@ def test_get_raw_token(self, get_raw_token_mock): result = get_access_token(cmd) # assert - get_raw_token_mock.assert_called_with(mock.ANY, None, None, None, None) + get_raw_token_mock.assert_called_with(mock.ANY, resource=None, scopes=None, subscription=None, tenant=None, + client_id=None) expected_result = { 'tokenType': 'bearer', 'accessToken': 'token123', @@ -66,12 +67,13 @@ def test_get_raw_token(self, get_raw_token_mock): subscription_id = '00000001-0000-0000-0000-000000000000' get_raw_token_mock.return_value = (('bearer', 'token123', token_entry), subscription_id, 'tenant123') result = get_access_token(cmd, subscription=subscription_id, resource=resource) - get_raw_token_mock.assert_called_with(mock.ANY, resource, None, subscription_id, None) + get_raw_token_mock.assert_called_with(mock.ANY, resource=resource, scopes=None, subscription=subscription_id, + tenant=None, client_id=None) # assert it takes customized scopes get_access_token(cmd, scopes='https://graph.microsoft.com/.default') - get_raw_token_mock.assert_called_with(mock.ANY, None, scopes='https://graph.microsoft.com/.default', - subscription=None, tenant=None) + get_raw_token_mock.assert_called_with(mock.ANY, resource=None, scopes='https://graph.microsoft.com/.default', + subscription=None, tenant=None, client_id=None) # test get token with tenant tenant_id = '00000000-0000-0000-0000-000000000000' @@ -85,7 +87,8 @@ def test_get_raw_token(self, get_raw_token_mock): 'tenant': tenant_id } self.assertEqual(result, expected_result) - get_raw_token_mock.assert_called_with(mock.ANY, None, None, None, tenant_id) + get_raw_token_mock.assert_called_with(mock.ANY, resource=None, scopes=None, subscription=None, + tenant=tenant_id, client_id=None) @mock.patch('azure.cli.command_modules.profile.custom.Profile', autospec=True) def test_get_login(self, profile_mock):