diff --git a/packages/api/env/portal/.env b/packages/api/env/portal/.env index 6d85d69ec..f1b3ee6ac 100644 --- a/packages/api/env/portal/.env +++ b/packages/api/env/portal/.env @@ -49,3 +49,12 @@ ENGINE_relational=mariadb@dbgate-plugin-mysql READONLY_relational=1 # docker run -p 3000:3000 -e CONNECTIONS=mongo -e URL_mongo=mongodb://localhost:27017 -e ENGINE_mongo=mongo@dbgate-plugin-mongo -e LABEL_mongo=mongo dbgate/dbgate:beta + +# LOGINS=x,y +# LOGIN_PASSWORD_x=x +# LOGIN_PASSWORD_y=LOGIN_PASSWORD_y +# LOGIN_PERMISSIONS_x=~* +# LOGIN_PERMISSIONS_y=~* + +# PERMISSIONS=~*,connections/relational +# PERMISSIONS=~* diff --git a/packages/api/src/controllers/config.js b/packages/api/src/controllers/config.js index f96877d5d..f724868fb 100644 --- a/packages/api/src/controllers/config.js +++ b/packages/api/src/controllers/config.js @@ -29,7 +29,7 @@ module.exports = { async get(_params, req) { const logins = getLogins(); const login = logins ? logins.find(x => x.login == (req.auth && req.auth.user)) : null; - const permissions = login ? login.permissions : null; + const permissions = login ? login.permissions : process.env.PERMISSIONS; return { runAsPortal: !!connections.portalConnections, diff --git a/packages/api/src/controllers/connections.js b/packages/api/src/controllers/connections.js index 4c64177de..7aad6ebb1 100644 --- a/packages/api/src/controllers/connections.js +++ b/packages/api/src/controllers/connections.js @@ -13,6 +13,7 @@ const JsonLinesDatabase = require('../utility/JsonLinesDatabase'); const processArgs = require('../utility/processArgs'); const { safeJsonParse } = require('dbgate-tools'); const platformInfo = require('../utility/platformInfo'); +const { connectionHasPermission, testConnectionPermission } = require('../utility/hasPermission'); function getNamedArgs() { const res = {}; @@ -165,12 +166,12 @@ module.exports = { }, list_meta: true, - async list() { + async list(_params, req) { if (portalConnections) { if (platformInfo.allowShellConnection) return portalConnections; - return portalConnections.map(maskConnection); + return portalConnections.map(maskConnection).filter(x => connectionHasPermission(x, req)); } - return this.datastore.find(); + return (await this.datastore.find()).filter(x => connectionHasPermission(x, req)); }, test_meta: true, @@ -217,16 +218,18 @@ module.exports = { }, update_meta: true, - async update({ _id, values }) { + async update({ _id, values }, req) { if (portalConnections) return; + testConnectionPermission(_id, req); const res = await this.datastore.patch(_id, values); socket.emitChanged('connection-list-changed'); return res; }, updateDatabase_meta: true, - async updateDatabase({ conid, database, values }) { + async updateDatabase({ conid, database, values }, req) { if (portalConnections) return; + testConnectionPermission(conid, req); const conn = await this.datastore.get(conid); let databases = (conn && conn.databases) || []; if (databases.find(x => x.name == database)) { @@ -242,8 +245,9 @@ module.exports = { }, delete_meta: true, - async delete(connection) { + async delete(connection, req) { if (portalConnections) return; + testConnectionPermission(connection, req); const res = await this.datastore.remove(connection._id); socket.emitChanged('connection-list-changed'); return res; @@ -260,7 +264,8 @@ module.exports = { }, get_meta: true, - async get({ conid }) { + async get({ conid }, req) { + testConnectionPermission(conid, req); return this.getCore({ conid, mask: true }); }, diff --git a/packages/api/src/controllers/databaseConnections.js b/packages/api/src/controllers/databaseConnections.js index 1078d4416..6be1a25c7 100644 --- a/packages/api/src/controllers/databaseConnections.js +++ b/packages/api/src/controllers/databaseConnections.js @@ -26,6 +26,7 @@ const generateDeploySql = require('../shell/generateDeploySql'); const { createTwoFilesPatch } = require('diff'); const diff2htmlPage = require('../utility/diff2htmlPage'); const processArgs = require('../utility/processArgs'); +const { testConnectionPermission } = require('../utility/hasPermission'); module.exports = { /** @type {import('dbgate-types').OpenedDatabaseConnection[]} */ @@ -130,7 +131,8 @@ module.exports = { }, queryData_meta: true, - async queryData({ conid, database, sql }) { + async queryData({ conid, database, sql }, req) { + testConnectionPermission(conid, req); console.log(`Processing query, conid=${conid}, database=${database}, sql=${sql}`); const opened = await this.ensureOpened(conid, database); // if (opened && opened.status && opened.status.name == 'error') { @@ -141,14 +143,16 @@ module.exports = { }, sqlSelect_meta: true, - async sqlSelect({ conid, database, select }) { + async sqlSelect({ conid, database, select }, req) { + testConnectionPermission(conid, req); const opened = await this.ensureOpened(conid, database); const res = await this.sendRequest(opened, { msgtype: 'sqlSelect', select }); return res; }, runScript_meta: true, - async runScript({ conid, database, sql }) { + async runScript({ conid, database, sql }, req) { + testConnectionPermission(conid, req); console.log(`Processing script, conid=${conid}, database=${database}, sql=${sql}`); const opened = await this.ensureOpened(conid, database); const res = await this.sendRequest(opened, { msgtype: 'runScript', sql }); @@ -156,13 +160,15 @@ module.exports = { }, collectionData_meta: true, - async collectionData({ conid, database, options }) { + async collectionData({ conid, database, options }, req) { + testConnectionPermission(conid, req); const opened = await this.ensureOpened(conid, database); const res = await this.sendRequest(opened, { msgtype: 'collectionData', options }); return res.result || null; }, - async loadDataCore(msgtype, { conid, database, ...args }) { + async loadDataCore(msgtype, { conid, database, ...args }, req) { + testConnectionPermission(conid, req); const opened = await this.ensureOpened(conid, database); const res = await this.sendRequest(opened, { msgtype, ...args }); if (res.errorMessage) { @@ -176,32 +182,38 @@ module.exports = { }, loadKeys_meta: true, - async loadKeys({ conid, database, root, filter }) { + async loadKeys({ conid, database, root, filter }, req) { + testConnectionPermission(conid, req); return this.loadDataCore('loadKeys', { conid, database, root, filter }); }, exportKeys_meta: true, - async exportKeys({ conid, database, options }) { + async exportKeys({ conid, database, options }, req) { + testConnectionPermission(conid, req); return this.loadDataCore('exportKeys', { conid, database, options }); }, loadKeyInfo_meta: true, - async loadKeyInfo({ conid, database, key }) { + async loadKeyInfo({ conid, database, key }, req) { + testConnectionPermission(conid, req); return this.loadDataCore('loadKeyInfo', { conid, database, key }); }, loadKeyTableRange_meta: true, - async loadKeyTableRange({ conid, database, key, cursor, count }) { + async loadKeyTableRange({ conid, database, key, cursor, count }, req) { + testConnectionPermission(conid, req); return this.loadDataCore('loadKeyTableRange', { conid, database, key, cursor, count }); }, loadFieldValues_meta: true, - async loadFieldValues({ conid, database, schemaName, pureName, field, search }) { + async loadFieldValues({ conid, database, schemaName, pureName, field, search }, req) { + testConnectionPermission(conid, req); return this.loadDataCore('loadFieldValues', { conid, database, schemaName, pureName, field, search }); }, callMethod_meta: true, - async callMethod({ conid, database, method, args }) { + async callMethod({ conid, database, method, args }, req) { + testConnectionPermission(conid, req); return this.loadDataCore('callMethod', { conid, database, method, args }); // const opened = await this.ensureOpened(conid, database); @@ -213,7 +225,8 @@ module.exports = { }, updateCollection_meta: true, - async updateCollection({ conid, database, changeSet }) { + async updateCollection({ conid, database, changeSet }, req) { + testConnectionPermission(conid, req); const opened = await this.ensureOpened(conid, database); const res = await this.sendRequest(opened, { msgtype: 'updateCollection', changeSet }); if (res.errorMessage) { @@ -225,7 +238,14 @@ module.exports = { }, status_meta: true, - async status({ conid, database }) { + async status({ conid, database }, req) { + if (!conid) { + return { + name: 'error', + message: 'No connection', + }; + } + testConnectionPermission(conid, req); const existing = this.opened.find(x => x.conid == conid && x.database == database); if (existing) { return { @@ -247,7 +267,8 @@ module.exports = { }, ping_meta: true, - async ping({ conid, database }) { + async ping({ conid, database }, req) { + testConnectionPermission(conid, req); let existing = this.opened.find(x => x.conid == conid && x.database == database); if (existing) { @@ -263,7 +284,8 @@ module.exports = { }, refresh_meta: true, - async refresh({ conid, database, keepOpen }) { + async refresh({ conid, database, keepOpen }, req) { + testConnectionPermission(conid, req); if (!keepOpen) this.close(conid, database); await this.ensureOpened(conid, database); @@ -271,7 +293,8 @@ module.exports = { }, syncModel_meta: true, - async syncModel({ conid, database, isFullRefresh }) { + async syncModel({ conid, database, isFullRefresh }, req) { + testConnectionPermission(conid, req); const conn = await this.ensureOpened(conid, database); conn.subprocess.send({ msgtype: 'syncModel', isFullRefresh }); return { status: 'ok' }; @@ -301,13 +324,15 @@ module.exports = { }, disconnect_meta: true, - async disconnect({ conid, database }) { + async disconnect({ conid, database }, req) { + testConnectionPermission(conid, req); await this.close(conid, database, true); return { status: 'ok' }; }, structure_meta: true, - async structure({ conid, database }) { + async structure({ conid, database }, req) { + testConnectionPermission(conid, req); if (conid == '__model') { const model = await importDbModel(database); return model; @@ -324,14 +349,19 @@ module.exports = { }, serverVersion_meta: true, - async serverVersion({ conid, database }) { + async serverVersion({ conid, database }, req) { + if (!conid) { + return null; + } + testConnectionPermission(conid, req); if (!conid) return null; const opened = await this.ensureOpened(conid, database); return opened.serverVersion || null; }, sqlPreview_meta: true, - async sqlPreview({ conid, database, objects, options }) { + async sqlPreview({ conid, database, objects, options }, req) { + testConnectionPermission(conid, req); // wait for structure await this.structure({ conid, database }); @@ -341,7 +371,8 @@ module.exports = { }, exportModel_meta: true, - async exportModel({ conid, database }) { + async exportModel({ conid, database }, req) { + testConnectionPermission(conid, req); const archiveFolder = await archive.getNewArchiveFolder({ database }); await fs.mkdir(path.join(archivedir(), archiveFolder)); const model = await this.structure({ conid, database }); @@ -351,7 +382,8 @@ module.exports = { }, generateDeploySql_meta: true, - async generateDeploySql({ conid, database, archiveFolder }) { + async generateDeploySql({ conid, database, archiveFolder }, req) { + testConnectionPermission(conid, req); const opened = await this.ensureOpened(conid, database); const res = await this.sendRequest(opened, { msgtype: 'generateDeploySql', diff --git a/packages/api/src/controllers/serverConnections.js b/packages/api/src/controllers/serverConnections.js index 28fd52cde..7f6ad2b6d 100644 --- a/packages/api/src/controllers/serverConnections.js +++ b/packages/api/src/controllers/serverConnections.js @@ -7,6 +7,7 @@ const { handleProcessCommunication } = require('../utility/processComm'); const lock = new AsyncLock(); const config = require('./config'); const processArgs = require('../utility/processArgs'); +const { testConnectionPermission } = require('../utility/hasPermission'); module.exports = { opened: [], @@ -90,19 +91,22 @@ module.exports = { }, disconnect_meta: true, - async disconnect({ conid }) { + async disconnect({ conid }, req) { + testConnectionPermission(conid, req); await this.close(conid, true); return { status: 'ok' }; }, listDatabases_meta: true, - async listDatabases({ conid }) { + async listDatabases({ conid }, req) { + testConnectionPermission(conid, req); const opened = await this.ensureOpened(conid); return opened.databases; }, version_meta: true, - async version({ conid }) { + async version({ conid }, req) { + testConnectionPermission(conid, req); const opened = await this.ensureOpened(conid); return opened.version; }, @@ -132,7 +136,8 @@ module.exports = { }, refresh_meta: true, - async refresh({ conid, keepOpen }) { + async refresh({ conid, keepOpen }, req) { + testConnectionPermission(conid, req); if (!keepOpen) this.close(conid); await this.ensureOpened(conid); @@ -140,7 +145,8 @@ module.exports = { }, createDatabase_meta: true, - async createDatabase({ conid, name }) { + async createDatabase({ conid, name }, req) { + testConnectionPermission(conid, req); const opened = await this.ensureOpened(conid); if (opened.connection.isReadOnly) return false; opened.subprocess.send({ msgtype: 'createDatabase', name }); diff --git a/packages/api/src/utility/hasPermission.js b/packages/api/src/utility/hasPermission.js index 54696b5b1..04d28112e 100644 --- a/packages/api/src/utility/hasPermission.js +++ b/packages/api/src/utility/hasPermission.js @@ -4,12 +4,21 @@ const _ = require('lodash'); const userPermissions = {}; function hasPermission(tested, req) { + if (!req) { + // request object not available, allow all + return true; + } const { user } = (req && req.auth) || {}; const key = user || ''; const logins = getLogins(); - if (!userPermissions[key] && logins) { - const login = logins.find(x => x.login == user); - userPermissions[key] = compilePermissions(login ? login.permissions : null); + + if (!userPermissions[key]) { + if (logins) { + const login = logins.find(x => x.login == user); + userPermissions[key] = compilePermissions(login ? login.permissions : null); + } else { + userPermissions[key] = compilePermissions(process.env.PERMISSIONS); + } } return testPermission(tested, userPermissions[key]); } @@ -50,7 +59,26 @@ function getLogins() { return loginsCache; } +function connectionHasPermission(connection, req) { + if (!connection) { + return true; + } + if (_.isString(connection)) { + return hasPermission(`connections/${connection}`, req); + } else { + return hasPermission(`connections/${connection._id}`, req); + } +} + +function testConnectionPermission(connection, req) { + if (!connectionHasPermission(connection, req)) { + throw new Error('Connection permission not granted'); + } +} + module.exports = { hasPermission, getLogins, + connectionHasPermission, + testConnectionPermission, }; diff --git a/packages/api/src/utility/useController.js b/packages/api/src/utility/useController.js index 6ab16c676..8ee431a42 100644 --- a/packages/api/src/utility/useController.js +++ b/packages/api/src/utility/useController.js @@ -47,7 +47,6 @@ module.exports = function useController(app, electron, route, controller) { let method = 'post'; let raw = false; - let rawParams = false; // if (_.isString(meta)) { // method = meta; @@ -55,7 +54,6 @@ module.exports = function useController(app, electron, route, controller) { if (_.isPlainObject(meta)) { method = meta.method; raw = meta.raw; - rawParams = meta.rawParams; } if (raw) { @@ -67,9 +65,7 @@ module.exports = function useController(app, electron, route, controller) { // controller._init_called = true; // } try { - let params = [{ ...req.body, ...req.query }, req]; - if (rawParams) params = [req, res]; - const data = await controller[key](...params); + const data = await controller[key]({ ...req.body, ...req.query }, req); res.json(data); } catch (e) { console.log(e);