permissions for connections

This commit is contained in:
Jan Prochazka
2022-07-17 10:03:17 +02:00
parent 55efdef181
commit 3a5301af6b
7 changed files with 119 additions and 43 deletions

View File

@@ -49,3 +49,12 @@ ENGINE_relational=mariadb@dbgate-plugin-mysql
READONLY_relational=1 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 # 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=~*

View File

@@ -29,7 +29,7 @@ module.exports = {
async get(_params, req) { async get(_params, req) {
const logins = getLogins(); const logins = getLogins();
const login = logins ? logins.find(x => x.login == (req.auth && req.auth.user)) : null; 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 { return {
runAsPortal: !!connections.portalConnections, runAsPortal: !!connections.portalConnections,

View File

@@ -13,6 +13,7 @@ const JsonLinesDatabase = require('../utility/JsonLinesDatabase');
const processArgs = require('../utility/processArgs'); const processArgs = require('../utility/processArgs');
const { safeJsonParse } = require('dbgate-tools'); const { safeJsonParse } = require('dbgate-tools');
const platformInfo = require('../utility/platformInfo'); const platformInfo = require('../utility/platformInfo');
const { connectionHasPermission, testConnectionPermission } = require('../utility/hasPermission');
function getNamedArgs() { function getNamedArgs() {
const res = {}; const res = {};
@@ -165,12 +166,12 @@ module.exports = {
}, },
list_meta: true, list_meta: true,
async list() { async list(_params, req) {
if (portalConnections) { if (portalConnections) {
if (platformInfo.allowShellConnection) return 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, test_meta: true,
@@ -217,16 +218,18 @@ module.exports = {
}, },
update_meta: true, update_meta: true,
async update({ _id, values }) { async update({ _id, values }, req) {
if (portalConnections) return; if (portalConnections) return;
testConnectionPermission(_id, req);
const res = await this.datastore.patch(_id, values); const res = await this.datastore.patch(_id, values);
socket.emitChanged('connection-list-changed'); socket.emitChanged('connection-list-changed');
return res; return res;
}, },
updateDatabase_meta: true, updateDatabase_meta: true,
async updateDatabase({ conid, database, values }) { async updateDatabase({ conid, database, values }, req) {
if (portalConnections) return; if (portalConnections) return;
testConnectionPermission(conid, req);
const conn = await this.datastore.get(conid); const conn = await this.datastore.get(conid);
let databases = (conn && conn.databases) || []; let databases = (conn && conn.databases) || [];
if (databases.find(x => x.name == database)) { if (databases.find(x => x.name == database)) {
@@ -242,8 +245,9 @@ module.exports = {
}, },
delete_meta: true, delete_meta: true,
async delete(connection) { async delete(connection, req) {
if (portalConnections) return; if (portalConnections) return;
testConnectionPermission(connection, req);
const res = await this.datastore.remove(connection._id); const res = await this.datastore.remove(connection._id);
socket.emitChanged('connection-list-changed'); socket.emitChanged('connection-list-changed');
return res; return res;
@@ -260,7 +264,8 @@ module.exports = {
}, },
get_meta: true, get_meta: true,
async get({ conid }) { async get({ conid }, req) {
testConnectionPermission(conid, req);
return this.getCore({ conid, mask: true }); return this.getCore({ conid, mask: true });
}, },

View File

@@ -26,6 +26,7 @@ const generateDeploySql = require('../shell/generateDeploySql');
const { createTwoFilesPatch } = require('diff'); const { createTwoFilesPatch } = require('diff');
const diff2htmlPage = require('../utility/diff2htmlPage'); const diff2htmlPage = require('../utility/diff2htmlPage');
const processArgs = require('../utility/processArgs'); const processArgs = require('../utility/processArgs');
const { testConnectionPermission } = require('../utility/hasPermission');
module.exports = { module.exports = {
/** @type {import('dbgate-types').OpenedDatabaseConnection[]} */ /** @type {import('dbgate-types').OpenedDatabaseConnection[]} */
@@ -130,7 +131,8 @@ module.exports = {
}, },
queryData_meta: true, 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}`); console.log(`Processing query, conid=${conid}, database=${database}, sql=${sql}`);
const opened = await this.ensureOpened(conid, database); const opened = await this.ensureOpened(conid, database);
// if (opened && opened.status && opened.status.name == 'error') { // if (opened && opened.status && opened.status.name == 'error') {
@@ -141,14 +143,16 @@ module.exports = {
}, },
sqlSelect_meta: true, 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 opened = await this.ensureOpened(conid, database);
const res = await this.sendRequest(opened, { msgtype: 'sqlSelect', select }); const res = await this.sendRequest(opened, { msgtype: 'sqlSelect', select });
return res; return res;
}, },
runScript_meta: true, 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}`); console.log(`Processing script, conid=${conid}, database=${database}, sql=${sql}`);
const opened = await this.ensureOpened(conid, database); const opened = await this.ensureOpened(conid, database);
const res = await this.sendRequest(opened, { msgtype: 'runScript', sql }); const res = await this.sendRequest(opened, { msgtype: 'runScript', sql });
@@ -156,13 +160,15 @@ module.exports = {
}, },
collectionData_meta: true, 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 opened = await this.ensureOpened(conid, database);
const res = await this.sendRequest(opened, { msgtype: 'collectionData', options }); const res = await this.sendRequest(opened, { msgtype: 'collectionData', options });
return res.result || null; 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 opened = await this.ensureOpened(conid, database);
const res = await this.sendRequest(opened, { msgtype, ...args }); const res = await this.sendRequest(opened, { msgtype, ...args });
if (res.errorMessage) { if (res.errorMessage) {
@@ -176,32 +182,38 @@ module.exports = {
}, },
loadKeys_meta: true, 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 }); return this.loadDataCore('loadKeys', { conid, database, root, filter });
}, },
exportKeys_meta: true, exportKeys_meta: true,
async exportKeys({ conid, database, options }) { async exportKeys({ conid, database, options }, req) {
testConnectionPermission(conid, req);
return this.loadDataCore('exportKeys', { conid, database, options }); return this.loadDataCore('exportKeys', { conid, database, options });
}, },
loadKeyInfo_meta: true, loadKeyInfo_meta: true,
async loadKeyInfo({ conid, database, key }) { async loadKeyInfo({ conid, database, key }, req) {
testConnectionPermission(conid, req);
return this.loadDataCore('loadKeyInfo', { conid, database, key }); return this.loadDataCore('loadKeyInfo', { conid, database, key });
}, },
loadKeyTableRange_meta: true, 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 }); return this.loadDataCore('loadKeyTableRange', { conid, database, key, cursor, count });
}, },
loadFieldValues_meta: true, 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 }); return this.loadDataCore('loadFieldValues', { conid, database, schemaName, pureName, field, search });
}, },
callMethod_meta: true, 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 }); return this.loadDataCore('callMethod', { conid, database, method, args });
// const opened = await this.ensureOpened(conid, database); // const opened = await this.ensureOpened(conid, database);
@@ -213,7 +225,8 @@ module.exports = {
}, },
updateCollection_meta: true, 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 opened = await this.ensureOpened(conid, database);
const res = await this.sendRequest(opened, { msgtype: 'updateCollection', changeSet }); const res = await this.sendRequest(opened, { msgtype: 'updateCollection', changeSet });
if (res.errorMessage) { if (res.errorMessage) {
@@ -225,7 +238,14 @@ module.exports = {
}, },
status_meta: true, 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); const existing = this.opened.find(x => x.conid == conid && x.database == database);
if (existing) { if (existing) {
return { return {
@@ -247,7 +267,8 @@ module.exports = {
}, },
ping_meta: true, 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); let existing = this.opened.find(x => x.conid == conid && x.database == database);
if (existing) { if (existing) {
@@ -263,7 +284,8 @@ module.exports = {
}, },
refresh_meta: true, refresh_meta: true,
async refresh({ conid, database, keepOpen }) { async refresh({ conid, database, keepOpen }, req) {
testConnectionPermission(conid, req);
if (!keepOpen) this.close(conid, database); if (!keepOpen) this.close(conid, database);
await this.ensureOpened(conid, database); await this.ensureOpened(conid, database);
@@ -271,7 +293,8 @@ module.exports = {
}, },
syncModel_meta: true, syncModel_meta: true,
async syncModel({ conid, database, isFullRefresh }) { async syncModel({ conid, database, isFullRefresh }, req) {
testConnectionPermission(conid, req);
const conn = await this.ensureOpened(conid, database); const conn = await this.ensureOpened(conid, database);
conn.subprocess.send({ msgtype: 'syncModel', isFullRefresh }); conn.subprocess.send({ msgtype: 'syncModel', isFullRefresh });
return { status: 'ok' }; return { status: 'ok' };
@@ -301,13 +324,15 @@ module.exports = {
}, },
disconnect_meta: true, disconnect_meta: true,
async disconnect({ conid, database }) { async disconnect({ conid, database }, req) {
testConnectionPermission(conid, req);
await this.close(conid, database, true); await this.close(conid, database, true);
return { status: 'ok' }; return { status: 'ok' };
}, },
structure_meta: true, structure_meta: true,
async structure({ conid, database }) { async structure({ conid, database }, req) {
testConnectionPermission(conid, req);
if (conid == '__model') { if (conid == '__model') {
const model = await importDbModel(database); const model = await importDbModel(database);
return model; return model;
@@ -324,14 +349,19 @@ module.exports = {
}, },
serverVersion_meta: true, serverVersion_meta: true,
async serverVersion({ conid, database }) { async serverVersion({ conid, database }, req) {
if (!conid) {
return null;
}
testConnectionPermission(conid, req);
if (!conid) return null; if (!conid) return null;
const opened = await this.ensureOpened(conid, database); const opened = await this.ensureOpened(conid, database);
return opened.serverVersion || null; return opened.serverVersion || null;
}, },
sqlPreview_meta: true, sqlPreview_meta: true,
async sqlPreview({ conid, database, objects, options }) { async sqlPreview({ conid, database, objects, options }, req) {
testConnectionPermission(conid, req);
// wait for structure // wait for structure
await this.structure({ conid, database }); await this.structure({ conid, database });
@@ -341,7 +371,8 @@ module.exports = {
}, },
exportModel_meta: true, exportModel_meta: true,
async exportModel({ conid, database }) { async exportModel({ conid, database }, req) {
testConnectionPermission(conid, req);
const archiveFolder = await archive.getNewArchiveFolder({ database }); const archiveFolder = await archive.getNewArchiveFolder({ database });
await fs.mkdir(path.join(archivedir(), archiveFolder)); await fs.mkdir(path.join(archivedir(), archiveFolder));
const model = await this.structure({ conid, database }); const model = await this.structure({ conid, database });
@@ -351,7 +382,8 @@ module.exports = {
}, },
generateDeploySql_meta: true, 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 opened = await this.ensureOpened(conid, database);
const res = await this.sendRequest(opened, { const res = await this.sendRequest(opened, {
msgtype: 'generateDeploySql', msgtype: 'generateDeploySql',

View File

@@ -7,6 +7,7 @@ const { handleProcessCommunication } = require('../utility/processComm');
const lock = new AsyncLock(); const lock = new AsyncLock();
const config = require('./config'); const config = require('./config');
const processArgs = require('../utility/processArgs'); const processArgs = require('../utility/processArgs');
const { testConnectionPermission } = require('../utility/hasPermission');
module.exports = { module.exports = {
opened: [], opened: [],
@@ -90,19 +91,22 @@ module.exports = {
}, },
disconnect_meta: true, disconnect_meta: true,
async disconnect({ conid }) { async disconnect({ conid }, req) {
testConnectionPermission(conid, req);
await this.close(conid, true); await this.close(conid, true);
return { status: 'ok' }; return { status: 'ok' };
}, },
listDatabases_meta: true, listDatabases_meta: true,
async listDatabases({ conid }) { async listDatabases({ conid }, req) {
testConnectionPermission(conid, req);
const opened = await this.ensureOpened(conid); const opened = await this.ensureOpened(conid);
return opened.databases; return opened.databases;
}, },
version_meta: true, version_meta: true,
async version({ conid }) { async version({ conid }, req) {
testConnectionPermission(conid, req);
const opened = await this.ensureOpened(conid); const opened = await this.ensureOpened(conid);
return opened.version; return opened.version;
}, },
@@ -132,7 +136,8 @@ module.exports = {
}, },
refresh_meta: true, refresh_meta: true,
async refresh({ conid, keepOpen }) { async refresh({ conid, keepOpen }, req) {
testConnectionPermission(conid, req);
if (!keepOpen) this.close(conid); if (!keepOpen) this.close(conid);
await this.ensureOpened(conid); await this.ensureOpened(conid);
@@ -140,7 +145,8 @@ module.exports = {
}, },
createDatabase_meta: true, createDatabase_meta: true,
async createDatabase({ conid, name }) { async createDatabase({ conid, name }, req) {
testConnectionPermission(conid, req);
const opened = await this.ensureOpened(conid); const opened = await this.ensureOpened(conid);
if (opened.connection.isReadOnly) return false; if (opened.connection.isReadOnly) return false;
opened.subprocess.send({ msgtype: 'createDatabase', name }); opened.subprocess.send({ msgtype: 'createDatabase', name });

View File

@@ -4,12 +4,21 @@ const _ = require('lodash');
const userPermissions = {}; const userPermissions = {};
function hasPermission(tested, req) { function hasPermission(tested, req) {
if (!req) {
// request object not available, allow all
return true;
}
const { user } = (req && req.auth) || {}; const { user } = (req && req.auth) || {};
const key = user || ''; const key = user || '';
const logins = getLogins(); const logins = getLogins();
if (!userPermissions[key] && logins) {
if (!userPermissions[key]) {
if (logins) {
const login = logins.find(x => x.login == user); const login = logins.find(x => x.login == user);
userPermissions[key] = compilePermissions(login ? login.permissions : null); userPermissions[key] = compilePermissions(login ? login.permissions : null);
} else {
userPermissions[key] = compilePermissions(process.env.PERMISSIONS);
}
} }
return testPermission(tested, userPermissions[key]); return testPermission(tested, userPermissions[key]);
} }
@@ -50,7 +59,26 @@ function getLogins() {
return loginsCache; 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 = { module.exports = {
hasPermission, hasPermission,
getLogins, getLogins,
connectionHasPermission,
testConnectionPermission,
}; };

View File

@@ -47,7 +47,6 @@ module.exports = function useController(app, electron, route, controller) {
let method = 'post'; let method = 'post';
let raw = false; let raw = false;
let rawParams = false;
// if (_.isString(meta)) { // if (_.isString(meta)) {
// method = meta; // method = meta;
@@ -55,7 +54,6 @@ module.exports = function useController(app, electron, route, controller) {
if (_.isPlainObject(meta)) { if (_.isPlainObject(meta)) {
method = meta.method; method = meta.method;
raw = meta.raw; raw = meta.raw;
rawParams = meta.rawParams;
} }
if (raw) { if (raw) {
@@ -67,9 +65,7 @@ module.exports = function useController(app, electron, route, controller) {
// controller._init_called = true; // controller._init_called = true;
// } // }
try { try {
let params = [{ ...req.body, ...req.query }, req]; const data = await controller[key]({ ...req.body, ...req.query }, req);
if (rawParams) params = [req, res];
const data = await controller[key](...params);
res.json(data); res.json(data);
} catch (e) { } catch (e) {
console.log(e); console.log(e);