1010
1111
1212class SharePointDatasourceProvider (DatasourceProvider ):
13- _AUTH_URL = "https://login.microsoftonline.com/common/oauth2/v2.0/authorize"
14- _TOKEN_URL = "https://login.microsoftonline.com/common/oauth2/v2.0/token"
1513 _API_BASE_URL = "https://graph.microsoft.com/v1.0"
1614 # SharePoint permission configuration - supports site-specific and general permissions
1715 _GENERAL_SCOPES = "openid offline_access User.Read Sites.Read.All Files.Read.All"
18-
16+
17+ def _get_tenant_id_for_auth (self , system_credentials : Mapping [str , Any ]) -> str :
18+ """
19+ Get tenant ID for authorization from system credentials.
20+ Defaults to 'common' for multi-tenant apps if not specified.
21+ """
22+ return system_credentials .get ("tenant_id" ) or "common"
23+
24+ def _get_tenant_id_for_token (self , credentials : Mapping [str , Any ]) -> str :
25+ """
26+ Get tenant ID for token refresh from saved credentials.
27+ For backward compatibility, defaults to 'common' if not saved.
28+ """
29+ return credentials .get ("tenant_id" ) or "common"
30+
31+ def _get_auth_url (self , tenant_id : str ) -> str :
32+ """Get the OAuth authorization URL with the appropriate tenant ID."""
33+ return f"https://login.microsoftonline.com/{ tenant_id } /oauth2/v2.0/authorize"
34+
35+ def _get_token_url (self , tenant_id : str ) -> str :
36+ """Get the OAuth token URL with the appropriate tenant ID."""
37+ return f"https://login.microsoftonline.com/{ tenant_id } /oauth2/v2.0/token"
38+
1939 def _get_sharepoint_scopes (self , subdomain : str ) -> str :
2040 """
2141 Generate SharePoint permission scopes based on subdomain
@@ -44,10 +64,12 @@ def _oauth_get_authorization_url(self, redirect_uri: str, system_credentials: Ma
4464 subdomain = system_credentials .get ("subdomain" )
4565 if not subdomain :
4666 raise DatasourceOAuthError ("Missing SharePoint subdomain configuration" )
47-
67+
4868 state = secrets .token_urlsafe (32 )
4969 scopes = self ._get_sharepoint_scopes (subdomain )
50-
70+ tenant_id = self ._get_tenant_id_for_auth (system_credentials )
71+ auth_url = self ._get_auth_url (tenant_id )
72+
5173 params = {
5274 "client_id" : system_credentials ["client_id" ],
5375 "redirect_uri" : redirect_uri ,
@@ -56,7 +78,7 @@ def _oauth_get_authorization_url(self, redirect_uri: str, system_credentials: Ma
5678 "state" : state ,
5779 "response_mode" : "query"
5880 }
59- return f"{ self . _AUTH_URL } ?{ urllib .parse .urlencode (params )} "
81+ return f"{ auth_url } ?{ urllib .parse .urlencode (params )} "
6082
6183 def _oauth_get_credentials (
6284 self , redirect_uri : str , system_credentials : Mapping [str , Any ], request : Request
@@ -86,8 +108,12 @@ def _oauth_get_credentials(
86108 if not subdomain :
87109 raise DatasourceOAuthError ("Missing SharePoint subdomain configuration" )
88110
111+ # Get tenant ID for this authorization (will be saved to credentials)
112+ tenant_id = self ._get_tenant_id_for_auth (system_credentials )
113+
89114 # Use the same permission scopes as the authorization URL
90115 scopes = self ._get_sharepoint_scopes (subdomain )
116+ token_url = self ._get_token_url (tenant_id )
91117
92118 token_data = {
93119 "grant_type" : "authorization_code" ,
@@ -97,14 +123,14 @@ def _oauth_get_credentials(
97123 "redirect_uri" : redirect_uri ,
98124 "scope" : scopes
99125 }
100-
126+
101127 headers = {
102128 "Content-Type" : "application/x-www-form-urlencoded" ,
103129 "Accept" : "application/json"
104130 }
105-
131+
106132 try :
107- response = requests .post (self . _TOKEN_URL , data = token_data , headers = headers , timeout = 30 )
133+ response = requests .post (token_url , data = token_data , headers = headers , timeout = 30 )
108134 response .raise_for_status ()
109135 response_data = response .json ()
110136
@@ -140,7 +166,8 @@ def _oauth_get_credentials(
140166 "access_token" : access_token ,
141167 "refresh_token" : refresh_token ,
142168 "token_type" : response_data .get ("token_type" , "bearer" ),
143- "subdomain" : subdomain # Save subdomain to credentials
169+ "subdomain" : subdomain , # Save subdomain to credentials
170+ "tenant_id" : tenant_id , # Save tenant_id to credentials for consistent token refresh
144171 },
145172 )
146173
@@ -170,24 +197,29 @@ def _oauth_refresh_credentials(
170197 if not subdomain :
171198 raise DatasourceOAuthError ("Missing SharePoint subdomain configuration" )
172199
200+ # Get tenant_id from saved credentials for consistent token refresh
201+ # For backward compatibility, defaults to "common" if not found
202+ tenant_id = self ._get_tenant_id_for_token (credentials )
203+
173204 # Use the same permission scopes as initial authorization
174205 scopes = self ._get_sharepoint_scopes (subdomain )
175-
206+ token_url = self ._get_token_url (tenant_id )
207+
176208 token_data = {
177209 "grant_type" : "refresh_token" ,
178210 "refresh_token" : refresh_token ,
179211 "client_id" : system_credentials ["client_id" ],
180212 "client_secret" : system_credentials ["client_secret" ],
181213 "scope" : scopes
182214 }
183-
215+
184216 headers = {
185217 "Content-Type" : "application/x-www-form-urlencoded" ,
186218 "Accept" : "application/json"
187219 }
188-
220+
189221 try :
190- response = requests .post (self . _TOKEN_URL , data = token_data , headers = headers , timeout = 30 )
222+ response = requests .post (token_url , data = token_data , headers = headers , timeout = 30 )
191223 response .raise_for_status ()
192224 response_data = response .json ()
193225
@@ -222,6 +254,7 @@ def _oauth_refresh_credentials(
222254 "client_id" : system_credentials .get ("client_id" ) or credentials .get ("client_id" ),
223255 "client_secret" : system_credentials .get ("client_secret" ) or credentials .get ("client_secret" ),
224256 "subdomain" : subdomain ,
257+ "tenant_id" : tenant_id , # Keep the same tenant_id for consistent token refresh
225258 "user_email" : user_email ,
226259 }
227260
0 commit comments