99import sys
1010import warnings
1111from threading import Lock
12+ import os
1213
1314import requests
1415
2021from .token_cache import TokenCache
2122import msal .telemetry
2223from .region import _detect_region
24+ from .throttled_http_client import ThrottledHttpClient
2325
2426
2527# The __init__.py will import this. Not the other way around.
26- __version__ = "1.13 .0"
28+ __version__ = "1.14 .0"
2729
2830logger = logging .getLogger (__name__ )
2931
@@ -69,6 +71,46 @@ def _clean_up(result):
6971 return result
7072
7173
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+
72114class ClientApplication (object ):
73115
74116 ACQUIRE_TOKEN_SILENT_ID = "84"
@@ -295,6 +337,10 @@ def __init__(
295337 a = requests .adapters .HTTPAdapter (max_retries = 1 )
296338 self .http_client .mount ("http://" , a )
297339 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+ )
298344
299345 self .app_name = app_name
300346 self .app_version = app_version
@@ -371,7 +417,7 @@ def _get_regional_authority(self, central_authority):
371417 self ._region_configured if is_region_specified else self ._region_detected )
372418 if region_to_use :
373419 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 )
375421 if central_authority .instance in (
376422 # The list came from https://github.com/AzureAD/microsoft-authentication-library-for-python/pull/358/files#r629400328
377423 "login.microsoftonline.com" ,
@@ -392,6 +438,7 @@ def _build_client(self, client_credential, authority, skip_regional_client=False
392438 "x-client-sku" : "MSAL.Python" , "x-client-ver" : __version__ ,
393439 "x-client-os" : sys .platform ,
394440 "x-client-cpu" : "x64" if sys .maxsize > 2 ** 32 else "x86" ,
441+ "x-ms-lib-capability" : "retry-after, h429" ,
395442 }
396443 if self .app_name :
397444 default_headers ['x-app-name' ] = self .app_name
@@ -1393,6 +1440,7 @@ def acquire_token_interactive(
13931440 },
13941441 data = dict (kwargs .pop ("data" , {}), claims = claims ),
13951442 headers = telemetry_context .generate_headers (),
1443+ browser_name = _preferred_browser (),
13961444 ** kwargs ))
13971445 telemetry_context .update_telemetry (response )
13981446 return response
0 commit comments