diff --git a/.docker/Dockerfile.rhel b/.docker/Dockerfile.rhel index 858fab195fcf..33c8647d81c9 100644 --- a/.docker/Dockerfile.rhel +++ b/.docker/Dockerfile.rhel @@ -1,6 +1,6 @@ FROM registry.access.redhat.com/rhscl/nodejs-8-rhel7 -ENV RC_VERSION 0.73.1 +ENV RC_VERSION 0.73.2 MAINTAINER buildmaster@rocket.chat diff --git a/.github/history.json b/.github/history.json index 28128902518c..e230a06e7817 100644 --- a/.github/history.json +++ b/.github/history.json @@ -24507,6 +24507,30 @@ ] } ] + }, + "0.73.2": { + "node_version": "8.11.4", + "npm_version": "6.4.1", + "mongo_versions": [ + "3.2", + "3.4", + "3.6", + "4.0" + ], + "pull_requests": [ + { + "pr": "13013", + "title": "[NEW] Cloud Integration", + "userLogin": "graywolf336", + "milestone": "0.73.2", + "contributors": [ + "graywolf336", + "geekgonecrazy", + "web-flow", + "sampaiodiego" + ] + } + ] } } } \ No newline at end of file diff --git a/.meteor/packages b/.meteor/packages index b77a8aa4096c..8b0d70d40e13 100644 --- a/.meteor/packages +++ b/.meteor/packages @@ -51,6 +51,7 @@ rocketchat:bot-helpers rocketchat:cas rocketchat:channel-settings rocketchat:channel-settings-mail-messages +rocketchat:cloud rocketchat:colors rocketchat:crowd rocketchat:custom-oauth diff --git a/.meteor/versions b/.meteor/versions index 447e1583da9c..9f4d57467dda 100644 --- a/.meteor/versions +++ b/.meteor/versions @@ -144,6 +144,7 @@ rocketchat:bot-helpers@0.0.1 rocketchat:cas@1.0.0 rocketchat:channel-settings@0.0.1 rocketchat:channel-settings-mail-messages@0.0.1 +rocketchat:cloud@0.0.1 rocketchat:colors@0.0.1 rocketchat:cors@0.0.1 rocketchat:crowd@1.0.0 diff --git a/.sandstorm/sandstorm-pkgdef.capnp b/.sandstorm/sandstorm-pkgdef.capnp index ca5d7e281bff..56710c7202f6 100644 --- a/.sandstorm/sandstorm-pkgdef.capnp +++ b/.sandstorm/sandstorm-pkgdef.capnp @@ -19,9 +19,9 @@ const pkgdef :Spk.PackageDefinition = ( appTitle = (defaultText = "Rocket.Chat"), - appVersion = 122, # Increment this for every release. + appVersion = 123, # Increment this for every release. - appMarketingVersion = (defaultText = "0.73.1"), + appMarketingVersion = (defaultText = "0.73.2"), # Human-readable representation of appVersion. Should match the way you # identify versions of your app in documentation and marketing. diff --git a/.travis/snap.sh b/.travis/snap.sh index 482f73d3d650..fa86aaa9065e 100755 --- a/.travis/snap.sh +++ b/.travis/snap.sh @@ -17,7 +17,7 @@ elif [[ $TRAVIS_TAG ]]; then RC_VERSION=$TRAVIS_TAG else CHANNEL=edge - RC_VERSION=0.73.1 + RC_VERSION=0.73.2 fi echo "Preparing to trigger a snap release for $CHANNEL channel" diff --git a/HISTORY.md b/HISTORY.md index 8d8f379d1306..70e3706d6402 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,4 +1,22 @@ +# 0.73.2 +`2019-01-07 ยท 1 ๐ŸŽ‰ ยท 3 ๐Ÿ‘ฉโ€๐Ÿ’ป๐Ÿ‘จโ€๐Ÿ’ป` + +### Engine versions +- Node: `8.11.4` +- NPM: `6.4.1` +- MongoDB: `3.2, 3.4, 3.6, 4.0` + +### ๐ŸŽ‰ New features + +- Cloud Integration ([#13013](https://github.com/RocketChat/Rocket.Chat/pull/13013)) + +### ๐Ÿ‘ฉโ€๐Ÿ’ป๐Ÿ‘จโ€๐Ÿ’ป Core Team ๐Ÿค“ + +- [@geekgonecrazy](https://github.com/geekgonecrazy) +- [@graywolf336](https://github.com/graywolf336) +- [@sampaiodiego](https://github.com/sampaiodiego) + # 0.73.1 `2018-12-28 ยท 1 ๐Ÿ› ยท 2 ๐Ÿ” ยท 2 ๐Ÿ‘ฉโ€๐Ÿ’ป๐Ÿ‘จโ€๐Ÿ’ป` diff --git a/package.json b/package.json index 4b5ee2a5260a..9ce7fe585822 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "Rocket.Chat", "description": "The Ultimate Open Source WebChat Platform", - "version": "0.73.1", + "version": "0.73.2", "author": { "name": "Rocket.Chat", "url": "https://rocket.chat/" diff --git a/packages/rocketchat-cloud/client/admin/callback.html b/packages/rocketchat-cloud/client/admin/callback.html new file mode 100644 index 000000000000..5d6c9c432f4c --- /dev/null +++ b/packages/rocketchat-cloud/client/admin/callback.html @@ -0,0 +1,16 @@ + diff --git a/packages/rocketchat-cloud/client/admin/callback.js b/packages/rocketchat-cloud/client/admin/callback.js new file mode 100644 index 000000000000..adf4bac1d211 --- /dev/null +++ b/packages/rocketchat-cloud/client/admin/callback.js @@ -0,0 +1,37 @@ +import './callback.html'; + +import { Meteor } from 'meteor/meteor'; +import { ReactiveVar } from 'meteor/reactive-var'; +import { Template } from 'meteor/templating'; + +import { FlowRouter } from 'meteor/kadira:flow-router'; + +import queryString from 'query-string'; + +Template.cloudCallback.onCreated(function() { + const instance = this; + + instance.loading = new ReactiveVar(true); + instance.callbackError = new ReactiveVar({ error: false }); + + const params = queryString.parse(location.search); + + if (params.error_code) { + instance.callbackError.set({ error: true, errorCode: params.error_code }); + } else { + Meteor.call('cloud:finishOAuthAuthorization', params.code, params.state, (error) => { + if (error) { + console.warn('cloud:finishOAuthAuthorization', error); + return; + } + + FlowRouter.go('/admin/cloud'); + }); + } +}); + +Template.cloudCallback.helpers({ + callbackError() { + return Template.instance().callbackError.get(); + }, +}); diff --git a/packages/rocketchat-cloud/client/admin/cloud.html b/packages/rocketchat-cloud/client/admin/cloud.html new file mode 100644 index 000000000000..c09e1281a77e --- /dev/null +++ b/packages/rocketchat-cloud/client/admin/cloud.html @@ -0,0 +1,83 @@ + diff --git a/packages/rocketchat-cloud/client/admin/cloud.js b/packages/rocketchat-cloud/client/admin/cloud.js new file mode 100644 index 000000000000..e68f0e890532 --- /dev/null +++ b/packages/rocketchat-cloud/client/admin/cloud.js @@ -0,0 +1,93 @@ +import './cloud.html'; + +import { Meteor } from 'meteor/meteor'; +import { ReactiveVar } from 'meteor/reactive-var'; +import { Template } from 'meteor/templating'; +import { t } from 'meteor/rocketchat:utils'; + +import queryString from 'query-string'; +import toastr from 'toastr'; + +Template.cloud.onCreated(function() { + const instance = this; + instance.info = new ReactiveVar(); + instance.loading = new ReactiveVar(true); + + instance.loadRegStatus = function _loadRegStatus() { + Meteor.call('cloud:checkRegisterStatus', (error, info) => { + if (error) { + console.warn('cloud:checkRegisterStatus', error); + return; + } + + instance.info.set(info); + instance.loading.set(false); + }); + }; + + instance.connectWorkspace = function _connectWorkspace(token) { + Meteor.call('cloud:connectWorkspace', token, (error, success) => { + if (error) { + toastr.error(error); + instance.loadRegStatus(); + return; + } + + if (!success) { + toastr.error('Invalid token'); + instance.loadRegStatus(); + return; + } + + toastr.success(t('Connected')); + + instance.loadRegStatus(); + }); + }; + + const params = queryString.parse(location.search); + + if (params.token) { + instance.connectWorkspace(); + } else { + instance.loadRegStatus(); + } +}); + +Template.cloud.helpers({ + info() { + return Template.instance().info.get(); + }, +}); + +Template.cloud.events({ + 'click .update-email-btn'() { + const val = $('input[name=cloudEmail]').val(); + + Meteor.call('cloud:updateEmail', val, (error) => { + if (error) { + console.warn(error); + return; + } + + toastr.success(t('Saved')); + }); + }, + + 'click .login-btn'() { + Meteor.call('cloud:getOAuthAuthorizationUrl', (error, url) => { + if (error) { + console.warn(error); + return; + } + + window.location.href = url; + }); + }, + + 'click .connect-btn'(e, i) { + const token = $('input[name=cloudToken]').val(); + + i.connectWorkspace(token); + }, +}); diff --git a/packages/rocketchat-cloud/client/index.js b/packages/rocketchat-cloud/client/index.js new file mode 100644 index 000000000000..e5dddf8944bf --- /dev/null +++ b/packages/rocketchat-cloud/client/index.js @@ -0,0 +1,28 @@ +import './admin/callback'; +import './admin/cloud'; + +import { BlazeLayout } from 'meteor/kadira:blaze-layout'; +import { FlowRouter } from 'meteor/kadira:flow-router'; + +FlowRouter.route('/admin/cloud', { + name: 'cloud-config', + action() { + BlazeLayout.render('main', { center: 'cloud', old: true }); + }, +}); + +FlowRouter.route('/admin/cloud/oauth-callback', { + name: 'cloud-oauth-callback', + action() { + BlazeLayout.render('main', { center: 'cloudCallback', old: true }); + }, +}); + +RocketChat.AdminBox.addOption({ + icon: 'cloud-plus', + href: 'admin/cloud', + i18nLabel: 'Cloud', + permissionGranted() { + return RocketChat.authz.hasAtLeastOnePermission(['manage-cloud']); + }, +}); diff --git a/packages/rocketchat-cloud/package.js b/packages/rocketchat-cloud/package.js new file mode 100644 index 000000000000..52480e9e66d6 --- /dev/null +++ b/packages/rocketchat-cloud/package.js @@ -0,0 +1,17 @@ +Package.describe({ + name: 'rocketchat:cloud', + version: '0.0.1', + summary: 'Package which interacts with the Rocket.Chat Cloud offerings.', + git: '', +}); + +Package.onUse(function(api) { + api.use([ + 'ecmascript', + 'rocketchat:lib', + 'templating', + ]); + + api.mainModule('client/index.js', 'client'); + api.mainModule('server/index.js', 'server'); +}); diff --git a/packages/rocketchat-cloud/server/functions/connectWorkspace.js b/packages/rocketchat-cloud/server/functions/connectWorkspace.js new file mode 100644 index 000000000000..f34cecd22449 --- /dev/null +++ b/packages/rocketchat-cloud/server/functions/connectWorkspace.js @@ -0,0 +1,70 @@ +import querystring from 'querystring'; +import { HTTP } from 'meteor/http'; + +import { getRedirectUri } from './getRedirectUri'; +import { retrieveRegistrationStatus } from './retrieveRegistrationStatus'; + +export function connectWorkspace(token) { + const { registeredWithWizard } = retrieveRegistrationStatus(); + if (!registeredWithWizard) { + return false; + } + + const redirectUri = getRedirectUri(); + + const regInfo = { + email: RocketChat.settings.get('Organization_Email'), + client_name: RocketChat.settings.get('Site_Name'), + redirect_uris: [redirectUri], + }; + + const cloudUrl = RocketChat.settings.get('Cloud_Url'); + let result; + try { + result = HTTP.post(`${ cloudUrl }/api/oauth/clients`, { + headers: { + Authorization: `Bearer ${ token }`, + }, + data: regInfo, + }); + } catch (e) { + return false; + } + + const { data } = result; + + if (!data) { + return false; + } + + RocketChat.models.Settings.updateValueById('Cloud_Workspace_Id', data.workspaceId); + RocketChat.models.Settings.updateValueById('Cloud_Workspace_Name', data.client_name); + RocketChat.models.Settings.updateValueById('Cloud_Workspace_Client_Id', data.client_id); + RocketChat.models.Settings.updateValueById('Cloud_Workspace_Client_Secret', data.client_secret); + RocketChat.models.Settings.updateValueById('Cloud_Workspace_Client_Secret_Expires_At', data.client_secret_expires_at); + RocketChat.models.Settings.updateValueById('Cloud_Workspace_Registration_Client_Uri', data.registration_client_uri); + + // Now that we have the client id and secret, let's get the access token + let authTokenResult; + try { + authTokenResult = HTTP.post(`${ cloudUrl }/api/oauth/token`, { + data: {}, + query: querystring.stringify({ + client_id: data.client_id, + client_secret: data.client_secret, + grant_type: 'client_credentials', + redirect_uri: redirectUri, + }), + }); + } catch (e) { + return false; + } + + const expiresAt = new Date(); + expiresAt.setSeconds(expiresAt.getSeconds() + authTokenResult.data.expires_in); + + RocketChat.models.Settings.updateValueById('Cloud_Workspace_Access_Token', authTokenResult.data.access_token); + RocketChat.models.Settings.updateValueById('Cloud_Workspace_Access_Token_Expires_At', expiresAt); + + return true; +} diff --git a/packages/rocketchat-cloud/server/functions/finishOAuthAuthorization.js b/packages/rocketchat-cloud/server/functions/finishOAuthAuthorization.js new file mode 100644 index 000000000000..80e2a67e17ea --- /dev/null +++ b/packages/rocketchat-cloud/server/functions/finishOAuthAuthorization.js @@ -0,0 +1,50 @@ +import querystring from 'querystring'; + +import { Meteor } from 'meteor/meteor'; +import { HTTP } from 'meteor/http'; + +import { getRedirectUri } from './getRedirectUri'; + +export function finishOAuthAuthorization(code, state) { + if (RocketChat.settings.get('Cloud_Workspace_Registration_State') !== state) { + throw new Meteor.Error('error-invalid-state', 'Invalid state provided', { method: 'cloud:finishOAuthAuthorization' }); + } + + const cloudUrl = RocketChat.settings.get('Cloud_Url'); + const clientId = RocketChat.settings.get('Cloud_Workspace_Client_Id'); + const clientSecret = RocketChat.settings.get('Cloud_Workspace_Client_Secret'); + + let result; + try { + result = HTTP.post(`${ cloudUrl }/api/oauth/token`, { + data: {}, + query: querystring.stringify({ + client_id: clientId, + client_secret: clientSecret, + grant_type: 'authorization_code', + code, + redirect_uri: getRedirectUri(), + }), + }); + } catch (e) { + return false; + } + + const expiresAt = new Date(); + expiresAt.setSeconds(expiresAt.getSeconds() + result.data.expires_in); + + RocketChat.models.Settings.updateValueById('Cloud_Workspace_Account_Associated', true); + RocketChat.models.Users.update({ _id: Meteor.userId() }, { + $set: { + 'services.cloud': { + accessToken: result.data.access_token, + expiresAt, + scope: result.data.scope, + tokenType: result.data.token_type, + refreshToken: result.data.refresh_token, + }, + }, + }); + + return true; +} diff --git a/packages/rocketchat-cloud/server/functions/getOAuthAuthorizationUrl.js b/packages/rocketchat-cloud/server/functions/getOAuthAuthorizationUrl.js new file mode 100644 index 000000000000..b3edc95090f9 --- /dev/null +++ b/packages/rocketchat-cloud/server/functions/getOAuthAuthorizationUrl.js @@ -0,0 +1,15 @@ +import { Random } from 'meteor/random'; + +import { getRedirectUri } from './getRedirectUri'; + +export function getOAuthAuthorizationUrl() { + const state = Random.id(); + + RocketChat.models.Settings.updateValueById('Cloud_Workspace_Registration_State', state); + + const cloudUrl = RocketChat.settings.get('Cloud_Url'); + const client_id = RocketChat.settings.get('Cloud_Workspace_Client_Id'); + const redirectUri = getRedirectUri(); + + return `${ cloudUrl }/authorize?response_type=code&client_id=${ client_id }&redirect_uri=${ redirectUri }&scope=offline_access&state=${ state }`; +} diff --git a/packages/rocketchat-cloud/server/functions/getRedirectUri.js b/packages/rocketchat-cloud/server/functions/getRedirectUri.js new file mode 100644 index 000000000000..21a219791c9e --- /dev/null +++ b/packages/rocketchat-cloud/server/functions/getRedirectUri.js @@ -0,0 +1,3 @@ +export function getRedirectUri() { + return `${ RocketChat.settings.get('Site_Url') }/admin/cloud/oauth-callback`.replace(/\/\/admin+/g, '/admin'); +} diff --git a/packages/rocketchat-cloud/server/functions/getWorkspaceAccessTokens.js b/packages/rocketchat-cloud/server/functions/getWorkspaceAccessTokens.js new file mode 100644 index 000000000000..d853ce5b6df3 --- /dev/null +++ b/packages/rocketchat-cloud/server/functions/getWorkspaceAccessTokens.js @@ -0,0 +1,50 @@ +import querystring from 'querystring'; +import { HTTP } from 'meteor/http'; + +import { getRedirectUri } from './getRedirectUri'; + +export function getWorkspaceAccessToken() { + if (!RocketChat.settings.get('Register_Server')) { + return ''; + } + + const client_id = RocketChat.settings.get('Cloud_Workspace_Client_Id'); + if (!client_id) { + return ''; + } + + const expires = RocketChat.models.Settings.findOneById('Cloud_Workspace_Access_Token_Expires_At'); + const now = new Date(); + + if (now < expires.value) { + return RocketChat.settings.get('Cloud_Workspace_Access_Token'); + } + + const cloudUrl = RocketChat.settings.get('Cloud_Url'); + const client_secret = RocketChat.settings.get('Cloud_Workspace_Client_Secret'); + const redirectUri = getRedirectUri(); + + let authTokenResult; + try { + authTokenResult = HTTP.post(`${ cloudUrl }/api/oauth/token`, { + data: {}, + query: querystring.stringify({ + client_id, + client_secret, + grant_type: 'client_credentials', + redirect_uri: redirectUri, + }), + }); + } catch (e) { + return ''; + } + + const expiresAt = new Date(); + expiresAt.setSeconds(expiresAt.getSeconds() + authTokenResult.data.expires_in); + + RocketChat.models.Settings.updateValueById('Cloud_Workspace_Access_Token', authTokenResult.data.access_token); + RocketChat.models.Settings.updateValueById('Cloud_Workspace_Access_Token_Expires_At', expiresAt); + + + return authTokenResult.data.access_token; +} diff --git a/packages/rocketchat-cloud/server/functions/getWorkspaceLicense.js b/packages/rocketchat-cloud/server/functions/getWorkspaceLicense.js new file mode 100644 index 000000000000..d9278a65d519 --- /dev/null +++ b/packages/rocketchat-cloud/server/functions/getWorkspaceLicense.js @@ -0,0 +1,34 @@ +import { HTTP } from 'meteor/http'; + +import { getWorkspaceAccessToken } from './getWorkspaceAccessToken'; + +export function getWorkspaceLicense() { + const token = getWorkspaceAccessToken(); + + if (!token) { + return { updated: false, license: '' }; + } + + + let licenseResult; + try { + licenseResult = HTTP.get(`${ RocketChat.settings.get('Cloud_Workspace_Registration_Client_Uri') }/license`, { + headers: { + Authorization: `Bearer ${ token }`, + }, + }); + } catch (e) { + return { updated: false, license: '' }; + } + + const remoteLicense = licenseResult.data; + const currentLicense = RocketChat.settings.get('Cloud_Workspace_License'); + + if (remoteLicense.updatedAt <= currentLicense._updatedAt) { + return { updated: false, license: '' }; + } + + RocketChat.models.Settings.updateValueById('Cloud_Workspace_License', remoteLicense.license); + + return { updated: true, license: remoteLicense.license }; +} diff --git a/packages/rocketchat-cloud/server/functions/retrieveRegistrationStatus.js b/packages/rocketchat-cloud/server/functions/retrieveRegistrationStatus.js new file mode 100644 index 000000000000..dd109853234e --- /dev/null +++ b/packages/rocketchat-cloud/server/functions/retrieveRegistrationStatus.js @@ -0,0 +1,18 @@ +export function retrieveRegistrationStatus() { + const info = { + registeredWithWizard: RocketChat.settings.get('Register_Server'), + workspaceConnected: (RocketChat.settings.get('Cloud_Workspace_Client_Id')) ? true : false, + userAssociated: (RocketChat.settings.get('Cloud_Workspace_Account_Associated')) ? true : false, + token: '', + email: '', + }; + + const firstUser = RocketChat.models.Users.getOldest({ emails: 1 }); + info.email = firstUser && firstUser.emails[0].address; + + if (RocketChat.settings.get('Organization_Email')) { + info.email = RocketChat.settings.get('Organization_Email'); + } + + return info; +} diff --git a/packages/rocketchat-cloud/server/index.js b/packages/rocketchat-cloud/server/index.js new file mode 100644 index 000000000000..c64f14941528 --- /dev/null +++ b/packages/rocketchat-cloud/server/index.js @@ -0,0 +1,11 @@ +import './methods'; +import { getWorkspaceAccessToken } from './functions/getWorkspaceAccessTokens'; + +if (RocketChat.models && RocketChat.models.Permissions) { + RocketChat.models.Permissions.createOrUpdate('manage-cloud', ['admin']); +} + +// Ensure the client/workspace access token is valid +getWorkspaceAccessToken(); + +export { getWorkspaceAccessToken }; diff --git a/packages/rocketchat-cloud/server/methods.js b/packages/rocketchat-cloud/server/methods.js new file mode 100644 index 000000000000..480274bd14ab --- /dev/null +++ b/packages/rocketchat-cloud/server/methods.js @@ -0,0 +1,72 @@ +import { Meteor } from 'meteor/meteor'; +import { check } from 'meteor/check'; + +import { retrieveRegistrationStatus } from './functions/retrieveRegistrationStatus'; +import { connectWorkspace } from './functions/connectWorkspace'; +import { getOAuthAuthorizationUrl } from './functions/getOAuthAuthorizationUrl'; +import { finishOAuthAuthorization } from './functions/finishOAuthAuthorization'; + +Meteor.methods({ + 'cloud:checkRegisterStatus'() { + if (!Meteor.userId()) { + throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'cloud:checkRegisterStatus' }); + } + + if (!RocketChat.authz.hasPermission(Meteor.userId(), 'manage-cloud')) { + throw new Meteor.Error('error-not-authorized', 'Not authorized', { method: 'cloud:checkRegisterStatus' }); + } + + return retrieveRegistrationStatus(); + }, + 'cloud:updateEmail'(email) { + check(email, String); + + if (!Meteor.userId()) { + throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'cloud:updateEmail' }); + } + + if (!RocketChat.authz.hasPermission(Meteor.userId(), 'manage-cloud')) { + throw new Meteor.Error('error-not-authorized', 'Not authorized', { method: 'cloud:updateEmail' }); + } + + RocketChat.models.Settings.updateValueById('Organization_Email', email); + }, + 'cloud:connectWorkspace'(token) { + check(token, String); + + if (!Meteor.userId()) { + throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'cloud:connectServer' }); + } + + if (!RocketChat.authz.hasPermission(Meteor.userId(), 'manage-cloud')) { + throw new Meteor.Error('error-not-authorized', 'Not authorized', { method: 'cloud:connectServer' }); + } + + return connectWorkspace(token); + }, + 'cloud:getOAuthAuthorizationUrl'() { + if (!Meteor.userId()) { + throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'cloud:connectServer' }); + } + + if (!RocketChat.authz.hasPermission(Meteor.userId(), 'manage-cloud')) { + throw new Meteor.Error('error-not-authorized', 'Not authorized', { method: 'cloud:connectServer' }); + } + + return getOAuthAuthorizationUrl(); + }, + 'cloud:finishOAuthAuthorization'(code, state) { + check(code, String); + check(state, String); + + if (!Meteor.userId()) { + throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'cloud:finishOAuthAuthorization' }); + } + + if (!RocketChat.authz.hasPermission(Meteor.userId(), 'manage-cloud')) { + throw new Meteor.Error('error-not-authorized', 'Not authorized', { method: 'cloud:connectServer' }); + } + + return finishOAuthAuthorization(code, state); + }, +}); diff --git a/packages/rocketchat-i18n/i18n/en.i18n.json b/packages/rocketchat-i18n/i18n/en.i18n.json index 4c12e0c15b40..5b590d9f54de 100644 --- a/packages/rocketchat-i18n/i18n/en.i18n.json +++ b/packages/rocketchat-i18n/i18n/en.i18n.json @@ -576,6 +576,21 @@ "Closed": "Closed", "Closed_by_visitor": "Closed by visitor", "Closing_chat": "Closing chat", + "Cloud": "Cloud", + "Cloud_connect": "Rocket.Chat Cloud Connect", + "Cloud_what_is_it": "What is this?", + "Cloud_what_is_it_description": "Rocket.Chat Cloud Connect allows you to connect your self-hosted Rocket.Chat Workspace to our Cloud. Doing so enables you to manage your licenses, Billing and Support in Rocket.Chat Cloud.", + "Cloud_workspace_connected_plus_account": "Your workspace is now connected to the Rocket.Chat Cloud and an account is associated.", + "Cloud_workspace_connected_without_account": "Your workspace is now connected to the Rocket.Chat Cloud. If you would like, you can login to the Rocket.Chat Cloud and associate your workspace with your Cloud account.", + "Cloud_login_to_cloud": "Login to Rocket.Chat Cloud", + "Cloud_address_to_send_registration_to": "The address to send your Cloud registration email to.", + "Cloud_update_email": "Update Email", + "Cloud_manually_input_token": "Manually enter the token received from the Cloud Registration Email.", + "Cloud_registration_required": "Registration Required", + "Cloud_registration_required_description": "Looks like during setup you didn't chose to register your workspace.", + "Cloud_registration_requried_link_text": "Click here to register your workspace.", + "Cloud_error_in_authenticating": "Error received while authenticating", + "Cloud_error_code": "Code: ", "Collaborative": "Collaborative", "Collapse_Embedded_Media_By_Default": "Collapse Embedded Media by Default", "color": "Color", @@ -590,6 +605,7 @@ "Computer": "Computer", "Confirm_new_encryption_password": "Confirm new encryption password", "Confirm_password": "Confirm your password", + "Connect": "Connect", "Connection_Closed": "Connection closed", "Connection_Reset": "Connection reset", "Consulting": "Consulting", @@ -2691,6 +2707,7 @@ "to_see_more_details_on_how_to_integrate": "to see more details on how to integrate.", "To_users": "To Users", "Toggle_original_translated": "Toggle original/translated", + "Token": "Token", "Token_Access": "Token Access", "Token_Controlled_Access": "Token Controlled Access", "Token_required": "Token required", @@ -3029,4 +3046,4 @@ "Your_push_was_sent_to_s_devices": "Your push was sent to %s devices", "Your_server_link": "Your server link", "Your_workspace_is_ready": "Your workspace is ready to use ๐ŸŽ‰" -} \ No newline at end of file +} diff --git a/packages/rocketchat-lib/rocketchat.info b/packages/rocketchat-lib/rocketchat.info index e27cf6ae0436..caaefcdd8572 100644 --- a/packages/rocketchat-lib/rocketchat.info +++ b/packages/rocketchat-lib/rocketchat.info @@ -1,3 +1,3 @@ { - "version": "0.73.1" + "version": "0.73.2" } diff --git a/packages/rocketchat-lib/server/startup/settings.js b/packages/rocketchat-lib/server/startup/settings.js index 7cb5f032b9dc..464314fbce11 100644 --- a/packages/rocketchat-lib/server/startup/settings.js +++ b/packages/rocketchat-lib/server/startup/settings.js @@ -2529,6 +2529,134 @@ RocketChat.settings.addGroup('Setup_Wizard', function() { this.add('Allow_Marketing_Emails', true, { type: 'boolean', }); + this.add('Register_Server', true, { + type: 'boolean', + }); + this.add('Organization_Email', '', { + type: 'string', + }); + }); + + this.section('Cloud_Info', function() { + this.add('Cloud_Url', 'https://cloud.rocket.chat', { + type: 'string', + hidden: true, + readonly: true, + enableQuery: { + _id: 'Register_Server', + value: true, + }, + }); + + this.add('Cloud_Workspace_Id', '', { + type: 'string', + hidden: true, + readonly: true, + enableQuery: { + _id: 'Register_Server', + value: true, + }, + }); + + this.add('Cloud_Workspace_Name', '', { + type: 'string', + hidden: true, + readonly: true, + enableQuery: { + _id: 'Register_Server', + value: true, + }, + }); + + this.add('Cloud_Workspace_Client_Id', '', { + type: 'string', + hidden: true, + readonly: true, + enableQuery: { + _id: 'Register_Server', + value: true, + }, + }); + + this.add('Cloud_Workspace_Client_Secret', '', { + type: 'string', + hidden: true, + readonly: true, + enableQuery: { + _id: 'Register_Server', + value: true, + }, + }); + + this.add('Cloud_Workspace_Client_Secret_Expires_At', '', { + type: 'int', + hidden: true, + readonly: true, + enableQuery: { + _id: 'Register_Server', + value: true, + }, + }); + + this.add('Cloud_Workspace_Registration_Client_Uri', '', { + type: 'string', + hidden: true, + readonly: true, + enableQuery: { + _id: 'Register_Server', + value: true, + }, + }); + + this.add('Cloud_Workspace_License', '', { + type: 'string', + hidden: true, + readonly: true, + enableQuery: { + _id: 'Register_Server', + value: true, + }, + }); + + this.add('Cloud_Workspace_Access_Token', '', { + type: 'string', + hidden: true, + readonly: true, + enableQuery: { + _id: 'Register_Server', + value: true, + }, + }); + + this.add('Cloud_Workspace_Access_Token_Expires_At', new Date(), { + type: 'date', + hidden: true, + readonly: true, + enableQuery: { + _id: 'Register_Server', + value: true, + }, + }); + + this.add('Cloud_Workspace_Registration_State', '', { + type: 'string', + hidden: true, + readonly: true, + enableQuery: { + _id: 'Register_Server', + value: true, + }, + }); + + this.add('Cloud_Workspace_Account_Associated', false, { + type: 'boolean', + hidden: true, + readonly: true, + enableQuery: { + _id: 'Register_Server', + value: true, + }, + }); }); }); diff --git a/packages/rocketchat-setup-wizard/client/setupWizard.js b/packages/rocketchat-setup-wizard/client/setupWizard.js index 817868adda84..d79dd8288f50 100644 --- a/packages/rocketchat-setup-wizard/client/setupWizard.js +++ b/packages/rocketchat-setup-wizard/client/setupWizard.js @@ -74,6 +74,14 @@ const persistSettings = (state, callback) => { _id: 'Statistics_reporting', value: state.registerServer, }, + { + _id: 'Apps_Framework_enabled', + value: state.registerServer, + }, + { + _id: 'Register_Server', + value: state.registerServer, + }, { _id: 'Allow_Marketing_Emails', value: state.optIn, diff --git a/packages/rocketchat-statistics/server/functions/get.js b/packages/rocketchat-statistics/server/functions/get.js index cc6a70c2f146..0ecfa629d74f 100644 --- a/packages/rocketchat-statistics/server/functions/get.js +++ b/packages/rocketchat-statistics/server/functions/get.js @@ -32,10 +32,12 @@ RocketChat.statistics.get = function _getStatistics() { } }); - if (statistics.wizard.allowMarketingEmails) { - const firstUser = RocketChat.models.Users.getOldest({ name: 1, emails: 1 }); - statistics.wizard.contactName = firstUser && firstUser.name; - statistics.wizard.contactEmail = firstUser && firstUser.emails[0].address; + const firstUser = RocketChat.models.Users.getOldest({ name: 1, emails: 1 }); + statistics.wizard.contactName = firstUser && firstUser.name; + statistics.wizard.contactEmail = firstUser && firstUser.emails && firstUser.emails[0].address; + + if (RocketChat.settings.get('Organization_Email')) { + statistics.wizard.contactEmail = RocketChat.settings.get('Organization_Email'); } // Version diff --git a/packages/rocketchat-version-check/package.js b/packages/rocketchat-version-check/package.js index a88d0e9e712f..2f76c06b8c3e 100644 --- a/packages/rocketchat-version-check/package.js +++ b/packages/rocketchat-version-check/package.js @@ -11,6 +11,7 @@ Package.onUse(function(api) { 'ecmascript', 'rocketchat:lib', 'rocketchat:logger', + 'rocketchat:cloud', 'littledata:synced-cron', ]); diff --git a/packages/rocketchat-version-check/server/functions/getNewUpdates.js b/packages/rocketchat-version-check/server/functions/getNewUpdates.js index 8426537cbd55..09ff8715e68a 100644 --- a/packages/rocketchat-version-check/server/functions/getNewUpdates.js +++ b/packages/rocketchat-version-check/server/functions/getNewUpdates.js @@ -1,6 +1,7 @@ import os from 'os'; import { HTTP } from 'meteor/http'; import { RocketChat } from 'meteor/rocketchat:lib'; +import { getWorkspaceAccessToken } from 'meteor/rocketchat:cloud'; import { MongoInternals } from 'meteor/mongo'; // import checkUpdate from '../checkUpdate'; @@ -24,8 +25,15 @@ export default () => { deployPlatform: process.env.DEPLOY_PLATFORM || 'selfinstall', }; + const headers = {}; + const token = getWorkspaceAccessToken(); + if (token) { + headers.Authorization = `Bearer ${ token }`; + } + const result = HTTP.get('https://releases.rocket.chat/updates/check', { params: data, + headers, }); return result.data; diff --git a/server/lib/cordova.js b/server/lib/cordova.js index 2eb5a33a0c0a..8dc30c65864e 100644 --- a/server/lib/cordova.js +++ b/server/lib/cordova.js @@ -2,8 +2,10 @@ import { Meteor } from 'meteor/meteor'; import { HTTP } from 'meteor/http'; import { TAPi18n } from 'meteor/tap:i18n'; import { SystemLogger } from 'meteor/rocketchat:logger'; +import { getWorkspaceAccessToken } from 'meteor/rocketchat:cloud'; import { Push } from 'meteor/rocketchat:push'; + Meteor.methods({ // log() { // return console.log(...arguments); @@ -80,8 +82,14 @@ function sendPush(service, token, options, tries = 0) { token, options, }, + headers: {}, }; + const workspaceAccesstoken = getWorkspaceAccessToken(); + if (token) { + data.headers.Authorization = `Bearer ${ workspaceAccesstoken }`; + } + return HTTP.post(`${ RocketChat.settings.get('Push_gateway') }/push/${ service }/send`, data, function(error, response) { if (response && response.statusCode === 406) { console.log('removing push token', token); diff --git a/server/startup/cron.js b/server/startup/cron.js index a4b5130d6455..a5c93b6e1a5a 100644 --- a/server/startup/cron.js +++ b/server/startup/cron.js @@ -1,6 +1,7 @@ import { Meteor } from 'meteor/meteor'; import { HTTP } from 'meteor/http'; import { Logger } from 'meteor/rocketchat:logger'; +import { getWorkspaceAccessToken } from 'meteor/rocketchat:cloud'; import { SyncedCron } from 'meteor/littledata:synced-cron'; const logger = new Logger('SyncedCron'); @@ -19,8 +20,16 @@ function generateStatistics() { if (RocketChat.settings.get('Statistics_reporting')) { try { + const headers = {}; + const token = getWorkspaceAccessToken(); + + if (token) { + headers.Authorization = `Bearer ${ token }`; + } + HTTP.post('https://collector.rocket.chat/', { data: statistics, + headers, }); } catch (error) { /* error*/ diff --git a/server/startup/migrations/v137.js b/server/startup/migrations/v137.js new file mode 100644 index 000000000000..7ebf4c7b7241 --- /dev/null +++ b/server/startup/migrations/v137.js @@ -0,0 +1,10 @@ +RocketChat.Migrations.add({ + version: 137, + up() { + const firstUser = RocketChat.models.Users.getOldest({ emails: 1 }); + const reportStats = RocketChat.settings.get('Statistics_reporting'); + + RocketChat.models.Settings.updateValueById('Organization_Email', firstUser && firstUser.emails && firstUser.emails[0].address); + RocketChat.models.Settings.updateValueById('Register_Server', reportStats); + }, +}); diff --git a/tests/end-to-end/ui/00-login.js b/tests/end-to-end/ui/00-login.js index 3511f01e9d5d..294b8c13d72a 100644 --- a/tests/end-to-end/ui/00-login.js +++ b/tests/end-to-end/ui/00-login.js @@ -141,7 +141,7 @@ describe('[Setup Wizard]', () => { describe('[Render - Final Step]', () => { it('it should render "Go to your workspace button', () => { - setupWizard.goToWorkspace.waitForVisible(15000); + setupWizard.goToWorkspace.waitForVisible(20000); setupWizard.goToWorkspace.isVisible().should.be.true; });