Skip to content

Commit

Permalink
Add login_hint argument to InteractiveBrowserCredential (#19229)
Browse files Browse the repository at this point in the history
  • Loading branch information
chlowell authored Jun 18, 2021
1 parent b37aa44 commit c8291ac
Show file tree
Hide file tree
Showing 3 changed files with 48 additions and 11 deletions.
5 changes: 4 additions & 1 deletion sdk/identity/azure-identity/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
# Release History

## 1.7.0b2 (Unreleased)

### Added
- `InteractiveBrowserCredential` keyword argument `login_hint` enables
pre-filling the username/email address field on the login page
([#19225](https://github.com/Azure/azure-sdk-for-python/issues/19225))

## 1.7.0b1 (2021-06-08)
Beginning with this release, this library requires Python 2.7 or 3.6+.
Expand Down
27 changes: 17 additions & 10 deletions sdk/identity/azure-identity/azure/identity/_credentials/browser.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,21 +32,23 @@ class InteractiveBrowserCredential(InteractiveCredential):
there with the authorization code flow, using PKCE (Proof Key for Code Exchange) internally to protect the code.
:keyword str authority: Authority of an Azure Active Directory endpoint, for example 'login.microsoftonline.com',
the authority for Azure Public Cloud (which is the default). :class:`~azure.identity.AzureAuthorityHosts`
defines authorities for other clouds.
the authority for Azure Public Cloud (which is the default). :class:`~azure.identity.AzureAuthorityHosts`
defines authorities for other clouds.
:keyword str tenant_id: an Azure Active Directory tenant ID. Defaults to the 'organizations' tenant, which can
authenticate work or school accounts.
authenticate work or school accounts.
:keyword str client_id: Client ID of the Azure Active Directory application users will sign in to. If
unspecified, users will authenticate to an Azure development application.
unspecified, users will authenticate to an Azure development application.
:keyword str login_hint: a username suggestion to pre-fill the login page's username/email address field. A user
may still log in with a different username.
:keyword str redirect_uri: a redirect URI for the application identified by `client_id` as configured in Azure
Active Directory, for example "http://localhost:8400". This is only required when passing a value for
`client_id`, and must match a redirect URI in the application's registration. The credential must be able to
bind a socket to this URI.
Active Directory, for example "http://localhost:8400". This is only required when passing a value for
`client_id`, and must match a redirect URI in the application's registration. The credential must be able to
bind a socket to this URI.
:keyword AuthenticationRecord authentication_record: :class:`AuthenticationRecord` returned by :func:`authenticate`
:keyword bool disable_automatic_authentication: if True, :func:`get_token` will raise
:class:`AuthenticationRequiredError` when user interaction is required to acquire a token. Defaults to False.
:class:`AuthenticationRequiredError` when user interaction is required to acquire a token. Defaults to False.
:keyword cache_persistence_options: configuration for persistent token caching. If unspecified, the credential
will cache tokens in memory.
will cache tokens in memory.
:paramtype cache_persistence_options: ~azure.identity.TokenCachePersistenceOptions
:keyword int timeout: seconds to wait for the user to complete authentication. Defaults to 300 (5 minutes).
:raises ValueError: invalid `redirect_uri`
Expand All @@ -62,6 +64,7 @@ def __init__(self, **kwargs):
else:
self._parsed_url = None

self._login_hint = kwargs.pop("login_hint", None)
self._timeout = kwargs.pop("timeout", 300)
self._server_class = kwargs.pop("_server_class", AuthCodeRedirectServer)
client_id = kwargs.pop("client_id", DEVELOPER_SIGN_ON_CLIENT_ID)
Expand Down Expand Up @@ -96,7 +99,11 @@ def _request_token(self, *scopes, **kwargs):
claims = kwargs.get("claims")
app = self._get_app()
flow = app.initiate_auth_code_flow(
scopes, redirect_uri=redirect_uri, prompt="select_account", claims_challenge=claims
scopes,
redirect_uri=redirect_uri,
prompt="select_account",
claims_challenge=claims,
login_hint=self._login_hint,
)
if "auth_uri" not in flow:
raise CredentialUnavailableError("Failed to begin authentication flow")
Expand Down
27 changes: 27 additions & 0 deletions sdk/identity/azure-identity/tests/test_browser_credential.py
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,33 @@ def test_claims_challenge():
assert kwargs["claims_challenge"] == expected_claims


def test_login_hint():
expected_username = "[email protected]"
auth_code_response = {"code": "authorization-code", "state": ["..."]}
server_class = Mock(return_value=Mock(wait_for_redirect=lambda: auth_code_response))
transport = Mock(send=Mock(side_effect=Exception("this test mocks MSAL, so no request should be sent")))

msal_acquire_token_result = dict(
build_aad_response(access_token="**", id_token=build_id_token()),
id_token_claims=id_token_claims("issuer", "subject", "audience", upn="upn"),
)
mock_msal_app = Mock(
acquire_token_by_auth_code_flow=Mock(return_value=msal_acquire_token_result),
initiate_auth_code_flow=Mock(return_value={"auth_uri": "http://localhost"}),
)

credential = InteractiveBrowserCredential(
_server_class=server_class, transport=transport, login_hint=expected_username
)
with patch("msal.PublicClientApplication", Mock(return_value=mock_msal_app)):
with patch(WEBBROWSER_OPEN, lambda _: True):
credential.authenticate(scopes=["scope"])

assert mock_msal_app.initiate_auth_code_flow.call_count == 1
_, kwargs = mock_msal_app.initiate_auth_code_flow.call_args
assert kwargs["login_hint"] == expected_username


@pytest.mark.parametrize(
"uname,is_wsl",
(
Expand Down

0 comments on commit c8291ac

Please sign in to comment.