[IMPROVE] Webdav methods sanitization (#23924)

pull/24009/head
Douglas Fabris 3 years ago committed by GitHub
parent 9160ba13c2
commit 160619d2ac
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      app/api/server/lib/webdav.ts
  2. 25
      app/models/server/raw/WebdavAccounts.ts
  3. 4
      app/webdav/client/addWebdavAccount.html
  4. 4
      app/webdav/client/addWebdavAccount.js
  5. 2
      app/webdav/client/startup/messageBoxActions.js
  6. 38
      app/webdav/server/methods/addWebdavAccount.ts
  7. 12
      app/webdav/server/methods/getFileFromWebdav.ts
  8. 12
      app/webdav/server/methods/getWebdavFileList.ts
  9. 13
      app/webdav/server/methods/getWebdavFilePreview.ts
  10. 8
      app/webdav/server/methods/removeWebdavAccount.ts
  11. 2
      app/webdav/server/methods/uploadFileToWebdav.ts
  12. 6
      definition/IWebdavAccount.ts
  13. 1
      server/startup/migrations/index.ts
  14. 10
      server/startup/migrations/v251.ts

@ -7,8 +7,7 @@ export async function findWebdavAccountsByUserId({ uid }: { uid: string }): Prom
projection: { projection: {
_id: 1, _id: 1,
username: 1, username: 1,
// eslint-disable-next-line @typescript-eslint/camelcase serverURL: 1,
server_url: 1,
name: 1, name: 1,
}, },
}).toArray(), }).toArray(),

@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/camelcase */
/** /**
* Webdav Accounts model * Webdav Accounts model
*/ */
@ -16,31 +15,31 @@ export class WebdavAccountsRaw extends BaseRaw<T> {
) { ) {
super(col, trash); super(col, trash);
this.col.createIndex({ user_id: 1 }); this.col.createIndex({ userId: 1 });
} }
findOneByIdAndUserId(_id: string, user_id: string, options: FindOneOptions<T>): Promise<T | null> { findOneByIdAndUserId(_id: string, userId: string, options: FindOneOptions<T>): Promise<T | null> {
return this.findOne({ _id, user_id }, options); return this.findOne({ _id, userId }, options);
} }
findOneByUserIdServerUrlAndUsername({ findOneByUserIdServerUrlAndUsername({
user_id, userId,
server_url, serverURL,
username, username,
}: { }: {
user_id: string; userId: string;
server_url: string; serverURL: string;
username: string; username: string;
}, options: FindOneOptions<T>): Promise<T | null> { }, options: FindOneOptions<T>): Promise<T | null> {
return this.findOne({ user_id, server_url, username }, options); return this.findOne({ userId, serverURL, username }, options);
} }
findWithUserId(user_id: string, options: FindOneOptions<T>): Cursor<T> { findWithUserId(userId: string, options: FindOneOptions<T>): Cursor<T> {
const query = { user_id }; const query = { userId };
return this.find(query, options); return this.find(query, options);
} }
removeByUserAndId(_id: string, user_id: string): Promise<DeleteWriteOpResultObject> { removeByUserAndId(_id: string, userId: string): Promise<DeleteWriteOpResultObject> {
return this.deleteOne({ _id, user_id }); return this.deleteOne({ _id, userId });
} }
} }

@ -28,9 +28,9 @@
</label> </label>
</div> </div>
<div class="rc-input"> <div class="rc-input">
<label class="rc-input__label" for="pass"> <label class="rc-input__label" for="password">
<div class="rc-input__wrapper"> <div class="rc-input__wrapper">
<input name="pass" id="pass" type="password" class="rc-input__element" autocapitalize="off" autocorrect="off" placeholder="{{_ 'Password' }}" autofocus> <input name="password" id="password" type="password" class="rc-input__element" autocapitalize="off" autocorrect="off" placeholder="{{_ 'Password' }}" autofocus>
<div class="input-error"></div> <div class="input-error"></div>
</div> </div>
</label> </label>

@ -54,8 +54,8 @@ const validate = function() {
if (!formObj.username) { if (!formObj.username) {
validationObj.username = t('Field_required'); validationObj.username = t('Field_required');
} }
if (!formObj.pass) { if (!formObj.password) {
validationObj.pass = t('Field_required'); validationObj.password = t('Field_required');
} }
form.find('input.error, select.error').removeClass('error'); form.find('input.error, select.error').removeClass('error');

@ -33,7 +33,7 @@ Meteor.startup(function() {
} }
accounts.forEach((account) => { accounts.forEach((account) => {
const name = account.name || `${ account.username }@${ account.server_url.replace(/^https?\:\/\//i, '') }`; const name = account.name || `${ account.username }@${ account.serverURL.replace(/^https?\:\/\//i, '') }`;
const title = t('Upload_From', { const title = t('Upload_From', {
name, name,
}); });

@ -21,12 +21,14 @@ Meteor.methods({
check(formData, Match.ObjectIncluding({ check(formData, Match.ObjectIncluding({
serverURL: String, serverURL: String,
username: String, username: String,
pass: String, password: String,
name: Match.Maybe(String),
})); }));
const duplicateAccount = await WebdavAccounts.findOneByUserIdServerUrlAndUsername({ user_id: userId, server_url: formData.serverURL, username: formData.username }); const duplicateAccount = await WebdavAccounts.findOneByUserIdServerUrlAndUsername({ userId, serverURL: formData.serverURL, username: formData.username }, {});
if (duplicateAccount !== undefined) {
throw new Meteor.Error('duplicated-account', { if (duplicateAccount !== null) {
throw new Meteor.Error('duplicated-account', 'Account not found', {
method: 'addWebdavAccount', method: 'addWebdavAccount',
}); });
} }
@ -36,16 +38,16 @@ Meteor.methods({
formData.serverURL, formData.serverURL,
{ {
username: formData.username, username: formData.username,
password: formData.pass, password: formData.password,
}, },
); );
const accountData = { const accountData = {
user_id: userId, userId,
server_url: formData.serverURL, serverURL: formData.serverURL,
username: formData.username, username: formData.username,
password: formData.pass, password: formData.password,
name: formData.name, name: formData.name ?? '',
}; };
await client.stat('/'); await client.stat('/');
@ -55,7 +57,7 @@ Meteor.methods({
account: accountData, account: accountData,
}); });
} catch (error) { } catch (error) {
throw new Meteor.Error('could-not-access-webdav', { method: 'addWebdavAccount' }); throw new Meteor.Error('could-not-access-webdav', 'Could not access webdav', { method: 'addWebdavAccount' });
} }
return true; return true;
}, },
@ -73,6 +75,8 @@ Meteor.methods({
check(data, Match.ObjectIncluding({ check(data, Match.ObjectIncluding({
serverURL: String, serverURL: String,
token: String,
name: Match.Maybe(String),
})); }));
try { try {
@ -82,17 +86,17 @@ Meteor.methods({
); );
const accountData = { const accountData = {
user_id: userId, userId,
server_url: data.serverURL, serverURL: data.serverURL,
token: data.token, token: data.token,
name: data.name, name: data.name ?? '',
}; };
await client.stat('/'); await client.stat('/');
await WebdavAccounts.updateOne({ await WebdavAccounts.updateOne({
user_id: userId, userId,
server_url: data.serverURL, serverURL: data.serverURL,
name: data.name, name: data.name ?? '',
}, { }, {
$set: accountData, $set: accountData,
}, { }, {
@ -103,7 +107,7 @@ Meteor.methods({
account: accountData, account: accountData,
}); });
} catch (error) { } catch (error) {
throw new Meteor.Error('could-not-access-webdav', { method: 'addWebdavAccount' }); throw new Meteor.Error('could-not-access-webdav', 'Could not access webdav', { method: 'addWebdavAccount' });
} }
return true; return true;

@ -1,32 +1,34 @@
import { Meteor } from 'meteor/meteor'; import { Meteor } from 'meteor/meteor';
import { settings } from '../../../settings'; import { settings } from '../../../settings/server';
import { getWebdavCredentials } from './getWebdavCredentials'; import { getWebdavCredentials } from './getWebdavCredentials';
import { WebdavAccounts } from '../../../models/server/raw'; import { WebdavAccounts } from '../../../models/server/raw';
import { WebdavClientAdapter } from '../lib/webdavClientAdapter'; import { WebdavClientAdapter } from '../lib/webdavClientAdapter';
Meteor.methods({ Meteor.methods({
async getFileFromWebdav(accountId, file) { async getFileFromWebdav(accountId, file) {
if (!Meteor.userId()) { const userId = Meteor.userId();
if (!userId) {
throw new Meteor.Error('error-invalid-user', 'Invalid User', { method: 'getFileFromWebdav' }); throw new Meteor.Error('error-invalid-user', 'Invalid User', { method: 'getFileFromWebdav' });
} }
if (!settings.get('Webdav_Integration_Enabled')) { if (!settings.get('Webdav_Integration_Enabled')) {
throw new Meteor.Error('error-not-allowed', 'WebDAV Integration Not Allowed', { method: 'getFileFromWebdav' }); throw new Meteor.Error('error-not-allowed', 'WebDAV Integration Not Allowed', { method: 'getFileFromWebdav' });
} }
const account = await WebdavAccounts.findOneByIdAndUserId(accountId, Meteor.userId()); const account = await WebdavAccounts.findOneByIdAndUserId(accountId, userId, {});
if (!account) { if (!account) {
throw new Meteor.Error('error-invalid-account', 'Invalid WebDAV Account', { method: 'getFileFromWebdav' }); throw new Meteor.Error('error-invalid-account', 'Invalid WebDAV Account', { method: 'getFileFromWebdav' });
} }
try { try {
const cred = getWebdavCredentials(account); const cred = getWebdavCredentials(account);
const client = new WebdavClientAdapter(account.server_url, cred); const client = new WebdavClientAdapter(account.serverURL, cred);
const fileContent = await client.getFileContents(file.filename); const fileContent = await client.getFileContents(file.filename);
const data = new Uint8Array(fileContent); const data = new Uint8Array(fileContent);
return { success: true, data }; return { success: true, data };
} catch (error) { } catch (error) {
throw new Meteor.Error('unable-to-get-file', { method: 'getFileFromWebdav' }); throw new Meteor.Error('unable-to-get-file', 'Unable to get file', { method: 'getFileFromWebdav' });
} }
}, },
}); });

@ -1,13 +1,15 @@
import { Meteor } from 'meteor/meteor'; import { Meteor } from 'meteor/meteor';
import { settings } from '../../../settings'; import { settings } from '../../../settings/server';
import { getWebdavCredentials } from './getWebdavCredentials'; import { getWebdavCredentials } from './getWebdavCredentials';
import { WebdavAccounts } from '../../../models/server/raw'; import { WebdavAccounts } from '../../../models/server/raw';
import { WebdavClientAdapter } from '../lib/webdavClientAdapter'; import { WebdavClientAdapter } from '../lib/webdavClientAdapter';
Meteor.methods({ Meteor.methods({
async getWebdavFileList(accountId, path) { async getWebdavFileList(accountId, path) {
if (!Meteor.userId()) { const userId = Meteor.userId();
if (!userId) {
throw new Meteor.Error('error-invalid-user', 'Invalid User', { method: 'getWebdavFileList' }); throw new Meteor.Error('error-invalid-user', 'Invalid User', { method: 'getWebdavFileList' });
} }
@ -15,18 +17,18 @@ Meteor.methods({
throw new Meteor.Error('error-not-allowed', 'WebDAV Integration Not Allowed', { method: 'getWebdavFileList' }); throw new Meteor.Error('error-not-allowed', 'WebDAV Integration Not Allowed', { method: 'getWebdavFileList' });
} }
const account = await WebdavAccounts.findOneByIdAndUserId(accountId, Meteor.userId()); const account = await WebdavAccounts.findOneByIdAndUserId(accountId, userId, {});
if (!account) { if (!account) {
throw new Meteor.Error('error-invalid-account', 'Invalid WebDAV Account', { method: 'getWebdavFileList' }); throw new Meteor.Error('error-invalid-account', 'Invalid WebDAV Account', { method: 'getWebdavFileList' });
} }
try { try {
const cred = getWebdavCredentials(account); const cred = getWebdavCredentials(account);
const client = new WebdavClientAdapter(account.server_url, cred); const client = new WebdavClientAdapter(account.serverURL, cred);
const data = await client.getDirectoryContents(path); const data = await client.getDirectoryContents(path);
return { success: true, data }; return { success: true, data };
} catch (error) { } catch (error) {
throw new Meteor.Error('could-not-access-webdav', { method: 'getWebdavFileList' }); throw new Meteor.Error('could-not-access-webdav', 'Could not access webdav', { method: 'getWebdavFileList' });
} }
}, },
}); });

@ -1,13 +1,15 @@
import { Meteor } from 'meteor/meteor'; import { Meteor } from 'meteor/meteor';
import { createClient } from 'webdav'; import { createClient } from 'webdav';
import { settings } from '../../../settings'; import { settings } from '../../../settings/server';
import { getWebdavCredentials } from './getWebdavCredentials'; import { getWebdavCredentials } from './getWebdavCredentials';
import { WebdavAccounts } from '../../../models/server/raw'; import { WebdavAccounts } from '../../../models/server/raw';
Meteor.methods({ Meteor.methods({
async getWebdavFilePreview(accountId, path) { async getWebdavFilePreview(accountId, path) {
if (!Meteor.userId()) { const userId = Meteor.userId();
if (!userId) {
throw new Meteor.Error('error-invalid-user', 'Invalid User', { method: 'getWebdavFilePreview' }); throw new Meteor.Error('error-invalid-user', 'Invalid User', { method: 'getWebdavFilePreview' });
} }
@ -15,17 +17,16 @@ Meteor.methods({
throw new Meteor.Error('error-not-allowed', 'WebDAV Integration Not Allowed', { method: 'getWebdavFilePreview' }); throw new Meteor.Error('error-not-allowed', 'WebDAV Integration Not Allowed', { method: 'getWebdavFilePreview' });
} }
const account = await WebdavAccounts.findOneByIdAndUserId(accountId, Meteor.userId()); const account = await WebdavAccounts.findOneByIdAndUserId(accountId, userId, {});
if (!account) { if (!account) {
throw new Meteor.Error('error-invalid-account', 'Invalid WebDAV Account', { method: 'getWebdavFilePreview' }); throw new Meteor.Error('error-invalid-account', 'Invalid WebDAV Account', { method: 'getWebdavFilePreview' });
} }
try { try {
const cred = getWebdavCredentials(account); const cred = getWebdavCredentials(account);
const client = createClient(account.server_url, cred); const client = createClient(account.serverURL, cred);
const serverURL = settings.get('Accounts_OAuth_Nextcloud_URL'); const serverURL = settings.get('Accounts_OAuth_Nextcloud_URL');
const res = await client.customRequest({ const res = await client.customRequest(`${ serverURL }/index.php/core/preview.png?file=${ path }&x=64&y=64`, {
url: `${ serverURL }/index.php/core/preview.png?file=${ path }&x=64&y=64`,
method: 'GET', method: 'GET',
responseType: 'arraybuffer', responseType: 'arraybuffer',
}); });

@ -6,15 +6,17 @@ import { Notifications } from '../../../notifications/server';
Meteor.methods({ Meteor.methods({
async removeWebdavAccount(accountId) { async removeWebdavAccount(accountId) {
if (!Meteor.userId()) { const userId = Meteor.userId();
if (!userId) {
throw new Meteor.Error('error-invalid-user', 'Invalid User', { method: 'removeWebdavAccount' }); throw new Meteor.Error('error-invalid-user', 'Invalid User', { method: 'removeWebdavAccount' });
} }
check(accountId, String); check(accountId, String);
const removed = await WebdavAccounts.removeByUserAndId(accountId, Meteor.userId()); const removed = await WebdavAccounts.removeByUserAndId(accountId, userId);
if (removed) { if (removed) {
Notifications.notifyUser(Meteor.userId(), 'webdav', { Notifications.notifyUser(userId, 'webdav', {
type: 'removed', type: 'removed',
account: { _id: accountId }, account: { _id: accountId },
}); });

@ -28,7 +28,7 @@ Meteor.methods({
try { try {
const cred = getWebdavCredentials(account); const cred = getWebdavCredentials(account);
const client = new WebdavClientAdapter(account.server_url, cred); const client = new WebdavClientAdapter(account.serverURL, cred);
// eslint-disable-next-line @typescript-eslint/no-empty-function // eslint-disable-next-line @typescript-eslint/no-empty-function
await client.createDirectory(uploadFolder).catch(() => {}); await client.createDirectory(uploadFolder).catch(() => {});
await client.putFileContents(`${ uploadFolder }/${ name }`, buffer, { overwrite: false }); await client.putFileContents(`${ uploadFolder }/${ name }`, buffer, { overwrite: false });

@ -1,9 +1,11 @@
import { IRocketChatRecord } from './IRocketChatRecord'; import { IRocketChatRecord } from './IRocketChatRecord';
export interface IWebdavAccount extends IRocketChatRecord { export interface IWebdavAccount extends IRocketChatRecord {
user_id: string; userId: string;
server_url: string; serverURL: string;
username: string; username: string;
password: string; password: string;
name: string; name: string;
} }
export type IWebdavAccountPayload = Omit<IWebdavAccount, 'userId' | '_id' | '_updatedAt'>

@ -74,4 +74,5 @@ import './v247';
import './v248'; import './v248';
import './v249'; import './v249';
import './v250'; import './v250';
import './v251';
import './xrun'; import './xrun';

@ -0,0 +1,10 @@
import { addMigration } from '../../lib/migrations';
import { WebdavAccounts } from '../../../app/models/server/raw';
addMigration({
version: 251,
async up() {
// eslint-disable-next-line quote-props
await WebdavAccounts.updateMany({}, { $rename: { 'user_id': 'userId', 'server_url': 'serverURL' } });
},
});
Loading…
Cancel
Save