Skip to content

Commit f2df4f8

Browse files
committed
Resolve merge conflicts
2 parents 25832df + a6d3d0d commit f2df4f8

File tree

5 files changed

+115
-59
lines changed

5 files changed

+115
-59
lines changed

msal/application.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -675,6 +675,7 @@ def _decide_broker(self, allow_broker, enable_pii_log):
675675
"allow_broker is deprecated. "
676676
"Please use PublicClientApplication(..., "
677677
"enable_broker_on_windows=True, "
678+
# No need to mention non-Windows platforms, because allow_broker is only for Windows
678679
"...)",
679680
DeprecationWarning)
680681
opted_in_for_broker = (
@@ -697,7 +698,7 @@ def _decide_broker(self, allow_broker, enable_pii_log):
697698
_init_broker(enable_pii_log)
698699
except RuntimeError:
699700
self._enable_broker = False
700-
logger.exception(
701+
logger.warning( # It is common on Mac and Linux where broker is not built-in
701702
"Broker is unavailable on this platform. "
702703
"We will fallback to non-broker.")
703704
logger.debug("Broker enabled? %s", self._enable_broker)
@@ -1922,7 +1923,12 @@ class PublicClientApplication(ClientApplication): # browser app or mobile app
19221923
DEVICE_FLOW_CORRELATION_ID = "_correlation_id"
19231924
CONSOLE_WINDOW_HANDLE = object()
19241925

1925-
def __init__(self, client_id, client_credential=None, **kwargs):
1926+
def __init__(
1927+
self, client_id, client_credential=None,
1928+
*,
1929+
enable_broker_on_windows=None,
1930+
enable_broker_on_mac=None,
1931+
**kwargs):
19261932
"""Same as :func:`ClientApplication.__init__`,
19271933
except that ``client_credential`` parameter shall remain ``None``.
19281934
@@ -2007,10 +2013,6 @@ def __init__(self, client_id, client_credential=None, **kwargs):
20072013
"""
20082014
if client_credential is not None:
20092015
raise ValueError("Public Client should not possess credentials")
2010-
# Using kwargs notation for now. We will switch to keyword-only arguments.
2011-
enable_broker_on_windows = kwargs.pop("enable_broker_on_windows", False)
2012-
enable_broker_on_mac = kwargs.pop("enable_broker_on_mac", False)
2013-
enable_broker_on_linux = kwargs.pop("enable_broker_on_linux", False)
20142016
self._enable_broker = bool(
20152017
enable_broker_on_windows and sys.platform == "win32"
20162018
or enable_broker_on_mac and sys.platform == "darwin"
@@ -2261,7 +2263,8 @@ def _acquire_token_interactive_via_broker(
22612263
# _signin_silently() only gets tokens for default account,
22622264
# but this seems to have been fixed in PyMsalRuntime 0.11.2
22632265
"access_token" in response and login_hint
2264-
and response.get("id_token_claims", {}) != login_hint)
2266+
and login_hint != response.get(
2267+
"id_token_claims", {}).get("preferred_username"))
22652268
wrong_account_error_message = (
22662269
'prompt="none" will not work for login_hint="non-default-user"')
22672270
if is_wrong_account:

tests/broker_util.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import logging
2+
3+
4+
logger = logging.getLogger(__name__)
5+
6+
7+
def is_pymsalruntime_installed() -> bool:
8+
try:
9+
import pymsalruntime
10+
logger.info("PyMsalRuntime installed and initialized")
11+
return True
12+
except ImportError:
13+
logger.info("PyMsalRuntime not installed")
14+
return False
15+
except RuntimeError:
16+
logger.warning(
17+
"PyMsalRuntime installed but failed to initialize the real broker. "
18+
"This may happen on Mac and Linux where broker is not built-in. "
19+
"Test cases shall attempt broker and test its fallback behavior."
20+
)
21+
return True

tests/test_account_source.py

Lines changed: 25 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,11 @@
33
from unittest.mock import patch
44
except:
55
from mock import patch
6-
try:
7-
import pymsalruntime
8-
broker_available = True
9-
except ImportError:
10-
broker_available = False
116
import msal
127
from tests import unittest
138
from tests.test_token_cache import build_response
149
from tests.http_client import MinimalResponse
10+
from tests.broker_util import is_pymsalruntime_installed
1511

1612

1713
SCOPE = "scope_foo"
@@ -24,54 +20,62 @@
2420
def _mock_post(url, headers=None, *args, **kwargs):
2521
return MinimalResponse(status_code=200, text=json.dumps(TOKEN_RESPONSE))
2622

27-
@unittest.skipUnless(broker_available, "These test cases need pip install msal[broker]")
23+
@unittest.skipUnless(is_pymsalruntime_installed(), "These test cases need pip install msal[broker]")
2824
@patch("msal.broker._acquire_token_silently", return_value=dict(
29-
TOKEN_RESPONSE, _account_id="placeholder"))
25+
TOKEN_RESPONSE, _account_id="placeholder"))
3026
@patch.object(msal.authority, "tenant_discovery", return_value={
3127
"authorization_endpoint": "https://contoso.com/placeholder",
3228
"token_endpoint": "https://contoso.com/placeholder",
3329
}) # Otherwise it would fail on OIDC discovery
3430
class TestAccountSourceBehavior(unittest.TestCase):
3531

32+
def setUp(self):
33+
self.app = msal.PublicClientApplication(
34+
"client_id",
35+
enable_broker_on_windows=True,
36+
)
37+
if not self.app._enable_broker:
38+
self.skipTest(
39+
"These test cases require patching msal.broker which is only possible "
40+
"when broker enabled successfully i.e. no RuntimeError")
41+
return super().setUp()
42+
3643
def test_device_flow_and_its_silent_call_should_bypass_broker(self, _, mocked_broker_ats):
37-
app = msal.PublicClientApplication("client_id", enable_broker_on_windows=True)
38-
result = app.acquire_token_by_device_flow({"device_code": "123"}, post=_mock_post)
44+
result = self.app.acquire_token_by_device_flow({"device_code": "123"}, post=_mock_post)
3945
self.assertEqual(result["token_source"], "identity_provider")
4046

41-
account = app.get_accounts()[0]
47+
account = self.app.get_accounts()[0]
4248
self.assertEqual(account["account_source"], "urn:ietf:params:oauth:grant-type:device_code")
4349

44-
result = app.acquire_token_silent_with_error(
50+
result = self.app.acquire_token_silent_with_error(
4551
[SCOPE], account, force_refresh=True, post=_mock_post)
4652
mocked_broker_ats.assert_not_called()
4753
self.assertEqual(result["token_source"], "identity_provider")
4854

4955
def test_ropc_flow_and_its_silent_call_should_invoke_broker(self, _, mocked_broker_ats):
50-
app = msal.PublicClientApplication("client_id", enable_broker_on_windows=True)
5156
with patch("msal.broker._signin_silently", return_value=dict(TOKEN_RESPONSE, _account_id="placeholder")):
52-
result = app.acquire_token_by_username_password(
57+
result = self.app.acquire_token_by_username_password(
5358
"username", "placeholder", [SCOPE], post=_mock_post)
5459
self.assertEqual(result["token_source"], "broker")
5560

56-
account = app.get_accounts()[0]
61+
account = self.app.get_accounts()[0]
5762
self.assertEqual(account["account_source"], "broker")
5863

59-
result = app.acquire_token_silent_with_error(
64+
result = self.app.acquire_token_silent_with_error(
6065
[SCOPE], account, force_refresh=True, post=_mock_post)
6166
self.assertEqual(result["token_source"], "broker")
6267

6368
def test_interactive_flow_and_its_silent_call_should_invoke_broker(self, _, mocked_broker_ats):
64-
app = msal.PublicClientApplication("client_id", enable_broker_on_windows=True)
65-
with patch.object(app, "_acquire_token_interactive_via_broker", return_value=dict(
69+
with patch.object(self.app, "_acquire_token_interactive_via_broker", return_value=dict(
6670
TOKEN_RESPONSE, _account_id="placeholder")):
67-
result = app.acquire_token_interactive(
68-
[SCOPE], parent_window_handle=app.CONSOLE_WINDOW_HANDLE)
71+
result = self.app.acquire_token_interactive(
72+
[SCOPE], parent_window_handle=self.app.CONSOLE_WINDOW_HANDLE)
6973
self.assertEqual(result["token_source"], "broker")
7074

71-
account = app.get_accounts()[0]
75+
account = self.app.get_accounts()[0]
7276
self.assertEqual(account["account_source"], "broker")
7377

74-
result = app.acquire_token_silent_with_error(
78+
result = self.app.acquire_token_silent_with_error(
7579
[SCOPE], account, force_refresh=True, post=_mock_post)
7680
mocked_broker_ats.assert_called_once()
7781
self.assertEqual(result["token_source"], "broker")

tests/test_application.py

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,12 @@
2020
logger = logging.getLogger(__name__)
2121
logging.basicConfig(level=logging.DEBUG)
2222

23+
_OIDC_DISCOVERY = "msal.authority.tenant_discovery"
24+
_OIDC_DISCOVERY_MOCK = Mock(return_value={
25+
"authorization_endpoint": "https://contoso.com/placeholder",
26+
"token_endpoint": "https://contoso.com/placeholder",
27+
})
28+
2329

2430
class TestHelperExtractCerts(unittest.TestCase): # It is used by SNI scenario
2531

@@ -58,10 +64,9 @@ def test_bytes_to_bytes(self):
5864

5965
class TestClientApplicationAcquireTokenSilentErrorBehaviors(unittest.TestCase):
6066

67+
@patch(_OIDC_DISCOVERY, new=_OIDC_DISCOVERY_MOCK)
6168
def setUp(self):
6269
self.authority_url = "https://login.microsoftonline.com/common"
63-
self.authority = msal.authority.Authority(
64-
self.authority_url, MinimalHttpClient())
6570
self.scopes = ["s1", "s2"]
6671
self.uid = "my_uid"
6772
self.utid = "my_utid"
@@ -116,12 +121,11 @@ def tester(url, **kwargs):
116121
self.assertEqual("", result.get("classification"))
117122

118123

124+
@patch(_OIDC_DISCOVERY, new=_OIDC_DISCOVERY_MOCK)
119125
class TestClientApplicationAcquireTokenSilentFociBehaviors(unittest.TestCase):
120126

121127
def setUp(self):
122128
self.authority_url = "https://login.microsoftonline.com/common"
123-
self.authority = msal.authority.Authority(
124-
self.authority_url, MinimalHttpClient())
125129
self.scopes = ["s1", "s2"]
126130
self.uid = "my_uid"
127131
self.utid = "my_utid"
@@ -148,7 +152,7 @@ def tester(url, data=None, **kwargs):
148152
self.assertEqual(self.frt, data.get("refresh_token"), "Should attempt the FRT")
149153
return MinimalResponse(status_code=400, text=error_response)
150154
app._acquire_token_silent_by_finding_rt_belongs_to_me_or_my_family(
151-
self.authority, self.scopes, self.account, post=tester)
155+
app.authority, self.scopes, self.account, post=tester)
152156
self.assertNotEqual([], app.token_cache.find(
153157
msal.TokenCache.CredentialType.REFRESH_TOKEN, query={"secret": self.frt}),
154158
"The FRT should not be removed from the cache")
@@ -168,7 +172,7 @@ def tester(url, data=None, **kwargs):
168172
self.assertEqual(rt, data.get("refresh_token"), "Should attempt the RT")
169173
return MinimalResponse(status_code=200, text='{}')
170174
app._acquire_token_silent_by_finding_rt_belongs_to_me_or_my_family(
171-
self.authority, self.scopes, self.account, post=tester)
175+
app.authority, self.scopes, self.account, post=tester)
172176

173177
def test_unknown_family_app_will_attempt_frt_and_join_family(self):
174178
def tester(url, data=None, **kwargs):
@@ -180,7 +184,7 @@ def tester(url, data=None, **kwargs):
180184
app = ClientApplication(
181185
"unknown_family_app", authority=self.authority_url, token_cache=self.cache)
182186
at = app._acquire_token_silent_by_finding_rt_belongs_to_me_or_my_family(
183-
self.authority, self.scopes, self.account, post=tester)
187+
app.authority, self.scopes, self.account, post=tester)
184188
logger.debug("%s.cache = %s", self.id(), self.cache.serialize())
185189
self.assertEqual("at", at.get("access_token"), "New app should get a new AT")
186190
app_metadata = app.token_cache.find(
@@ -202,7 +206,7 @@ def tester(url, data=None, **kwargs):
202206
app = ClientApplication(
203207
"preexisting_family_app", authority=self.authority_url, token_cache=self.cache)
204208
resp = app._acquire_token_silent_by_finding_rt_belongs_to_me_or_my_family(
205-
self.authority, self.scopes, self.account, post=tester)
209+
app.authority, self.scopes, self.account, post=tester)
206210
logger.debug("%s.cache = %s", self.id(), self.cache.serialize())
207211
self.assertEqual(json.loads(error_response), resp, "Error raised will be returned")
208212

@@ -237,7 +241,7 @@ def test_family_app_remove_account(self):
237241

238242
class TestClientApplicationForAuthorityMigration(unittest.TestCase):
239243

240-
@classmethod
244+
# Chose to not mock oidc discovery, because AuthorityMigration might rely on real data
241245
def setUp(self):
242246
self.environment_in_cache = "sts.windows.net"
243247
self.authority_url_in_app = "https://login.microsoftonline.com/common"
@@ -444,6 +448,7 @@ def mock_post(url, headers=None, *args, **kwargs):
444448
self.assertRefreshOn(result, new_refresh_in)
445449

446450

451+
# TODO Patching oidc discovery ends up failing. But we plan to remove offline telemetry anyway.
447452
class TestTelemetryMaintainingOfflineState(unittest.TestCase):
448453
authority_url = "https://login.microsoftonline.com/common"
449454
scopes = ["s1", "s2"]
@@ -524,6 +529,7 @@ def mock_post(url, headers=None, *args, **kwargs):
524529

525530
class TestTelemetryOnClientApplication(unittest.TestCase):
526531
@classmethod
532+
@patch(_OIDC_DISCOVERY, new=_OIDC_DISCOVERY_MOCK)
527533
def setUpClass(cls): # Initialization at runtime, not interpret-time
528534
cls.app = ClientApplication(
529535
"client_id", authority="https://login.microsoftonline.com/common")
@@ -552,6 +558,7 @@ def mock_post(url, headers=None, *args, **kwargs):
552558

553559
class TestTelemetryOnPublicClientApplication(unittest.TestCase):
554560
@classmethod
561+
@patch(_OIDC_DISCOVERY, new=_OIDC_DISCOVERY_MOCK)
555562
def setUpClass(cls): # Initialization at runtime, not interpret-time
556563
cls.app = PublicClientApplication(
557564
"client_id", authority="https://login.microsoftonline.com/common")
@@ -581,6 +588,7 @@ def mock_post(url, headers=None, *args, **kwargs):
581588

582589
class TestTelemetryOnConfidentialClientApplication(unittest.TestCase):
583590
@classmethod
591+
@patch(_OIDC_DISCOVERY, new=_OIDC_DISCOVERY_MOCK)
584592
def setUpClass(cls): # Initialization at runtime, not interpret-time
585593
cls.app = ConfidentialClientApplication(
586594
"client_id", client_credential="secret",
@@ -626,6 +634,7 @@ def mock_post(url, headers=None, *args, **kwargs):
626634
self.assertEqual(at, result.get("access_token"))
627635

628636

637+
@patch(_OIDC_DISCOVERY, new=_OIDC_DISCOVERY_MOCK)
629638
class TestClientApplicationWillGroupAccounts(unittest.TestCase):
630639
def test_get_accounts(self):
631640
client_id = "my_app"
@@ -678,15 +687,24 @@ def mock_post(url, headers=None, *args, **kwargs):
678687
with self.assertWarns(DeprecationWarning):
679688
app.acquire_token_for_client(["scope"], post=mock_post)
680689

690+
@patch(_OIDC_DISCOVERY, new=Mock(return_value={
691+
"authorization_endpoint": "https://contoso.com/common",
692+
"token_endpoint": "https://contoso.com/common",
693+
}))
681694
def test_common_authority_should_emit_warning(self):
682695
self._test_certain_authority_should_emit_warning(
683696
authority="https://login.microsoftonline.com/common")
684697

698+
@patch(_OIDC_DISCOVERY, new=Mock(return_value={
699+
"authorization_endpoint": "https://contoso.com/organizations",
700+
"token_endpoint": "https://contoso.com/organizations",
701+
}))
685702
def test_organizations_authority_should_emit_warning(self):
686703
self._test_certain_authority_should_emit_warning(
687704
authority="https://login.microsoftonline.com/organizations")
688705

689706

707+
@patch(_OIDC_DISCOVERY, new=_OIDC_DISCOVERY_MOCK)
690708
class TestRemoveTokensForClient(unittest.TestCase):
691709
def test_remove_tokens_for_client_should_remove_client_tokens_only(self):
692710
at_for_user = "AT for user"
@@ -716,6 +734,7 @@ def test_remove_tokens_for_client_should_remove_client_tokens_only(self):
716734
self.assertEqual(at_for_user, remaining_tokens[0].get("secret"))
717735

718736

737+
@patch(_OIDC_DISCOVERY, new=_OIDC_DISCOVERY_MOCK)
719738
class TestScopeDecoration(unittest.TestCase):
720739
def _test_client_id_should_be_a_valid_scope(self, client_id, other_scopes):
721740
# B2C needs this https://learn.microsoft.com/en-us/azure/active-directory-b2c/access-tokens#openid-connect-scopes
@@ -855,4 +874,3 @@ def test_app_did_not_register_redirect_uri_should_error_out(self):
855874
parent_window_handle=app.CONSOLE_WINDOW_HANDLE,
856875
)
857876
self.assertEqual(result.get("error"), "broker_error")
858-

0 commit comments

Comments
 (0)