From 206e2553a3bfd26d2c86e5edcb3d0e1ff256b548 Mon Sep 17 00:00:00 2001
From: Marcos Spessatto Defendi <marcos.defendi@ulbra.inf.br>
Date: Tue, 20 Nov 2018 22:35:18 -0200
Subject: [PATCH] [NEW] Add permission to enable personal access token to
 specific roles (#12309)

* Add permission to enable personal access token to specific roles

* Update v136.js

* Update v136.js
---
 .../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             | 15 ++++++++
 tests/end-to-end/api/01-users.js              | 36 +++++--------------
 13 files changed, 36 insertions(+), 48 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 348bc6ba9a8..98260fe5ef8 100644
--- a/imports/personal-access-tokens/client/personalAccessTokens.js
+++ b/imports/personal-access-tokens/client/personalAccessTokens.js
@@ -12,7 +12,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) || [];
@@ -46,7 +46,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 68f337e357c..ff6ca2f764c 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 6b36f9adf25..48f030c895f 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 ff7be8de698..3e94195132d 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 ca18192c8e7..861bc4add4a 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 bdc75b9a9ec..3760a85f740 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 fa2aba8cfb2..00000000000
--- 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 c9476c34f3a..d1e96b8c1f2 100644
--- a/packages/rocketchat-api/server/v1/users.js
+++ b/packages/rocketchat-api/server/v1/users.js
@@ -498,8 +498,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 f09c5027d4f..db5114060f3 100644
--- a/packages/rocketchat-authorization/server/startup.js
+++ b/packages/rocketchat-authorization/server/startup.js
@@ -21,6 +21,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 0a6cedc7e73..865c277dc84 100644
--- a/packages/rocketchat-i18n/i18n/en.i18n.json
+++ b/packages/rocketchat-i18n/i18n/en.i18n.json
@@ -278,8 +278,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 8d38234ebf2..f1c8d945636 100644
--- a/packages/rocketchat-ui-account/client/accountFlex.js
+++ b/packages/rocketchat-ui-account/client/accountFlex.js
@@ -15,7 +15,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 00000000000..41f2e2dc4d6
--- /dev/null
+++ b/server/startup/migrations/v136.js
@@ -0,0 +1,15 @@
+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 } });
+		}
+
+		RocketChat.models.Settings.remove({
+			_id: 'API_Enable_Personal_Access_Tokens',
+		});
+	},
+});
diff --git a/tests/end-to-end/api/01-users.js b/tests/end-to-end/api/01-users.js
index 692e0605d64..28bbb9e4247 100644
--- a/tests/end-to-end/api/01-users.js
+++ b/tests/end-to-end/api/01-users.js
@@ -1247,17 +1247,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'))
@@ -1359,18 +1349,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)
@@ -1381,7 +1361,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);
 				});
@@ -1395,7 +1375,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);
 				});
@@ -1406,7 +1386,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);
 				});
@@ -1420,7 +1400,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);
 				});
@@ -1434,7 +1414,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);
 				});
-- 
GitLab