All: Resolves #1266: Add support for OneDrive for Business (#3433)

pull/3589/head
jonath92 2020-08-08 01:35:30 +02:00 committed by GitHub
parent aa147bbcdc
commit 799a9e810d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 56 additions and 28 deletions

View File

@ -82,6 +82,15 @@ class SyncTargetOneDrive extends BaseSyncTarget {
}
async initFileApi() {
let context = Setting.value(`sync.${this.syncTargetId()}.context`);
context = context === '' ? null : JSON.parse(context);
let accountProperties = context ? context.accountProperties : null;
if (!accountProperties) {
accountProperties = await this.api_.execAccountPropertiesRequest();
context ? context.accountProperties = accountProperties : context = { accountProperties: accountProperties };
Setting.setValue(`sync.${this.syncTargetId()}.context`, JSON.stringify(context));
}
this.api_.setAccountProperties(accountProperties);
const appDir = await this.api().appDirectory();
const fileApi = new FileApi(appDir, new FileApiDriverOneDrive(this.api()));
fileApi.setSyncTargetId(this.syncTargetId());
@ -90,8 +99,15 @@ class SyncTargetOneDrive extends BaseSyncTarget {
}
async initSynchronizer() {
if (!(await this.isAuthenticated())) throw new Error('User is not authentified');
return new Synchronizer(this.db(), await this.fileApi(), Setting.value('appType'));
try {
if (!(await this.isAuthenticated())) throw new Error('User is not authentified');
return new Synchronizer(this.db(), await this.fileApi(), Setting.value('appType'));
} catch (error) {
BaseSyncTarget.dispatch({ type: 'SYNC_REPORT_UPDATE', report: { errors: [error] } });
throw error;
}
}
}

View File

@ -217,7 +217,9 @@ class FileApiDriverOneDrive {
};
const freshStartDelta = () => {
const url = `${this.makePath_(path)}:/delta`;
// Business Accounts are only allowed to make delta requests to the root. For some reason /delta gives an error for personal accounts and :/delta an error for business accounts
const accountProperties = this.api_.accountProperties_;
const url = (accountProperties.accountType === 'business') ? `/drives/${accountProperties.driveId}/root/delta` : `${this.makePath_(path)}:/delta`;
const query = this.itemFilter_();
query.select += ',deleted';
return { url: url, query: query };
@ -265,14 +267,14 @@ class FileApiDriverOneDrive {
const items = [];
// The delta API might return things that happen in subdirectories of the root and we don't want to
// deal with these since all the files we're interested in are at the root (The .resource dir
// is special since it's managed directly by the clients and resources never change - only the
// associated .md file at the root is synced). So in the loop below we check that the parent is
// indeed the root, otherwise the item is skipped.
// (Not sure but it's possible the delta API also returns events for files that are copied outside
// of the app directory and later deleted or modified. We also don't want to deal with
// these files during sync).
// The delta API might return things that happens in subdirectories and outside of the joplin directory.
// We don't want to deal with these since all the files we're interested in are at the root of the joplin directory
// (The .resource dir is special since it's managed directly by the clients and resources never change - only the
// associated .md file at the root is synced). So in the loop below we check that the parent is indeed the joplin
// directory, otherwise the item is skipped.
// At OneDrive for Business delta requests can only make at the root of OneDrive. Not sure but it's possible that
// the delta API also returns events for files that are copied outside of the app directory and later deleted or
// modified when using OneDrive Personal).
for (let i = 0; i < response.value.length; i++) {
const v = response.value[i];

View File

@ -13,6 +13,7 @@ class OneDriveApi {
this.clientId_ = clientId;
this.clientSecret_ = clientSecret;
this.auth_ = null;
this.accountProperties_ = null;
this.isPublic_ = isPublic;
this.listeners_ = {
authRefreshed: [],
@ -73,14 +74,15 @@ class OneDriveApi {
}
async appDirectory() {
const r = await this.execJson('GET', '/drive/special/approot');
const driveId = this.accountProperties_.driveId;
const r = await this.execJson('GET', `/me/drives/${driveId}/special/approot`);
return `${r.parentReference.path}/${r.name}`;
}
authCodeUrl(redirectUri) {
const query = {
client_id: this.clientId_,
scope: 'files.readwrite offline_access',
scope: 'files.readwrite offline_access sites.readwrite.all',
response_type: 'code',
redirect_uri: redirectUri,
};
@ -320,6 +322,29 @@ class OneDriveApi {
throw new Error(`Could not execute request after multiple attempts: ${method} ${url}`);
}
setAccountProperties(accountProperties) {
this.accountProperties_ = accountProperties;
}
async execAccountPropertiesRequest() {
const response = await shim.fetch('https://graph.microsoft.com/v1.0/me/drive', {
method: 'GET',
headers: {
'Authorization': this.token(),
},
});
if (!response.ok) {
const text = await response.text();
throw new Error(`Could not retrieve account details (drive ID, Account type): ${response.status}: ${response.statusText}: ${text}`);
} else {
const data = await response.json();
const accountProperties = { accountType: data.driveType, driveId: data.id };
return accountProperties;
}
}
async execJson(method, path, query, data) {
const response = await this.exec(method, path, query, data);
const errorResponseText = await response.text();

View File

@ -2,7 +2,6 @@ const { Logger } = require('lib/logger.js');
const Setting = require('lib/models/Setting.js');
const { shim } = require('lib/shim.js');
const SyncTargetRegistry = require('lib/SyncTargetRegistry.js');
const { _ } = require('lib/locale.js');
const reg = {};
@ -141,20 +140,6 @@ reg.scheduleSync = async (delay = null, syncOptions = null) => {
} catch (error) {
reg.logger().info('Could not run background sync:');
reg.logger().info(error);
// Special case to display OneDrive Business error. This is the full error that's received when trying to use a OneDrive Business account:
//
// {"error":"invalid_client","error_description":"AADSTS50011: The reply address 'http://localhost:1917' does not match the reply addresses configured for
// the application: 'cbabb902-d276-4ea4-aa88-062a5889d6dc'. More details: not specified\r\nTrace ID: 6e63dac6-8b37-47e2-bd1b-4768f8713400\r\nCorrelation
// ID: acfd6503-8d97-4349-ae2e-e7a19dd7b6bc\r\nTimestamp: 2017-12-01 13:35:55Z","error_codes":[50011],"timestamp":"2017-12-01 13:35:55Z","trace_id":
// "6e63dac6-8b37-47e2-bd1b-4768f8713400","correlation_id":"acfd6503-8d97-4349-ae2e-e7a19dd7b6bc"}: TOKEN: null Error: {"error":"invalid_client",
// "error_description":"AADSTS50011: The reply address 'http://localhost:1917' does not match the reply addresses configured for the application:
// 'cbabb902-d276-4ea4-aa88-062a5889d6dc'. More details: not specified\r\nTrace ID: 6e63dac6-8b37-47e2-bd1b-4768f8713400\r\nCorrelation ID
// acfd6503-8d97-4349-ae2e-e7a19dd7b6bc\r\nTimestamp: 2017-12-01 13:35:55Z","error_codes":[50011],"timestamp":"2017-12-01 13:35:55Z","trace_id":
// "6e63dac6-8b37-47e2-bd1b-4768f8713400","correlation_id":"acfd6503-8d97-4349-ae2e-e7a19dd7b6bc"}
if (error && error.message && error.message.indexOf('"invalid_client"') >= 0) {
reg.showErrorMessageBox(_('Could not synchronise with OneDrive.\n\nThis error often happens when using OneDrive for Business, which unfortunately cannot be supported.\n\nPlease consider using a regular OneDrive account.'));
}
}
reg.setupRecurrentSync();
promiseResolve();