5
5
from urlparse import urlparse
6
6
import logging
7
7
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
-
13
8
from .exceptions import MsalServiceError
14
9
15
10
16
11
logger = 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
+
17
19
WORLD_WIDE = 'login.microsoftonline.com' # There was an alias login.windows.net
18
20
WELL_KNOWN_AUTHORITY_HOSTS = set ([
19
21
WORLD_WIDE ,
20
- 'login.chinacloudapi.cn' ,
22
+ AZURE_CHINA ,
21
23
'login-us.microsoftonline.com' ,
22
- 'login.microsoftonline.us' ,
23
- 'login.microsoftonline.de' ,
24
+ AZURE_US_GOVERNMENT ,
24
25
])
25
26
WELL_KNOWN_B2C_HOSTS = [
26
27
"b2clogin.com" ,
30
31
]
31
32
32
33
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
+
33
47
class Authority (object ):
34
48
"""This class represents an (already-validated) authority.
35
49
@@ -39,9 +53,10 @@ class Authority(object):
39
53
_domains_without_user_realm_discovery = set ([])
40
54
41
55
@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
45
60
46
61
def __init__ (self , authority_url , http_client , validate_authority = True ):
47
62
"""Creates an authority instance, and also validates it.
@@ -53,6 +68,8 @@ def __init__(self, authority_url, http_client, validate_authority=True):
53
68
performed.
54
69
"""
55
70
self ._http_client = http_client
71
+ if isinstance (authority_url , AuthorityBuilder ):
72
+ authority_url = str (authority_url )
56
73
authority , self .instance , tenant = canonicalize (authority_url )
57
74
parts = authority .path .split ('/' )
58
75
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):
62
79
payload = instance_discovery (
63
80
"https://{}{}/oauth2/v2.0/authorize" .format (
64
81
self .instance , authority .path ),
65
- self .http_client )
82
+ self ._http_client )
66
83
if payload .get ("error" ) == "invalid_instance" :
67
84
raise ValueError (
68
85
"invalid_instance: "
@@ -82,12 +99,13 @@ def __init__(self, authority_url, http_client, validate_authority=True):
82
99
try :
83
100
openid_config = tenant_discovery (
84
101
tenant_discovery_endpoint ,
85
- self .http_client )
102
+ self ._http_client )
86
103
except ValueError :
87
104
raise ValueError (
88
105
"Unable to get authority configuration for {}. "
89
106
"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 (
91
109
authority_url ))
92
110
logger .debug ("openid_config = %s" , openid_config )
93
111
self .authorization_endpoint = openid_config ['authorization_endpoint' ]
@@ -101,7 +119,7 @@ def user_realm_discovery(self, username, correlation_id=None, response=None):
101
119
# "federation_protocol", "cloud_audience_urn",
102
120
# "federation_metadata_url", "federation_active_auth_url", etc.
103
121
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 (
105
123
"https://{netloc}/common/userrealm/{username}?api-version=1.0" .format (
106
124
netloc = self .instance , username = username ),
107
125
headers = {'Accept' : 'application/json' ,
@@ -148,7 +166,10 @@ def tenant_discovery(tenant_discovery_endpoint, http_client, **kwargs):
148
166
if 400 <= resp .status_code < 500 :
149
167
# Nonexist tenant would hit this path
150
168
# 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
+ ))
152
173
# Transient network error would hit this path
153
174
resp .raise_for_status ()
154
175
raise RuntimeError ( # A fallback here, in case resp.raise_for_status() is no-op
0 commit comments