9
9
import sys
10
10
import warnings
11
11
from threading import Lock
12
+ import os
12
13
13
14
import requests
14
15
20
21
from .token_cache import TokenCache
21
22
import msal .telemetry
22
23
from .region import _detect_region
24
+ from .throttled_http_client import ThrottledHttpClient
23
25
24
26
25
27
# The __init__.py will import this. Not the other way around.
26
- __version__ = "1.13 .0"
28
+ __version__ = "1.14 .0"
27
29
28
30
logger = logging .getLogger (__name__ )
29
31
@@ -69,6 +71,46 @@ def _clean_up(result):
69
71
return result
70
72
71
73
74
+ def _preferred_browser ():
75
+ """Register Edge and return a name suitable for subsequent webbrowser.get(...)
76
+ when appropriate. Otherwise return None.
77
+ """
78
+ # On Linux, only Edge will provide device-based Conditional Access support
79
+ if sys .platform != "linux" : # On other platforms, we have no browser preference
80
+ return None
81
+ browser_path = "/usr/bin/microsoft-edge" # Use a full path owned by sys admin
82
+ user_has_no_preference = "BROWSER" not in os .environ
83
+ user_wont_mind_edge = "microsoft-edge" in os .environ .get ("BROWSER" , "" ) # Note:
84
+ # BROWSER could contain "microsoft-edge" or "/path/to/microsoft-edge".
85
+ # Python documentation (https://docs.python.org/3/library/webbrowser.html)
86
+ # does not document the name being implicitly register,
87
+ # so there is no public API to know whether the ENV VAR browser would work.
88
+ # Therefore, we would not bother examine the env var browser's type.
89
+ # We would just register our own Edge instance.
90
+ if (user_has_no_preference or user_wont_mind_edge ) and os .path .exists (browser_path ):
91
+ try :
92
+ import webbrowser # Lazy import. Some distro may not have this.
93
+ browser_name = "msal-edge" # Avoid popular name "microsoft-edge"
94
+ # otherwise `BROWSER="microsoft-edge"; webbrowser.get("microsoft-edge")`
95
+ # would return a GenericBrowser instance which won't work.
96
+ try :
97
+ registration_available = isinstance (
98
+ webbrowser .get (browser_name ), webbrowser .BackgroundBrowser )
99
+ except webbrowser .Error :
100
+ registration_available = False
101
+ if not registration_available :
102
+ logger .debug ("Register %s with %s" , browser_name , browser_path )
103
+ # By registering our own browser instance with our own name,
104
+ # rather than populating a process-wide BROWSER enn var,
105
+ # this approach does not have side effect on non-MSAL code path.
106
+ webbrowser .register ( # Even double-register happens to work fine
107
+ browser_name , None , webbrowser .BackgroundBrowser (browser_path ))
108
+ return browser_name
109
+ except ImportError :
110
+ pass # We may still proceed
111
+ return None
112
+
113
+
72
114
class ClientApplication (object ):
73
115
74
116
ACQUIRE_TOKEN_SILENT_ID = "84"
@@ -295,6 +337,10 @@ def __init__(
295
337
a = requests .adapters .HTTPAdapter (max_retries = 1 )
296
338
self .http_client .mount ("http://" , a )
297
339
self .http_client .mount ("https://" , a )
340
+ self .http_client = ThrottledHttpClient (
341
+ self .http_client ,
342
+ {} # Hard code an in-memory cache, for now
343
+ )
298
344
299
345
self .app_name = app_name
300
346
self .app_version = app_version
@@ -371,7 +417,7 @@ def _get_regional_authority(self, central_authority):
371
417
self ._region_configured if is_region_specified else self ._region_detected )
372
418
if region_to_use :
373
419
logger .info ('Region to be used: {}' .format (repr (region_to_use )))
374
- regional_host = ("{}.login.microsoft .com" .format (region_to_use )
420
+ regional_host = ("{}.r. login.microsoftonline .com" .format (region_to_use )
375
421
if central_authority .instance in (
376
422
# The list came from https://github.com/AzureAD/microsoft-authentication-library-for-python/pull/358/files#r629400328
377
423
"login.microsoftonline.com" ,
@@ -392,6 +438,7 @@ def _build_client(self, client_credential, authority, skip_regional_client=False
392
438
"x-client-sku" : "MSAL.Python" , "x-client-ver" : __version__ ,
393
439
"x-client-os" : sys .platform ,
394
440
"x-client-cpu" : "x64" if sys .maxsize > 2 ** 32 else "x86" ,
441
+ "x-ms-lib-capability" : "retry-after, h429" ,
395
442
}
396
443
if self .app_name :
397
444
default_headers ['x-app-name' ] = self .app_name
@@ -1393,6 +1440,7 @@ def acquire_token_interactive(
1393
1440
},
1394
1441
data = dict (kwargs .pop ("data" , {}), claims = claims ),
1395
1442
headers = telemetry_context .generate_headers (),
1443
+ browser_name = _preferred_browser (),
1396
1444
** kwargs ))
1397
1445
telemetry_context .update_telemetry (response )
1398
1446
return response
0 commit comments