Skip to content

Commit 5ded022

Browse files
committed
add default scopes for known clouds
1 parent a2e467a commit 5ded022

3 files changed

Lines changed: 52 additions & 8 deletions

File tree

sdk/identity/azure-identity/azure/identity/_internal/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ def _scopes_to_resource(*scopes):
6161
"InteractiveCredential",
6262
"MsalTransportAdapter",
6363
"MsalTransportResponse",
64+
"normalize_authority",
6465
"PublicClientCredential",
6566
"wrap_exceptions",
6667
]

sdk/identity/azure-identity/azure/identity/_internal/msal_credentials.py

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@
2020

2121
from .exception_wrapper import wrap_exceptions
2222
from .msal_transport_adapter import MsalTransportAdapter
23-
from .._exceptions import AuthenticationRequiredError
23+
from .._constants import KnownAuthorities
24+
from .._exceptions import AuthenticationRequiredError, CredentialUnavailableError
2425
from .._internal import get_default_authority, normalize_authority
2526
from .._auth_record import AuthenticationRecord
2627

@@ -41,6 +42,13 @@
4142

4243
_LOGGER = logging.getLogger(__name__)
4344

45+
_DEFAULT_AUTHENTICATE_SCOPES = {
46+
"https://" + KnownAuthorities.AZURE_CHINA: ("https://management.core.chinacloudapi.cn//.default",),
47+
"https://" + KnownAuthorities.AZURE_GERMANY: ("https://management.core.cloudapi.de//.default",),
48+
"https://" + KnownAuthorities.AZURE_GOVERNMENT: ("https://management.core.usgovcloudapi.net//.default",),
49+
"https://" + KnownAuthorities.AZURE_PUBLIC_CLOUD: ("https://management.core.windows.net//.default",),
50+
}
51+
4452

4553
def _decode_client_info(raw):
4654
"""Taken from msal.oauth2cli.oidc"""
@@ -91,11 +99,9 @@ class MsalCredential(ABC):
9199

92100
def __init__(self, client_id, client_credential=None, **kwargs):
93101
# type: (str, Optional[Union[str, Mapping[str, str]]], **Any) -> None
94-
tenant_id = kwargs.pop("tenant_id", None) or "organizations"
95102
authority = kwargs.pop("authority", None)
96-
authority = normalize_authority(authority) if authority else get_default_authority()
97-
98-
self._base_url = "/".join((authority, tenant_id.strip("/")))
103+
self._authority = normalize_authority(authority) if authority else get_default_authority()
104+
self._tenant_id = kwargs.pop("tenant_id", None) or "organizations"
99105

100106
self._client_credential = client_credential
101107
self._client_id = client_id
@@ -130,7 +136,7 @@ def _create_app(self, cls):
130136
app = cls(
131137
client_id=self._client_id,
132138
client_credential=self._client_credential,
133-
authority=self._base_url,
139+
authority="{}/{}".format(self._authority, self._tenant_id),
134140
token_cache=self._cache,
135141
)
136142

@@ -242,15 +248,24 @@ def authenticate(self, **kwargs):
242248
# type: (**Any) -> AuthenticationRecord
243249
"""Interactively authenticate a user.
244250
245-
:keyword Sequence[str] scopes: optional scopes to request during authentication, such as those provided by
251+
:keyword Sequence[str] scopes: scopes to request during authentication, such as those provided by
246252
:func:`AuthenticationRequiredError.scopes`. If provided, successful authentication will cache an access token
247253
for these scopes.
248254
:rtype: ~azure.identity.AuthenticationRecord
249255
:raises ~azure.core.exceptions.ClientAuthenticationError: authentication failed. The error's ``message``
250256
attribute gives a reason.
251257
"""
252258

253-
scopes = kwargs.pop("scopes", None) or ("https://management.azure.com/.default",)
259+
scopes = kwargs.pop("scopes", None)
260+
if not scopes:
261+
if self._authority not in _DEFAULT_AUTHENTICATE_SCOPES:
262+
# the credential is configured to use a cloud whose ARM scope we can't determine
263+
raise CredentialUnavailableError(
264+
message="Authenticating in this environment requires a value for the 'scopes' keyword argument."
265+
)
266+
267+
scopes = _DEFAULT_AUTHENTICATE_SCOPES[self._authority]
268+
254269
_ = self.get_token(*scopes, _allow_prompt=True, **kwargs)
255270
return self.authentication_record # type: ignore
256271

sdk/identity/azure-identity/tests/test_msal_interactive_credential.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,34 @@ def validate_scopes(*scopes, **_):
142142
assert request_token.call_count == 1, "validation method wasn't called"
143143

144144

145+
@pytest.mark.parametrize(
146+
"authority,expected_scope",
147+
(
148+
(KnownAuthorities.AZURE_CHINA, "https://management.core.chinacloudapi.cn//.default"),
149+
(KnownAuthorities.AZURE_GERMANY, "https://management.core.cloudapi.de//.default"),
150+
(KnownAuthorities.AZURE_GOVERNMENT, "https://management.core.usgovcloudapi.net//.default"),
151+
(KnownAuthorities.AZURE_PUBLIC_CLOUD, "https://management.core.windows.net//.default"),
152+
),
153+
)
154+
def test_authenticate_default_scopes(authority, expected_scope):
155+
"""when given no scopes, authenticate should default to the ARM scope appropriate for the configured authority"""
156+
157+
def validate_scopes(*scopes):
158+
assert scopes == (expected_scope,)
159+
return {"access_token": "**", "expires_in": 42}
160+
161+
request_token = Mock(wraps=validate_scopes)
162+
MockCredential(authority=authority, request_token=request_token).authenticate()
163+
assert request_token.call_count == 1
164+
165+
166+
def test_authenticate_unknown_cloud():
167+
"""authenticate should raise when given no scopes in an unknown cloud"""
168+
169+
with pytest.raises(CredentialUnavailableError):
170+
MockCredential(authority="localhost").authenticate()
171+
172+
145173
@pytest.mark.parametrize("option", (True, False))
146174
def test_authenticate_ignores_disable_automatic_authentication(option):
147175
"""authenticate should prompt for authentication regardless of the credential's configuration"""

0 commit comments

Comments
 (0)