Skip to content

Commit d33a93b

Browse files
authored
[Identity] Encode claims for certain credentials (#42885)
Subprocess-based credentials rely on the claims being base64-encoded, however out authentication policies call get_token/get_token_info with claims as a JSON string. Here, we ensure that the claims are encoded before being used. Signed-off-by: Paul Van Eck <[email protected]>
1 parent 48248c2 commit d33a93b

15 files changed

+59
-37
lines changed

.vscode/cspell.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -643,7 +643,8 @@
643643
{
644644
"filename": "sdk/identity/azure-identity/tests/*.py",
645645
"words": [
646-
"infile"
646+
"infile",
647+
"acrs"
647648
]
648649
},
649650
{

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

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

1919
from .. import CredentialUnavailableError
20-
from .._internal import resolve_tenant, within_dac, validate_tenant_id, validate_scope
20+
from .._internal import encode_base64, resolve_tenant, within_dac, validate_tenant_id, validate_scope
2121
from .._internal.decorators import log_get_token
2222

2323

@@ -184,7 +184,7 @@ def _get_token_base(
184184
if tenant:
185185
command_args += ["--tenant-id", tenant]
186186
if claims:
187-
command_args += ["--claims", claims]
187+
command_args += ["--claims", encode_base64(claims)]
188188
output = _run_command(command_args, self._process_timeout)
189189

190190
token = parse_token(output)

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
from .. import CredentialUnavailableError
1919
from .._internal import (
2020
_scopes_to_resource,
21+
encode_base64,
2122
resolve_tenant,
2223
within_dac,
2324
validate_tenant_id,
@@ -155,8 +156,8 @@ def _get_token_base(
155156
self, *scopes: str, options: Optional[TokenRequestOptions] = None, **kwargs: Any
156157
) -> AccessTokenInfo:
157158
# Check for claims challenge first
158-
if options and options.get("claims"):
159-
error_message = CLAIMS_UNSUPPORTED_ERROR.format(claims_value=options.get("claims"))
159+
if options and "claims" in options and options["claims"]:
160+
error_message = CLAIMS_UNSUPPORTED_ERROR.format(claims_value=encode_base64(options["claims"]))
160161

161162
# Add tenant if provided in options
162163
if options.get("tenant_id"):

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from .. import CredentialUnavailableError
1616
from .._internal import (
1717
_scopes_to_resource,
18+
encode_base64,
1819
resolve_tenant,
1920
within_dac,
2021
validate_tenant_id,
@@ -183,8 +184,8 @@ def _get_token_base(
183184
) -> AccessTokenInfo:
184185

185186
# Check if claims challenge is provided
186-
if options and options.get("claims"):
187-
error_message = CLAIMS_UNSUPPORTED_ERROR.format(claims_value=options.get("claims"))
187+
if options and "claims" in options and options["claims"]:
188+
error_message = CLAIMS_UNSUPPORTED_ERROR.format(claims_value=encode_base64(options["claims"]))
188189
if options.get("tenant_id"):
189190
error_message += f" -Tenant {options.get('tenant_id')}"
190191
raise CredentialUnavailableError(message=error_message)

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from .decorators import wrap_exceptions
1010
from .interactive import InteractiveCredential
1111
from .utils import (
12+
encode_base64,
1213
get_default_authority,
1314
normalize_authority,
1415
process_credential_exclusions,
@@ -46,6 +47,7 @@ def _scopes_to_resource(*scopes) -> str:
4647
"AadClientBase",
4748
"AuthCodeRedirectServer",
4849
"AadClientCertificate",
50+
"encode_base64",
4951
"get_default_authority",
5052
"InteractiveCredential",
5153
"normalize_authority",

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
# Copyright (c) Microsoft Corporation.
33
# Licensed under the MIT License.
44
# ------------------------------------
5+
import base64
56
import os
67
import platform
78
import logging
@@ -223,3 +224,8 @@ def is_wsl() -> bool:
223224
platform_name = getattr(uname, "system", uname[0]).lower()
224225
release = getattr(uname, "release", uname[2]).lower()
225226
return platform_name == "linux" and "microsoft" in release
227+
228+
229+
def encode_base64(s: str) -> str:
230+
encoded = base64.b64encode(s.encode("utf-8"))
231+
return encoded.decode("utf-8")

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
sanitize_output,
2727
extract_cli_error_message,
2828
)
29-
from ..._internal import resolve_tenant, within_dac, validate_tenant_id, validate_scope
29+
from ..._internal import encode_base64, resolve_tenant, within_dac, validate_tenant_id, validate_scope
3030

3131

3232
_LOGGER = logging.getLogger(__name__)
@@ -176,7 +176,7 @@ async def _get_token_base(
176176
if tenant:
177177
command_args += ["--tenant-id", tenant]
178178
if claims:
179-
command_args += ["--claims", claims]
179+
command_args += ["--claims", encode_base64(claims)]
180180
output = await _run_command(command_args, self._process_timeout)
181181

182182
token = parse_token(output)

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
)
2828
from ..._internal import (
2929
_scopes_to_resource,
30+
encode_base64,
3031
resolve_tenant,
3132
within_dac,
3233
validate_tenant_id,
@@ -149,8 +150,8 @@ async def _get_token_base(
149150
self, *scopes: str, options: Optional[TokenRequestOptions] = None, **kwargs: Any
150151
) -> AccessTokenInfo:
151152
# Check for claims challenge first
152-
if options and options.get("claims"):
153-
error_message = CLAIMS_UNSUPPORTED_ERROR.format(claims_value=options.get("claims"))
153+
if options and "claims" in options and options["claims"]:
154+
error_message = CLAIMS_UNSUPPORTED_ERROR.format(claims_value=encode_base64(options["claims"]))
154155

155156
# Add tenant if provided in options
156157
if options.get("tenant_id"):

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
parse_token,
1919
CLAIMS_UNSUPPORTED_ERROR,
2020
)
21-
from ..._internal import resolve_tenant, validate_tenant_id, validate_scope
21+
from ..._internal import encode_base64, resolve_tenant, validate_tenant_id, validate_scope
2222

2323

2424
class AzurePowerShellCredential(AsyncContextManager):
@@ -124,8 +124,8 @@ async def _get_token_base(
124124
) -> AccessTokenInfo:
125125

126126
# Check if claims challenge is provided
127-
if options and options.get("claims"):
128-
error_message = CLAIMS_UNSUPPORTED_ERROR.format(claims_value=options.get("claims"))
127+
if options and "claims" in options and options["claims"]:
128+
error_message = CLAIMS_UNSUPPORTED_ERROR.format(claims_value=encode_base64(options["claims"]))
129129
if options.get("tenant_id"):
130130
error_message += f" -Tenant {options.get('tenant_id')}"
131131
raise CredentialUnavailableError(message=error_message)

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -343,7 +343,7 @@ def fake_check_output(command_line, **_):
343343
def test_claims_challenge_raises_error(get_token_method):
344344
"""The credential should raise CredentialUnavailableError when claims challenge is provided"""
345345

346-
claims = "test-claims-challenge"
346+
claims = '{"access_token":{"acrs":{"essential":true,"values":["p1"]}}}'
347347
credential = AzureDeveloperCliCredential()
348348

349349
expected_message = "Suggestion: re-authentication required, run `azd auth login` to acquire a new token."
@@ -405,15 +405,16 @@ def test_empty_claims_does_not_raise_error(get_token_method):
405405
def test_claims_command_line_argument(get_token_method):
406406
"""The credential should pass claims as --claims argument to azd command"""
407407

408-
claims = "test-claims-challenge"
408+
claims = '{"access_token":{"acrs":{"essential":true,"values":["p1"]}}}'
409+
expected_encoded_claims = "eyJhY2Nlc3NfdG9rZW4iOnsiYWNycyI6eyJlc3NlbnRpYWwiOnRydWUsInZhbHVlcyI6WyJwMSJdfX19"
409410
access_token = "access token"
410411
expected_expires_on = 1602015811
411412

412413
def fake_check_output(command_line, **kwargs):
413414
# Verify that claims are passed as --claims argument
414415
assert "--claims" in command_line
415416
claims_index = command_line.index("--claims")
416-
assert command_line[claims_index + 1] == claims
417+
assert command_line[claims_index + 1] == expected_encoded_claims
417418

418419
return json.dumps(
419420
{

0 commit comments

Comments
 (0)