Skip to content
Open
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
24 changes: 23 additions & 1 deletion src/azure-cli/azure/cli/command_modules/acr/_docker_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,28 @@ def _handle_challenge_phase(login_server,
return token_params


def _resolve_acr_scope(cli_ctx):
"""Determine the AAD resource (audience) to request a token for, for the ACR /oauth2/exchange endpoint.

Resolution order:
1. ``az config set acr.audience_resource=<value>`` — operator override. If the value
starts with ``https://`` it is used verbatim, otherwise it is treated as a short
name and expanded to ``https://<value>.azure.net``.
2. The default public ACR audience ``https://containerregistry.azure.net``.

This lets disconnected environments (e.g. Azure Local Disconnected Operations) pin
the audience their local IDP knows about, instead of relying on runtime fallback.
"""
configured = None
try:
configured = cli_ctx.config.get('acr', 'audience_resource', fallback=None)
except Exception: # pylint: disable=broad-except
configured = None
if configured:
return configured if configured.startswith('https://') else "https://{}.azure.net".format(configured)
return "https://{}.azure.net".format(ACR_AUDIENCE_RESOURCE_NAME)
Comment on lines +142 to +148
Copy link

Copilot AI Apr 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The config parsing here treats any override that doesn’t start with https:// as a short name and appends .azure.net. This will produce an invalid audience for common inputs like containerregistry.azure.net (no scheme) or non-HTTPS schemes like http://... / api://... (it becomes https://http://....azure.net). Consider detecting “already a URL/host” more robustly (e.g., if it contains :// use verbatim; else if it contains a dot treat it as a host and just prefix https://; else treat it as a short name and append .azure.net).

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot apply changes based on this feedback



def _get_aad_token_after_challenge(cli_ctx,
token_params,
login_server,
Expand All @@ -141,7 +163,7 @@ def _get_aad_token_after_challenge(cli_ctx,
from azure.cli.core._profile import Profile
profile = Profile(cli_ctx=cli_ctx)

scope = "https://{}.azure.net".format(ACR_AUDIENCE_RESOURCE_NAME)
scope = _resolve_acr_scope(cli_ctx)

# this might be a cross tenant scenario, so pass subscription to get_raw_token
creds, _, tenant = profile.get_raw_token(subscription=get_subscription_id(cli_ctx),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,13 @@
get_access_credentials,
get_authorization_header,
get_manifest_authorization_header,
_resolve_acr_scope,
RepoAccessTokenPermission,
HelmAccessTokenPermission,
EMPTY_GUID
)
from azure.cli.command_modules.acr._docker_utils import ResourceNotFound
from azure.cli.command_modules.acr._constants import ACR_AUDIENCE_RESOURCE_NAME
from azure.cli.core.mock import DummyCli


Expand Down Expand Up @@ -1446,3 +1448,43 @@ def _setup_cmd(self):
mock_sku.premium.value = 'Premium'
cmd.get_models.return_value = mock_sku
return cmd


class ResolveAcrScopeTests(unittest.TestCase):
"""Unit tests for _docker_utils._resolve_acr_scope.

The helper resolves the AAD audience used for ACR's /oauth2/exchange call.
Operators can override the default via ``az config set acr.audience_resource=<value>``.
"""

@staticmethod
def _ctx(configured):
cli_ctx = mock.MagicMock()
cli_ctx.config.get.return_value = configured
return cli_ctx

def test_default_when_unset(self):
self.assertEqual(
_resolve_acr_scope(self._ctx(None)),
"https://{}.azure.net".format(ACR_AUDIENCE_RESOURCE_NAME),
)

def test_short_name_is_expanded(self):
self.assertEqual(
_resolve_acr_scope(self._ctx("containerregistry")),
"https://containerregistry.azure.net",
)

def test_full_url_is_used_verbatim(self):
self.assertEqual(
_resolve_acr_scope(self._ctx("https://customregistry.example.com")),
"https://customregistry.example.com",
)

def test_config_exception_falls_back_to_default(self):
cli_ctx = mock.MagicMock()
cli_ctx.config.get.side_effect = RuntimeError("no config")
self.assertEqual(
_resolve_acr_scope(cli_ctx),
"https://{}.azure.net".format(ACR_AUDIENCE_RESOURCE_NAME),
)
Comment on lines +1472 to +1490
Copy link

Copilot AI Apr 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new tests don’t cover overrides that are likely user inputs but currently mis-parsed, such as containerregistry.azure.net (no scheme) or a non-HTTPS scheme (http://.../api://...). Adding coverage for these cases will help prevent regressions once the parsing logic is made more robust.

Copilot uses AI. Check for mistakes.
Loading