Skip to content

Commit 2342bd0

Browse files
committed
[Identity] Bug fixes for devtime credentials
Signed-off-by: Paul Van Eck <paulvaneck@microsoft.com>
1 parent d2c98bd commit 2342bd0

11 files changed

Lines changed: 76 additions & 20 deletions

File tree

sdk/identity/azure-identity/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Release History
22

3+
## 1.14.1 (2023-10-09)
4+
5+
### Bugs Fixed
6+
7+
- Bug fixes for developer credentials
8+
39
## 1.14.0 (2023-08-08)
410

511
### Features Added

sdk/identity/azure-identity/azure/identity/_credentials/azd_cli.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
from azure.core.exceptions import ClientAuthenticationError
1717

1818
from .. import CredentialUnavailableError
19-
from .._internal import resolve_tenant, within_dac
19+
from .._internal import resolve_tenant, within_dac, validate_tenant_id, validate_scope
2020
from .._internal.decorators import log_get_token
2121

2222
CLI_NOT_FOUND = (
@@ -76,7 +76,8 @@ def __init__(
7676
additionally_allowed_tenants: Optional[List[str]] = None,
7777
process_timeout: int = 10,
7878
) -> None:
79-
79+
if tenant_id:
80+
validate_tenant_id(tenant_id)
8081
self.tenant_id = tenant_id
8182
self._additionally_allowed_tenants = additionally_allowed_tenants or []
8283
self._process_timeout = process_timeout
@@ -121,6 +122,11 @@ def get_token(
121122
if not scopes:
122123
raise ValueError("Missing scope in request. \n")
123124

125+
if tenant_id:
126+
validate_tenant_id(tenant_id)
127+
for scope in scopes:
128+
validate_scope(scope)
129+
124130
commandString = " --scope ".join(scopes)
125131
command = COMMAND_LINE.format(commandString)
126132
tenant = resolve_tenant(

sdk/identity/azure-identity/azure/identity/_credentials/azure_cli.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
from azure.core.exceptions import ClientAuthenticationError
1717

1818
from .. import CredentialUnavailableError
19-
from .._internal import _scopes_to_resource, resolve_tenant, within_dac
19+
from .._internal import _scopes_to_resource, resolve_tenant, within_dac, validate_tenant_id, validate_scope
2020
from .._internal.decorators import log_get_token
2121

2222

@@ -54,7 +54,8 @@ def __init__(
5454
additionally_allowed_tenants: Optional[List[str]] = None,
5555
process_timeout: int = 10,
5656
) -> None:
57-
57+
if tenant_id:
58+
validate_tenant_id(tenant_id)
5859
self.tenant_id = tenant_id
5960
self._additionally_allowed_tenants = additionally_allowed_tenants or []
6061
self._process_timeout = process_timeout
@@ -94,6 +95,10 @@ def get_token(
9495
:raises ~azure.core.exceptions.ClientAuthenticationError: the credential invoked the Azure CLI but didn't
9596
receive an access token.
9697
"""
98+
if tenant_id:
99+
validate_tenant_id(tenant_id)
100+
for scope in scopes:
101+
validate_scope(scope)
97102

98103
resource = _scopes_to_resource(*scopes)
99104
command = COMMAND_LINE.format(resource)

sdk/identity/azure-identity/azure/identity/_credentials/azure_powershell.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414
from .azure_cli import get_safe_working_dir
1515
from .. import CredentialUnavailableError
16-
from .._internal import _scopes_to_resource, resolve_tenant, within_dac
16+
from .._internal import _scopes_to_resource, resolve_tenant, within_dac, validate_tenant_id, validate_scope
1717
from .._internal.decorators import log_get_token
1818

1919

@@ -68,7 +68,8 @@ def __init__(
6868
additionally_allowed_tenants: Optional[List[str]] = None,
6969
process_timeout: int = 10,
7070
) -> None:
71-
71+
if tenant_id:
72+
validate_tenant_id(tenant_id)
7273
self.tenant_id = tenant_id
7374
self._additionally_allowed_tenants = additionally_allowed_tenants or []
7475
self._process_timeout = process_timeout
@@ -109,6 +110,11 @@ def get_token(
109110
:raises ~azure.core.exceptions.ClientAuthenticationError: the credential invoked Azure PowerShell but didn't
110111
receive an access token
111112
"""
113+
if tenant_id:
114+
validate_tenant_id(tenant_id)
115+
for scope in scopes:
116+
validate_scope(scope)
117+
112118
tenant_id = resolve_tenant(
113119
default_tenant=self.tenant_id,
114120
tenant_id=tenant_id,

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
get_default_authority,
1313
normalize_authority,
1414
resolve_tenant,
15+
validate_scope,
1516
validate_tenant_id,
1617
within_credential_chain,
1718
within_dac,
@@ -47,6 +48,7 @@ def _scopes_to_resource(*scopes) -> str:
4748
"InteractiveCredential",
4849
"normalize_authority",
4950
"resolve_tenant",
51+
"validate_scope",
5052
"within_credential_chain",
5153
"within_dac",
5254
"wrap_exceptions",

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

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import os
66
import logging
77
from contextvars import ContextVar
8+
from string import ascii_letters, digits
89
from typing import List, Optional
910

1011
from urllib.parse import urlparse
@@ -17,6 +18,9 @@
1718

1819
_LOGGER = logging.getLogger(__name__)
1920

21+
VALID_TENANT_ID_CHARACTERS = frozenset(ascii_letters + digits + "-.")
22+
VALID_SCOPE_CHARACTERS = frozenset(ascii_letters + digits + "_-.:/")
23+
2024

2125
def normalize_authority(authority: str) -> str:
2226
"""Ensure authority uses https, strip trailing spaces and /.
@@ -43,19 +47,28 @@ def get_default_authority() -> str:
4347
return normalize_authority(authority)
4448

4549

46-
VALID_TENANT_ID_CHARACTERS = frozenset("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" + "0123456789" + "-.")
50+
def validate_scope(scope: str) -> None:
51+
"""Raise ValueError if scope is empty or contains a character invalid for a scope
52+
53+
:param str scope: scope to validate
54+
:raises: ValueError if scope is empty or contains a character invalid for a scope.
55+
"""
56+
if not scope or any(c not in VALID_SCOPE_CHARACTERS for c in scope):
57+
raise ValueError(
58+
"An invalid scope was provided. Only alphanumeric characters, '.', '-', '_', ':', and '/' are allowed."
59+
)
4760

4861

4962
def validate_tenant_id(tenant_id: str) -> None:
5063
"""Raise ValueError if tenant_id is empty or contains a character invalid for a tenant ID.
5164
52-
:param str tenant_id: tenant id to validate
65+
:param str tenant_id: tenant ID to validate
5366
:raises: ValueError if tenant_id is empty or contains a character invalid for a tenant ID.
5467
"""
5568
if not tenant_id or any(c not in VALID_TENANT_ID_CHARACTERS for c in tenant_id):
5669
raise ValueError(
57-
"Invalid tenant id provided. You can locate your tenant id by following the instructions here: "
58-
+ "https://docs.microsoft.com/partner-center/find-ids-and-domain-names"
70+
"Invalid tenant ID provided. You can locate your tenant ID by following the instructions here: "
71+
+ "https://learn.microsoft.com/partner-center/find-ids-and-domain-names"
5972
)
6073

6174

sdk/identity/azure-identity/azure/identity/aio/_credentials/azd_cli.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
parse_token,
2424
sanitize_output,
2525
)
26-
from ..._internal import resolve_tenant, within_dac
26+
from ..._internal import resolve_tenant, within_dac, validate_tenant_id, validate_scope
2727

2828

2929
class AzureDeveloperCliCredential(AsyncContextManager):
@@ -73,7 +73,8 @@ def __init__(
7373
additionally_allowed_tenants: Optional[List[str]] = None,
7474
process_timeout: int = 10,
7575
) -> None:
76-
76+
if tenant_id:
77+
validate_tenant_id(tenant_id)
7778
self.tenant_id = tenant_id
7879
self._additionally_allowed_tenants = additionally_allowed_tenants or []
7980
self._process_timeout = process_timeout
@@ -110,6 +111,11 @@ async def get_token(
110111
if not scopes:
111112
raise ValueError("Missing scope in request. \n")
112113

114+
if tenant_id:
115+
validate_tenant_id(tenant_id)
116+
for scope in scopes:
117+
validate_scope(scope)
118+
113119
commandString = " --scope ".join(scopes)
114120
command = COMMAND_LINE.format(commandString)
115121
tenant = resolve_tenant(

sdk/identity/azure-identity/azure/identity/aio/_credentials/azure_cli.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
parse_token,
2424
sanitize_output,
2525
)
26-
from ..._internal import _scopes_to_resource, resolve_tenant, within_dac
26+
from ..._internal import _scopes_to_resource, resolve_tenant, within_dac, validate_tenant_id, validate_scope
2727

2828

2929
class AzureCliCredential(AsyncContextManager):
@@ -54,7 +54,8 @@ def __init__(
5454
additionally_allowed_tenants: Optional[List[str]] = None,
5555
process_timeout: int = 10,
5656
) -> None:
57-
57+
if tenant_id:
58+
validate_tenant_id(tenant_id)
5859
self.tenant_id = tenant_id
5960
self._additionally_allowed_tenants = additionally_allowed_tenants or []
6061
self._process_timeout = process_timeout
@@ -88,6 +89,11 @@ async def get_token(
8889
if sys.platform.startswith("win") and not isinstance(asyncio.get_event_loop(), asyncio.ProactorEventLoop):
8990
return _SyncAzureCliCredential().get_token(*scopes, tenant_id=tenant_id, **kwargs)
9091

92+
if tenant_id:
93+
validate_tenant_id(tenant_id)
94+
for scope in scopes:
95+
validate_scope(scope)
96+
9197
resource = _scopes_to_resource(*scopes)
9298
command = COMMAND_LINE.format(resource)
9399
tenant = resolve_tenant(

sdk/identity/azure-identity/azure/identity/aio/_credentials/azure_powershell.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
raise_for_error,
1818
parse_token,
1919
)
20-
from ..._internal import resolve_tenant
20+
from ..._internal import resolve_tenant, validate_tenant_id, validate_scope
2121

2222

2323
class AzurePowerShellCredential(AsyncContextManager):
@@ -48,7 +48,8 @@ def __init__(
4848
additionally_allowed_tenants: Optional[List[str]] = None,
4949
process_timeout: int = 10,
5050
) -> None:
51-
51+
if tenant_id:
52+
validate_tenant_id(tenant_id)
5253
self.tenant_id = tenant_id
5354
self._additionally_allowed_tenants = additionally_allowed_tenants or []
5455
self._process_timeout = process_timeout
@@ -83,6 +84,11 @@ async def get_token(
8384
if sys.platform.startswith("win") and not isinstance(asyncio.get_event_loop(), asyncio.ProactorEventLoop):
8485
return _SyncCredential().get_token(*scopes, tenant_id=tenant_id, **kwargs)
8586

87+
if tenant_id:
88+
validate_tenant_id(tenant_id)
89+
for scope in scopes:
90+
validate_scope(scope)
91+
8692
tenant_id = resolve_tenant(
8793
default_tenant=self.tenant_id,
8894
tenant_id=tenant_id,

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ def test_get_token_tenant_id(stderr):
110110

111111
Popen = get_mock_Popen(stdout=stdout, stderr=stderr)
112112
with patch(POPEN, Popen):
113-
token = AzurePowerShellCredential().get_token(scope, tenant_id="tenant_id")
113+
token = AzurePowerShellCredential().get_token(scope, tenant_id="tenant-id")
114114

115115
assert token.token == expected_access_token
116116
assert token.expires_on == expected_expires_on
@@ -315,5 +315,5 @@ def fake_Popen(command, **_):
315315
assert token.token == expected_token
316316

317317
with patch.dict("os.environ", {EnvironmentVariables.AZURE_IDENTITY_DISABLE_MULTITENANTAUTH: "true"}):
318-
token = credential.get_token("scope", tenant_id="some tenant")
318+
token = credential.get_token("scope", tenant_id="some-tenant")
319319
assert token.token == expected_token

0 commit comments

Comments
 (0)