Skip to content

Commit 20c0da3

Browse files
committed
Change known_authority_hosts from per-class to per-instance
1 parent b4c46af commit 20c0da3

File tree

2 files changed

+53
-66
lines changed

2 files changed

+53
-66
lines changed

msal/application.py

Lines changed: 36 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -159,47 +159,10 @@ class ClientApplication(object):
159159

160160
ATTEMPT_REGION_DISCOVERY = True # "TryAutoDetect"
161161

162-
_known_authority_hosts = None
163-
164-
@classmethod
165-
def set_known_authority_hosts(cls, known_authority_hosts):
166-
"""Declare a list of hosts which you allow MSAL to operate with.
167-
168-
If your app operates with some authorities that you know and own,
169-
such as some ADFS or B2C or private cloud,
170-
it is recommended and sometimes required that you declare them here,
171-
so that MSAL will use your authorities without discovery,
172-
and reject most of the other undefined authorities.
173-
174-
``known_authority_hosts`` is meant to be a static and per-deployment setting.
175-
This classmethod shall be called at most once,
176-
during your entire app's starting-up,
177-
before your initializing any ``PublicClientApplication`` or
178-
``ConfidentialClientApplication`` instance(s).
179-
180-
:param list[str] known_authority_hosts:
181-
Authorities that you known, for example::
182-
183-
[
184-
"contoso.com", # Your own domain
185-
"login.azs", # This can be a private cloud
186-
]
187-
188-
New in version 1.19
189-
"""
190-
new_input = frozenset(known_authority_hosts)
191-
if (cls._known_authority_hosts is not None
192-
and cls._known_authority_hosts != new_input):
193-
raise ValueError(
194-
"The known_authority_hosts are considered static. "
195-
"Once configured, they should not be changed.")
196-
cls._known_authority_hosts = new_input
197-
logger.debug('known_authority_hosts is set to %s', known_authority_hosts)
198-
199-
def _union_known_authority_hosts(cls, url=None, host=None):
162+
def _union_known_authority_hosts(self, url=None, host=None):
200163
host = host if host else urlparse(url).netloc.split(":")[0]
201-
return (cls._known_authority_hosts.union([host])
202-
if cls._known_authority_hosts else frozenset([host]))
164+
return (self._known_authority_hosts.union([host])
165+
if self._known_authority_hosts else frozenset([host]))
203166

204167
def __init__(
205168
self, client_id,
@@ -215,6 +178,7 @@ def __init__(
215178
# when we would eventually want to add this feature to PCA in future.
216179
exclude_scopes=None,
217180
http_cache=None,
181+
known_authority_hosts=None,
218182
):
219183
"""Create an instance of application.
220184
@@ -450,11 +414,41 @@ def __init__(
450414
Personally Identifiable Information (PII). Encryption is unnecessary.
451415
452416
New in version 1.16.0.
417+
418+
:param list[str] known_authority_hosts:
419+
Historically, MSAL would try to connect to a central endpoint
420+
to acquire some metadata for an unfamiliar authority.
421+
This behavior is known as Instance Discovery.
422+
423+
If you know a list of hosts which you allow MSAL to operate with,
424+
you can declare them here, so that MSAL will use them as-is,
425+
and also reject most of other authorities such as a call like this:
426+
``ClientApplication("id", authority="https://undefined.com", known_authority_hosts=["contoso.com", "fabricam.com"])``.
427+
428+
Typically, the ADFS or B2C or private cloud authorities
429+
is recommended and sometimes required to be declared here.
430+
431+
This is meant to be a static and per-deployment setting.
432+
The recommended pattern is to load your predefined constant list
433+
from a configuration file,
434+
and never create new MSAL ``ClientApplication`` instances with
435+
unknown and untrusted authorities during runtime.
436+
437+
Values would look like::
438+
439+
[
440+
"contoso.com", # Your own domain
441+
"login.azs", # This can be a private cloud
442+
]
443+
444+
New in version 1.19
453445
"""
454446
self.client_id = client_id
455447
self.client_credential = client_credential
456448
self.client_claims = client_claims
457449
self._client_capabilities = client_capabilities
450+
self._known_authority_hosts = frozenset(
451+
known_authority_hosts if known_authority_hosts else [])
458452

459453
if exclude_scopes and not isinstance(exclude_scopes, list):
460454
raise ValueError(
@@ -498,7 +492,7 @@ def __init__(
498492
self.authority = Authority(
499493
authority_to_use,
500494
self.http_client, validate_authority=validate_authority,
501-
known_authority_hosts=self.__class__._known_authority_hosts,
495+
known_authority_hosts=self._known_authority_hosts,
502496
)
503497
except ValueError: # Those are explicit authority validation errors
504498
raise
@@ -839,7 +833,7 @@ def get_authorization_request_url(
839833
the_authority = Authority(
840834
authority,
841835
self.http_client,
842-
known_authority_hosts=self.__class__._known_authority_hosts,
836+
known_authority_hosts=self._known_authority_hosts,
843837
) if authority else self.authority
844838

845839
client = _ClientWithCcsRoutingInfo(

tests/test_authority.py

Lines changed: 17 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -171,59 +171,52 @@ def test_by_default_b2c_should_skip_validation_and_instance_metadata(
171171
app.get_accounts() # This could make an instance metadata call for authority aliases
172172
instance_metadata.assert_not_called()
173173

174-
def test_known_authorities_should_allow_multiple_idempotent_calls_but_no_changes(self, *mocks):
175-
known_hosts = ["contoso.com", "fabricam.com"]
176-
msal.ClientApplication.set_known_authority_hosts(known_hosts)
177-
msal.ClientApplication.set_known_authority_hosts(known_hosts) # Allows idempotent calls
178-
msal.ClientApplication.set_known_authority_hosts(
179-
# Optional. Here we treat hosts orderless therefore accept equivalent call
180-
reversed(known_hosts))
181-
with self.assertRaises(ValueError):
182-
msal.ClientApplication.set_known_authority_hosts(["hacked.com"])
183-
184174
def test_known_authority_hosts_should_not_coexist_with_validate_authority_boolean(self, *mocks):
185-
msal.ClientApplication.set_known_authority_hosts(["private.cloud"])
186175
with self.assertRaises(ValueError):
187176
msal.ClientApplication(
188177
"id",
189-
authority="https://unknown.com/common", validate_authority=False)
178+
authority="https://unknown.com/common",
179+
validate_authority=False, known_authority_hosts=["private.cloud"])
190180

191181
def test_known_to_developer_authority_should_skip_validation_and_instance_metadata(
192182
self, instance_metadata, known_to_microsoft_validation, _):
193-
msal.ClientApplication.set_known_authority_hosts(["private.cloud"])
194-
app = msal.ClientApplication("foo", authority="https://private.cloud/foo")
183+
app = msal.ClientApplication(
184+
"foo",
185+
authority="https://private.cloud/foo",
186+
known_authority_hosts=["private.cloud"])
195187
known_to_microsoft_validation.assert_not_called()
196188
app.get_accounts() # This could make an instance metadata call for authority aliases
197189
instance_metadata.assert_not_called()
198190

199191
def test_known_to_developer_setting_should_still_let_a_known_to_microsoft_authority_skip_validation_and_use_instance_metadata( # i.e. same as the without-known-to-developer behavior
200192
self, instance_metadata, known_to_microsoft_validation, _):
201-
msal.ClientApplication.set_known_authority_hosts(["private.cloud"])
202193
app = msal.ClientApplication(
203-
"id", authority="https://login.microsoftonline.com/common")
194+
"id",
195+
authority="https://login.microsoftonline.com/common",
196+
known_authority_hosts=["private.cloud"])
204197
known_to_microsoft_validation.assert_not_called()
205198
app.get_accounts() # This could make an instance metadata call for authority aliases
206199
instance_metadata.assert_called_once_with()
207200

208201
def test_known_authorities_should_make_unknown_adfs_perform_validation_and_instance_metadata(
209202
self, instance_metadata, known_to_microsoft_validation, _):
210-
msal.ClientApplication.set_known_authority_hosts(["private.cloud"])
211-
app = msal.ClientApplication("foo", authority="https://unknown.com/adfs")
203+
app = msal.ClientApplication(
204+
"foo",
205+
authority="https://unknown.com/adfs",
206+
known_authority_hosts=["private.cloud"])
212207
known_to_microsoft_validation.assert_called_once_with(ANY, ANY) # Python 3.5+
213208
# We effectively mocked a passed validation, so that MSAL will proceed
214209
app.get_accounts() # This could make an instance metadata call for authority aliases
215210
instance_metadata.assert_called_once_with()
216211

217212
def test_known_authorities_should_make_unknown_b2c_perform_validation_and_instance_metadata(
218213
self, instance_metadata, known_to_microsoft_validation, _):
219-
msal.ClientApplication.set_known_authority_hosts(["private.cloud"])
220-
app = msal.ClientApplication("foo", authority="https://b2clogin.com/contoso/b2c_policy")
214+
app = msal.ClientApplication(
215+
"foo",
216+
authority="https://b2clogin.com/contoso/b2c_policy",
217+
known_authority_hosts=["private.cloud"])
221218
known_to_microsoft_validation.assert_called_once_with(ANY, ANY) # Python 3.5+
222219
# We effectively mocked a passed validation, so that MSAL will proceed
223220
app.get_accounts() # This could make an instance metadata call for authority aliases
224221
instance_metadata.assert_called_once_with()
225222

226-
def tearDown(self):
227-
# This is not part of public API. We do it here only for unit-testing.
228-
msal.ClientApplication._known_authority_hosts = None # Reset
229-

0 commit comments

Comments
 (0)