From c64cccb41595225e7864996eed80a66272024739 Mon Sep 17 00:00:00 2001 From: Marcos Defendi Date: Wed, 10 Oct 2018 16:42:53 -0300 Subject: [PATCH 1/3] Add permission to enable personal access token to specific roles --- .../client/personalAccessTokens.js | 4 +- .../server/api/methods/generateToken.js | 4 +- .../server/api/methods/regenerateToken.js | 4 +- .../server/api/methods/removeToken.js | 4 +- .../personal-access-tokens/server/index.js | 1 - .../publications/personalAccessTokens.js | 2 +- .../personal-access-tokens/server/settings.js | 5 -- packages/rocketchat-api/server/v1/users.js | 4 +- .../server/startup.js | 1 + packages/rocketchat-i18n/i18n/en.i18n.json | 2 - .../client/accountFlex.js | 2 +- server/startup/migrations/v136.js | 11 ++++ tests/end-to-end/api/01-users.js | 59 +++++++------------ 13 files changed, 44 insertions(+), 59 deletions(-) delete mode 100644 imports/personal-access-tokens/server/settings.js create mode 100644 server/startup/migrations/v136.js diff --git a/imports/personal-access-tokens/client/personalAccessTokens.js b/imports/personal-access-tokens/client/personalAccessTokens.js index c28896968dfc..1b8947b91b0d 100644 --- a/imports/personal-access-tokens/client/personalAccessTokens.js +++ b/imports/personal-access-tokens/client/personalAccessTokens.js @@ -8,7 +8,7 @@ const PersonalAccessTokens = new Mongo.Collection('personal_access_tokens'); Template.accountTokens.helpers({ isAllowed() { - return RocketChat.settings.get('API_Enable_Personal_Access_Tokens'); + return RocketChat.authz.hasAllPermission(['create-personal-access-tokens']); }, tokens() { return (PersonalAccessTokens.find({}).fetch()[0] && PersonalAccessTokens.find({}).fetch()[0].tokens) || []; @@ -42,7 +42,7 @@ Template.accountTokens.events({ return toastr.error(t(error.error)); } showSuccessModal(token); - instance.find('#input-token-name').value = ''; + instance.find('#tokenName').value = ''; }); }, 'click .remove-personal-access-token'() { diff --git a/imports/personal-access-tokens/server/api/methods/generateToken.js b/imports/personal-access-tokens/server/api/methods/generateToken.js index 68f337e357c5..ff6ca2f764c6 100644 --- a/imports/personal-access-tokens/server/api/methods/generateToken.js +++ b/imports/personal-access-tokens/server/api/methods/generateToken.js @@ -7,8 +7,8 @@ Meteor.methods({ if (!Meteor.userId()) { throw new Meteor.Error('not-authorized', 'Not Authorized', { method: 'personalAccessTokens:generateToken' }); } - if (!RocketChat.settings.get('API_Enable_Personal_Access_Tokens')) { - throw new Meteor.Error('error-personal-access-tokens-are-current-disabled', 'Personal Access Tokens are currently disabled', { method: 'personalAccessTokens:generateToken' }); + if (!RocketChat.authz.hasPermission(Meteor.userId(), 'create-personal-access-tokens')) { + throw new Meteor.Error('not-authorized', 'Not Authorized', { method: 'personalAccessTokens:generateToken' }); } const token = Random.secret(); diff --git a/imports/personal-access-tokens/server/api/methods/regenerateToken.js b/imports/personal-access-tokens/server/api/methods/regenerateToken.js index 6b36f9adf25c..48f030c895f8 100644 --- a/imports/personal-access-tokens/server/api/methods/regenerateToken.js +++ b/imports/personal-access-tokens/server/api/methods/regenerateToken.js @@ -5,8 +5,8 @@ Meteor.methods({ if (!Meteor.userId()) { throw new Meteor.Error('not-authorized', 'Not Authorized', { method: 'personalAccessTokens:regenerateToken' }); } - if (!RocketChat.settings.get('API_Enable_Personal_Access_Tokens')) { - throw new Meteor.Error('error-personal-access-tokens-are-current-disabled', 'Personal Access Tokens are currently disabled', { method: 'personalAccessTokens:regenerateToken' }); + if (!RocketChat.authz.hasPermission(Meteor.userId(), 'create-personal-access-tokens')) { + throw new Meteor.Error('not-authorized', 'Not Authorized', { method: 'personalAccessTokens:regenerateToken' }); } const tokenExist = RocketChat.models.Users.findPersonalAccessTokenByTokenNameAndUserId({ diff --git a/imports/personal-access-tokens/server/api/methods/removeToken.js b/imports/personal-access-tokens/server/api/methods/removeToken.js index ff7be8de698c..3e94195132d2 100644 --- a/imports/personal-access-tokens/server/api/methods/removeToken.js +++ b/imports/personal-access-tokens/server/api/methods/removeToken.js @@ -5,8 +5,8 @@ Meteor.methods({ if (!Meteor.userId()) { throw new Meteor.Error('not-authorized', 'Not Authorized', { method: 'personalAccessTokens:removeToken' }); } - if (!RocketChat.settings.get('API_Enable_Personal_Access_Tokens')) { - throw new Meteor.Error('error-personal-access-tokens-are-current-disabled', 'Personal Access Tokens are currently disabled', { method: 'personalAccessTokens:removeToken' }); + if (!RocketChat.authz.hasPermission(Meteor.userId(), 'create-personal-access-tokens')) { + throw new Meteor.Error('not-authorized', 'Not Authorized', { method: 'personalAccessTokens:removeToken' }); } const tokenExist = RocketChat.models.Users.findPersonalAccessTokenByTokenNameAndUserId({ userId: Meteor.userId(), diff --git a/imports/personal-access-tokens/server/index.js b/imports/personal-access-tokens/server/index.js index ca18192c8e77..861bc4add4a6 100644 --- a/imports/personal-access-tokens/server/index.js +++ b/imports/personal-access-tokens/server/index.js @@ -1,5 +1,4 @@ import './api/methods'; -import './settings'; import './models'; import './publications'; diff --git a/imports/personal-access-tokens/server/publications/personalAccessTokens.js b/imports/personal-access-tokens/server/publications/personalAccessTokens.js index bdc75b9a9ecf..3760a85f7401 100644 --- a/imports/personal-access-tokens/server/publications/personalAccessTokens.js +++ b/imports/personal-access-tokens/server/publications/personalAccessTokens.js @@ -4,7 +4,7 @@ Meteor.publish('personalAccessTokens', function() { if (!this.userId) { return this.ready(); } - if (!RocketChat.settings.get('API_Enable_Personal_Access_Tokens')) { + if (!RocketChat.authz.hasPermission(this.userId, 'create-personal-access-tokens')) { return this.ready(); } const self = this; diff --git a/imports/personal-access-tokens/server/settings.js b/imports/personal-access-tokens/server/settings.js deleted file mode 100644 index fa2aba8cfb24..000000000000 --- a/imports/personal-access-tokens/server/settings.js +++ /dev/null @@ -1,5 +0,0 @@ -RocketChat.settings.addGroup('General', function() { - this.section('REST API', function() { - this.add('API_Enable_Personal_Access_Tokens', false, { type: 'boolean', public: true }); - }); -}); diff --git a/packages/rocketchat-api/server/v1/users.js b/packages/rocketchat-api/server/v1/users.js index c231830a6c2b..ef9f6c062fc3 100644 --- a/packages/rocketchat-api/server/v1/users.js +++ b/packages/rocketchat-api/server/v1/users.js @@ -452,8 +452,8 @@ RocketChat.API.v1.addRoute('users.regeneratePersonalAccessToken', { authRequired RocketChat.API.v1.addRoute('users.getPersonalAccessTokens', { authRequired: true }, { get() { - if (!RocketChat.settings.get('API_Enable_Personal_Access_Tokens')) { - throw new Meteor.Error('error-personal-access-tokens-are-current-disabled', 'Personal Access Tokens are currently disabled'); + if (!RocketChat.authz.hasPermission(this.userId, 'create-personal-access-tokens')) { + throw new Meteor.Error('not-authorized', 'Not Authorized'); } const loginTokens = RocketChat.models.Users.getLoginTokensByUserId(this.userId).fetch()[0]; const getPersonalAccessTokens = () => loginTokens.services.resume.loginTokens diff --git a/packages/rocketchat-authorization/server/startup.js b/packages/rocketchat-authorization/server/startup.js index 1ddb15068bad..5b4d14feed99 100644 --- a/packages/rocketchat-authorization/server/startup.js +++ b/packages/rocketchat-authorization/server/startup.js @@ -19,6 +19,7 @@ Meteor.startup(function() { { _id: 'create-c', roles : ['admin', 'user', 'bot'] }, { _id: 'create-d', roles : ['admin', 'user', 'bot'] }, { _id: 'create-p', roles : ['admin', 'user', 'bot'] }, + { _id: 'create-personal-access-tokens', roles : ['admin', 'user'] }, { _id: 'create-user', roles : ['admin'] }, { _id: 'clean-channel-history', roles : ['admin'] }, { _id: 'delete-c', roles : ['admin', 'owner'] }, diff --git a/packages/rocketchat-i18n/i18n/en.i18n.json b/packages/rocketchat-i18n/i18n/en.i18n.json index ab294d7e47c8..515b055ef0b8 100644 --- a/packages/rocketchat-i18n/i18n/en.i18n.json +++ b/packages/rocketchat-i18n/i18n/en.i18n.json @@ -274,8 +274,6 @@ "API_Enable_CORS": "Enable CORS", "API_Enable_Direct_Message_History_EndPoint": "Enable Direct Message History Endpoint", "API_Enable_Direct_Message_History_EndPoint_Description": "This enables the `/api/v1/im.history.others` which allows the viewing of direct messages sent by other users that the caller is not part of.", - "API_Enable_Personal_Access_Tokens": "Enable Personal Access Tokens to REST API", - "API_Enable_Personal_Access_Tokens_Description": "Enable personal access tokens for use with the REST API", "API_Enable_Shields": "Enable Shields", "API_Enable_Shields_Description": "Enable shields available at `/api/v1/shield.svg`", "API_GitHub_Enterprise_URL": "Server URL", diff --git a/packages/rocketchat-ui-account/client/accountFlex.js b/packages/rocketchat-ui-account/client/accountFlex.js index 40fca5532992..8b58ccd501ec 100644 --- a/packages/rocketchat-ui-account/client/accountFlex.js +++ b/packages/rocketchat-ui-account/client/accountFlex.js @@ -13,7 +13,7 @@ Template.accountFlex.helpers({ return RocketChat.settings.get('Accounts_AllowUserProfileChange'); }, accessTokensEnabled() { - return RocketChat.settings.get('API_Enable_Personal_Access_Tokens'); + return RocketChat.authz.hasAllPermission(['create-personal-access-tokens']); }, encryptionEnabled() { return RocketChat.settings.get('E2E_Enable'); diff --git a/server/startup/migrations/v136.js b/server/startup/migrations/v136.js new file mode 100644 index 000000000000..890ea12187ed --- /dev/null +++ b/server/startup/migrations/v136.js @@ -0,0 +1,11 @@ +RocketChat.Migrations.add({ + version: 136, + up() { + const personalTokensEnabled = RocketChat.settings.get('API_Enable_Personal_Access_Tokens'); + const roles = RocketChat.models.Roles.find({ scope: 'Users' }).fetch().map((role) => role._id); + + if (personalTokensEnabled) { + RocketChat.models.Permissions.upsert({ _id: 'create-personal-access-tokens' }, { $set: { roles } }); + } + }, +}); diff --git a/tests/end-to-end/api/01-users.js b/tests/end-to-end/api/01-users.js index 10b72f616769..a4d58bcf34d7 100644 --- a/tests/end-to-end/api/01-users.js +++ b/tests/end-to-end/api/01-users.js @@ -18,6 +18,18 @@ import { adminEmail, preferences, password } from '../../data/user.js'; import { imgURL } from '../../data/interactions.js'; import { customFieldText, clearCustomFields, setCustomFields } from '../../data/custom-fields.js'; +const updatePermission = (permission, roles) => new Promise((resolve) => { + request.post(api('permissions.update')) + .set(credentials) + .send({ permissions: [{ _id: permission, roles }] }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + }) + .end(resolve); +}); + describe('[Users]', function() { this.retries(0); @@ -987,17 +999,6 @@ describe('[Users]', function() { }); describe('[/users.delete]', () => { - const updatePermission = (permission, roles) => new Promise((resolve) => { - request.post(api('permissions.update')) - .set(credentials) - .send({ permissions: [{ _id: permission, roles }] }) - .expect('Content-Type', 'application/json') - .expect(200) - .expect((res) => { - expect(res.body).to.have.property('success', true); - }) - .end(resolve); - }); const testUsername = `testuser${ +new Date() }`; let targetUser; it('register a new user...', (done) => { @@ -1054,17 +1055,7 @@ describe('[Users]', function() { describe('Personal Access Tokens', () => { const tokenName = `${ Date.now() }token`; describe('successful cases', () => { - it('Enable "API_Enable_Personal_Access_Tokens" setting...', (done) => { - request.post('/api/v1/settings/API_Enable_Personal_Access_Tokens') - .set(credentials) - .send({ value: true }) - .expect('Content-Type', 'application/json') - .expect(200) - .expect((res) => { - expect(res.body).to.have.property('success', true); - }) - .end(done); - }); + it('Grant necessary permission "create-personal-accss-tokens" to user', (done) => updatePermission('create-personal-access-tokens', ['admin']).then(done)); describe('[/users.generatePersonalAccessToken]', () => { it('should return a personal access token to user', (done) => { request.post(api('users.generatePersonalAccessToken')) @@ -1166,18 +1157,8 @@ describe('[Users]', function() { }); }); describe('unsuccessful cases', () => { - it('disable "API_Enable_Personal_Access_Tokens" setting...', (done) => { - request.post('/api/v1/settings/API_Enable_Personal_Access_Tokens') - .set(credentials) - .send({ value: false }) - .expect('Content-Type', 'application/json') - .expect(200) - .expect((res) => { - expect(res.body).to.have.property('success', true); - }) - .end(done); - }); - describe('should return an error when setting "API_Enable_Personal_Access_Tokens" is disabled for the following routes', () => { + it('Remove necessary permission "create-personal-accss-tokens" to user', (done) => updatePermission('create-personal-access-tokens', []).then(done)); + describe('should return an error when the user dont have the necessary permission "create-personal-access-tokens"', () => { it('/users.generatePersonalAccessToken', (done) => { request.post(api('users.generatePersonalAccessToken')) .set(credentials) @@ -1188,7 +1169,7 @@ describe('[Users]', function() { .expect(400) .expect((res) => { expect(res.body).to.have.property('success', false); - expect(res.body.errorType).to.be.equal('error-personal-access-tokens-are-current-disabled'); + expect(res.body.errorType).to.be.equal('not-authorized'); }) .end(done); }); @@ -1202,7 +1183,7 @@ describe('[Users]', function() { .expect(400) .expect((res) => { expect(res.body).to.have.property('success', false); - expect(res.body.errorType).to.be.equal('error-personal-access-tokens-are-current-disabled'); + expect(res.body.errorType).to.be.equal('not-authorized'); }) .end(done); }); @@ -1213,7 +1194,7 @@ describe('[Users]', function() { .expect(400) .expect((res) => { expect(res.body).to.have.property('success', false); - expect(res.body.errorType).to.be.equal('error-personal-access-tokens-are-current-disabled'); + expect(res.body.errorType).to.be.equal('not-authorized'); }) .end(done); }); @@ -1227,7 +1208,7 @@ describe('[Users]', function() { .expect(400) .expect((res) => { expect(res.body).to.have.property('success', false); - expect(res.body.errorType).to.be.equal('error-personal-access-tokens-are-current-disabled'); + expect(res.body.errorType).to.be.equal('not-authorized'); }) .end(done); }); @@ -1241,7 +1222,7 @@ describe('[Users]', function() { .expect(400) .expect((res) => { expect(res.body).to.have.property('success', false); - expect(res.body.errorType).to.be.equal('error-personal-access-tokens-are-current-disabled'); + expect(res.body.errorType).to.be.equal('not-authorized'); }) .end(done); }); From 44bebfc7afec76d5e013b6af488070ca8afc04ea Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Tue, 20 Nov 2018 21:06:46 -0200 Subject: [PATCH 2/3] Update v136.js --- server/startup/migrations/v136.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/server/startup/migrations/v136.js b/server/startup/migrations/v136.js index 890ea12187ed..ac6196bfb588 100644 --- a/server/startup/migrations/v136.js +++ b/server/startup/migrations/v136.js @@ -7,5 +7,9 @@ RocketChat.Migrations.add({ if (personalTokensEnabled) { RocketChat.models.Permissions.upsert({ _id: 'create-personal-access-tokens' }, { $set: { roles } }); } + + RocketChat.models.Settings.remove({ + _id: 'API_Enable_Personal_Access_Tokens', + }); }, }); From 905d36b7977962c0bcf4f19cc264b621e21fc901 Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Tue, 20 Nov 2018 22:15:21 -0200 Subject: [PATCH 3/3] Update v136.js --- server/startup/migrations/v136.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/startup/migrations/v136.js b/server/startup/migrations/v136.js index ac6196bfb588..41f2e2dc4d60 100644 --- a/server/startup/migrations/v136.js +++ b/server/startup/migrations/v136.js @@ -7,7 +7,7 @@ RocketChat.Migrations.add({ if (personalTokensEnabled) { RocketChat.models.Permissions.upsert({ _id: 'create-personal-access-tokens' }, { $set: { roles } }); } - + RocketChat.models.Settings.remove({ _id: 'API_Enable_Personal_Access_Tokens', });