55 from urlparse import urlparse
66import logging
77
8- # Historically some customers patched this module-wide requests instance.
9- # We keep it here for now. They will be removed in next major release.
10- import requests
11- import requests as _requests
12-
138from .exceptions import MsalServiceError
149
1510
1611logger = logging .getLogger (__name__ )
12+
13+ # Endpoints were copied from here
14+ # https://docs.microsoft.com/en-us/azure/active-directory/develop/authentication-national-cloud#azure-ad-authentication-endpoints
15+ AZURE_US_GOVERNMENT = "login.microsoftonline.us"
16+ AZURE_CHINA = "login.chinacloudapi.cn"
17+ AZURE_PUBLIC = "login.microsoftonline.com"
18+
1719WORLD_WIDE = 'login.microsoftonline.com' # There was an alias login.windows.net
1820WELL_KNOWN_AUTHORITY_HOSTS = set ([
1921 WORLD_WIDE ,
20- 'login.chinacloudapi.cn' ,
22+ AZURE_CHINA ,
2123 'login-us.microsoftonline.com' ,
22- 'login.microsoftonline.us' ,
23- 'login.microsoftonline.de' ,
24+ AZURE_US_GOVERNMENT ,
2425 ])
2526WELL_KNOWN_B2C_HOSTS = [
2627 "b2clogin.com" ,
3031 ]
3132
3233
34+ class AuthorityBuilder (object ):
35+ def __init__ (self , instance , tenant ):
36+ """A helper to save caller from doing string concatenation.
37+
38+ Usage is documented in :func:`application.ClientApplication.__init__`.
39+ """
40+ self ._instance = instance .rstrip ("/" )
41+ self ._tenant = tenant .strip ("/" )
42+
43+ def __str__ (self ):
44+ return "https://{}/{}" .format (self ._instance , self ._tenant )
45+
46+
3347class Authority (object ):
3448 """This class represents an (already-validated) authority.
3549
@@ -39,9 +53,10 @@ class Authority(object):
3953 _domains_without_user_realm_discovery = set ([])
4054
4155 @property
42- def http_client (self ): # Obsolete. We will remove this in next major release.
43- # A workaround: if module-wide requests is patched, we honor it.
44- return self ._http_client if requests is _requests else requests
56+ def http_client (self ): # Obsolete. We will remove this eventually
57+ warnings .warn (
58+ "authority.http_client might be removed in MSAL Python 1.21+" , DeprecationWarning )
59+ return self ._http_client
4560
4661 def __init__ (self , authority_url , http_client , validate_authority = True ):
4762 """Creates an authority instance, and also validates it.
@@ -53,6 +68,8 @@ def __init__(self, authority_url, http_client, validate_authority=True):
5368 performed.
5469 """
5570 self ._http_client = http_client
71+ if isinstance (authority_url , AuthorityBuilder ):
72+ authority_url = str (authority_url )
5673 authority , self .instance , tenant = canonicalize (authority_url )
5774 parts = authority .path .split ('/' )
5875 is_b2c = any (self .instance .endswith ("." + d ) for d in WELL_KNOWN_B2C_HOSTS ) or (
@@ -62,7 +79,7 @@ def __init__(self, authority_url, http_client, validate_authority=True):
6279 payload = instance_discovery (
6380 "https://{}{}/oauth2/v2.0/authorize" .format (
6481 self .instance , authority .path ),
65- self .http_client )
82+ self ._http_client )
6683 if payload .get ("error" ) == "invalid_instance" :
6784 raise ValueError (
6885 "invalid_instance: "
@@ -82,12 +99,13 @@ def __init__(self, authority_url, http_client, validate_authority=True):
8299 try :
83100 openid_config = tenant_discovery (
84101 tenant_discovery_endpoint ,
85- self .http_client )
102+ self ._http_client )
86103 except ValueError :
87104 raise ValueError (
88105 "Unable to get authority configuration for {}. "
89106 "Authority would typically be in a format of "
90- "https://login.microsoftonline.com/your_tenant_name" .format (
107+ "https://login.microsoftonline.com/your_tenant "
108+ "Also please double check your tenant name or GUID is correct." .format (
91109 authority_url ))
92110 logger .debug ("openid_config = %s" , openid_config )
93111 self .authorization_endpoint = openid_config ['authorization_endpoint' ]
@@ -101,7 +119,7 @@ def user_realm_discovery(self, username, correlation_id=None, response=None):
101119 # "federation_protocol", "cloud_audience_urn",
102120 # "federation_metadata_url", "federation_active_auth_url", etc.
103121 if self .instance not in self .__class__ ._domains_without_user_realm_discovery :
104- resp = response or self .http_client .get (
122+ resp = response or self ._http_client .get (
105123 "https://{netloc}/common/userrealm/{username}?api-version=1.0" .format (
106124 netloc = self .instance , username = username ),
107125 headers = {'Accept' : 'application/json' ,
@@ -148,7 +166,10 @@ def tenant_discovery(tenant_discovery_endpoint, http_client, **kwargs):
148166 if 400 <= resp .status_code < 500 :
149167 # Nonexist tenant would hit this path
150168 # e.g. https://login.microsoftonline.com/nonexist_tenant/v2.0/.well-known/openid-configuration
151- raise ValueError ("OIDC Discovery endpoint rejects our request" )
169+ raise ValueError (
170+ "OIDC Discovery endpoint rejects our request. Error: {}" .format (
171+ resp .text # Expose it as-is b/c OIDC defines no error response format
172+ ))
152173 # Transient network error would hit this path
153174 resp .raise_for_status ()
154175 raise RuntimeError ( # A fallback here, in case resp.raise_for_status() is no-op
0 commit comments