Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add login_hint argument to InteractiveBrowserCredential #19229

Merged
merged 5 commits into from
Jun 18, 2021
Merged
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
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