diff --git a/.meteor/packages b/.meteor/packages index b1fccebabf03..91db90974b71 100644 --- a/.meteor/packages +++ b/.meteor/packages @@ -154,6 +154,7 @@ rocketchat:ui-vrecord rocketchat:user-data-download rocketchat:version rocketchat:videobridge +rocketchat:webdav rocketchat:webrtc rocketchat:wordpress rocketchat:nrr diff --git a/.meteor/versions b/.meteor/versions index 9a11dd0ee272..ebbb45a870c4 100644 --- a/.meteor/versions +++ b/.meteor/versions @@ -250,6 +250,7 @@ rocketchat:user-data-download@1.0.0 rocketchat:version@1.0.0 rocketchat:version-check@0.0.1 rocketchat:videobridge@0.2.0 +rocketchat:webdav@0.0.1 rocketchat:webrtc@0.0.1 rocketchat:wordpress@0.0.1 routepolicy@1.0.13 diff --git a/packages/rocketchat-file-upload/ufs/Webdav/server.js b/packages/rocketchat-file-upload/ufs/Webdav/server.js index d437317e1931..19c46700815a 100644 --- a/packages/rocketchat-file-upload/ufs/Webdav/server.js +++ b/packages/rocketchat-file-upload/ufs/Webdav/server.js @@ -12,7 +12,6 @@ export class WebdavStore extends UploadFS.Store { super(options); - const client = new Webdav( options.connection.credentials.server, options.connection.credentials.username, @@ -127,7 +126,6 @@ export class WebdavStore extends UploadFS.Store { writeStream.pipe(webdavStream); return writeStream; }; - } } diff --git a/packages/rocketchat-i18n/i18n/en.i18n.json b/packages/rocketchat-i18n/i18n/en.i18n.json index 2aecb1725cae..bf7f12cc6d85 100644 --- a/packages/rocketchat-i18n/i18n/en.i18n.json +++ b/packages/rocketchat-i18n/i18n/en.i18n.json @@ -596,6 +596,7 @@ "Copy": "Copy", "Copy_to_clipboard": "Copy to clipboard", "COPY_TO_CLIPBOARD": "COPY TO CLIPBOARD", + "could-not-access-webdav": "Could not access WebDAV", "Count": "Count", "Country": "Country", "Country_Afghanistan": "Afghanistan", @@ -976,6 +977,7 @@ "Drop_to_upload_file": "Drop to upload file", "Dry_run": "Dry run", "Dry_run_description": "Will only send one email, to the same address as in From. The email must belong to a valid user.", + "duplicated-account": "Duplicated account", "Duplicate_archived_channel_name": "An archived Channel with name `#%s` exists", "Duplicate_archived_private_group_name": "An archived Private Group with name '%s' exists", "Duplicate_channel_name": "A Channel with name '%s' exists", @@ -1078,6 +1080,7 @@ "error-importer-not-defined": "The importer was not defined correctly, it is missing the Import class.", "error-input-is-not-a-valid-field": "__input__ is not a valid __field__", "error-invalid-actionlink": "Invalid action link", + "error-invalid-account": "Invalid Account", "error-invalid-arguments": "Invalid arguments", "error-invalid-asset": "Invalid asset", "error-invalid-channel": "Invalid channel.", @@ -2295,6 +2298,7 @@ "Save_changes": "Save changes", "Save_Mobile_Bandwidth": "Save Mobile Bandwidth", "Save_to_enable_this_action": "Save to enable this action", + "Save_To_Webdav": "Save to WebDAV", "Save_your_encryption_password": "Save your encryption password", "Saved": "Saved", "Saving": "Saving", @@ -2675,7 +2679,9 @@ "Upload_file_description": "File description", "Upload_file_name": "File name", "Upload_file_question": "Upload file?", + "Upload_Folder_Path": "Upload Folder Path", "Upload_user_avatar": "Upload avatar", + "Upload_From" : "Upload from __name__", "Uploading_file": "Uploading file...", "Uptime": "Uptime", "URL": "URL", @@ -2843,6 +2849,13 @@ "We_are_offline_Sorry_for_the_inconvenience": "We are offline. Sorry for the inconvenience.", "We_have_sent_password_email": "We have sent you an email with password reset instructions. If you do not receive an email shortly, please come back and try again.", "We_have_sent_registration_email": "We have sent you an email to confirm your registration. If you do not receive an email shortly, please come back and try again.", + "WebDAV_Accounts": "WebDAV Accounts", + "Webdav_add_new_account": "Add new WebDAV account", + "webdav-account-saved": "WebDAV account saved", + "Webdav_Integration_Enabled": "Webdav Integration Enabled", + "Webdav_Server_URL": "WebDAV Server Access URL", + "Webdav_Username": "WebDAV Username", + "Webdav_Password": "WebDAV Password", "Webhook_URL": "Webhook URL", "Webhooks": "Webhooks", "WebRTC_direct_audio_call_from_%s": "Direct audio call from %s", diff --git a/packages/rocketchat-lib/lib/messageBox.js b/packages/rocketchat-lib/lib/messageBox.js index 321ea63c7154..514fb1258ffb 100644 --- a/packages/rocketchat-lib/lib/messageBox.js +++ b/packages/rocketchat-lib/lib/messageBox.js @@ -1,4 +1,6 @@ -RocketChat.messageBox = {}; +import EventEmitter from 'wolfy87-eventemitter'; + +RocketChat.messageBox = new EventEmitter; RocketChat.messageBox.actions = new class { constructor() { @@ -31,7 +33,12 @@ RocketChat.messageBox.actions = new class { this.actions[group].push({ ...config, label }); } - + remove(group, expression) { + if (!group || !this.actions[group]) { + return false; + } + return (this.actions[group] = this.actions[group].filter((action) => expression.test(action.id))); + } get(group) { if (!group) { return Object.keys(this.actions).reduce((ret, key) => { diff --git a/packages/rocketchat-ui-account/client/accountFlex.html b/packages/rocketchat-ui-account/client/accountFlex.html index 3033f12a8804..c8ba335c8b8c 100644 --- a/packages/rocketchat-ui-account/client/accountFlex.html +++ b/packages/rocketchat-ui-account/client/accountFlex.html @@ -22,6 +22,8 @@

{{_ "My_Account"}}

{{> sidebarItem menuItem "Encryption" "key" "account" "encryption" }} {{/if}} + {{> sidebarItem menuItem "Integrations" "code" "account" "integrations" }} + {{#if accessTokensEnabled}} {{> sidebarItem menuItem "Personal_Access_Tokens" "key" "account" "tokens" }} {{/if}} diff --git a/packages/rocketchat-ui-account/client/accountIntegrations.html b/packages/rocketchat-ui-account/client/accountIntegrations.html new file mode 100644 index 000000000000..9448566c99db --- /dev/null +++ b/packages/rocketchat-ui-account/client/accountIntegrations.html @@ -0,0 +1,28 @@ + diff --git a/packages/rocketchat-ui-account/client/accountIntegrations.js b/packages/rocketchat-ui-account/client/accountIntegrations.js new file mode 100644 index 000000000000..1a4185294e36 --- /dev/null +++ b/packages/rocketchat-ui-account/client/accountIntegrations.js @@ -0,0 +1,33 @@ +/* global */ + +import toastr from 'toastr'; + +Template.accountIntegrations.helpers({ + webdavAccounts() { + return RocketChat.models.WebdavAccounts.find().fetch(); + }, + getOptionValue(account) { + return account.name || `${ account.username }@${ account.server_url.replace(/^https?\:\/\//i, '') }`; + }, +}); + +Template.accountIntegrations.events({ + 'click .webdav-account-remove'(e) { + e.preventDefault(); + const selectEl = document.getElementById('webdav-accounts'); + const { options } = selectEl; + const selectedOption = selectEl.value; + const optionIndex = Array.from(options).findIndex((option) => option.value === selectedOption); + + Meteor.call('removeWebdavAccount', selectedOption, function(error) { + if (error) { + return toastr.error(t(error.error)); + } + + toastr.success(t('webdav-account-removed')); + modal.close(); + }); + + selectEl.remove(optionIndex); + }, +}); diff --git a/packages/rocketchat-ui-account/package.js b/packages/rocketchat-ui-account/package.js index e7a65cdcee53..1644d5a587a6 100644 --- a/packages/rocketchat-ui-account/package.js +++ b/packages/rocketchat-ui-account/package.js @@ -21,6 +21,7 @@ Package.onUse(function(api) { api.addFiles('client/account.html', 'client'); api.addFiles('client/accountFlex.html', 'client'); + api.addFiles('client/accountIntegrations.html', 'client'); api.addFiles('client/accountPreferences.html', 'client'); api.addFiles('client/accountProfile.html', 'client'); api.addFiles('client/avatar/avatar.html', 'client'); @@ -28,6 +29,7 @@ Package.onUse(function(api) { api.addFiles('client/account.js', 'client'); api.addFiles('client/accountFlex.js', 'client'); + api.addFiles('client/accountIntegrations.js', 'client'); api.addFiles('client/accountPreferences.js', 'client'); api.addFiles('client/accountProfile.js', 'client'); api.addFiles('client/avatar/avatar.js', 'client'); diff --git a/packages/rocketchat-ui-master/public/icons.svg b/packages/rocketchat-ui-master/public/icons.svg index bd06c6df92c6..dcd151205cc0 100644 --- a/packages/rocketchat-ui-master/public/icons.svg +++ b/packages/rocketchat-ui-master/public/icons.svg @@ -460,6 +460,22 @@ + + + + + + + + + + diff --git a/packages/rocketchat-ui-message/client/messageBox.js b/packages/rocketchat-ui-message/client/messageBox.js index 02a5121e1949..92ea0e42864f 100644 --- a/packages/rocketchat-ui-message/client/messageBox.js +++ b/packages/rocketchat-ui-message/client/messageBox.js @@ -653,6 +653,7 @@ Template.messageBox.onCreated(function() { this.dataReply = new ReactiveVar(''); // if user is replying to a mssg, this will contain data of the mssg being replied to this.isMessageFieldEmpty = new ReactiveVar(true); this.sendIcon = new ReactiveVar(false); + RocketChat.messageBox.emit('created', this); }); Meteor.startup(function() { diff --git a/packages/rocketchat-ui/client/views/app/modal.js b/packages/rocketchat-ui/client/views/app/modal.js index 61b047a38ffb..5991ba4e26f7 100644 --- a/packages/rocketchat-ui/client/views/app/modal.js +++ b/packages/rocketchat-ui/client/views/app/modal.js @@ -24,7 +24,7 @@ this.modal = { this.config = config; if (config.dontAskAgain) { - const dontAskAgainList = RocketChat.getUserPreference(Meteor.user(), 'dontAskAgainList'); + const dontAskAgainList = RocketChat.getUserPreference(Meteor.userId(), 'dontAskAgainList'); if (dontAskAgainList && dontAskAgainList.some((dontAsk) => dontAsk.action === config.dontAskAgain.action)) { this.confirm(true); @@ -66,7 +66,7 @@ this.modal = { errorEl.style.display = 'block'; }, onKeydown(e) { - if (e.key === 'Enter') { + if (e.key === 'Enter' && !/input|textarea|button/i.test(e.currentTarget.activeElement.tagName)) { e.preventDefault(); e.stopPropagation(); diff --git a/packages/rocketchat-webdav/README.md b/packages/rocketchat-webdav/README.md new file mode 100644 index 000000000000..920030805783 --- /dev/null +++ b/packages/rocketchat-webdav/README.md @@ -0,0 +1,3 @@ +# RocketChat WebDAV + +Package for RocketChat users to interact with WebDAV servers (Tested with ownCloud and Nextcloud). diff --git a/packages/rocketchat-webdav/client/actionButton.js b/packages/rocketchat-webdav/client/actionButton.js new file mode 100644 index 000000000000..b7c4ef80af34 --- /dev/null +++ b/packages/rocketchat-webdav/client/actionButton.js @@ -0,0 +1,45 @@ +/* globals modal, RocketChat*/ + +Meteor.startup(function() { + + RocketChat.MessageAction.addButton({ + id: 'webdav-upload', + icon: 'upload', + label: t('Save_To_Webdav'), + condition: (message) => { + if (RocketChat.models.Subscriptions.findOne({ rid: message.rid }) == null) { + return false; + } + if (RocketChat.models.WebdavAccounts.findOne() == null) { + return false; + } + if (!message.file) { + return false; + } + + return RocketChat.settings.get('Webdav_Integration_Enabled'); + }, + action() { + const [, message] = this._arguments; + const [attachment] = message.attachments; + const { file } = message; + const url = RocketChat.getURL(attachment.title_link, { full: true }); + modal.open({ + data: { + message, + attachment, + file, + url, + }, + title: t('Save_To_Webdav'), + content: 'selectWebdavAccount', + showCancelButton: true, + showConfirmButton: false, + closeOnCancel: true, + html: true, + }); + }, + order: 100, + group: 'menu', + }); +}); diff --git a/packages/rocketchat-webdav/client/addWebdavAccount.html b/packages/rocketchat-webdav/client/addWebdavAccount.html new file mode 100644 index 000000000000..567a1ba1dbda --- /dev/null +++ b/packages/rocketchat-webdav/client/addWebdavAccount.html @@ -0,0 +1,43 @@ + diff --git a/packages/rocketchat-webdav/client/addWebdavAccount.js b/packages/rocketchat-webdav/client/addWebdavAccount.js new file mode 100644 index 000000000000..b066380f36cb --- /dev/null +++ b/packages/rocketchat-webdav/client/addWebdavAccount.js @@ -0,0 +1,70 @@ +import _ from 'underscore'; +import toastr from 'toastr'; + +Template.addWebdavAccount.helpers({ + btnAddNewServer() { + if (Template.instance().loading.get()) { + return `${ t('Please_wait') }...`; + } + return t('Webdav_add_new_account'); + }, +}); + +Template.addWebdavAccount.events({ + 'submit #add-webdav'(event, instance) { + event.preventDefault(); + const formData = instance.validate(); + instance.loading.set(true); + if (formData) { + Meteor.call('addWebdavAccount', formData, function(error, response) { + if (error) { + return toastr.error(t(error.error)); + } + if (!response.success) { + return toastr.error(t(response.message)); + } + toastr.success(t(response.message)); + modal.close(); + }); + } + instance.loading.set(false); + }, +}); + +const validate = function() { + const form = $(this.firstNode); + const formData = form.serializeArray(); + const validationObj = {}; + + const formObj = formData.reduce((ret, { value, name }) => { + ret[name] = value; + return ret; + }, {}); + + if (!formObj.serverURL) { + validationObj.serverURL = t('Field_required'); + } + if (!formObj.username) { + validationObj.username = t('Field_required'); + } + if (!formObj.pass) { + validationObj.pass = t('Field_required'); + } + + form.find('input.error, select.error').removeClass('error'); + form.find('.input-error').text(''); + if (_.isEmpty(validationObj)) { + return formObj; + } + Object.entries(validationObj).forEach(([key, value]) => { + form.find(`input[name=${ key }], select[name=${ key }]`).addClass('error'); + form.find(`input[name=${ key }]~.input-error, select[name=${ key }]~.input-error`).text(value); + }); + this.loading.set(false); + return false; +}; + +Template.addWebdavAccount.onCreated(function() { + this.loading = new ReactiveVar(false); + this.validate = validate.bind(this); +}); diff --git a/packages/rocketchat-webdav/client/collections/WebdavAccounts.js b/packages/rocketchat-webdav/client/collections/WebdavAccounts.js new file mode 100644 index 000000000000..9844a1169235 --- /dev/null +++ b/packages/rocketchat-webdav/client/collections/WebdavAccounts.js @@ -0,0 +1 @@ +RocketChat.models.WebdavAccounts = new Mongo.Collection('rocketchat_webdav_accounts'); diff --git a/packages/rocketchat-webdav/client/selectWebdavAccount.html b/packages/rocketchat-webdav/client/selectWebdavAccount.html new file mode 100644 index 000000000000..69b58eb2d909 --- /dev/null +++ b/packages/rocketchat-webdav/client/selectWebdavAccount.html @@ -0,0 +1,13 @@ + diff --git a/packages/rocketchat-webdav/client/selectWebdavAccount.js b/packages/rocketchat-webdav/client/selectWebdavAccount.js new file mode 100644 index 000000000000..bb4fcd979948 --- /dev/null +++ b/packages/rocketchat-webdav/client/selectWebdavAccount.js @@ -0,0 +1,39 @@ +/* global */ +import toastr from 'toastr'; + +Template.selectWebdavAccount.helpers({ + webdavAccounts() { + return RocketChat.models.WebdavAccounts.find().fetch(); + }, + usernamePlusServer(account) { + return account.name || `${ account.username }@${ account.server_url.replace(/^https?\:\/\//i, '') }`; + }, +}); +Template.selectWebdavAccount.events({ + 'click .webdav-account'() { + modal.close(); + const accountId = this._id; + const { url } = Template.instance().data; + const name = Template.instance().data.attachment.title; + + const fileRequest = new XMLHttpRequest(); + fileRequest.open('GET', url, true); + fileRequest.responseType = 'arraybuffer'; + fileRequest.onload = function() { + const arrayBuffer = fileRequest.response; + if (arrayBuffer) { + const fileData = new Uint8Array(arrayBuffer); + Meteor.call('uploadFileToWebdav', accountId, fileData, name, (error, response) => { + if (error) { + return toastr.error(t(error.error)); + } + if (!response.success) { + return toastr.error(t(response.message)); + } + return toastr.success(t('File_uploaded')); + }); + } + }; + fileRequest.send(null); + }, +}); diff --git a/packages/rocketchat-webdav/client/webdavFilePicker.html b/packages/rocketchat-webdav/client/webdavFilePicker.html new file mode 100644 index 000000000000..78507b90b555 --- /dev/null +++ b/packages/rocketchat-webdav/client/webdavFilePicker.html @@ -0,0 +1,29 @@ + diff --git a/packages/rocketchat-webdav/client/webdavFilePicker.js b/packages/rocketchat-webdav/client/webdavFilePicker.js new file mode 100644 index 000000000000..34da1203b8bf --- /dev/null +++ b/packages/rocketchat-webdav/client/webdavFilePicker.js @@ -0,0 +1,196 @@ +/* global fileUploadHandler, Handlebars */ +import _ from 'underscore'; +import toastr from 'toastr'; +import { Session } from 'meteor/session'; +import { call } from 'meteor/rocketchat:lib'; +Template.webdavFilePicker.rendered = async function() { + const { accountId } = this.data; + Session.set('webdavCurrentFolder', '/'); + const response = await call('getWebdavFileList', accountId, '/'); + if (!response.success) { + modal.close(); + return toastr.error(t(response.message)); + } + Session.set('webdavNodes', response.data); +}; +Template.webdavFilePicker.destroyed = function() { + Session.set('webdavNodes', []); +}; +Template.webdavFilePicker.helpers({ + iconType() { + // add icon for different types + let icon = 'file-generic'; + let type = ''; + + let extension = this.basename.split('.').pop(); + if (extension === this.basename) { + extension = ''; + } + + if (this.type === 'directory') { + icon = 'folder'; + type = 'directory'; + } else if (this.mime.match(/application\/pdf/)) { + icon = 'file-pdf'; + type = 'ppt'; + } else if (['application/vnd.oasis.opendocument.text', 'application/vnd.oasis.opendocument.presentation'].includes(this.mime)) { + icon = 'file-document'; + type = 'document'; + } else if (['application/vnd.ms-excel', 'application/vnd.oasis.opendocument.spreadsheet', + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'].includes(this.mime)) { + icon = 'file-sheets'; + type = 'sheets'; + } else if (['application/vnd.ms-powerpoint', 'application/vnd.oasis.opendocument.presentation'].includes(this.mime)) { + icon = 'file-sheets'; + type = 'ppt'; + } + return { icon, type, extension }; + }, + webdavNodes() { + return Session.get('webdavNodes'); + }, + webdavCurrentFolder() { + return Session.get('webdavCurrentFolder'); + }, +}); +Template.webdavFilePicker.events({ + async 'click #webdav-go-back'() { + const { accountId } = Template.instance().data; + let currentFolder = Session.get('webdavCurrentFolder'); + + // determine parent directory to go back + let parentFolder = '/'; + if (currentFolder && currentFolder !== '/') { + if (currentFolder[currentFolder.length - 1] === '/') { + currentFolder = currentFolder.slice(0, -1); + } + parentFolder = currentFolder.substr(0, currentFolder.lastIndexOf('/') + 1); + } + Session.set('webdavCurrentFolder', parentFolder); + Session.set('webdavNodes', []); + const response = await call('getWebdavFileList', accountId, parentFolder); + if (!response.success) { + return toastr.error(t(response.message)); + } + Session.set('webdavNodes', response.data); + }, + async 'click .webdav_directory'() { + const { accountId } = Template.instance().data; + Session.set('webdavCurrentFolder', this.filename); + Session.set('webdavNodes', []); + const response = await call('getWebdavFileList', accountId, this.filename); + if (!response.success) { + modal.close(); + return toastr.error(t(response.message)); + } + Session.set('webdavNodes', response.data); + }, + async 'click .webdav_file'() { + const roomId = Session.get('openedRoom'); + const { accountId } = Template.instance().data; + const file = this; + const response = await call('getFileFromWebdav', accountId, file); + + if (!response.success) { + modal.close(); + return toastr.error(t('Failed_to_get_webdav_file')); + } + const blob = new Blob([response.data], { type: response.type }); + // converting to file object + blob.lastModified = file.lastmod; + blob.name = file.basename; + const text = ` +
+
+ +
+
+ +
+
`; + + return modal.open({ + title: t('Upload_file_question'), + text, + showCancelButton: true, + closeOnConfirm: false, + closeOnCancel: false, + confirmButtonText: t('Send'), + cancelButtonText: t('Cancel'), + html: true, + onRendered: () => $('#file-name').focus(), + }, function(isConfirm) { + const record = { + name: document.getElementById('file-name').value || blob.name, + size: blob.size, + type: blob.type, + rid: roomId, + description: document.getElementById('file-description').value, + }; + modal.close(); + + if (!isConfirm) { + return; + } + + const upload = fileUploadHandler('Uploads', record, blob); + + let uploading = Session.get('uploading') || []; + uploading.push({ + id: upload.id, + name: upload.getFileName(), + percentage: 0, + }); + + Session.set('uploading', uploading); + + upload.onProgress = function(progress) { + uploading = Session.get('uploading'); + + const item = _.findWhere(uploading, { id: upload.id }); + if (item != null) { + item.percentage = Math.round(progress * 100) || 0; + return Session.set('uploading', uploading); + } + }; + + upload.start(function(error, file, storage) { + if (error) { + let uploading = Session.get('uploading'); + if (!Array.isArray(uploading)) { + uploading = []; + } + + const item = _.findWhere(uploading, { id: upload.id }); + + if (_.isObject(item)) { + item.error = error.message; + item.percentage = 0; + } else { + uploading.push({ + error: error.error, + percentage: 0, + }); + } + + Session.set('uploading', uploading); + return; + } + + if (file) { + Meteor.call('sendFileMessage', roomId, storage, file, () => { + Meteor.setTimeout(() => { + const uploading = Session.get('uploading'); + if (uploading !== null) { + const item = _.findWhere(uploading, { + id: upload.id, + }); + return Session.set('uploading', _.without(uploading, item)); + } + }, 2000); + }); + } + }); + }); + }, +}); diff --git a/packages/rocketchat-webdav/package.js b/packages/rocketchat-webdav/package.js new file mode 100644 index 000000000000..853358c5720b --- /dev/null +++ b/packages/rocketchat-webdav/package.js @@ -0,0 +1,42 @@ +Package.describe({ + name: 'rocketchat:webdav', + version: '0.0.1', + + summary: 'Package for RocketChat users to interact with WebDAV servers.', + + git: '', + + documentation: 'README.md', +}); + +Package.onUse(function(api) { + api.use('ecmascript'); + api.use('mongo'); + api.use('templating'); + api.use('less'); + api.use('rocketchat:lib'); + api.use('rocketchat:api'); + api.use('rocketchat:grant'); + + api.addFiles('client/actionButton.js', 'client'); + api.addFiles('client/addWebdavAccount.html', 'client'); + api.addFiles('client/addWebdavAccount.js', 'client'); + api.addFiles('client/webdavFilePicker.html', 'client'); + api.addFiles('client/webdavFilePicker.js', 'client'); + api.addFiles('client/selectWebdavAccount.html', 'client'); + api.addFiles('client/selectWebdavAccount.js', 'client'); + + api.addFiles('client/collections/WebdavAccounts.js', 'client'); + + api.addFiles('server/methods/addWebdavAccount.js', 'server'); + api.addFiles('server/methods/removeWebdavAccount.js', 'server'); + api.addFiles('server/methods/getWebdavFileList.js', 'server'); + api.addFiles('server/methods/getFileFromWebdav.js', 'server'); + api.addFiles('server/methods/uploadFileToWebdav.js', 'server'); + api.addFiles('server/models/WebdavAccounts.js', 'server'); + api.addFiles('server/publications/webdavAccounts.js', 'server'); + + api.addFiles('startup/messageBoxActions.js', 'client'); + api.addFiles('startup/subscription.js', 'client'); + api.addFiles('startup/settings.js', 'server'); +}); diff --git a/packages/rocketchat-webdav/server/methods/addWebdavAccount.js b/packages/rocketchat-webdav/server/methods/addWebdavAccount.js new file mode 100644 index 000000000000..b4f7a3af515e --- /dev/null +++ b/packages/rocketchat-webdav/server/methods/addWebdavAccount.js @@ -0,0 +1,49 @@ +import Webdav from 'webdav'; + +Meteor.methods({ + async addWebdavAccount(formData) { + + const userId = Meteor.userId(); + + if (!userId) { + throw new Meteor.Error('error-invalid-user', 'Invalid User', { method: 'addWebdavAccount' }); + } + + if (!RocketChat.settings.get('Webdav_Integration_Enabled')) { + throw new Meteor.Error('error-not-allowed', 'WebDAV Integration Not Allowed', { method: 'addWebdavAccount' }); + } + + check(formData, Match.ObjectIncluding({ + serverURL: String, + username: String, + pass: String, + })); + + const client = new Webdav( + formData.serverURL, + formData.username, + formData.pass + ); + + try { + await client.stat('/'); + } catch (error) { + return { success: false, message: 'could-not-access-webdav', error }; + } + + const accountData = { + user_id: userId, + server_url: formData.serverURL, + username: formData.username, + password: formData.pass, + name: formData.name, + }; + try { + RocketChat.models.WebdavAccounts.insert(accountData); + return { success: true, message: 'webdav-account-saved' }; + } catch (error) { + return { success: false, message: error.code === 11000 ? 'duplicated-account' : 'unknown-write-error', error }; + } + + }, +}); diff --git a/packages/rocketchat-webdav/server/methods/getFileFromWebdav.js b/packages/rocketchat-webdav/server/methods/getFileFromWebdav.js new file mode 100644 index 000000000000..3ad57fce1d3a --- /dev/null +++ b/packages/rocketchat-webdav/server/methods/getFileFromWebdav.js @@ -0,0 +1,29 @@ +import Webdav from 'webdav'; + +Meteor.methods({ + async getFileFromWebdav(accountId, file) { + if (!Meteor.userId()) { + throw new Meteor.Error('error-invalid-user', 'Invalid User', { method: 'addNewWebdavAccount' }); + } + if (!RocketChat.settings.get('Webdav_Integration_Enabled')) { + throw new Meteor.Error('error-not-allowed', 'WebDAV Integration Not Allowed', { method: 'addNewWebdavAccount' }); + } + + const account = RocketChat.models.WebdavAccounts.findOne({ _id: accountId, user_id: Meteor.userId() }); + if (!account) { + throw new Meteor.Error('error-invalid-account', 'Invalid WebDAV Account', { method: 'addNewWebdavAccount' }); + } + const client = new Webdav( + account.server_url, + account.username, + account.password + ); + try { + const fileContent = await client.getFileContents(file.filename); + const data = new Uint8Array(fileContent); + return { success: true, data }; + } catch (error) { + return { success: false, data: error }; + } + }, +}); diff --git a/packages/rocketchat-webdav/server/methods/getWebdavFileList.js b/packages/rocketchat-webdav/server/methods/getWebdavFileList.js new file mode 100644 index 000000000000..93c3dcea0d63 --- /dev/null +++ b/packages/rocketchat-webdav/server/methods/getWebdavFileList.js @@ -0,0 +1,30 @@ +import Webdav from 'webdav'; + +Meteor.methods({ + async getWebdavFileList(accountId, path) { + if (!Meteor.userId()) { + throw new Meteor.Error('error-invalid-user', 'Invalid User', { method: 'addNewWebdavAccount' }); + } + + if (!RocketChat.settings.get('Webdav_Integration_Enabled')) { + throw new Meteor.Error('error-not-allowed', 'WebDAV Integration Not Allowed', { method: 'addNewWebdavAccount' }); + } + + const account = RocketChat.models.WebdavAccounts.findOne({ _id: accountId, user_id: Meteor.userId() }); + if (!account) { + throw new Meteor.Error('error-invalid-account', 'Invalid WebDAV Account', { method: 'addNewWebdavAccount' }); + } + + const client = new Webdav( + account.server_url, + account.username, + account.password + ); + try { + const data = await client.getDirectoryContents(path); + return { success: true, data }; + } catch (error) { + return { success: false, message: 'could-not-access-webdav', error }; + } + }, +}); diff --git a/packages/rocketchat-webdav/server/methods/removeWebdavAccount.js b/packages/rocketchat-webdav/server/methods/removeWebdavAccount.js new file mode 100644 index 000000000000..4cc461de072c --- /dev/null +++ b/packages/rocketchat-webdav/server/methods/removeWebdavAccount.js @@ -0,0 +1,13 @@ +Meteor.methods({ + removeWebdavAccount(accountId) { + if (!Meteor.userId()) { + throw new Meteor.Error('error-invalid-user', 'Invalid User', { method: 'removeWebdavAccount' }); + } + // if (!RocketChat.settings.get('Webdav_Integration_Enabled')) { + // throw new Meteor.Error('error-not-allowed', 'WebDAV Integration Not Allowed', {method: 'removeWebdavAccount'}); + // } + check(accountId, String); + + return RocketChat.models.WebdavAccounts.removeByUserAndId(accountId, Meteor.userId()); + }, +}); diff --git a/packages/rocketchat-webdav/server/methods/uploadFileToWebdav.js b/packages/rocketchat-webdav/server/methods/uploadFileToWebdav.js new file mode 100644 index 000000000000..394cbc3bcbaa --- /dev/null +++ b/packages/rocketchat-webdav/server/methods/uploadFileToWebdav.js @@ -0,0 +1,64 @@ +import Future from 'fibers/future'; +import Webdav from 'webdav'; +import stream from 'stream'; + +Meteor.methods({ + async uploadFileToWebdav(accountId, fileData, name) { + const uploadFolder = 'Rocket.Chat Uploads/'; + if (!Meteor.userId()) { + throw new Meteor.Error('error-invalid-user', 'Invalid User', { method: 'uploadFileToWebdav' }); + } + if (!RocketChat.settings.get('Webdav_Integration_Enabled')) { + throw new Meteor.Error('error-not-allowed', 'WebDAV Integration Not Allowed', { method: 'uploadFileToWebdav' }); + } + + const account = RocketChat.models.WebdavAccounts.findOne({ _id: accountId }); + if (!account) { + throw new Meteor.Error('error-invalid-account', 'Invalid WebDAV Account', { method: 'uploadFileToWebdav' }); + } + const client = new Webdav( + account.server_url, + account.username, + account.password + ); + const future = new Future(); + + // create buffer stream from file data + let bufferStream = new stream.PassThrough(); + if (fileData) { + bufferStream.end(fileData); + } else { + bufferStream = null; + } + + // create a write stream on remote webdav server + const writeStream = client.createWriteStream(`${ uploadFolder }/${ name }`); + writeStream.on('end', function() { + future.return({ success: true }); + }); + writeStream.on('error', function() { + future.return({ success: false, message: 'FileUpload_Error' }); + }); + + await client.stat(uploadFolder).then(function() { + bufferStream.pipe(writeStream); + }).catch(function(err) { + if (err.status === 404) { + client.createDirectory(uploadFolder).then(function() { + bufferStream.pipe(writeStream); + }).catch(function() { + if (err.status === 404) { + future.return({ success: false, message: 'webdav-server-not-found' }); + } else { + future.return({ success: false, message: 'FileUpload_Error' }); + } + }); + } else if (err.status === 401) { + future.return({ success: false, message: 'error-invalid-account' }); + } else { + future.return({ success: false, message: 'FileUpload_Error' }); + } + }); + return future.wait(); + }, +}); diff --git a/packages/rocketchat-webdav/server/models/WebdavAccounts.js b/packages/rocketchat-webdav/server/models/WebdavAccounts.js new file mode 100644 index 000000000000..0c4d5dba7bfa --- /dev/null +++ b/packages/rocketchat-webdav/server/models/WebdavAccounts.js @@ -0,0 +1,26 @@ +/** + * Webdav Accounts model + */ +class WebdavAccounts extends RocketChat.models._Base { + constructor() { + super('webdav_accounts'); + + this.tryEnsureIndex({ user_id: 1, server_url: 1, username: 1, name: 1 }, { unique: 1 }); + } + + findWithUserId(user_id, options) { + const query = { user_id }; + return this.find(query, options); + } + + removeByUserAndId(_id, user_id) { + return this.remove({ _id, user_id }); + } + + removeById(_id) { + return this.remove({ _id }); + } + +} + +RocketChat.models.WebdavAccounts = new WebdavAccounts(); diff --git a/packages/rocketchat-webdav/server/publications/webdavAccounts.js b/packages/rocketchat-webdav/server/publications/webdavAccounts.js new file mode 100644 index 000000000000..40723b452c64 --- /dev/null +++ b/packages/rocketchat-webdav/server/publications/webdavAccounts.js @@ -0,0 +1,14 @@ +Meteor.publish('webdavAccounts', function() { + if (!this.userId) { + return this.error(new Meteor.Error('error-not-authorized', 'Not authorized', { publish: 'webdavAccounts' })); + } + + return RocketChat.models.WebdavAccounts.findWithUserId(this.userId, { + fields: { + _id:1, + username: 1, + server_url: 1, + name: 1, + }, + }); +}); diff --git a/packages/rocketchat-webdav/startup/messageBoxActions.js b/packages/rocketchat-webdav/startup/messageBoxActions.js new file mode 100644 index 000000000000..75dc81dc280f --- /dev/null +++ b/packages/rocketchat-webdav/startup/messageBoxActions.js @@ -0,0 +1,55 @@ +/* globals modal, RocketChat */ +RocketChat.messageBox.actions.add('WebDAV', 'Add Server', { + id: 'add-webdav', + icon: 'plus', + condition: () => RocketChat.settings.get('Webdav_Integration_Enabled'), + action() { + modal.open({ + title: t('Webdav_add_new_account'), + content: 'addWebdavAccount', + showCancelButton: false, + showConfirmButton: false, + showFooter: false, + closeOnCancel: true, + html: true, + }); + }, +}); + +Meteor.startup(function() { + Tracker.autorun(() => { + const accounts = RocketChat.models.WebdavAccounts.find(); + + + if (accounts.count() === 0) { + return RocketChat.messageBox.actions.remove(/webdav-upload-/ig); + } + + accounts.forEach((account) => { + const name = account.name || `${ account.username }@${ account.server_url.replace(/^https?\:\/\//i, '') }`; + const title = t('Upload_From', { + name, + }); + RocketChat.messageBox.actions.add('WebDAV', name, { + id: `webdav-upload-${ account._id.toLowerCase() }`, + icon: 'cloud-plus', + condition: () => RocketChat.settings.get('Webdav_Integration_Enabled'), + action() { + modal.open({ + data: { + name, + accountId: account._id, + }, + title, + content: 'webdavFilePicker', + showCancelButton: true, + showFooter: false, + showConfirmButton: false, + closeOnCancel: true, + html: true, + }); + }, + }); + }); + }); +}); diff --git a/packages/rocketchat-webdav/startup/settings.js b/packages/rocketchat-webdav/startup/settings.js new file mode 100644 index 000000000000..feac17852f4d --- /dev/null +++ b/packages/rocketchat-webdav/startup/settings.js @@ -0,0 +1,6 @@ +RocketChat.settings.addGroup('Webdav Integration', function() { + this.add('Webdav_Integration_Enabled', false, { + type: 'boolean', + public: true, + }); +}); diff --git a/packages/rocketchat-webdav/startup/subscription.js b/packages/rocketchat-webdav/startup/subscription.js new file mode 100644 index 000000000000..0f6247066b38 --- /dev/null +++ b/packages/rocketchat-webdav/startup/subscription.js @@ -0,0 +1,6 @@ +import { Tracker } from 'meteor/tracker'; +Tracker.autorun(() => { + if (Meteor.userId()) { + Meteor.subscribe('webdavAccounts'); + } +});