diff --git a/backend/open_webui/config.py b/backend/open_webui/config.py
index 1bc880bbfa..4d31b5e940 100644
--- a/backend/open_webui/config.py
+++ b/backend/open_webui/config.py
@@ -1763,6 +1763,13 @@ ONEDRIVE_CLIENT_ID = PersistentConfig(
os.environ.get("ONEDRIVE_CLIENT_ID", ""),
)
+ONEDRIVE_SHAREPOINT_URL = PersistentConfig(
+ "ONEDRIVE_SHAREPOINT_URL",
+ "onedrive.sharepoint_url",
+ os.environ.get("ONEDRIVE_SHAREPOINT_URL", ""),
+)
+
+
# RAG Content Extraction
CONTENT_EXTRACTION_ENGINE = PersistentConfig(
"CONTENT_EXTRACTION_ENGINE",
diff --git a/backend/open_webui/main.py b/backend/open_webui/main.py
index fce3d463be..89222ecce8 100644
--- a/backend/open_webui/main.py
+++ b/backend/open_webui/main.py
@@ -100,6 +100,7 @@ from open_webui.config import (
# OpenAI
ENABLE_OPENAI_API,
ONEDRIVE_CLIENT_ID,
+ ONEDRIVE_SHAREPOINT_URL,
OPENAI_API_BASE_URLS,
OPENAI_API_KEYS,
OPENAI_API_CONFIGS,
@@ -240,6 +241,7 @@ from open_webui.config import (
GOOGLE_DRIVE_CLIENT_ID,
GOOGLE_DRIVE_API_KEY,
ONEDRIVE_CLIENT_ID,
+ ONEDRIVE_SHAREPOINT_URL,
ENABLE_RAG_HYBRID_SEARCH,
ENABLE_RAG_LOCAL_WEB_FETCH,
ENABLE_WEB_LOADER_SSL_VERIFICATION,
@@ -1327,7 +1329,10 @@ async def get_app_config(request: Request):
"client_id": GOOGLE_DRIVE_CLIENT_ID.value,
"api_key": GOOGLE_DRIVE_API_KEY.value,
},
- "onedrive": {"client_id": ONEDRIVE_CLIENT_ID.value},
+ "onedrive": {
+ "client_id": ONEDRIVE_CLIENT_ID.value,
+ "sharepoint_url": ONEDRIVE_SHAREPOINT_URL.value,
+ },
"license_metadata": app.state.LICENSE_METADATA,
**(
{
diff --git a/src/lib/components/chat/MessageInput.svelte b/src/lib/components/chat/MessageInput.svelte
index ca6487cf58..b17cabeadd 100644
--- a/src/lib/components/chat/MessageInput.svelte
+++ b/src/lib/components/chat/MessageInput.svelte
@@ -1063,9 +1063,9 @@
);
}
}}
- uploadOneDriveHandler={async () => {
+ uploadOneDriveHandler={async (authorityType) => {
try {
- const fileData = await pickAndDownloadFile();
+ const fileData = await pickAndDownloadFile(authorityType);
if (fileData) {
const file = new File([fileData.blob], fileData.name, {
type: fileData.blob.type || 'application/octet-stream'
diff --git a/src/lib/components/chat/MessageInput/InputMenu.svelte b/src/lib/components/chat/MessageInput/InputMenu.svelte
index 27fe2cde29..ab455ddeba 100644
--- a/src/lib/components/chat/MessageInput/InputMenu.svelte
+++ b/src/lib/components/chat/MessageInput/InputMenu.svelte
@@ -229,94 +229,66 @@
{/if}
{#if $config?.features?.enable_onedrive_integration}
- {
- uploadOneDriveHandler();
- }}
- >
-
- {$i18n.t('OneDrive')}
-
+
{$i18n.t('Microsoft OneDrive (personal)')}
+
+ {
+ uploadOneDriveHandler('organizations');
+ }}
+ >
+
+
{$i18n.t('Microsoft OneDrive (work/school)')}
+
Includes SharePoint
+
+
+
+
{/if}
diff --git a/src/lib/utils/onedrive-file-picker.ts b/src/lib/utils/onedrive-file-picker.ts
index 60d2bb13c9..47b4e96273 100644
--- a/src/lib/utils/onedrive-file-picker.ts
+++ b/src/lib/utils/onedrive-file-picker.ts
@@ -2,70 +2,128 @@ import { PublicClientApplication } from '@azure/msal-browser';
import type { PopupRequest } from '@azure/msal-browser';
import { v4 as uuidv4 } from 'uuid';
-let CLIENT_ID = '';
+class OneDriveConfig {
+ private static instance: OneDriveConfig;
+ private clientId: string = '';
+ private sharepointUrl: string = '';
+ private msalInstance: PublicClientApplication | null = null;
+ private currentAuthorityType: 'personal' | 'organizations' = 'personal';
-async function getCredentials() {
- if (CLIENT_ID) return;
+ private constructor() {}
- const response = await fetch('/api/config');
- if (!response.ok) {
- throw new Error('Failed to fetch OneDrive credentials');
- }
- const config = await response.json();
- CLIENT_ID = config.onedrive?.client_id;
- if (!CLIENT_ID) {
- throw new Error('OneDrive client ID not configured');
- }
-}
-
-let msalInstance: PublicClientApplication | null = null;
-
-// Initialize MSAL authentication
-async function initializeMsal() {
- try {
- if (!CLIENT_ID) {
- await getCredentials();
+ public static getInstance(): OneDriveConfig {
+ if (!OneDriveConfig.instance) {
+ OneDriveConfig.instance = new OneDriveConfig();
}
+ return OneDriveConfig.instance;
+ }
- const msalParams = {
- auth: {
- authority: 'https://login.microsoftonline.com/consumers',
- clientId: CLIENT_ID
- }
+ public async initialize(authorityType?: 'personal' | 'organizations'): Promise {
+ if (authorityType && this.currentAuthorityType !== authorityType) {
+ this.currentAuthorityType = authorityType;
+ this.msalInstance = null;
+ }
+ await this.getCredentials();
+ }
+
+ public async ensureInitialized(authorityType?: 'personal' | 'organizations'): Promise {
+ await this.initialize(authorityType);
+ }
+
+ private async getCredentials(): Promise {
+
+ const headers: HeadersInit = {
+ 'Content-Type': 'application/json'
};
- if (!msalInstance) {
- msalInstance = new PublicClientApplication(msalParams);
- if (msalInstance.initialize) {
- await msalInstance.initialize();
+ const response = await fetch('/api/config', {
+ headers,
+ credentials: 'include'
+ });
+
+ if (!response.ok) {
+ throw new Error('Failed to fetch OneDrive credentials');
+ }
+
+ const config = await response.json();
+
+ const newClientId = config.onedrive?.client_id;
+ const newSharepointUrl = config.onedrive?.sharepoint_url;
+
+ if (!newClientId) {
+ throw new Error('OneDrive configuration is incomplete');
+ }
+
+ this.clientId = newClientId;
+ this.sharepointUrl = newSharepointUrl;
+ }
+
+ public async getMsalInstance(authorityType?: 'personal' | 'organizations'): Promise {
+ await this.ensureInitialized(authorityType);
+
+ if (!this.msalInstance) {
+ const authorityEndpoint = this.currentAuthorityType === 'organizations' ? 'common' : 'consumers';
+ const msalParams = {
+ auth: {
+ authority: `https://login.microsoftonline.com/${authorityEndpoint}`,
+ clientId: this.clientId
+ }
+ };
+
+ this.msalInstance = new PublicClientApplication(msalParams);
+ if (this.msalInstance.initialize) {
+ await this.msalInstance.initialize();
}
}
- return msalInstance;
- } catch (error) {
- throw new Error(
- 'MSAL initialization failed: ' + (error instanceof Error ? error.message : String(error))
- );
+ return this.msalInstance;
+ }
+
+ public getAuthorityType(): 'personal' | 'organizations' {
+ return this.currentAuthorityType;
+ }
+
+ public getSharepointUrl(): string {
+ return this.sharepointUrl;
+ }
+
+ public getBaseUrl(): string {
+ if (this.currentAuthorityType === 'organizations') {
+ if (!this.sharepointUrl || this.sharepointUrl === '') {
+ throw new Error('Sharepoint URL not configured');
+ }
+
+ let sharePointBaseUrl = this.sharepointUrl.replace(/^https?:\/\//, '');
+ sharePointBaseUrl = sharePointBaseUrl.replace(/\/$/, '');
+
+ return `https://${sharePointBaseUrl}`;
+ } else {
+ return 'https://onedrive.live.com/picker';
+ }
}
}
-// Retrieve OneDrive access token
-async function getToken(): Promise {
- const authParams: PopupRequest = { scopes: ['OneDrive.ReadWrite'] };
- let accessToken = '';
- try {
- msalInstance = await initializeMsal();
- if (!msalInstance) {
- throw new Error('MSAL not initialized');
- }
+// Retrieve OneDrive access token
+async function getToken(resource?: string, authorityType?: 'personal' | 'organizations'): Promise {
+ const config = OneDriveConfig.getInstance();
+ await config.ensureInitialized(authorityType);
+
+ const currentAuthorityType = config.getAuthorityType();
+
+ const scopes = currentAuthorityType === 'organizations'
+ ? [`${resource || config.getBaseUrl()}/.default`]
+ : ['OneDrive.ReadWrite'];
+
+ const authParams: PopupRequest = { scopes };
+ let accessToken = '';
+
+ try {
+ const msalInstance = await config.getMsalInstance(authorityType);
const resp = await msalInstance.acquireTokenSilent(authParams);
accessToken = resp.accessToken;
} catch (err) {
- if (!msalInstance) {
- throw new Error('MSAL not initialized');
- }
-
+ const msalInstance = await config.getMsalInstance(authorityType);
try {
const resp = await msalInstance.loginPopup(authParams);
msalInstance.setActiveAccount(resp.account);
@@ -88,60 +146,121 @@ async function getToken(): Promise {
return accessToken;
}
-const baseUrl = 'https://onedrive.live.com/picker';
-const params = {
- sdk: '8.0',
+interface PickerParams {
+ sdk: string;
entry: {
- oneDrive: {
- files: {}
- }
- },
- authentication: {},
+ oneDrive: Record;
+ };
+ authentication: Record;
messaging: {
- origin: window?.location?.origin,
- channelId: uuidv4()
- },
+ origin: string;
+ channelId: string;
+ };
typesAndSources: {
- mode: 'files',
- pivots: {
- oneDrive: true,
- recent: true
+ mode: string;
+ pivots: Record;
+ };
+}
+
+interface PickerResult {
+ command?: string;
+ items?: OneDriveFileInfo[];
+ [key: string]: any;
+}
+
+// Get picker parameters based on account type
+function getPickerParams(): PickerParams {
+ const channelId = uuidv4();
+ const config = OneDriveConfig.getInstance();
+
+ const params: PickerParams = {
+ sdk: '8.0',
+ entry: {
+ oneDrive: {}
+ },
+ authentication: {},
+ messaging: {
+ origin: window?.location?.origin || '',
+ channelId
+ },
+ typesAndSources: {
+ mode: 'files',
+ pivots: {
+ oneDrive: true,
+ recent: true
+ }
}
+ };
+
+ // For personal accounts, set files object in oneDrive
+ if (config.getAuthorityType() !== 'organizations') {
+ params.entry.oneDrive = { files: {} };
}
-};
+
+ return params;
+}
+
+interface OneDriveFileInfo {
+ id: string;
+ name: string;
+ parentReference: {
+ driveId: string;
+ };
+ '@sharePoint.endpoint': string;
+ [key: string]: any;
+}
// Download file from OneDrive
-async function downloadOneDriveFile(fileInfo: any): Promise {
- const accessToken = await getToken();
+async function downloadOneDriveFile(fileInfo: OneDriveFileInfo, authorityType?: 'personal' | 'organizations'): Promise {
+ const accessToken = await getToken(undefined, authorityType);
if (!accessToken) {
throw new Error('Unable to retrieve OneDrive access token.');
}
+
+ // The endpoint URL is provided in the file info
const fileInfoUrl = `${fileInfo['@sharePoint.endpoint']}/drives/${fileInfo.parentReference.driveId}/items/${fileInfo.id}`;
+
const response = await fetch(fileInfoUrl, {
headers: {
Authorization: `Bearer ${accessToken}`
}
});
+
if (!response.ok) {
- throw new Error('Failed to fetch file information.');
+ throw new Error(`Failed to fetch file information: ${response.status} ${response.statusText}`);
}
+
const fileData = await response.json();
const downloadUrl = fileData['@content.downloadUrl'];
- const downloadResponse = await fetch(downloadUrl);
- if (!downloadResponse.ok) {
- throw new Error('Failed to download file.');
+
+ if (!downloadUrl) {
+ throw new Error('Download URL not found in file data');
}
+
+ const downloadResponse = await fetch(downloadUrl);
+
+ if (!downloadResponse.ok) {
+ throw new Error(`Failed to download file: ${downloadResponse.status} ${downloadResponse.statusText}`);
+ }
+
return await downloadResponse.blob();
}
// Open OneDrive file picker and return selected file metadata
-export async function openOneDrivePicker(): Promise {
+export async function openOneDrivePicker(authorityType?: 'personal' | 'organizations'): Promise {
if (typeof window === 'undefined') {
throw new Error('Not in browser environment');
}
+
+ // Initialize OneDrive config with the specified authority type
+ const config = OneDriveConfig.getInstance();
+ await config.initialize(authorityType);
+
return new Promise((resolve, reject) => {
let pickerWindow: Window | null = null;
let channelPort: MessagePort | null = null;
+ const params = getPickerParams();
+ const baseUrl = config.getBaseUrl();
const handleWindowMessage = (event: MessageEvent) => {
if (event.source !== pickerWindow) return;
@@ -166,7 +285,9 @@ export async function openOneDrivePicker(): Promise {
switch (command.command) {
case 'authenticate': {
try {
- const newToken = await getToken();
+ // Pass the resource from the command for org accounts
+ const resource = config.getAuthorityType() === 'organizations' ? command.resource : undefined;
+ const newToken = await getToken(resource, authorityType);
if (newToken) {
channelPort?.postMessage({
type: 'result',
@@ -178,9 +299,12 @@ export async function openOneDrivePicker(): Promise {
}
} catch (err) {
channelPort?.postMessage({
- result: 'error',
- error: { code: 'tokenError', message: 'Failed to get token' },
- isExpected: true
+ type: 'result',
+ id: portData.id,
+ data: {
+ result: 'error',
+ error: { code: 'tokenError', message: 'Failed to get token' }
+ }
});
}
break;
@@ -227,7 +351,7 @@ export async function openOneDrivePicker(): Promise {
const initializePicker = async () => {
try {
- const authToken = await getToken();
+ const authToken = await getToken(undefined, authorityType);
if (!authToken) {
return reject(new Error('Failed to acquire access token'));
}
@@ -240,8 +364,14 @@ export async function openOneDrivePicker(): Promise {
const queryString = new URLSearchParams({
filePicker: JSON.stringify(params)
});
- const url = `${baseUrl}?${queryString.toString()}`;
+ let url = '';
+ if(config.getAuthorityType() === 'organizations') {
+ url = baseUrl + `/_layouts/15/FilePicker.aspx?${queryString}`;
+ } else {
+ url = baseUrl + `?${queryString}`;
+ }
+
const form = pickerWindow.document.createElement('form');
form.setAttribute('action', url);
form.setAttribute('method', 'POST');
@@ -268,17 +398,17 @@ export async function openOneDrivePicker(): Promise {
}
// Pick and download file from OneDrive
-export async function pickAndDownloadFile(): Promise<{ blob: Blob; name: string } | null> {
- const pickerResult = await openOneDrivePicker();
+export async function pickAndDownloadFile(authorityType?: 'personal' | 'organizations'): Promise<{ blob: Blob; name: string } | null> {
+ const pickerResult = await openOneDrivePicker(authorityType);
if (!pickerResult || !pickerResult.items || pickerResult.items.length === 0) {
return null;
}
const selectedFile = pickerResult.items[0];
- const blob = await downloadOneDriveFile(selectedFile);
+ const blob = await downloadOneDriveFile(selectedFile, authorityType);
return { blob, name: selectedFile.name };
}
-export { downloadOneDriveFile };
+export { downloadOneDriveFile };
\ No newline at end of file