Skip to content
Draft
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
6 changes: 6 additions & 0 deletions scripts/ci/credscan/CredScanSuppressions.json
Original file line number Diff line number Diff line change
Expand Up @@ -555,6 +555,12 @@
"src\\azure-cli\\azure\\cli\\command_modules\\container\\tests\\latest\\recordings\\test_container_create_with_acr.yaml"
],
"_justification": "[Container] one-off password used in test, not persisted"
},
{
"file": [
"src\\azure-cli\\azure\\cli\\command_modules\\profile\\tests\\latest\\test_profile_custom.py"
],
"_justification": "[Profile] Test access token. The signature has been redacted to make the token invalid."
}
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ def load_arguments(self, command):
c.argument('resource', options_list=['--resource'], help='Azure resource endpoints in AAD v1.0.')
c.argument('scopes', options_list=['--scope'], nargs='*', help='Space-separated AAD scopes in AAD v2.0. Default to Azure Resource Manager.')
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 MSI or Cloud Shell account')
c.argument('show_claims', help='Show the decoded claims of the access token.')


COMMAND_LOADER_CLS = ProfileCommandsLoader
3 changes: 3 additions & 0 deletions src/azure-cli/azure/cli/command_modules/profile/_help.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,9 @@
- name: Get an access token to use with MS Graph API
text: >
az account get-access-token --resource-type ms-graph
- name: Show the decoded claims of the access token, instead of the token itself
text: >
az account get-access-token --show-claims
"""

helps['self-test'] = """
Expand Down
7 changes: 6 additions & 1 deletion src/azure-cli/azure/cli/command_modules/profile/custom.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,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,
show_claims=False):
"""
get AAD token to access to a specified resource.
Use 'az cloud show' command for other Azure resources
Expand All @@ -66,6 +67,10 @@ def get_access_token(cmd, subscription=None, resource=None, scopes=None, resourc
creds, subscription, tenant = profile.get_raw_token(subscription=subscription, resource=resource, scopes=scopes,
tenant=tenant)

if show_claims:
import jwt
return jwt.decode(creds[1], algorithms=['RS256'], options={'verify_signature': False})

result = {
'tokenType': creds[0],
'accessToken': creds[1],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,21 @@
from knack.util import CLIError


# The test access token is created using following commands.

# 1. Create a new user with tenant admin account.
# az ad user create --display-name "Azure CLI Test User" --user-principal-name azure-cli-test-user@AzureSDKTeam.onmicrosoft.com --password xxx

# 2. Use the new user account to log in and get an access token. You may do this on another machine or WSL.
# MAKE SURE to redact the signature (3rd) segment of the token to invalidate it.
# az login --username azure-cli-test-user@AzureSDKTeam.onmicrosoft.com --password xxx --allow-no-subscriptions
# az account get-access-token --query accessToken --output tsv

# 3. At last, use the admin account to delete the user.
# az ad user delete --id azure-cli-test-user@AzureSDKTeam.onmicrosoft.com
TEST_USER_ACCESS_TOKEN = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6ImpTMVhvMU9XRGpfNTJ2YndHTmd2UU8yVnpNYyIsImtpZCI6ImpTMVhvMU9XRGpfNTJ2YndHTmd2UU8yVnpNYyJ9.eyJhdWQiOiJodHRwczovL21hbmFnZW1lbnQuY29yZS53aW5kb3dzLm5ldC8iLCJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC81NDgyNmIyMi0zOGQ2LTRmYjItYmFkOS1iN2I5M2EzZTljNWEvIiwiaWF0IjoxNjU0NjcxNDg2LCJuYmYiOjE2NTQ2NzE0ODYsImV4cCI6MTY1NDY3NTQzMiwiYWNyIjoiMSIsImFpbyI6IkFUUUF5LzhUQUFBQVgzeCtvV092RVR6alY5Nm1hNnVqeEp3OWFUenJGWHV4SnFrRmFRb3ZtL1I3WUdBVjA5SDFCVmluOUVNQXVYeVIiLCJhbXIiOlsicHdkIl0sImFwcGlkIjoiMDRiMDc3OTUtOGRkYi00NjFhLWJiZWUtMDJmOWUxYmY3YjQ2IiwiYXBwaWRhY3IiOiIwIiwiaXBhZGRyIjoiMTY3LjIyMC4yNTUuMjciLCJuYW1lIjoiQXp1cmUgQ0xJIFRlc3QgVXNlciIsIm9pZCI6IjY2NTY2YjBmLTg1OTAtNDQxYy1hYmJhLWQ4ZWQxNjQ2YTEwYiIsInB1aWQiOiIxMDAzMjAwMjAzNDREQzM5IiwicmgiOiIwLkFUY0FJbXVDVk5ZNHNrLTYyYmU1T2o2Y1drWklmM2tBdXRkUHVrUGF3ZmoyTUJNM0FIZy4iLCJzY3AiOiJ1c2VyX2ltcGVyc29uYXRpb24iLCJzdWIiOiJiWVNQZXpNeHF5TDQxRHBBdWhxRjJ3ZGpoLWhJbm5SOFpCeFRrTGw2V21RIiwidGlkIjoiNTQ4MjZiMjItMzhkNi00ZmIyLWJhZDktYjdiOTNhM2U5YzVhIiwidW5pcXVlX25hbWUiOiJhenVyZS1jbGktdGVzdC11c2VyQEF6dXJlU0RLVGVhbS5vbm1pY3Jvc29mdC5jb20iLCJ1cG4iOiJhenVyZS1jbGktdGVzdC11c2VyQEF6dXJlU0RLVGVhbS5vbm1pY3Jvc29mdC5jb20iLCJ1dGkiOiIydzVNZDBPaE4wNkhhR2ZmTEQxQkFBIiwidmVyIjoiMS4wIiwid2lkcyI6WyJiNzlmYmY0ZC0zZWY5LTQ2ODktODE0My03NmIxOTRlODU1MDkiXSwieG1zX3RjZHQiOjE0MTIyMDY4NDB9.redacted'


class ProfileCommandTest(unittest.TestCase):
@mock.patch('azure.cli.core.api.load_subscriptions', autospec=True)
@mock.patch('azure.cli.command_modules.profile.custom.logger', autospec=True)
Expand All @@ -37,15 +52,15 @@ def test_get_raw_token(self, get_raw_token_mock):
cmd = mock.MagicMock()
cmd.cli_ctx = DummyCli()

get_raw_token_mock.return_value = (['bearer', 'token123', {'expiresOn': '2100-01-01'}], 'sub123', 'tenant123')
get_raw_token_mock.return_value = (['bearer', TEST_USER_ACCESS_TOKEN, {'expiresOn': '2100-01-01'}], 'sub123', 'tenant123')

result = get_access_token(cmd)

# assert
get_raw_token_mock.assert_called_with(mock.ANY, None, None, None, None)
expected_result = {
'tokenType': 'bearer',
'accessToken': 'token123',
'accessToken': TEST_USER_ACCESS_TOKEN,
'expiresOn': '2100-01-01',
'subscription': 'sub123',
'tenant': 'tenant123'
Expand All @@ -55,7 +70,7 @@ def test_get_raw_token(self, get_raw_token_mock):
# assert it takes customized resource, subscription
resource = 'https://graph.microsoft.com/'
subscription_id = '00000001-0000-0000-0000-000000000000'
get_raw_token_mock.return_value = (['bearer', 'token123', {'expiresOn': '2100-01-01'}], subscription_id,
get_raw_token_mock.return_value = (['bearer', TEST_USER_ACCESS_TOKEN, {'expiresOn': '2100-01-01'}], 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)
Expand All @@ -67,17 +82,24 @@ def test_get_raw_token(self, get_raw_token_mock):

# test get token with tenant
tenant_id = '00000000-0000-0000-0000-000000000000'
get_raw_token_mock.return_value = (['bearer', 'token123', {'expiresOn': '2100-01-01'}], None, tenant_id)
get_raw_token_mock.return_value = (['bearer', TEST_USER_ACCESS_TOKEN, {'expiresOn': '2100-01-01'}], None, tenant_id)
result = get_access_token(cmd, tenant=tenant_id)
expected_result = {
'tokenType': 'bearer',
'accessToken': 'token123',
'accessToken': TEST_USER_ACCESS_TOKEN,
'expiresOn': '2100-01-01',
'tenant': tenant_id
}
self.assertEqual(result, expected_result)
get_raw_token_mock.assert_called_with(mock.ANY, None, None, None, tenant_id)

# Test showing claims of the access token
result = get_access_token(cmd, show_claims=True)
assert result['oid'] == '66566b0f-8590-441c-abba-d8ed1646a10b'
assert result['tid'] == '54826b22-38d6-4fb2-bad9-b7b93a3e9c5a'
assert result['name'] == 'Azure CLI Test User'
assert result['upn'] == 'azure-cli-test-user@AzureSDKTeam.onmicrosoft.com'

@mock.patch('azure.cli.command_modules.profile.custom.Profile', autospec=True)
def test_get_login(self, profile_mock):
invoked = []
Expand Down
Loading