Skip to content

Commit 337f114

Browse files
authored
feat: support single tenant (langgenius#2448)
1 parent 4a72fe4 commit 337f114

3 files changed

Lines changed: 65 additions & 15 deletions

File tree

datasources/sharepoint_datasource/manifest.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
version: 0.2.2
1+
version: 0.2.3
22
type: plugin
33
author: langgenius
44
name: sharepoint_datasource

datasources/sharepoint_datasource/provider/sharepoint.py

Lines changed: 47 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,32 @@
1010

1111

1212
class 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

datasources/sharepoint_datasource/provider/sharepoint.yaml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,18 @@ oauth_schema:
4747
zh_Hans: mycompany
4848
en_US: mycompany
4949
required: true
50+
- name: tenant_id
51+
type: text-input
52+
label:
53+
zh_Hans: 租户 ID
54+
en_US: Tenant ID
55+
help:
56+
zh_Hans: '输入您的 Azure 租户 ID (用于单租户应用)。留空或使用 "common" 表示多租户应用。'
57+
en_US: 'Enter your Azure tenant ID (for single-tenant apps). Leave blank or use "common" for multi-tenant apps.'
58+
placeholder:
59+
zh_Hans: common (多租户) 或您的租户 ID
60+
en_US: common (multi-tenant) or your tenant ID
61+
required: false
5062
credentials_schema:
5163
- name: access_token
5264
type: secret-input
@@ -63,6 +75,11 @@ oauth_schema:
6375
label:
6476
zh_Hans: SharePoint 子域名
6577
en_US: SharePoint Subdomain
78+
- name: tenant_id
79+
type: text-input
80+
label:
81+
zh_Hans: 租户 ID
82+
en_US: Tenant ID
6683
datasources:
6784
- datasources/docs_in_site.yaml
6885
- datasources/docs_in_group.yaml

0 commit comments

Comments
 (0)