Skip to content

Commit 3d6e977

Browse files
committed
Refactor test infrastructure to expose a known bug
Apply the refactor to similar code path
1 parent 28b45a3 commit 3d6e977

File tree

3 files changed

+85
-35
lines changed

3 files changed

+85
-35
lines changed

msal/application.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1857,6 +1857,7 @@ def _acquire_token_interactive_via_broker(
18571857
if login_hint and prompt != "select_account": # OIDC prompts when the user did not sign in
18581858
accounts = self.get_accounts(username=login_hint)
18591859
if len(accounts) == 1: # Unambiguously proceed with this account
1860+
logger.debug("Calling broker._acquire_token_silently()")
18601861
response = _acquire_token_silently( # When it works, it bypasses prompt
18611862
authority,
18621863
self.client_id,
@@ -1868,6 +1869,7 @@ def _acquire_token_interactive_via_broker(
18681869
return self._process_broker_response(response, scopes, data)
18691870
# login_hint undecisive or not exists
18701871
if prompt == "none" or not prompt: # Must/Can attempt _signin_silently()
1872+
logger.debug("Calling broker._signin_silently()")
18711873
response = _signin_silently( # Unlike OIDC, it doesn't honor login_hint
18721874
authority, self.client_id, scopes,
18731875
validateAuthority=validate_authority,
@@ -1903,7 +1905,8 @@ def _acquire_token_interactive_via_broker(
19031905
pass # It will fall back to the _signin_interactively()
19041906
else:
19051907
return self._process_broker_response(response, scopes, data)
1906-
# Falls back to _signin_interactively()
1908+
1909+
logger.debug("Falls back to broker._signin_interactively()")
19071910
on_before_launching_ui(ui="broker")
19081911
response = _signin_interactively(
19091912
authority, self.client_id, scopes,

msal/broker.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ def _signin_silently(
144144
def _signin_interactively(
145145
authority, client_id, scopes,
146146
parent_window_handle, # None means auto-detect for console apps
147-
prompt=None,
147+
prompt=None, # Note: This function does not really use this parameter
148148
login_hint=None,
149149
claims=None,
150150
correlation_id=None,

tests/test_e2e.py

Lines changed: 80 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,25 @@ def _get_app_and_auth_code(
5454
assert ac is not None
5555
return (app, ac, redirect_uri)
5656

57+
def _render(url, description=None):
58+
# Render a url in html if description is available, otherwise return url as-is
59+
return "<a href='{url}' target=_blank>{description}</a>".format(
60+
url=url, description=description) if description else url
61+
62+
63+
def _get_hint(html_mode=None, username=None, lab_name=None, username_uri=None):
64+
return "Sign in with {user} whose password is available from {lab}".format(
65+
user=("<b>{}</b>".format(username) if html_mode else username)
66+
if username
67+
else "the upn from {}".format(_render(
68+
username_uri, description="here" if html_mode else None)),
69+
lab=_render(
70+
"https://aka.ms/GetLabUserSecret?Secret=" + (lab_name or "msidlabXYZ"),
71+
description="this password api" if html_mode else None,
72+
),
73+
)
74+
75+
5776
@unittest.skipIf(os.getenv("TRAVIS_TAG"), "Skip e2e tests during tagged release")
5877
class E2eTestCase(unittest.TestCase):
5978

@@ -212,25 +231,31 @@ def _test_device_flow(
212231

213232
def _test_acquire_token_interactive(
214233
self, client_id=None, authority=None, scope=None, port=None,
215-
username_uri="", # But you would want to provide one
234+
username=None, lab_name=None,
235+
username_uri="", # Unnecessary if you provided username and lab_name
216236
data=None, # Needed by ssh-cert feature
217237
prompt=None,
238+
enable_msa_passthrough=None,
218239
**ignored):
219240
assert client_id and authority and scope
220241
self.app = self._build_app(client_id, authority=authority)
242+
logger.info(_get_hint( # Useful when testing broker which shows no welcome_template
243+
username=username, lab_name=lab_name, username_uri=username_uri))
221244
result = self.app.acquire_token_interactive(
222245
scope,
246+
login_hint=username,
223247
prompt=prompt,
224248
timeout=120,
225249
port=port,
226250
parent_window_handle=self.app.CONSOLE_WINDOW_HANDLE, # This test app is a console app
251+
enable_msa_passthrough=enable_msa_passthrough, # Needed when testing MSA-PT app
227252
welcome_template= # This is an undocumented feature for testing
228253
"""<html><body><h1>{id}</h1><ol>
229-
<li>Get a username from the upn shown at <a href="{username_uri}">here</a></li>
230-
<li>Get its password from https://aka.ms/GetLabUserSecret?Secret=msidlabXYZ
231-
(replace the lab name with the labName from the link above).</li>
254+
<li>{hint}</li>
232255
<li><a href="$auth_uri">Sign In</a> or <a href="$abort_uri">Abort</a></li>
233-
</ol></body></html>""".format(id=self.id(), username_uri=username_uri),
256+
</ol></body></html>""".format(id=self.id(), hint=_get_hint(
257+
html_mode=True,
258+
username=username, lab_name=lab_name, username_uri=username_uri)),
234259
data=data or {},
235260
)
236261
self.assertIn(
@@ -239,6 +264,11 @@ def _test_acquire_token_interactive(
239264
# Note: No interpolation here, cause error won't always present
240265
error=result.get("error"),
241266
error_description=result.get("error_description")))
267+
if username and result.get("id_token_claims", {}).get("preferred_username"):
268+
self.assertEqual(
269+
username, result["id_token_claims"]["preferred_username"],
270+
"You are expected to sign in as account {}, but tokens returned is for {}".format(
271+
username, result["id_token_claims"]["preferred_username"]))
242272
self.assertCacheWorksForUser(result, scope, username=None, data=data or {})
243273
return result # For further testing
244274

@@ -260,7 +290,7 @@ def test_ssh_cert_for_service_principal(self):
260290
self.assertEqual("ssh-cert", result["token_type"])
261291

262292
@unittest.skipIf(os.getenv("TRAVIS"), "Browser automation is not yet implemented")
263-
def test_ssh_cert_for_user(self):
293+
def test_ssh_cert_for_user_should_work_with_any_account(self):
264294
result = self._test_acquire_token_interactive(
265295
client_id="04b07795-8ddb-461a-bbee-02f9e1bf7b46", # Azure CLI is one
266296
# of the only 2 clients that are PreAuthz to use ssh cert feature
@@ -555,7 +585,8 @@ def _test_acquire_token_by_auth_code(
555585

556586
def _test_acquire_token_by_auth_code_flow(
557587
self, client_id=None, authority=None, port=None, scope=None,
558-
username_uri="", # But you would want to provide one
588+
username=None, lab_name=None,
589+
username_uri="", # Optional if you provided username and lab_name
559590
**ignored):
560591
assert client_id and authority and scope
561592
self.app = msal.ClientApplication(
@@ -568,11 +599,11 @@ def _test_acquire_token_by_auth_code_flow(
568599
auth_response = receiver.get_auth_response(
569600
auth_uri=flow["auth_uri"], state=flow["state"], timeout=60,
570601
welcome_template="""<html><body><h1>{id}</h1><ol>
571-
<li>Get a username from the upn shown at <a href="{username_uri}">here</a></li>
572-
<li>Get its password from https://aka.ms/GetLabUserSecret?Secret=msidlabXYZ
573-
(replace the lab name with the labName from the link above).</li>
602+
<li>{hint}</li>
574603
<li><a href="$auth_uri">Sign In</a> or <a href="$abort_uri">Abort</a></li>
575-
</ol></body></html>""".format(id=self.id(), username_uri=username_uri),
604+
</ol></body></html>""".format(id=self.id(), hint=_get_hint(
605+
html_mode=True,
606+
username=username, lab_name=lab_name, username_uri=username_uri)),
576607
)
577608
if auth_response is None:
578609
self.skipTest("Timed out. Did not have test settings in hand? Prepare and retry.")
@@ -592,6 +623,11 @@ def _test_acquire_token_by_auth_code_flow(
592623
# Note: No interpolation here, cause error won't always present
593624
error=result.get("error"),
594625
error_description=result.get("error_description")))
626+
if username and result.get("id_token_claims", {}).get("preferred_username"):
627+
self.assertEqual(
628+
username, result["id_token_claims"]["preferred_username"],
629+
"You are expected to sign in as account {}, but tokens returned is for {}".format(
630+
username, result["id_token_claims"]["preferred_username"]))
595631
self.assertCacheWorksForUser(result, scope, username=None)
596632

597633
def _test_acquire_token_obo(self, config_pca, config_cca,
@@ -689,10 +725,23 @@ def test_adfs2019_fed_user(self):
689725

690726
@unittest.skipIf(os.getenv("TRAVIS"), "Browser automation is not yet implemented")
691727
def test_cloud_acquire_token_interactive(self):
692-
config = self.get_lab_user(usertype="cloud")
693-
self._test_acquire_token_interactive(
694-
username_uri="https://msidlab.com/api/user?usertype=cloud",
695-
**config)
728+
self._test_acquire_token_interactive(**self.get_lab_user(usertype="cloud"))
729+
730+
@unittest.skipIf(os.getenv("TRAVIS"), "Browser automation is not yet implemented")
731+
def test_msa_pt_app_signin_via_organizations_authority_without_login_hint(self):
732+
"""There is/was an upstream bug. See test case full docstring for the details.
733+
734+
When a MSAL-PT flow that account control is launched, user has 2+ AAD accounts in WAM,
735+
selects an AAD account that is NOT the default AAD account from the OS,
736+
it will incorrectly get tokens for default AAD account.
737+
"""
738+
self._test_acquire_token_interactive(**dict(
739+
self.get_lab_user(usertype="cloud"), # This is generally not the current laptop's default AAD account
740+
authority="https://login.microsoftonline.com/organizations",
741+
client_id="04b07795-8ddb-461a-bbee-02f9e1bf7b46", # Azure CLI is an MSA-PT app
742+
enable_msa_passthrough=True,
743+
prompt="select_account", # In MSAL Python, this resets login_hint
744+
))
696745

697746
def test_ropc_adfs2019_onprem(self):
698747
# Configuration is derived from https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/blob/4.7.0/tests/Microsoft.Identity.Test.Common/TestConstants.cs#L250-L259
@@ -719,22 +768,22 @@ def test_adfs2019_onprem_acquire_token_by_auth_code(self):
719768
@unittest.skipIf(os.getenv("TRAVIS"), "Browser automation is not yet implemented")
720769
def test_adfs2019_onprem_acquire_token_by_auth_code_flow(self):
721770
config = self.get_lab_user(usertype="onprem", federationProvider="ADFSv2019")
722-
config["authority"] = "https://fs.%s.com/adfs" % config["lab_name"]
723-
config["scope"] = self.adfs2019_scopes
724-
config["port"] = 8080
725-
self._test_acquire_token_by_auth_code_flow(
726-
username_uri="https://msidlab.com/api/user?usertype=onprem&federationprovider=ADFSv2019",
727-
**config)
771+
self._test_acquire_token_by_auth_code_flow(**dict(
772+
config,
773+
authority="https://fs.%s.com/adfs" % config["lab_name"],
774+
scope=self.adfs2019_scopes,
775+
port=8080,
776+
))
728777

729778
@unittest.skipIf(os.getenv("TRAVIS"), "Browser automation is not yet implemented")
730779
def test_adfs2019_onprem_acquire_token_interactive(self):
731780
config = self.get_lab_user(usertype="onprem", federationProvider="ADFSv2019")
732-
config["authority"] = "https://fs.%s.com/adfs" % config["lab_name"]
733-
config["scope"] = self.adfs2019_scopes
734-
config["port"] = 8080
735-
self._test_acquire_token_interactive(
736-
username_uri="https://msidlab.com/api/user?usertype=onprem&federationprovider=ADFSv2019",
737-
**config)
781+
self._test_acquire_token_interactive(**dict(
782+
config,
783+
authority="https://fs.%s.com/adfs" % config["lab_name"],
784+
scope=self.adfs2019_scopes,
785+
port=8080,
786+
))
738787

739788
@unittest.skipUnless(
740789
os.getenv("LAB_OBO_CLIENT_SECRET"),
@@ -816,14 +865,12 @@ def test_b2c_acquire_token_by_auth_code(self):
816865

817866
@unittest.skipIf(os.getenv("TRAVIS"), "Browser automation is not yet implemented")
818867
def test_b2c_acquire_token_by_auth_code_flow(self):
819-
config = self.get_lab_app_object(azureenvironment="azureb2ccloud")
820-
self._test_acquire_token_by_auth_code_flow(
868+
self._test_acquire_token_by_auth_code_flow(**dict(
869+
self.get_lab_user(usertype="b2c", b2cprovider="local"),
821870
authority=self._build_b2c_authority("B2C_1_SignInPolicy"),
822-
client_id=config["appId"],
823871
port=3843, # Lab defines 4 of them: [3843, 4584, 4843, 60000]
824-
scope=config["scopes"],
825-
username_uri="https://msidlab.com/api/user?usertype=b2c&b2cprovider=local",
826-
)
872+
scope=self.get_lab_app_object(azureenvironment="azureb2ccloud")["scopes"],
873+
))
827874

828875
def test_b2c_acquire_token_by_ropc(self):
829876
config = self.get_lab_app_object(azureenvironment="azureb2ccloud")

0 commit comments

Comments
 (0)