SYNC: Merge pull request #8 from dbgate/feature/db-table-permissions

This commit is contained in:
Jan Prochazka
2025-08-22 09:45:32 +02:00
committed by Diflow
parent f48b4a6c62
commit d2d6e2f554
28 changed files with 1316 additions and 277 deletions

View File

@@ -2,6 +2,7 @@ DEVMODE=1
SHELL_SCRIPTING=1 SHELL_SCRIPTING=1
ALLOW_DBGATE_PRIVATE_CLOUD=1 ALLOW_DBGATE_PRIVATE_CLOUD=1
DEVWEB=1 DEVWEB=1
LOCAL_AUTH_PROXY=1
# LOCAL_AI_GATEWAY=true # LOCAL_AI_GATEWAY=true
# REDIRECT_TO_DBGATE_CLOUD_LOGIN=1 # REDIRECT_TO_DBGATE_CLOUD_LOGIN=1

View File

@@ -36,12 +36,24 @@ class AuthProviderBase {
return !!req?.user || !!req?.auth; return !!req?.user || !!req?.auth;
} }
getCurrentPermissions(req) { async getCurrentPermissions(req) {
const login = this.getCurrentLogin(req); const login = this.getCurrentLogin(req);
const permissions = process.env[`LOGIN_PERMISSIONS_${login}`]; const permissions = process.env[`LOGIN_PERMISSIONS_${login}`];
return permissions || process.env.PERMISSIONS; return permissions || process.env.PERMISSIONS;
} }
async checkCurrentConnectionPermission(req, conid) {
return true;
}
async getCurrentDatabasePermissions(req) {
return [];
}
async getCurrentTablePermissions(req) {
return [];
}
getLoginPageConnections() { getLoginPageConnections() {
return null; return null;
} }

View File

@@ -51,6 +51,7 @@ function authMiddleware(req, res, next) {
'/auth/oauth-token', '/auth/oauth-token',
'/auth/login', '/auth/login',
'/auth/redirect', '/auth/redirect',
'/redirect',
'/stream', '/stream',
'/storage/get-connections-for-login-page', '/storage/get-connections-for-login-page',
'/storage/set-admin-password', '/storage/set-admin-password',
@@ -139,9 +140,9 @@ module.exports = {
const accessToken = jwt.sign( const accessToken = jwt.sign(
{ {
login: 'superadmin', login: 'superadmin',
permissions: await storage.loadSuperadminPermissions(),
roleId: -3, roleId: -3,
licenseUid, licenseUid,
amoid: 'superadmin',
}, },
getTokenSecret(), getTokenSecret(),
{ {

View File

@@ -3,7 +3,7 @@ const os = require('os');
const path = require('path'); const path = require('path');
const axios = require('axios'); const axios = require('axios');
const { datadir, getLogsFilePath } = require('../utility/directories'); const { datadir, getLogsFilePath } = require('../utility/directories');
const { hasPermission } = require('../utility/hasPermission'); const { hasPermission, loadPermissionsFromRequest } = require('../utility/hasPermission');
const socket = require('../utility/socket'); const socket = require('../utility/socket');
const _ = require('lodash'); const _ = require('lodash');
const AsyncLock = require('async-lock'); const AsyncLock = require('async-lock');
@@ -46,7 +46,7 @@ module.exports = {
async get(_params, req) { async get(_params, req) {
const authProvider = getAuthProviderFromReq(req); const authProvider = getAuthProviderFromReq(req);
const login = authProvider.getCurrentLogin(req); const login = authProvider.getCurrentLogin(req);
const permissions = authProvider.getCurrentPermissions(req); const permissions = await authProvider.getCurrentPermissions(req);
const isUserLoggedIn = authProvider.isUserLoggedIn(req); const isUserLoggedIn = authProvider.isUserLoggedIn(req);
const singleConid = authProvider.getSingleConnectionId(req); const singleConid = authProvider.getSingleConnectionId(req);
@@ -280,7 +280,8 @@ module.exports = {
updateSettings_meta: true, updateSettings_meta: true,
async updateSettings(values, req) { async updateSettings(values, req) {
if (!hasPermission(`settings/change`, req)) return false; const loadedPermissions = await loadPermissionsFromRequest(req);
if (!hasPermission(`settings/change`, loadedPermissions)) return false;
cachedSettingsValue = null; cachedSettingsValue = null;
const res = await lock.acquire('settings', async () => { const res = await lock.acquire('settings', async () => {
@@ -392,7 +393,8 @@ module.exports = {
exportConnectionsAndSettings_meta: true, exportConnectionsAndSettings_meta: true,
async exportConnectionsAndSettings(_params, req) { async exportConnectionsAndSettings(_params, req) {
if (!hasPermission(`admin/config`, req)) { const loadedPermissions = await loadPermissionsFromRequest(req);
if (!hasPermission(`admin/config`, loadedPermissions)) {
throw new Error('Permission denied: admin/config'); throw new Error('Permission denied: admin/config');
} }
@@ -416,7 +418,8 @@ module.exports = {
importConnectionsAndSettings_meta: true, importConnectionsAndSettings_meta: true,
async importConnectionsAndSettings({ db }, req) { async importConnectionsAndSettings({ db }, req) {
if (!hasPermission(`admin/config`, req)) { const loadedPermissions = await loadPermissionsFromRequest(req);
if (!hasPermission(`admin/config`, loadedPermissions)) {
throw new Error('Permission denied: admin/config'); throw new Error('Permission denied: admin/config');
} }

View File

@@ -14,7 +14,7 @@ const JsonLinesDatabase = require('../utility/JsonLinesDatabase');
const processArgs = require('../utility/processArgs'); const processArgs = require('../utility/processArgs');
const { safeJsonParse, getLogger, extractErrorLogData } = require('dbgate-tools'); const { safeJsonParse, getLogger, extractErrorLogData } = require('dbgate-tools');
const platformInfo = require('../utility/platformInfo'); const platformInfo = require('../utility/platformInfo');
const { connectionHasPermission, testConnectionPermission } = require('../utility/hasPermission'); const { connectionHasPermission, testConnectionPermission, loadPermissionsFromRequest } = require('../utility/hasPermission');
const pipeForkLogs = require('../utility/pipeForkLogs'); const pipeForkLogs = require('../utility/pipeForkLogs');
const requireEngineDriver = require('../utility/requireEngineDriver'); const requireEngineDriver = require('../utility/requireEngineDriver');
const { getAuthProviderById } = require('../auth/authProvider'); const { getAuthProviderById } = require('../auth/authProvider');
@@ -227,6 +227,7 @@ module.exports = {
list_meta: true, list_meta: true,
async list(_params, req) { async list(_params, req) {
const storage = require('./storage'); const storage = require('./storage');
const loadedPermissions = await loadPermissionsFromRequest(req);
const storageConnections = await storage.connections(req); const storageConnections = await storage.connections(req);
if (storageConnections) { if (storageConnections) {
@@ -234,9 +235,9 @@ module.exports = {
} }
if (portalConnections) { if (portalConnections) {
if (platformInfo.allowShellConnection) return portalConnections; if (platformInfo.allowShellConnection) return portalConnections;
return portalConnections.map(maskConnection).filter(x => connectionHasPermission(x, req)); return portalConnections.map(maskConnection).filter(x => connectionHasPermission(x, loadedPermissions));
} }
return (await this.datastore.find()).filter(x => connectionHasPermission(x, req)); return (await this.datastore.find()).filter(x => connectionHasPermission(x, loadedPermissions));
}, },
async getUsedEngines() { async getUsedEngines() {
@@ -375,7 +376,7 @@ module.exports = {
update_meta: true, update_meta: true,
async update({ _id, values }, req) { async update({ _id, values }, req) {
if (portalConnections) return; if (portalConnections) return;
testConnectionPermission(_id, req); await 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;
@@ -392,7 +393,7 @@ module.exports = {
updateDatabase_meta: true, updateDatabase_meta: true,
async updateDatabase({ conid, database, values }, req) { async updateDatabase({ conid, database, values }, req) {
if (portalConnections) return; if (portalConnections) return;
testConnectionPermission(conid, req); await 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)) {
@@ -410,7 +411,7 @@ module.exports = {
delete_meta: true, delete_meta: true,
async delete(connection, req) { async delete(connection, req) {
if (portalConnections) return; if (portalConnections) return;
testConnectionPermission(connection, req); await 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;
@@ -452,7 +453,7 @@ module.exports = {
_id: '__model', _id: '__model',
}; };
} }
testConnectionPermission(conid, req); await testConnectionPermission(conid, req);
return this.getCore({ conid, mask: true }); return this.getCore({ conid, mask: true });
}, },

View File

@@ -29,7 +29,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'); const { testConnectionPermission, hasPermission, loadPermissionsFromRequest, loadTablePermissionsFromRequest, getTablePermissionRole, loadDatabasePermissionsFromRequest, getDatabasePermissionRole, getTablePermissionRoleLevelIndex, testDatabaseRolePermission } = require('../utility/hasPermission');
const { MissingCredentialsError } = require('../utility/exceptions'); const { MissingCredentialsError } = require('../utility/exceptions');
const pipeForkLogs = require('../utility/pipeForkLogs'); const pipeForkLogs = require('../utility/pipeForkLogs');
const crypto = require('crypto'); const crypto = require('crypto');
@@ -100,7 +100,7 @@ module.exports = {
socket.emitChanged(`database-status-changed`, { conid, database }); socket.emitChanged(`database-status-changed`, { conid, database });
}, },
handle_ping() {}, handle_ping() { },
// session event handlers // session event handlers
@@ -235,7 +235,7 @@ module.exports = {
queryData_meta: true, queryData_meta: true,
async queryData({ conid, database, sql }, req) { async queryData({ conid, database, sql }, req) {
testConnectionPermission(conid, req); await testConnectionPermission(conid, req);
logger.info({ conid, database, sql }, 'DBGM-00007 Processing query'); logger.info({ conid, database, sql }, 'DBGM-00007 Processing query');
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') {
@@ -247,7 +247,7 @@ module.exports = {
sqlSelect_meta: true, sqlSelect_meta: true,
async sqlSelect({ conid, database, select, auditLogSessionGroup }, req) { async sqlSelect({ conid, database, select, auditLogSessionGroup }, req) {
testConnectionPermission(conid, req); await testConnectionPermission(conid, req);
const opened = await this.ensureOpened(conid, database); const opened = await this.ensureOpened(conid, database);
const res = await this.sendRequest( const res = await this.sendRequest(
opened, opened,
@@ -256,24 +256,23 @@ module.exports = {
auditLogger: auditLogger:
auditLogSessionGroup && select?.from?.name?.pureName auditLogSessionGroup && select?.from?.name?.pureName
? response => { ? response => {
sendToAuditLog(req, { sendToAuditLog(req, {
category: 'dbop', category: 'dbop',
component: 'DatabaseConnectionsController', component: 'DatabaseConnectionsController',
event: 'sql.select', event: 'sql.select',
action: 'select', action: 'select',
severity: 'info', severity: 'info',
conid, conid,
database, database,
schemaName: select?.from?.name?.schemaName, schemaName: select?.from?.name?.schemaName,
pureName: select?.from?.name?.pureName, pureName: select?.from?.name?.pureName,
sumint1: response?.rows?.length, sumint1: response?.rows?.length,
sessionParam: `${conid}::${database}::${select?.from?.name?.schemaName || '0'}::${ sessionParam: `${conid}::${database}::${select?.from?.name?.schemaName || '0'}::${select?.from?.name?.pureName
select?.from?.name?.pureName
}`, }`,
sessionGroup: auditLogSessionGroup, sessionGroup: auditLogSessionGroup,
message: `Loaded table data from ${select?.from?.name?.pureName}`, message: `Loaded table data from ${select?.from?.name?.pureName}`,
}); });
} }
: null, : null,
} }
); );
@@ -282,7 +281,9 @@ module.exports = {
runScript_meta: true, runScript_meta: true,
async runScript({ conid, database, sql, useTransaction, logMessage }, req) { async runScript({ conid, database, sql, useTransaction, logMessage }, req) {
testConnectionPermission(conid, req); const loadedPermissions = await loadPermissionsFromRequest(req);
await testConnectionPermission(conid, req, loadedPermissions);
await testDatabaseRolePermission(conid, database, 'run_script', req);
logger.info({ conid, database, sql }, 'DBGM-00008 Processing script'); logger.info({ conid, database, sql }, 'DBGM-00008 Processing script');
const opened = await this.ensureOpened(conid, database); const opened = await this.ensureOpened(conid, database);
sendToAuditLog(req, { sendToAuditLog(req, {
@@ -303,7 +304,7 @@ module.exports = {
runOperation_meta: true, runOperation_meta: true,
async runOperation({ conid, database, operation, useTransaction }, req) { async runOperation({ conid, database, operation, useTransaction }, req) {
testConnectionPermission(conid, req); await testConnectionPermission(conid, req);
logger.info({ conid, database, operation }, 'DBGM-00009 Processing operation'); logger.info({ conid, database, operation }, 'DBGM-00009 Processing operation');
sendToAuditLog(req, { sendToAuditLog(req, {
@@ -325,7 +326,7 @@ module.exports = {
collectionData_meta: true, collectionData_meta: true,
async collectionData({ conid, database, options, auditLogSessionGroup }, req) { async collectionData({ conid, database, options, auditLogSessionGroup }, req) {
testConnectionPermission(conid, req); await testConnectionPermission(conid, req);
const opened = await this.ensureOpened(conid, database); const opened = await this.ensureOpened(conid, database);
const res = await this.sendRequest( const res = await this.sendRequest(
opened, opened,
@@ -334,21 +335,21 @@ module.exports = {
auditLogger: auditLogger:
auditLogSessionGroup && options?.pureName auditLogSessionGroup && options?.pureName
? response => { ? response => {
sendToAuditLog(req, { sendToAuditLog(req, {
category: 'dbop', category: 'dbop',
component: 'DatabaseConnectionsController', component: 'DatabaseConnectionsController',
event: 'nosql.collectionData', event: 'nosql.collectionData',
action: 'select', action: 'select',
severity: 'info', severity: 'info',
conid, conid,
database, database,
pureName: options?.pureName, pureName: options?.pureName,
sumint1: response?.result?.rows?.length, sumint1: response?.result?.rows?.length,
sessionParam: `${conid}::${database}::${options?.pureName}`, sessionParam: `${conid}::${database}::${options?.pureName}`,
sessionGroup: auditLogSessionGroup, sessionGroup: auditLogSessionGroup,
message: `Loaded collection data ${options?.pureName}`, message: `Loaded collection data ${options?.pureName}`,
}); });
} }
: null, : null,
} }
); );
@@ -356,7 +357,7 @@ module.exports = {
}, },
async loadDataCore(msgtype, { conid, database, ...args }, req) { async loadDataCore(msgtype, { conid, database, ...args }, req) {
testConnectionPermission(conid, req); await 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) {
@@ -371,7 +372,7 @@ module.exports = {
schemaList_meta: true, schemaList_meta: true,
async schemaList({ conid, database }, req) { async schemaList({ conid, database }, req) {
testConnectionPermission(conid, req); await testConnectionPermission(conid, req);
return this.loadDataCore('schemaList', { conid, database }); return this.loadDataCore('schemaList', { conid, database });
}, },
@@ -383,43 +384,43 @@ module.exports = {
loadKeys_meta: true, loadKeys_meta: true,
async loadKeys({ conid, database, root, filter, limit }, req) { async loadKeys({ conid, database, root, filter, limit }, req) {
testConnectionPermission(conid, req); await testConnectionPermission(conid, req);
return this.loadDataCore('loadKeys', { conid, database, root, filter, limit }); return this.loadDataCore('loadKeys', { conid, database, root, filter, limit });
}, },
scanKeys_meta: true, scanKeys_meta: true,
async scanKeys({ conid, database, root, pattern, cursor, count }, req) { async scanKeys({ conid, database, root, pattern, cursor, count }, req) {
testConnectionPermission(conid, req); await testConnectionPermission(conid, req);
return this.loadDataCore('scanKeys', { conid, database, root, pattern, cursor, count }); return this.loadDataCore('scanKeys', { conid, database, root, pattern, cursor, count });
}, },
exportKeys_meta: true, exportKeys_meta: true,
async exportKeys({ conid, database, options }, req) { async exportKeys({ conid, database, options }, req) {
testConnectionPermission(conid, req); await 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 }, req) { async loadKeyInfo({ conid, database, key }, req) {
testConnectionPermission(conid, req); await 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 }, req) { async loadKeyTableRange({ conid, database, key, cursor, count }, req) {
testConnectionPermission(conid, req); await 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, dataType }, req) { async loadFieldValues({ conid, database, schemaName, pureName, field, search, dataType }, req) {
testConnectionPermission(conid, req); await testConnectionPermission(conid, req);
return this.loadDataCore('loadFieldValues', { conid, database, schemaName, pureName, field, search, dataType }); return this.loadDataCore('loadFieldValues', { conid, database, schemaName, pureName, field, search, dataType });
}, },
callMethod_meta: true, callMethod_meta: true,
async callMethod({ conid, database, method, args }, req) { async callMethod({ conid, database, method, args }, req) {
testConnectionPermission(conid, req); await 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);
@@ -432,7 +433,8 @@ module.exports = {
updateCollection_meta: true, updateCollection_meta: true,
async updateCollection({ conid, database, changeSet }, req) { async updateCollection({ conid, database, changeSet }, req) {
testConnectionPermission(conid, req); await 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) {
@@ -443,6 +445,36 @@ module.exports = {
return res.result || null; return res.result || null;
}, },
saveTableData_meta: true,
async saveTableData({ conid, database, changeSet }, req) {
await testConnectionPermission(conid, req);
const databasePermissions = await loadDatabasePermissionsFromRequest(req);
const tablePermissions = await loadTablePermissionsFromRequest(req);
const fieldsAndRoles = [
[changeSet.inserts, 'create_update_delete'],
[changeSet.deletes, 'create_update_delete'],
[changeSet.updates, 'update_only'],
]
for (const [operations, requiredRole] of fieldsAndRoles) {
for (const operation of operations) {
const role = getTablePermissionRole(conid, database, 'tables', operation.schemaName, operation.pureName, tablePermissions, databasePermissions);
if (getTablePermissionRoleLevelIndex(role) < getTablePermissionRoleLevelIndex(requiredRole)) {
throw new Error('Permission not granted');
}
}
}
const opened = await this.ensureOpened(conid, database);
const res = await this.sendRequest(opened, { msgtype: 'saveTableData', changeSet });
if (res.errorMessage) {
return {
errorMessage: res.errorMessage,
};
}
return res.result || null;
},
status_meta: true, status_meta: true,
async status({ conid, database }, req) { async status({ conid, database }, req) {
if (!conid) { if (!conid) {
@@ -451,7 +483,7 @@ module.exports = {
message: 'No connection', message: 'No connection',
}; };
} }
testConnectionPermission(conid, req); await 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 {
@@ -474,7 +506,7 @@ module.exports = {
ping_meta: true, ping_meta: true,
async ping({ conid, database }, req) { async ping({ conid, database }, req) {
testConnectionPermission(conid, req); await 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) {
@@ -502,7 +534,7 @@ module.exports = {
refresh_meta: true, refresh_meta: true,
async refresh({ conid, database, keepOpen }, req) { async refresh({ conid, database, keepOpen }, req) {
testConnectionPermission(conid, req); await 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);
@@ -516,7 +548,7 @@ module.exports = {
return { status: 'ok' }; return { status: 'ok' };
} }
testConnectionPermission(conid, req); await 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' };
@@ -553,7 +585,7 @@ module.exports = {
disconnect_meta: true, disconnect_meta: true,
async disconnect({ conid, database }, req) { async disconnect({ conid, database }, req) {
testConnectionPermission(conid, req); await testConnectionPermission(conid, req);
await this.close(conid, database, true); await this.close(conid, database, true);
return { status: 'ok' }; return { status: 'ok' };
}, },
@@ -563,8 +595,9 @@ module.exports = {
if (!conid || !database) { if (!conid || !database) {
return {}; return {};
} }
const loadedPermissions = await loadPermissionsFromRequest(req);
testConnectionPermission(conid, req); await testConnectionPermission(conid, req, loadedPermissions);
if (conid == '__model') { if (conid == '__model') {
const model = await importDbModel(database); const model = await importDbModel(database);
const trans = await loadModelTransform(modelTransFile); const trans = await loadModelTransform(modelTransFile);
@@ -586,6 +619,38 @@ module.exports = {
message: `Loaded database structure for ${database}`, message: `Loaded database structure for ${database}`,
}); });
if (!hasPermission(`all-tables`, loadedPermissions)) {
// filter databases by permissions
const tablePermissions = await loadTablePermissionsFromRequest(req);
const databasePermissions = await loadDatabasePermissionsFromRequest(req);
const databasePermissionRole = getDatabasePermissionRole(conid, database, databasePermissions);
function applyTablePermissionRole(list, objectTypeField) {
const res = [];
for (const item of list ?? []) {
const tablePermissionRole = getTablePermissionRole(conid, database, objectTypeField, item.schemaName, item.pureName, tablePermissions, databasePermissionRole);
if (tablePermissionRole != 'deny') {
res.push({
...item,
tablePermissionRole,
});
}
}
return res;
}
const res = {
...opened.structure,
tables: applyTablePermissionRole(opened.structure.tables, 'tables'),
views: applyTablePermissionRole(opened.structure.views, 'views'),
procedures: applyTablePermissionRole(opened.structure.procedures, 'procedures'),
functions: applyTablePermissionRole(opened.structure.functions, 'functions'),
triggers: applyTablePermissionRole(opened.structure.triggers, 'triggers'),
collections: applyTablePermissionRole(opened.structure.collections, 'collections'),
}
return res;
}
return opened.structure; return opened.structure;
// 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) return existing.status; // if (existing) return existing.status;
@@ -600,7 +665,7 @@ module.exports = {
if (!conid) { if (!conid) {
return null; return null;
} }
testConnectionPermission(conid, req); await 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;
@@ -608,7 +673,7 @@ module.exports = {
sqlPreview_meta: true, sqlPreview_meta: true,
async sqlPreview({ conid, database, objects, options }, req) { async sqlPreview({ conid, database, objects, options }, req) {
testConnectionPermission(conid, req); await testConnectionPermission(conid, req);
// wait for structure // wait for structure
await this.structure({ conid, database }); await this.structure({ conid, database });
@@ -619,7 +684,7 @@ module.exports = {
exportModel_meta: true, exportModel_meta: true,
async exportModel({ conid, database, outputFolder, schema }, req) { async exportModel({ conid, database, outputFolder, schema }, req) {
testConnectionPermission(conid, req); await testConnectionPermission(conid, req);
const realFolder = outputFolder.startsWith('archive:') const realFolder = outputFolder.startsWith('archive:')
? resolveArchiveFolder(outputFolder.substring('archive:'.length)) ? resolveArchiveFolder(outputFolder.substring('archive:'.length))
@@ -637,7 +702,7 @@ module.exports = {
exportModelSql_meta: true, exportModelSql_meta: true,
async exportModelSql({ conid, database, outputFolder, outputFile, schema }, req) { async exportModelSql({ conid, database, outputFolder, outputFile, schema }, req) {
testConnectionPermission(conid, req); await testConnectionPermission(conid, req);
const connection = await connections.getCore({ conid }); const connection = await connections.getCore({ conid });
const driver = requireEngineDriver(connection); const driver = requireEngineDriver(connection);
@@ -651,7 +716,7 @@ module.exports = {
generateDeploySql_meta: true, generateDeploySql_meta: true,
async generateDeploySql({ conid, database, archiveFolder }, req) { async generateDeploySql({ conid, database, archiveFolder }, req) {
testConnectionPermission(conid, req); await 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',
@@ -816,17 +881,17 @@ module.exports = {
return { return {
...(command == 'backup' ...(command == 'backup'
? driver.backupDatabaseCommand( ? driver.backupDatabaseCommand(
connection, connection,
{ outputFile, database, options, selectedTables, skippedTables, argsFormat }, { outputFile, database, options, selectedTables, skippedTables, argsFormat },
// @ts-ignore // @ts-ignore
externalTools externalTools
) )
: driver.restoreDatabaseCommand( : driver.restoreDatabaseCommand(
connection, connection,
{ inputFile, database, options, argsFormat }, { inputFile, database, options, argsFormat },
// @ts-ignore // @ts-ignore
externalTools externalTools
)), )),
transformMessage: driver.transformNativeCommandMessage transformMessage: driver.transformNativeCommandMessage
? message => driver.transformNativeCommandMessage(message, command) ? message => driver.transformNativeCommandMessage(message, command)
: null, : null,
@@ -923,7 +988,7 @@ module.exports = {
executeSessionQuery_meta: true, executeSessionQuery_meta: true,
async executeSessionQuery({ sesid, conid, database, sql }, req) { async executeSessionQuery({ sesid, conid, database, sql }, req) {
testConnectionPermission(conid, req); await testConnectionPermission(conid, req);
logger.info({ sesid, sql }, 'DBGM-00010 Processing query'); logger.info({ sesid, sql }, 'DBGM-00010 Processing query');
sessions.dispatchMessage(sesid, 'Query execution started'); sessions.dispatchMessage(sesid, 'Query execution started');
@@ -935,7 +1000,7 @@ module.exports = {
evalJsonScript_meta: true, evalJsonScript_meta: true,
async evalJsonScript({ conid, database, script, runid }, req) { async evalJsonScript({ conid, database, script, runid }, req) {
testConnectionPermission(conid, req); await testConnectionPermission(conid, req);
const opened = await this.ensureOpened(conid, database); const opened = await this.ensureOpened(conid, database);
opened.subprocess.send({ msgtype: 'evalJsonScript', script, runid }); opened.subprocess.send({ msgtype: 'evalJsonScript', script, runid });

View File

@@ -3,7 +3,7 @@ const path = require('path');
const crypto = require('crypto'); const crypto = require('crypto');
const { filesdir, archivedir, resolveArchiveFolder, uploadsdir, appdir, jsldir } = require('../utility/directories'); const { filesdir, archivedir, resolveArchiveFolder, uploadsdir, appdir, jsldir } = require('../utility/directories');
const getChartExport = require('../utility/getChartExport'); const getChartExport = require('../utility/getChartExport');
const { hasPermission } = require('../utility/hasPermission'); const { hasPermission, loadPermissionsFromRequest } = require('../utility/hasPermission');
const socket = require('../utility/socket'); const socket = require('../utility/socket');
const scheduler = require('./scheduler'); const scheduler = require('./scheduler');
const getDiagramExport = require('../utility/getDiagramExport'); const getDiagramExport = require('../utility/getDiagramExport');
@@ -31,7 +31,8 @@ function deserialize(format, text) {
module.exports = { module.exports = {
list_meta: true, list_meta: true,
async list({ folder }, req) { async list({ folder }, req) {
if (!hasPermission(`files/${folder}/read`, req)) return []; const loadedPermissions = await loadPermissionsFromRequest(req);
if (!hasPermission(`files/${folder}/read`, loadedPermissions)) return [];
const dir = path.join(filesdir(), folder); const dir = path.join(filesdir(), folder);
if (!(await fs.exists(dir))) return []; if (!(await fs.exists(dir))) return [];
const files = (await fs.readdir(dir)).map(file => ({ folder, file })); const files = (await fs.readdir(dir)).map(file => ({ folder, file }));
@@ -40,10 +41,11 @@ module.exports = {
listAll_meta: true, listAll_meta: true,
async listAll(_params, req) { async listAll(_params, req) {
const loadedPermissions = await loadPermissionsFromRequest(req);
const folders = await fs.readdir(filesdir()); const folders = await fs.readdir(filesdir());
const res = []; const res = [];
for (const folder of folders) { for (const folder of folders) {
if (!hasPermission(`files/${folder}/read`, req)) continue; if (!hasPermission(`files/${folder}/read`, loadedPermissions)) continue;
const dir = path.join(filesdir(), folder); const dir = path.join(filesdir(), folder);
const files = (await fs.readdir(dir)).map(file => ({ folder, file })); const files = (await fs.readdir(dir)).map(file => ({ folder, file }));
res.push(...files); res.push(...files);
@@ -53,7 +55,8 @@ module.exports = {
delete_meta: true, delete_meta: true,
async delete({ folder, file }, req) { async delete({ folder, file }, req) {
if (!hasPermission(`files/${folder}/write`, req)) return false; const loadedPermissions = await loadPermissionsFromRequest(req);
if (!hasPermission(`files/${folder}/write`, loadedPermissions)) return false;
if (!checkSecureFilePathsWithoutDirectory(folder, file)) { if (!checkSecureFilePathsWithoutDirectory(folder, file)) {
return false; return false;
} }
@@ -65,7 +68,8 @@ module.exports = {
rename_meta: true, rename_meta: true,
async rename({ folder, file, newFile }, req) { async rename({ folder, file, newFile }, req) {
if (!hasPermission(`files/${folder}/write`, req)) return false; const loadedPermissions = await loadPermissionsFromRequest(req);
if (!hasPermission(`files/${folder}/write`, loadedPermissions)) return false;
if (!checkSecureFilePathsWithoutDirectory(folder, file, newFile)) { if (!checkSecureFilePathsWithoutDirectory(folder, file, newFile)) {
return false; return false;
} }
@@ -86,10 +90,11 @@ module.exports = {
copy_meta: true, copy_meta: true,
async copy({ folder, file, newFile }, req) { async copy({ folder, file, newFile }, req) {
const loadedPermissions = await loadPermissionsFromRequest(req);
if (!checkSecureFilePathsWithoutDirectory(folder, file, newFile)) { if (!checkSecureFilePathsWithoutDirectory(folder, file, newFile)) {
return false; return false;
} }
if (!hasPermission(`files/${folder}/write`, req)) return false; if (!hasPermission(`files/${folder}/write`, loadedPermissions)) return false;
await fs.copyFile(path.join(filesdir(), folder, file), path.join(filesdir(), folder, newFile)); await fs.copyFile(path.join(filesdir(), folder, file), path.join(filesdir(), folder, newFile));
socket.emitChanged(`files-changed`, { folder }); socket.emitChanged(`files-changed`, { folder });
socket.emitChanged(`all-files-changed`); socket.emitChanged(`all-files-changed`);
@@ -113,7 +118,8 @@ module.exports = {
}); });
return deserialize(format, text); return deserialize(format, text);
} else { } else {
if (!hasPermission(`files/${folder}/read`, req)) return null; const loadedPermissions = await loadPermissionsFromRequest(req);
if (!hasPermission(`files/${folder}/read`, loadedPermissions)) return null;
const text = await fs.readFile(path.join(filesdir(), folder, file), { encoding: 'utf-8' }); const text = await fs.readFile(path.join(filesdir(), folder, file), { encoding: 'utf-8' });
return deserialize(format, text); return deserialize(format, text);
} }
@@ -131,18 +137,19 @@ module.exports = {
save_meta: true, save_meta: true,
async save({ folder, file, data, format }, req) { async save({ folder, file, data, format }, req) {
const loadedPermissions = await loadPermissionsFromRequest(req);
if (!checkSecureFilePathsWithoutDirectory(folder, file)) { if (!checkSecureFilePathsWithoutDirectory(folder, file)) {
return false; return false;
} }
if (folder.startsWith('archive:')) { if (folder.startsWith('archive:')) {
if (!hasPermission(`archive/write`, req)) return false; if (!hasPermission(`archive/write`, loadedPermissions)) return false;
const dir = resolveArchiveFolder(folder.substring('archive:'.length)); const dir = resolveArchiveFolder(folder.substring('archive:'.length));
await fs.writeFile(path.join(dir, file), serialize(format, data)); await fs.writeFile(path.join(dir, file), serialize(format, data));
socket.emitChanged(`archive-files-changed`, { folder: folder.substring('archive:'.length) }); socket.emitChanged(`archive-files-changed`, { folder: folder.substring('archive:'.length) });
return true; return true;
} else if (folder.startsWith('app:')) { } else if (folder.startsWith('app:')) {
if (!hasPermission(`apps/write`, req)) return false; if (!hasPermission(`apps/write`, loadedPermissions)) return false;
const app = folder.substring('app:'.length); const app = folder.substring('app:'.length);
await fs.writeFile(path.join(appdir(), app, file), serialize(format, data)); await fs.writeFile(path.join(appdir(), app, file), serialize(format, data));
socket.emitChanged(`app-files-changed`, { app }); socket.emitChanged(`app-files-changed`, { app });
@@ -150,7 +157,7 @@ module.exports = {
apps.emitChangedDbApp(folder); apps.emitChangedDbApp(folder);
return true; return true;
} else { } else {
if (!hasPermission(`files/${folder}/write`, req)) return false; if (!hasPermission(`files/${folder}/write`, loadedPermissions)) return false;
const dir = path.join(filesdir(), folder); const dir = path.join(filesdir(), folder);
if (!(await fs.exists(dir))) { if (!(await fs.exists(dir))) {
await fs.mkdir(dir); await fs.mkdir(dir);
@@ -177,7 +184,8 @@ module.exports = {
favorites_meta: true, favorites_meta: true,
async favorites(_params, req) { async favorites(_params, req) {
if (!hasPermission(`files/favorites/read`, req)) return []; const loadedPermissions = await loadPermissionsFromRequest(req);
if (!hasPermission(`files/favorites/read`, loadedPermissions)) return [];
const dir = path.join(filesdir(), 'favorites'); const dir = path.join(filesdir(), 'favorites');
if (!(await fs.exists(dir))) return []; if (!(await fs.exists(dir))) return [];
const files = await fs.readdir(dir); const files = await fs.readdir(dir);
@@ -234,16 +242,17 @@ module.exports = {
getFileRealPath_meta: true, getFileRealPath_meta: true,
async getFileRealPath({ folder, file }, req) { async getFileRealPath({ folder, file }, req) {
const loadedPermissions = await loadPermissionsFromRequest(req);
if (folder.startsWith('archive:')) { if (folder.startsWith('archive:')) {
if (!hasPermission(`archive/write`, req)) return false; if (!hasPermission(`archive/write`, loadedPermissions)) return false;
const dir = resolveArchiveFolder(folder.substring('archive:'.length)); const dir = resolveArchiveFolder(folder.substring('archive:'.length));
return path.join(dir, file); return path.join(dir, file);
} else if (folder.startsWith('app:')) { } else if (folder.startsWith('app:')) {
if (!hasPermission(`apps/write`, req)) return false; if (!hasPermission(`apps/write`, loadedPermissions)) return false;
const app = folder.substring('app:'.length); const app = folder.substring('app:'.length);
return path.join(appdir(), app, file); return path.join(appdir(), app, file);
} else { } else {
if (!hasPermission(`files/${folder}/write`, req)) return false; if (!hasPermission(`files/${folder}/write`, loadedPermissions)) return false;
const dir = path.join(filesdir(), folder); const dir = path.join(filesdir(), folder);
if (!(await fs.exists(dir))) { if (!(await fs.exists(dir))) {
await fs.mkdir(dir); await fs.mkdir(dir);
@@ -297,7 +306,8 @@ module.exports = {
exportFile_meta: true, exportFile_meta: true,
async exportFile({ folder, file, filePath }, req) { async exportFile({ folder, file, filePath }, req) {
if (!hasPermission(`files/${folder}/read`, req)) return false; const loadedPermissions = await loadPermissionsFromRequest(req);
if (!hasPermission(`files/${folder}/read`, loadedPermissions)) return false;
await fs.copyFile(path.join(filesdir(), folder, file), filePath); await fs.copyFile(path.join(filesdir(), folder, file), filePath);
return true; return true;
}, },

View File

@@ -7,7 +7,7 @@ const socket = require('../utility/socket');
const compareVersions = require('compare-versions'); const compareVersions = require('compare-versions');
const requirePlugin = require('../shell/requirePlugin'); const requirePlugin = require('../shell/requirePlugin');
const downloadPackage = require('../utility/downloadPackage'); const downloadPackage = require('../utility/downloadPackage');
const { hasPermission } = require('../utility/hasPermission'); const { hasPermission, loadPermissionsFromRequest } = require('../utility/hasPermission');
const _ = require('lodash'); const _ = require('lodash');
const packagedPluginsContent = require('../packagedPluginsContent'); const packagedPluginsContent = require('../packagedPluginsContent');
@@ -118,7 +118,8 @@ module.exports = {
install_meta: true, install_meta: true,
async install({ packageName }, req) { async install({ packageName }, req) {
if (!hasPermission(`plugins/install`, req)) return; const loadedPermissions = await loadPermissionsFromRequest(req);
if (!hasPermission(`plugins/install`, loadedPermissions)) return;
const dir = path.join(pluginsdir(), packageName); const dir = path.join(pluginsdir(), packageName);
// @ts-ignore // @ts-ignore
if (!(await fs.exists(dir))) { if (!(await fs.exists(dir))) {
@@ -132,7 +133,8 @@ module.exports = {
uninstall_meta: true, uninstall_meta: true,
async uninstall({ packageName }, req) { async uninstall({ packageName }, req) {
if (!hasPermission(`plugins/install`, req)) return; const loadedPermissions = await loadPermissionsFromRequest(req);
if (!hasPermission(`plugins/install`, loadedPermissions)) return;
const dir = path.join(pluginsdir(), packageName); const dir = path.join(pluginsdir(), packageName);
await fs.rmdir(dir, { recursive: true }); await fs.rmdir(dir, { recursive: true });
socket.emitChanged(`installed-plugins-changed`); socket.emitChanged(`installed-plugins-changed`);
@@ -143,7 +145,8 @@ module.exports = {
upgrade_meta: true, upgrade_meta: true,
async upgrade({ packageName }, req) { async upgrade({ packageName }, req) {
if (!hasPermission(`plugins/install`, req)) return; const loadedPermissions = await loadPermissionsFromRequest(req);
if (!hasPermission(`plugins/install`, loadedPermissions)) return;
const dir = path.join(pluginsdir(), packageName); const dir = path.join(pluginsdir(), packageName);
// @ts-ignore // @ts-ignore
if (await fs.exists(dir)) { if (await fs.exists(dir)) {

View File

@@ -21,6 +21,7 @@ const processArgs = require('../utility/processArgs');
const platformInfo = require('../utility/platformInfo'); const platformInfo = require('../utility/platformInfo');
const { checkSecureDirectories, checkSecureDirectoriesInScript } = require('../utility/security'); const { checkSecureDirectories, checkSecureDirectoriesInScript } = require('../utility/security');
const { sendToAuditLog, logJsonRunnerScript } = require('../utility/auditlog'); const { sendToAuditLog, logJsonRunnerScript } = require('../utility/auditlog');
const { testStandardPermission } = require('../utility/hasPermission');
const logger = getLogger('runners'); const logger = getLogger('runners');
function extractPlugins(script) { function extractPlugins(script) {
@@ -273,6 +274,8 @@ module.exports = {
start_meta: true, start_meta: true,
async start({ script }, req) { async start({ script }, req) {
await testStandardPermission('run-shell-script', req);
const runid = crypto.randomUUID(); const runid = crypto.randomUUID();
if (script.type == 'json') { if (script.type == 'json') {

View File

@@ -3,7 +3,7 @@ const fs = require('fs-extra');
const path = require('path'); const path = require('path');
const cron = require('node-cron'); const cron = require('node-cron');
const runners = require('./runners'); const runners = require('./runners');
const { hasPermission } = require('../utility/hasPermission'); const { hasPermission, loadPermissionsFromRequest } = require('../utility/hasPermission');
const { getLogger } = require('dbgate-tools'); const { getLogger } = require('dbgate-tools');
const logger = getLogger('scheduler'); const logger = getLogger('scheduler');
@@ -30,7 +30,8 @@ module.exports = {
}, },
async reload(_params, req) { async reload(_params, req) {
if (!hasPermission('files/shell/read', req)) return; const loadedPermissions = await loadPermissionsFromRequest(req);
if (!hasPermission('files/shell/read', loadedPermissions)) return;
const shellDir = path.join(filesdir(), 'shell'); const shellDir = path.join(filesdir(), 'shell');
await this.unload(); await this.unload();
if (!(await fs.exists(shellDir))) return; if (!(await fs.exists(shellDir))) return;

View File

@@ -8,7 +8,13 @@ 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'); const {
testConnectionPermission,
loadPermissionsFromRequest,
hasPermission,
loadDatabasePermissionsFromRequest,
getDatabasePermissionRole,
} = require('../utility/hasPermission');
const { MissingCredentialsError } = require('../utility/exceptions'); const { MissingCredentialsError } = require('../utility/exceptions');
const pipeForkLogs = require('../utility/pipeForkLogs'); const pipeForkLogs = require('../utility/pipeForkLogs');
const { getLogger, extractErrorLogData } = require('dbgate-tools'); const { getLogger, extractErrorLogData } = require('dbgate-tools');
@@ -40,7 +46,7 @@ module.exports = {
existing.status = status; existing.status = status;
socket.emitChanged(`server-status-changed`); socket.emitChanged(`server-status-changed`);
}, },
handle_ping() {}, handle_ping() { },
handle_response(conid, { msgid, ...response }) { handle_response(conid, { msgid, ...response }) {
const [resolve, reject] = this.requests[msgid]; const [resolve, reject] = this.requests[msgid];
resolve(response); resolve(response);
@@ -135,7 +141,7 @@ module.exports = {
disconnect_meta: true, disconnect_meta: true,
async disconnect({ conid }, req) { async disconnect({ conid }, req) {
testConnectionPermission(conid, req); await testConnectionPermission(conid, req);
await this.close(conid, true); await this.close(conid, true);
return { status: 'ok' }; return { status: 'ok' };
}, },
@@ -144,7 +150,9 @@ module.exports = {
async listDatabases({ conid }, req) { async listDatabases({ conid }, req) {
if (!conid) return []; if (!conid) return [];
if (conid == '__model') return []; if (conid == '__model') return [];
testConnectionPermission(conid, req); const loadedPermissions = await loadPermissionsFromRequest(req);
await testConnectionPermission(conid, req, loadedPermissions);
const opened = await this.ensureOpened(conid); const opened = await this.ensureOpened(conid);
sendToAuditLog(req, { sendToAuditLog(req, {
category: 'serverop', category: 'serverop',
@@ -157,12 +165,29 @@ module.exports = {
sessionGroup: 'listDatabases', sessionGroup: 'listDatabases',
message: `Loaded databases for connection`, message: `Loaded databases for connection`,
}); });
if (!hasPermission(`all-databases`, loadedPermissions)) {
// filter databases by permissions
const databasePermissions = await loadDatabasePermissionsFromRequest(req);
const res = [];
for (const db of opened?.databases ?? []) {
const databasePermissionRole = getDatabasePermissionRole(db.id, db.name, databasePermissions);
if (databasePermissionRole != 'deny') {
res.push({
...db,
databasePermissionRole,
});
}
}
return res;
}
return opened?.databases ?? []; return opened?.databases ?? [];
}, },
version_meta: true, version_meta: true,
async version({ conid }, req) { async version({ conid }, req) {
testConnectionPermission(conid, req); await testConnectionPermission(conid, req);
const opened = await this.ensureOpened(conid); const opened = await this.ensureOpened(conid);
return opened?.version ?? null; return opened?.version ?? null;
}, },
@@ -202,7 +227,7 @@ module.exports = {
refresh_meta: true, refresh_meta: true,
async refresh({ conid, keepOpen }, req) { async refresh({ conid, keepOpen }, req) {
testConnectionPermission(conid, req); await testConnectionPermission(conid, req);
if (!keepOpen) this.close(conid); if (!keepOpen) this.close(conid);
await this.ensureOpened(conid); await this.ensureOpened(conid);
@@ -210,7 +235,7 @@ module.exports = {
}, },
async sendDatabaseOp({ conid, msgtype, name }, req) { async sendDatabaseOp({ conid, msgtype, name }, req) {
testConnectionPermission(conid, req); await testConnectionPermission(conid, req);
const opened = await this.ensureOpened(conid); const opened = await this.ensureOpened(conid);
if (!opened) { if (!opened) {
return null; return null;
@@ -252,7 +277,7 @@ module.exports = {
}, },
async loadDataCore(msgtype, { conid, ...args }, req) { async loadDataCore(msgtype, { conid, ...args }, req) {
testConnectionPermission(conid, req); await testConnectionPermission(conid, req);
const opened = await this.ensureOpened(conid); const opened = await this.ensureOpened(conid);
if (!opened) { if (!opened) {
return null; return null;
@@ -270,8 +295,8 @@ module.exports = {
serverSummary_meta: true, serverSummary_meta: true,
async serverSummary({ conid }, req) { async serverSummary({ conid }, req) {
await testConnectionPermission(conid, req);
logger.info({ conid }, 'DBGM-00260 Processing server summary'); logger.info({ conid }, 'DBGM-00260 Processing server summary');
testConnectionPermission(conid, req);
return this.loadDataCore('serverSummary', { conid }); return this.loadDataCore('serverSummary', { conid });
}, },
@@ -306,7 +331,7 @@ module.exports = {
summaryCommand_meta: true, summaryCommand_meta: true,
async summaryCommand({ conid, command, row }, req) { async summaryCommand({ conid, command, row }, req) {
testConnectionPermission(conid, req); await testConnectionPermission(conid, req);
const opened = await this.ensureOpened(conid); const opened = await this.ensureOpened(conid);
if (!opened) { if (!opened) {
return null; return null;

View File

@@ -12,6 +12,7 @@ const { getLogger, extractErrorLogData } = require('dbgate-tools');
const pipeForkLogs = require('../utility/pipeForkLogs'); const pipeForkLogs = require('../utility/pipeForkLogs');
const config = require('./config'); const config = require('./config');
const { sendToAuditLog } = require('../utility/auditlog'); const { sendToAuditLog } = require('../utility/auditlog');
const { testStandardPermission, testDatabaseRolePermission } = require('../utility/hasPermission');
const logger = getLogger('sessions'); const logger = getLogger('sessions');
@@ -94,7 +95,7 @@ module.exports = {
socket.emit(`session-initialize-file-${jslid}`); socket.emit(`session-initialize-file-${jslid}`);
}, },
handle_ping() {}, handle_ping() { },
create_meta: true, create_meta: true,
async create({ conid, database }) { async create({ conid, database }) {
@@ -148,10 +149,12 @@ module.exports = {
executeQuery_meta: true, executeQuery_meta: true,
async executeQuery({ sesid, sql, autoCommit, autoDetectCharts, limitRows, frontMatter }, req) { async executeQuery({ sesid, sql, autoCommit, autoDetectCharts, limitRows, frontMatter }, req) {
await testStandardPermission('dbops/query', req);
const session = this.opened.find(x => x.sesid == sesid); const session = this.opened.find(x => x.sesid == sesid);
if (!session) { if (!session) {
throw new Error('Invalid session'); throw new Error('Invalid session');
} }
await testDatabaseRolePermission(session.conid, session.database, 'run_script', req);
sendToAuditLog(req, { sendToAuditLog(req, {
category: 'dbop', category: 'dbop',

View File

@@ -17,13 +17,14 @@ const requireEngineDriver = require('../utility/requireEngineDriver');
const { connectUtility } = require('../utility/connectUtility'); const { connectUtility } = require('../utility/connectUtility');
const { handleProcessCommunication } = require('../utility/processComm'); const { handleProcessCommunication } = require('../utility/processComm');
const generateDeploySql = require('../shell/generateDeploySql'); const generateDeploySql = require('../shell/generateDeploySql');
const { dumpSqlSelect } = require('dbgate-sqltree'); const { dumpSqlSelect, scriptToSql } = require('dbgate-sqltree');
const { allowExecuteCustomScript, handleQueryStream } = require('../utility/handleQueryStream'); const { allowExecuteCustomScript, handleQueryStream } = require('../utility/handleQueryStream');
const dbgateApi = require('../shell'); const dbgateApi = require('../shell');
const requirePlugin = require('../shell/requirePlugin'); const requirePlugin = require('../shell/requirePlugin');
const path = require('path'); const path = require('path');
const { rundir } = require('../utility/directories'); const { rundir } = require('../utility/directories');
const fs = require('fs-extra'); const fs = require('fs-extra');
const { changeSetToSql } = require('dbgate-datalib');
const logger = getLogger('dbconnProcess'); const logger = getLogger('dbconnProcess');
@@ -348,6 +349,27 @@ async function handleUpdateCollection({ msgid, changeSet }) {
} }
} }
async function handleSaveTableData({ msgid, changeSet }) {
await waitStructure();
try {
const driver = requireEngineDriver(storedConnection);
const script = driver.createSaveChangeSetScript(changeSet, analysedStructure, () =>
changeSetToSql(changeSet, analysedStructure, driver.dialect)
);
const sql = scriptToSql(driver, script);
await driver.script(dbhan, sql, { useTransaction: true });
process.send({ msgtype: 'response', msgid });
} catch (err) {
process.send({
msgtype: 'response',
msgid,
errorMessage: extractErrorMessage(err, 'Error executing SQL script'),
});
}
}
async function handleSqlPreview({ msgid, objects, options }) { async function handleSqlPreview({ msgid, objects, options }) {
await waitStructure(); await waitStructure();
const driver = requireEngineDriver(storedConnection); const driver = requireEngineDriver(storedConnection);
@@ -464,6 +486,7 @@ const messageHandlers = {
runScript: handleRunScript, runScript: handleRunScript,
runOperation: handleRunOperation, runOperation: handleRunOperation,
updateCollection: handleUpdateCollection, updateCollection: handleUpdateCollection,
saveTableData: handleSaveTableData,
collectionData: handleCollectionData, collectionData: handleCollectionData,
loadKeys: handleLoadKeys, loadKeys: handleLoadKeys,
scanKeys: handleScanKeys, scanKeys: handleScanKeys,

View File

@@ -695,27 +695,27 @@ module.exports = {
} }
}, },
{ {
"pureName": "roles", "pureName": "database_permission_roles",
"columns": [ "columns": [
{ {
"pureName": "roles", "pureName": "database_permission_roles",
"columnName": "id", "columnName": "id",
"dataType": "int", "dataType": "int",
"autoIncrement": true, "autoIncrement": true,
"notNull": true "notNull": true
}, },
{ {
"pureName": "roles", "pureName": "database_permission_roles",
"columnName": "name", "columnName": "name",
"dataType": "varchar(250)", "dataType": "varchar(100)",
"notNull": false "notNull": true
} }
], ],
"foreignKeys": [], "foreignKeys": [],
"primaryKey": { "primaryKey": {
"pureName": "roles", "pureName": "database_permission_roles",
"constraintType": "primaryKey", "constraintType": "primaryKey",
"constraintName": "PK_roles", "constraintName": "PK_database_permission_roles",
"columns": [ "columns": [
{ {
"columnName": "id" "columnName": "id"
@@ -725,15 +725,23 @@ module.exports = {
"preloadedRows": [ "preloadedRows": [
{ {
"id": -1, "id": -1,
"name": "anonymous-user" "name": "view"
}, },
{ {
"id": -2, "id": -2,
"name": "logged-user" "name": "read_content"
}, },
{ {
"id": -3, "id": -3,
"name": "superadmin" "name": "write_data"
},
{
"id": -4,
"name": "run_script"
},
{
"id": -5,
"name": "deny"
} }
] ]
}, },
@@ -799,6 +807,98 @@ module.exports = {
] ]
} }
}, },
{
"pureName": "role_databases",
"columns": [
{
"pureName": "role_databases",
"columnName": "id",
"dataType": "int",
"autoIncrement": true,
"notNull": true
},
{
"pureName": "role_databases",
"columnName": "role_id",
"dataType": "int",
"notNull": true
},
{
"pureName": "role_databases",
"columnName": "connection_id",
"dataType": "int",
"notNull": false
},
{
"pureName": "role_databases",
"columnName": "database_names_list",
"dataType": "varchar(1000)",
"notNull": false
},
{
"pureName": "role_databases",
"columnName": "database_names_regex",
"dataType": "varchar(1000)",
"notNull": false
},
{
"pureName": "role_databases",
"columnName": "database_permission_role_id",
"dataType": "int",
"notNull": true
}
],
"foreignKeys": [
{
"constraintType": "foreignKey",
"constraintName": "FK_role_databases_role_id",
"pureName": "role_databases",
"refTableName": "roles",
"deleteAction": "CASCADE",
"columns": [
{
"columnName": "role_id",
"refColumnName": "id"
}
]
},
{
"constraintType": "foreignKey",
"constraintName": "FK_role_databases_connection_id",
"pureName": "role_databases",
"refTableName": "connections",
"deleteAction": "CASCADE",
"columns": [
{
"columnName": "connection_id",
"refColumnName": "id"
}
]
},
{
"constraintType": "foreignKey",
"constraintName": "FK_role_databases_database_permission_role_id",
"pureName": "role_databases",
"refTableName": "database_permission_roles",
"columns": [
{
"columnName": "database_permission_role_id",
"refColumnName": "id"
}
]
}
],
"primaryKey": {
"pureName": "role_databases",
"constraintType": "primaryKey",
"constraintName": "PK_role_databases",
"columns": [
{
"columnName": "id"
}
]
}
},
{ {
"pureName": "role_permissions", "pureName": "role_permissions",
"columns": [ "columns": [
@@ -849,39 +949,132 @@ module.exports = {
} }
}, },
{ {
"pureName": "users", "pureName": "role_tables",
"columns": [ "columns": [
{ {
"pureName": "users", "pureName": "role_tables",
"columnName": "id", "columnName": "id",
"dataType": "int", "dataType": "int",
"autoIncrement": true, "autoIncrement": true,
"notNull": true "notNull": true
}, },
{ {
"pureName": "users", "pureName": "role_tables",
"columnName": "login", "columnName": "role_id",
"dataType": "varchar(250)", "dataType": "int",
"notNull": true
},
{
"pureName": "role_tables",
"columnName": "connection_id",
"dataType": "int",
"notNull": false "notNull": false
}, },
{ {
"pureName": "users", "pureName": "role_tables",
"columnName": "password", "columnName": "database_names_list",
"dataType": "varchar(250)", "dataType": "varchar(1000)",
"notNull": false "notNull": false
}, },
{ {
"pureName": "users", "pureName": "role_tables",
"columnName": "email", "columnName": "database_names_regex",
"dataType": "varchar(250)", "dataType": "varchar(1000)",
"notNull": false "notNull": false
},
{
"pureName": "role_tables",
"columnName": "schema_names_list",
"dataType": "varchar(1000)",
"notNull": false
},
{
"pureName": "role_tables",
"columnName": "schema_names_regex",
"dataType": "varchar(1000)",
"notNull": false
},
{
"pureName": "role_tables",
"columnName": "table_names_list",
"dataType": "varchar(1000)",
"notNull": false
},
{
"pureName": "role_tables",
"columnName": "table_names_regex",
"dataType": "varchar(1000)",
"notNull": false
},
{
"pureName": "role_tables",
"columnName": "table_permission_role_id",
"dataType": "int",
"notNull": true
},
{
"pureName": "role_tables",
"columnName": "table_permission_scope_id",
"dataType": "int",
"notNull": true
}
],
"foreignKeys": [
{
"constraintType": "foreignKey",
"constraintName": "FK_role_tables_role_id",
"pureName": "role_tables",
"refTableName": "roles",
"deleteAction": "CASCADE",
"columns": [
{
"columnName": "role_id",
"refColumnName": "id"
}
]
},
{
"constraintType": "foreignKey",
"constraintName": "FK_role_tables_connection_id",
"pureName": "role_tables",
"refTableName": "connections",
"deleteAction": "CASCADE",
"columns": [
{
"columnName": "connection_id",
"refColumnName": "id"
}
]
},
{
"constraintType": "foreignKey",
"constraintName": "FK_role_tables_table_permission_role_id",
"pureName": "role_tables",
"refTableName": "table_permission_roles",
"columns": [
{
"columnName": "table_permission_role_id",
"refColumnName": "id"
}
]
},
{
"constraintType": "foreignKey",
"constraintName": "FK_role_tables_table_permission_scope_id",
"pureName": "role_tables",
"refTableName": "table_permission_scopes",
"columns": [
{
"columnName": "table_permission_scope_id",
"refColumnName": "id"
}
]
} }
], ],
"foreignKeys": [],
"primaryKey": { "primaryKey": {
"pureName": "users", "pureName": "role_tables",
"constraintType": "primaryKey", "constraintType": "primaryKey",
"constraintName": "PK_users", "constraintName": "PK_role_tables",
"columns": [ "columns": [
{ {
"columnName": "id" "columnName": "id"
@@ -889,6 +1082,167 @@ module.exports = {
] ]
} }
}, },
{
"pureName": "roles",
"columns": [
{
"pureName": "roles",
"columnName": "id",
"dataType": "int",
"autoIncrement": true,
"notNull": true
},
{
"pureName": "roles",
"columnName": "name",
"dataType": "varchar(250)",
"notNull": false
}
],
"foreignKeys": [],
"primaryKey": {
"pureName": "roles",
"constraintType": "primaryKey",
"constraintName": "PK_roles",
"columns": [
{
"columnName": "id"
}
]
},
"preloadedRows": [
{
"id": -1,
"name": "anonymous-user"
},
{
"id": -2,
"name": "logged-user"
},
{
"id": -3,
"name": "superadmin"
}
]
},
{
"pureName": "table_permission_roles",
"columns": [
{
"pureName": "table_permission_roles",
"columnName": "id",
"dataType": "int",
"autoIncrement": true,
"notNull": true
},
{
"pureName": "table_permission_roles",
"columnName": "name",
"dataType": "varchar(100)",
"notNull": true
}
],
"foreignKeys": [],
"primaryKey": {
"pureName": "table_permission_roles",
"constraintType": "primaryKey",
"constraintName": "PK_table_permission_roles",
"columns": [
{
"columnName": "id"
}
]
},
"preloadedRows": [
{
"id": -1,
"name": "read"
},
{
"id": -2,
"name": "update_only"
},
{
"id": -3,
"name": "create_update_delete"
},
{
"id": -4,
"name": "run_script"
},
{
"id": -5,
"name": "deny"
}
]
},
{
"pureName": "table_permission_scopes",
"columns": [
{
"pureName": "table_permission_scopes",
"columnName": "id",
"dataType": "int",
"autoIncrement": true,
"notNull": true
},
{
"pureName": "table_permission_scopes",
"columnName": "name",
"dataType": "varchar(100)",
"notNull": true
}
],
"foreignKeys": [],
"primaryKey": {
"pureName": "table_permission_scopes",
"constraintType": "primaryKey",
"constraintName": "PK_table_permission_scopes",
"columns": [
{
"columnName": "id"
}
]
},
"preloadedRows": [
{
"id": -1,
"name": "all_objects"
},
{
"id": -2,
"name": "tables"
},
{
"id": -3,
"name": "views"
},
{
"id": -4,
"name": "tables_views_collections"
},
{
"id": -5,
"name": "procedures"
},
{
"id": -6,
"name": "functions"
},
{
"id": -7,
"name": "triggers"
},
{
"id": -8,
"name": "sql_objects"
},
{
"id": -9,
"name": "collections"
}
]
},
{ {
"pureName": "user_connections", "pureName": "user_connections",
"columns": [ "columns": [
@@ -951,6 +1305,98 @@ module.exports = {
] ]
} }
}, },
{
"pureName": "user_databases",
"columns": [
{
"pureName": "user_databases",
"columnName": "id",
"dataType": "int",
"autoIncrement": true,
"notNull": true
},
{
"pureName": "user_databases",
"columnName": "user_id",
"dataType": "int",
"notNull": true
},
{
"pureName": "user_databases",
"columnName": "connection_id",
"dataType": "int",
"notNull": false
},
{
"pureName": "user_databases",
"columnName": "database_names_list",
"dataType": "varchar(1000)",
"notNull": false
},
{
"pureName": "user_databases",
"columnName": "database_names_regex",
"dataType": "varchar(1000)",
"notNull": false
},
{
"pureName": "user_databases",
"columnName": "database_permission_role_id",
"dataType": "int",
"notNull": true
}
],
"foreignKeys": [
{
"constraintType": "foreignKey",
"constraintName": "FK_user_databases_user_id",
"pureName": "user_databases",
"refTableName": "users",
"deleteAction": "CASCADE",
"columns": [
{
"columnName": "user_id",
"refColumnName": "id"
}
]
},
{
"constraintType": "foreignKey",
"constraintName": "FK_user_databases_connection_id",
"pureName": "user_databases",
"refTableName": "connections",
"deleteAction": "CASCADE",
"columns": [
{
"columnName": "connection_id",
"refColumnName": "id"
}
]
},
{
"constraintType": "foreignKey",
"constraintName": "FK_user_databases_database_permission_role_id",
"pureName": "user_databases",
"refTableName": "database_permission_roles",
"columns": [
{
"columnName": "database_permission_role_id",
"refColumnName": "id"
}
]
}
],
"primaryKey": {
"pureName": "user_databases",
"constraintType": "primaryKey",
"constraintName": "PK_user_databases",
"columns": [
{
"columnName": "id"
}
]
}
},
{ {
"pureName": "user_permissions", "pureName": "user_permissions",
"columns": [ "columns": [
@@ -1061,6 +1507,181 @@ module.exports = {
} }
] ]
} }
},
{
"pureName": "user_tables",
"columns": [
{
"pureName": "user_tables",
"columnName": "id",
"dataType": "int",
"autoIncrement": true,
"notNull": true
},
{
"pureName": "user_tables",
"columnName": "user_id",
"dataType": "int",
"notNull": true
},
{
"pureName": "user_tables",
"columnName": "connection_id",
"dataType": "int",
"notNull": false
},
{
"pureName": "user_tables",
"columnName": "database_names_list",
"dataType": "varchar(1000)",
"notNull": false
},
{
"pureName": "user_tables",
"columnName": "database_names_regex",
"dataType": "varchar(1000)",
"notNull": false
},
{
"pureName": "user_tables",
"columnName": "schema_names_list",
"dataType": "varchar(1000)",
"notNull": false
},
{
"pureName": "user_tables",
"columnName": "schema_names_regex",
"dataType": "varchar(1000)",
"notNull": false
},
{
"pureName": "user_tables",
"columnName": "table_names_list",
"dataType": "varchar(1000)",
"notNull": false
},
{
"pureName": "user_tables",
"columnName": "table_names_regex",
"dataType": "varchar(1000)",
"notNull": false
},
{
"pureName": "user_tables",
"columnName": "table_permission_role_id",
"dataType": "int",
"notNull": true
},
{
"pureName": "user_tables",
"columnName": "table_permission_scope_id",
"dataType": "int",
"notNull": true
}
],
"foreignKeys": [
{
"constraintType": "foreignKey",
"constraintName": "FK_user_tables_user_id",
"pureName": "user_tables",
"refTableName": "users",
"deleteAction": "CASCADE",
"columns": [
{
"columnName": "user_id",
"refColumnName": "id"
}
]
},
{
"constraintType": "foreignKey",
"constraintName": "FK_user_tables_connection_id",
"pureName": "user_tables",
"refTableName": "connections",
"deleteAction": "CASCADE",
"columns": [
{
"columnName": "connection_id",
"refColumnName": "id"
}
]
},
{
"constraintType": "foreignKey",
"constraintName": "FK_user_tables_table_permission_role_id",
"pureName": "user_tables",
"refTableName": "table_permission_roles",
"columns": [
{
"columnName": "table_permission_role_id",
"refColumnName": "id"
}
]
},
{
"constraintType": "foreignKey",
"constraintName": "FK_user_tables_table_permission_scope_id",
"pureName": "user_tables",
"refTableName": "table_permission_scopes",
"columns": [
{
"columnName": "table_permission_scope_id",
"refColumnName": "id"
}
]
}
],
"primaryKey": {
"pureName": "user_tables",
"constraintType": "primaryKey",
"constraintName": "PK_user_tables",
"columns": [
{
"columnName": "id"
}
]
}
},
{
"pureName": "users",
"columns": [
{
"pureName": "users",
"columnName": "id",
"dataType": "int",
"autoIncrement": true,
"notNull": true
},
{
"pureName": "users",
"columnName": "login",
"dataType": "varchar(250)",
"notNull": false
},
{
"pureName": "users",
"columnName": "password",
"dataType": "varchar(250)",
"notNull": false
},
{
"pureName": "users",
"columnName": "email",
"dataType": "varchar(250)",
"notNull": false
}
],
"foreignKeys": [],
"primaryKey": {
"pureName": "users",
"constraintType": "primaryKey",
"constraintName": "PK_users",
"columns": [
{
"columnName": "id"
}
]
}
} }
], ],
"collections": [], "collections": [],

View File

@@ -1,101 +1,302 @@
const { compilePermissions, testPermission } = require('dbgate-tools'); const { compilePermissions, testPermission, getPermissionsCacheKey } = require('dbgate-tools');
const _ = require('lodash'); const _ = require('lodash');
const { getAuthProviderFromReq } = require('../auth/authProvider'); const { getAuthProviderFromReq } = require('../auth/authProvider');
const cachedPermissions = {}; const cachedPermissions = {};
function hasPermission(tested, req) { async function loadPermissionsFromRequest(req) {
const authProvider = getAuthProviderFromReq(req);
if (!req) { if (!req) {
// request object not available, allow all return null;
}
const loadedPermissions = await authProvider.getCurrentPermissions(req);
return loadedPermissions;
}
function hasPermission(tested, loadedPermissions) {
if (!loadedPermissions) {
// not available, allow all
return true; return true;
} }
const permissions = getAuthProviderFromReq(req).getCurrentPermissions(req); const permissionsKey = getPermissionsCacheKey(loadedPermissions);
if (!cachedPermissions[permissionsKey]) {
if (!cachedPermissions[permissions]) { cachedPermissions[permissionsKey] = compilePermissions(loadedPermissions);
cachedPermissions[permissions] = compilePermissions(permissions);
} }
return testPermission(tested, cachedPermissions[permissions]); return testPermission(tested, cachedPermissions[permissionsKey]);
// const { user } = (req && req.auth) || {};
// const { login } = (process.env.OAUTH_PERMISSIONS && req && req.user) || {};
// const key = user || login || '';
// const logins = getLogins();
// 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]);
} }
// let loginsCache = null; function connectionHasPermission(connection, loadedPermissions) {
// let loginsLoaded = false;
// function getLogins() {
// if (loginsLoaded) {
// return loginsCache;
// }
// const res = [];
// if (process.env.LOGIN && process.env.PASSWORD) {
// res.push({
// login: process.env.LOGIN,
// password: process.env.PASSWORD,
// permissions: process.env.PERMISSIONS,
// });
// }
// if (process.env.LOGINS) {
// const logins = _.compact(process.env.LOGINS.split(',').map(x => x.trim()));
// for (const login of logins) {
// const password = process.env[`LOGIN_PASSWORD_${login}`];
// const permissions = process.env[`LOGIN_PERMISSIONS_${login}`];
// if (password) {
// res.push({
// login,
// password,
// permissions,
// });
// }
// }
// } else if (process.env.OAUTH_PERMISSIONS) {
// const login_permission_keys = Object.keys(process.env).filter(key => _.startsWith(key, 'LOGIN_PERMISSIONS_'));
// for (const permissions_key of login_permission_keys) {
// const login = permissions_key.replace('LOGIN_PERMISSIONS_', '');
// const permissions = process.env[permissions_key];
// userPermissions[login] = compilePermissions(permissions);
// }
// }
// loginsCache = res.length > 0 ? res : null;
// loginsLoaded = true;
// return loginsCache;
// }
function connectionHasPermission(connection, req) {
if (!connection) { if (!connection) {
return true; return true;
} }
if (_.isString(connection)) { if (_.isString(connection)) {
return hasPermission(`connections/${connection}`, req); return hasPermission(`connections/${connection}`, loadedPermissions);
} else { } else {
return hasPermission(`connections/${connection._id}`, req); return hasPermission(`connections/${connection._id}`, loadedPermissions);
} }
} }
function testConnectionPermission(connection, req) { async function testConnectionPermission(connection, req, loadedPermissions) {
if (!connectionHasPermission(connection, req)) { if (!loadedPermissions) {
throw new Error('Connection permission not granted'); loadedPermissions = await loadPermissionsFromRequest(req);
}
if (process.env.STORAGE_DATABASE) {
if (hasPermission(`all-connections`, loadedPermissions)) {
return;
}
const conid = _.isString(connection) ? connection : connection?._id;
const authProvider = getAuthProviderFromReq(req);
if (!req) {
return;
}
if (!await authProvider.checkCurrentConnectionPermission(req, conid)) {
throw new Error('Connection permission not granted');
}
} else {
if (!connectionHasPermission(connection, loadedPermissions)) {
throw new Error('Connection permission not granted');
}
} }
} }
async function loadDatabasePermissionsFromRequest(req) {
const authProvider = getAuthProviderFromReq(req);
if (!req) {
return null;
}
const databasePermissions = await authProvider.getCurrentDatabasePermissions(req);
return databasePermissions;
}
async function loadTablePermissionsFromRequest(req) {
const authProvider = getAuthProviderFromReq(req);
if (!req) {
return null;
}
const tablePermissions = await authProvider.getCurrentTablePermissions(req);
return tablePermissions;
}
function matchDatabasePermissionRow(conid, database, permissionRow) {
if (permissionRow.connection_id) {
if (conid != permissionRow.connection_id) {
return false;
}
}
if (permissionRow.database_names_list) {
const items = permissionRow.database_names_list.split('\n');
if (!items.find(item => item.trim()?.toLowerCase() === database?.toLowerCase())) {
return false;
}
}
if (permissionRow.database_names_regex) {
const regex = new RegExp(permissionRow.database_names_regex, 'i');
if (!regex.test(database)) {
return false;
}
}
return true;
}
function matchTablePermissionRow(objectTypeField, schemaName, pureName, permissionRow) {
if (permissionRow.table_names_list) {
const items = permissionRow.table_names_list.split('\n');
if (!items.find(item => item.trim()?.toLowerCase() === pureName?.toLowerCase())) {
return false;
}
}
if (permissionRow.table_names_regex) {
const regex = new RegExp(permissionRow.table_names_regex, 'i');
if (!regex.test(pureName)) {
return false;
}
}
if (permissionRow.schema_names_list) {
const items = permissionRow.schema_names_list.split('\n');
if (!items.find(item => item.trim()?.toLowerCase() === schemaName?.toLowerCase())) {
return false;
}
}
if (permissionRow.schema_names_regex) {
const regex = new RegExp(permissionRow.schema_names_regex, 'i');
if (!regex.test(schemaName)) {
return false;
}
}
return true;
}
const DATABASE_ROLE_ID_NAMES = {
'-1': 'view',
'-2': 'read_content',
'-3': 'write_data',
'-4': 'run_script',
'-5': 'deny',
};
function getDatabaseRoleLevelIndex(roleName) {
if (!roleName) {
return 6;
}
if (roleName == 'run_script') {
return 5;
}
if (roleName == 'write_data') {
return 4;
}
if (roleName == 'read_content') {
return 3;
}
if (roleName == 'view') {
return 2;
}
if (roleName == 'deny') {
return 1;
}
return 6;
}
function getTablePermissionRoleLevelIndex(roleName) {
if (!roleName) {
return 6;
}
if (roleName == 'run_script') {
return 5;
}
if (roleName == 'create_update_delete') {
return 4;
}
if (roleName == 'update_only') {
return 3;
}
if (roleName == 'read') {
return 2;
}
if (roleName == 'deny') {
return 1;
}
return 6;
}
function getDatabasePermissionRole(conid, database, loadedDatabasePermissions) {
let res = 'deny';
for (const permissionRow of loadedDatabasePermissions) {
if (!matchDatabasePermissionRow(conid, database, permissionRow)) {
continue;
}
res = DATABASE_ROLE_ID_NAMES[permissionRow.database_permission_role_id];
}
return res;
}
const TABLE_ROLE_ID_NAMES = {
'-1': 'read',
'-2': 'update_only',
'-3': 'create_update_delete',
'-4': 'run_script',
'-5': 'deny',
};
const TABLE_SCOPE_ID_NAMES = {
'-1': 'all_objects',
'-2': 'tables',
'-3': 'views',
'-4': 'tables_views_collections',
'-5': 'procedures',
'-6': 'functions',
'-7': 'triggers',
'-8': 'sql_objects',
'-9': 'collections',
};
function getTablePermissionRole(conid, database, objectTypeField, schemaName, pureName, loadedTablePermissions, databasePermissionRole) {
let res = databasePermissionRole == 'read_content' ? 'read' :
databasePermissionRole == 'write_data' ? 'create_update_delete' :
databasePermissionRole == 'run_script' ? 'run_script' :
'deny';
for (const permissionRow of loadedTablePermissions) {
if (!matchDatabasePermissionRow(conid, database, permissionRow)) {
continue;
}
if (!matchTablePermissionRow(objectTypeField, schemaName, pureName, permissionRow)) {
continue;
}
const scope = TABLE_SCOPE_ID_NAMES[permissionRow.table_permission_scope_id];
switch (scope) {
case 'tables':
if (objectTypeField != 'tables') continue;
break;
case 'views':
if (objectTypeField != 'views') continue;
break;
case 'tables_views_collections':
if (objectTypeField != 'tables' && objectTypeField != 'views' && objectTypeField != 'collections') continue;
break;
case 'procedures':
if (objectTypeField != 'procedures') continue;
break;
case 'functions':
if (objectTypeField != 'functions') continue;
break;
case 'triggers':
if (objectTypeField != 'triggers') continue;
break;
case 'sql_objects':
if (objectTypeField != 'procedures' && objectTypeField != 'functions' && objectTypeField != 'triggers')
continue;
break;
case 'collections':
if (objectTypeField != 'collections') continue;
break;
}
res = TABLE_ROLE_ID_NAMES[permissionRow.table_permission_role_id];
}
return res;
}
async function testStandardPermission(permission, req, loadedPermissions) {
if (!loadedPermissions) {
loadedPermissions = await loadPermissionsFromRequest(req);
}
if (!hasPermission(permission, loadedPermissions)) {
throw new Error('Permission not granted');
}
}
async function testDatabaseRolePermission(conid, database, requiredRole, req) {
if (!process.env.STORAGE_DATABASE) {
return;
}
const loadedPermissions = await loadPermissionsFromRequest(req);
if (hasPermission(`all-databases`, loadedPermissions)) {
return;
}
const databasePermissions = await loadDatabasePermissionsFromRequest(req);
const role = getDatabasePermissionRole(conid, database, databasePermissions);
const requiredIndex = getDatabaseRoleLevelIndex(requiredRole);
const roleIndex = getDatabaseRoleLevelIndex(role);
if (roleIndex < requiredIndex) {
throw new Error('Permission not granted');
}
}
module.exports = { module.exports = {
hasPermission, hasPermission,
connectionHasPermission, connectionHasPermission,
testConnectionPermission, testConnectionPermission,
loadPermissionsFromRequest,
loadDatabasePermissionsFromRequest,
loadTablePermissionsFromRequest,
getDatabasePermissionRole,
getTablePermissionRole,
testStandardPermission,
testDatabaseRolePermission,
getTablePermissionRoleLevelIndex
}; };

View File

@@ -57,6 +57,12 @@ export function compilePermissions(permissions: string[] | string): CompiledPerm
return res; return res;
} }
export function getPermissionsCacheKey(permissions: string[] | string) {
if (!permissions) return null;
if (_isString(permissions)) return permissions;
return permissions.join('|');
}
export function testPermission(tested: string, permissions: CompiledPermissions) { export function testPermission(tested: string, permissions: CompiledPermissions) {
let allow = true; let allow = true;
@@ -103,9 +109,9 @@ export function getPredefinedPermissions(predefinedRoleName: string) {
case 'superadmin': case 'superadmin':
return ['*', '~widgets/*', 'widgets/admin', 'widgets/database', '~all-connections']; return ['*', '~widgets/*', 'widgets/admin', 'widgets/database', '~all-connections'];
case 'logged-user': case 'logged-user':
return ['*', '~widgets/admin', '~admin/*', '~internal-storage', '~all-connections']; return ['*', '~widgets/admin', '~admin/*', '~internal-storage', '~all-connections', '~run-shell-script'];
case 'anonymous-user': case 'anonymous-user':
return ['*', '~widgets/admin', '~admin/*', '~internal-storage', '~all-connections']; return ['*', '~widgets/admin', '~admin/*', '~internal-storage', '~all-connections', '~run-shell-script'];
default: default:
return null; return null;
} }

View File

@@ -22,7 +22,7 @@ export interface ColumnsConstraintInfo extends ConstraintInfo {
columns: ColumnReference[]; columns: ColumnReference[];
} }
export interface PrimaryKeyInfo extends ColumnsConstraintInfo {} export interface PrimaryKeyInfo extends ColumnsConstraintInfo { }
export interface ForeignKeyInfo extends ColumnsConstraintInfo { export interface ForeignKeyInfo extends ColumnsConstraintInfo {
refSchemaName?: string; refSchemaName?: string;
@@ -39,7 +39,7 @@ export interface IndexInfo extends ColumnsConstraintInfo {
filterDefinition?: string; filterDefinition?: string;
} }
export interface UniqueInfo extends ColumnsConstraintInfo {} export interface UniqueInfo extends ColumnsConstraintInfo { }
export interface CheckInfo extends ConstraintInfo { export interface CheckInfo extends ConstraintInfo {
definition: string; definition: string;
@@ -77,6 +77,7 @@ export interface DatabaseObjectInfo extends NamedObjectInfo {
hashCode?: string; hashCode?: string;
objectTypeField?: string; objectTypeField?: string;
objectComment?: string; objectComment?: string;
tablePermissionRole?: 'read' | 'update_only' | 'create_update_delete' | 'deny';
} }
export interface SqlObjectInfo extends DatabaseObjectInfo { export interface SqlObjectInfo extends DatabaseObjectInfo {
@@ -134,7 +135,7 @@ export interface CallableObjectInfo extends SqlObjectInfo {
parameters?: ParameterInfo[]; parameters?: ParameterInfo[];
} }
export interface ProcedureInfo extends CallableObjectInfo {} export interface ProcedureInfo extends CallableObjectInfo { }
export interface FunctionInfo extends CallableObjectInfo { export interface FunctionInfo extends CallableObjectInfo {
returnType?: string; returnType?: string;
@@ -145,17 +146,17 @@ export interface TriggerInfo extends SqlObjectInfo {
functionName?: string; functionName?: string;
tableName?: string; tableName?: string;
triggerTiming?: triggerTiming?:
| 'BEFORE' | 'BEFORE'
| 'AFTER' | 'AFTER'
| 'INSTEAD OF' | 'INSTEAD OF'
| 'BEFORE EACH ROW' | 'BEFORE EACH ROW'
| 'INSTEAD OF' | 'INSTEAD OF'
| 'AFTER EACH ROW' | 'AFTER EACH ROW'
| 'AFTER STATEMENT' | 'AFTER STATEMENT'
| 'BEFORE STATEMENT' | 'BEFORE STATEMENT'
| 'AFTER EVENT' | 'AFTER EVENT'
| 'BEFORE EVENT' | 'BEFORE EVENT'
| null; | null;
triggerLevel?: 'ROW' | 'STATEMENT'; triggerLevel?: 'ROW' | 'STATEMENT';
eventType?: 'INSERT' | 'UPDATE' | 'DELETE' | 'TRUNCATE'; eventType?: 'INSERT' | 'UPDATE' | 'DELETE' | 'TRUNCATE';
} }

View File

@@ -167,7 +167,7 @@ await dbgateApi.deployDb(${JSON.stringify(
isProApp() && { text: 'Data deployer', onClick: handleOpenDataDeployTab }, isProApp() && { text: 'Data deployer', onClick: handleOpenDataDeployTab },
$currentDatabase && [ $currentDatabase && [
{ text: 'Generate deploy DB SQL', onClick: handleGenerateDeploySql }, { text: 'Generate deploy DB SQL', onClick: handleGenerateDeploySql },
{ text: 'Shell: Deploy DB', onClick: handleGenerateDeployScript }, hasPermission(`run-shell-script`) && { text: 'Shell: Deploy DB', onClick: handleGenerateDeployScript },
], ],
data.name != 'default' && data.name != 'default' &&
isProApp() && isProApp() &&

View File

@@ -382,7 +382,8 @@
$extensions, $extensions,
$currentDatabase, $currentDatabase,
$apps, $apps,
$openedSingleDatabaseConnections $openedSingleDatabaseConnections,
data.databasePermissionRole,
), ),
], ],

View File

@@ -46,7 +46,8 @@
$extensions, $extensions,
$currentDatabase, $currentDatabase,
$apps, $apps,
$openedSingleDatabaseConnections $openedSingleDatabaseConnections,
databasePermissionRole
) { ) {
const apps = filterAppsForDatabase(connection, name, $apps); const apps = filterAppsForDatabase(connection, name, $apps);
const handleNewQuery = () => { const handleNewQuery = () => {
@@ -412,11 +413,12 @@ await dbgateApi.executeQuery(${JSON.stringify(
driver?.databaseEngineTypes?.includes('sql') || driver?.databaseEngineTypes?.includes('document'); driver?.databaseEngineTypes?.includes('sql') || driver?.databaseEngineTypes?.includes('document');
return [ return [
hasPermission(`dbops/query`) && { hasPermission(`dbops/query`) &&
onClick: handleNewQuery, isAllowedDatabaseRunScript(databasePermissionRole) && {
text: _t('database.newQuery', { defaultMessage: 'New query' }), onClick: handleNewQuery,
isNewQuery: true, text: _t('database.newQuery', { defaultMessage: 'New query' }),
}, isNewQuery: true,
},
hasPermission(`dbops/model/edit`) && hasPermission(`dbops/model/edit`) &&
!connection.isReadOnly && !connection.isReadOnly &&
driver?.databaseEngineTypes?.includes('sql') && { driver?.databaseEngineTypes?.includes('sql') && {
@@ -545,12 +547,13 @@ await dbgateApi.executeQuery(${JSON.stringify(
{ divider: true }, { divider: true },
driver?.databaseEngineTypes?.includes('sql') && driver?.databaseEngineTypes?.includes('sql') &&
hasPermission(`run-shell-script`) &&
hasPermission(`dbops/dropdb`) && { hasPermission(`dbops/dropdb`) && {
onClick: handleGenerateDropAllObjectsScript, onClick: handleGenerateDropAllObjectsScript,
text: _t('database.shellDropAllObjects', { defaultMessage: 'Shell: Drop all objects' }), text: _t('database.shellDropAllObjects', { defaultMessage: 'Shell: Drop all objects' }),
}, },
{ hasPermission(`run-shell-script`) && {
onClick: handleGenerateRunScript, onClick: handleGenerateRunScript,
text: _t('database.shellRunScript', { defaultMessage: 'Shell: Run script' }), text: _t('database.shellRunScript', { defaultMessage: 'Shell: Run script' }),
}, },
@@ -625,7 +628,7 @@ await dbgateApi.executeQuery(${JSON.stringify(
import ConfirmModal from '../modals/ConfirmModal.svelte'; import ConfirmModal from '../modals/ConfirmModal.svelte';
import { closeMultipleTabs } from '../tabpanel/TabsPanel.svelte'; import { closeMultipleTabs } from '../tabpanel/TabsPanel.svelte';
import NewCollectionModal from '../modals/NewCollectionModal.svelte'; import NewCollectionModal from '../modals/NewCollectionModal.svelte';
import hasPermission from '../utility/hasPermission'; import hasPermission, { isAllowedDatabaseRunScript } from '../utility/hasPermission';
import { openImportExportTab } from '../utility/importExportTools'; import { openImportExportTab } from '../utility/importExportTools';
import newTable from '../tableeditor/newTable'; import newTable from '../tableeditor/newTable';
import { loadSchemaList, switchCurrentDatabase } from '../utility/common'; import { loadSchemaList, switchCurrentDatabase } from '../utility/common';
@@ -636,6 +639,7 @@ await dbgateApi.executeQuery(${JSON.stringify(
import { getNumberIcon } from '../icons/FontIcon.svelte'; import { getNumberIcon } from '../icons/FontIcon.svelte';
import { getDatabaseClickActionSetting } from '../settings/settingsTools'; import { getDatabaseClickActionSetting } from '../settings/settingsTools';
import { _t } from '../translations'; import { _t } from '../translations';
import { dataGridRowHeight } from '../datagrid/DataGridRowHeightMeter.svelte';
export let data; export let data;
export let passProps; export let passProps;
@@ -647,7 +651,8 @@ await dbgateApi.executeQuery(${JSON.stringify(
$extensions, $extensions,
$currentDatabase, $currentDatabase,
$apps, $apps,
$openedSingleDatabaseConnections $openedSingleDatabaseConnections,
data.databasePermissionRole
); );
} }
@@ -697,6 +702,9 @@ await dbgateApi.executeQuery(${JSON.stringify(
).length ).length
) )
: ''} : ''}
statusIconBefore={data.databasePermissionRole == 'read_content' || data.databasePermissionRole == 'view'
? 'icon lock'
: null}
menu={createMenu} menu={createMenu}
showPinnedInsteadOfUnpin={passProps?.showPinnedInsteadOfUnpin} showPinnedInsteadOfUnpin={passProps?.showPinnedInsteadOfUnpin}
onPin={isPinned ? null : () => pinnedDatabases.update(list => [...list, data])} onPin={isPinned ? null : () => pinnedDatabases.update(list => [...list, data])}

View File

@@ -703,15 +703,29 @@
} }
function createMenus(objectTypeField, driver, data): ReturnType<typeof createMenusCore> { function createMenus(objectTypeField, driver, data): ReturnType<typeof createMenusCore> {
return createMenusCore(objectTypeField, driver, data).filter(x => { const coreMenus = createMenusCore(objectTypeField, driver, data);
if (x.scriptTemplate) {
return hasPermission(`dbops/sql-template/${x.scriptTemplate}`); const filteredSumenus = coreMenus.map(item => {
if (!item.submenu) {
return item;
} }
if (x.sqlGeneratorProps) { return {
return hasPermission(`dbops/sql-generator`); ...item,
} submenu: item.submenu.filter(x => {
return true; if (x.scriptTemplate) {
return hasPermission(`dbops/sql-template/${x.scriptTemplate}`);
}
if (x.sqlGeneratorProps) {
return hasPermission(`dbops/sql-generator`);
}
return true;
}),
};
}); });
const filteredNoEmptySubmenus = filteredSumenus.filter(x => !x.submenu || x.submenu.length > 0);
return filteredNoEmptySubmenus;
} }
function getObjectTitle(connection, schemaName, pureName) { function getObjectTitle(connection, schemaName, pureName) {
@@ -1062,6 +1076,7 @@
: null} : null}
extInfo={getExtInfo(data)} extInfo={getExtInfo(data)}
isChoosed={matchDatabaseObjectAppObject($selectedDatabaseObjectAppObject, data)} isChoosed={matchDatabaseObjectAppObject($selectedDatabaseObjectAppObject, data)}
statusIconBefore={data.tablePermissionRole == 'read' ? 'icon lock' : null}
on:click={() => handleObjectClick(data, 'leftClick')} on:click={() => handleObjectClick(data, 'leftClick')}
on:middleclick={() => handleObjectClick(data, 'middleClick')} on:middleclick={() => handleObjectClick(data, 'middleClick')}
on:dblclick={() => handleObjectClick(data, 'dblClick')} on:dblclick={() => handleObjectClick(data, 'dblClick')}

View File

@@ -322,6 +322,7 @@ registerCommand({
toolbar: true, toolbar: true,
toolbarName: 'New table', toolbarName: 'New table',
testEnabled: () => { testEnabled: () => {
if (!hasPermission('dbops/model/edit')) return false;
const driver = findEngineDriver(get(currentDatabase)?.connection, getExtensions()); const driver = findEngineDriver(get(currentDatabase)?.connection, getExtensions());
return !!get(currentDatabase) && driver?.databaseEngineTypes?.includes('sql'); return !!get(currentDatabase) && driver?.databaseEngineTypes?.includes('sql');
}, },
@@ -671,7 +672,7 @@ registerCommand({
name: 'Export database', name: 'Export database',
toolbar: true, toolbar: true,
icon: 'icon export', icon: 'icon export',
testEnabled: () => getCurrentDatabase() != null, testEnabled: () => getCurrentDatabase() != null && hasPermission(`dbops/export`),
onClick: () => { onClick: () => {
openImportExportTab({ openImportExportTab({
targetStorageType: getDefaultFileFormat(getExtensions()).storageType, targetStorageType: getDefaultFileFormat(getExtensions()).storageType,
@@ -691,7 +692,8 @@ if (isProApp()) {
icon: 'icon compare', icon: 'icon compare',
testEnabled: () => testEnabled: () =>
getCurrentDatabase() != null && getCurrentDatabase() != null &&
findEngineDriver(getCurrentDatabase()?.connection, getExtensions())?.databaseEngineTypes?.includes('sql'), findEngineDriver(getCurrentDatabase()?.connection, getExtensions())?.databaseEngineTypes?.includes('sql')
&& hasPermission(`dbops/export`),
onClick: () => { onClick: () => {
openNewTab( openNewTab(
{ {

View File

@@ -77,7 +77,10 @@
{ showHintColumns: getBoolSettingsValue('dataGrid.showHintColumns', true) }, { showHintColumns: getBoolSettingsValue('dataGrid.showHintColumns', true) },
$serverVersion, $serverVersion,
table => getDictionaryDescription(table, conid, database, $apps, $connections), table => getDictionaryDescription(table, conid, database, $apps, $connections),
forceReadOnly || $connection?.isReadOnly, forceReadOnly ||
$connection?.isReadOnly ||
extendedDbInfo?.tables?.find(x => x.pureName == pureName && x.schemaName == schemaName)
?.tablePermissionRole == 'read',
isRawMode, isRawMode,
$settingsValue $settingsValue
) )

View File

@@ -74,6 +74,8 @@
'icon arrow-link': 'mdi mdi-arrow-top-right-thick', 'icon arrow-link': 'mdi mdi-arrow-top-right-thick',
'icon reset': 'mdi mdi-cancel', 'icon reset': 'mdi mdi-cancel',
'icon send': 'mdi mdi-send', 'icon send': 'mdi mdi-send',
'icon regex': 'mdi mdi-regex',
'icon list': 'mdi mdi-format-list-bulleted-triangle',
'icon window-restore': 'mdi mdi-window-restore', 'icon window-restore': 'mdi mdi-window-restore',
'icon window-maximize': 'mdi mdi-window-maximize', 'icon window-maximize': 'mdi mdi-window-maximize',

View File

@@ -3,6 +3,7 @@
import runCommand from '../commands/runCommand'; import runCommand from '../commands/runCommand';
import newQuery from '../query/newQuery'; import newQuery from '../query/newQuery';
import { commandsCustomized, selectedWidget } from '../stores'; import { commandsCustomized, selectedWidget } from '../stores';
import hasPermission from '../utility/hasPermission';
import { isProApp } from '../utility/proTools'; import { isProApp } from '../utility/proTools';
import ModalBase from './ModalBase.svelte'; import ModalBase from './ModalBase.svelte';
import { closeCurrentModal } from './modalTools'; import { closeCurrentModal } from './modalTools';
@@ -19,6 +20,7 @@
newQuery({ multiTabIndex }); newQuery({ multiTabIndex });
}, },
testid: 'NewObjectModal_query', testid: 'NewObjectModal_query',
testEnabled: () => hasPermission('dbops/query'),
}, },
{ {
icon: 'icon connection', icon: 'icon connection',
@@ -114,7 +116,7 @@
isProFeature: true, isProFeature: true,
disabledMessage: 'Database chat is not available for current database', disabledMessage: 'Database chat is not available for current database',
testid: 'NewObjectModal_databaseChat', testid: 'NewObjectModal_databaseChat',
} },
]; ];
</script> </script>
@@ -122,7 +124,11 @@
<div class="create-header">Create new</div> <div class="create-header">Create new</div>
<div class="wrapper"> <div class="wrapper">
{#each NEW_ITEMS as item} {#each NEW_ITEMS as item}
{@const enabled = item.command ? $commandsCustomized[item.command]?.enabled : true} {@const enabled = item.command
? $commandsCustomized[item.command]?.enabled
: item.testEnabled
? item.testEnabled()
: true}
<NewObjectButton <NewObjectButton
icon={item.icon} icon={item.icon}
title={item.title} title={item.title}

View File

@@ -40,7 +40,7 @@
execute: true, execute: true,
toggleComment: true, toggleComment: true,
findReplace: true, findReplace: true,
executeAdditionalCondition: () => getCurrentEditor()?.hasConnection(), executeAdditionalCondition: () => getCurrentEditor()?.hasConnection() && hasPermission('dbops/query'),
copyPaste: true, copyPaste: true,
}); });
registerCommand({ registerCommand({

View File

@@ -80,7 +80,7 @@
import invalidateCommands from '../commands/invalidateCommands'; import invalidateCommands from '../commands/invalidateCommands';
import { showModal } from '../modals/modalTools'; import { showModal } from '../modals/modalTools';
import ErrorMessageModal from '../modals/ErrorMessageModal.svelte'; import ErrorMessageModal from '../modals/ErrorMessageModal.svelte';
import { useConnectionInfo, useDatabaseInfo } from '../utility/metadataLoaders'; import { getTableInfo, useConnectionInfo, useDatabaseInfo } from '../utility/metadataLoaders';
import { scriptToSql } from 'dbgate-sqltree'; import { scriptToSql } from 'dbgate-sqltree';
import { extensions, lastUsedDefaultActions } from '../stores'; import { extensions, lastUsedDefaultActions } from '../stores';
import ConfirmSqlModal from '../modals/ConfirmSqlModal.svelte'; import ConfirmSqlModal from '../modals/ConfirmSqlModal.svelte';
@@ -156,30 +156,47 @@
} }
} }
export function save() { export async function save() {
const driver = findEngineDriver($connection, $extensions); const driver = findEngineDriver($connection, $extensions);
const tablePermissionRole = (await getTableInfo({ conid, database, schemaName, pureName }))?.tablePermissionRole;
const script = driver.createSaveChangeSetScript($changeSetStore?.value, $dbinfo, () => if (tablePermissionRole == 'create_update_delete' || tablePermissionRole == 'update_only') {
changeSetToSql($changeSetStore?.value, $dbinfo, driver.dialect) const resp = await apiCall('database-connections/save-table-data', {
); conid,
database,
const deleteCascades = getDeleteCascades($changeSetStore?.value, $dbinfo); changeSet: $changeSetStore?.value,
const sql = scriptToSql(driver, script);
const deleteCascadesScripts = _.map(deleteCascades, ({ title, commands }) => ({
title,
script: scriptToSql(driver, commands),
}));
// console.log('deleteCascadesScripts', deleteCascadesScripts);
if (getBoolSettingsValue('skipConfirm.tableDataSave', false) && !deleteCascadesScripts?.length) {
handleConfirmSql(sql);
} else {
showModal(ConfirmSqlModal, {
sql,
onConfirm: confirmedSql => handleConfirmSql(confirmedSql),
engine: driver.engine,
deleteCascadesScripts,
skipConfirmSettingKey: deleteCascadesScripts?.length ? null : 'skipConfirm.tableDataSave',
}); });
const { errorMessage } = resp || {};
if (errorMessage) {
showModal(ErrorMessageModal, { title: 'Error when saving', message: errorMessage });
} else {
dispatchChangeSet({ type: 'reset', value: createChangeSet() });
cache.update(reloadDataCacheFunc);
showSnackbarSuccess('Saved to database');
}
} else {
const script = driver.createSaveChangeSetScript($changeSetStore?.value, $dbinfo, () =>
changeSetToSql($changeSetStore?.value, $dbinfo, driver.dialect)
);
const deleteCascades = getDeleteCascades($changeSetStore?.value, $dbinfo);
const sql = scriptToSql(driver, script);
const deleteCascadesScripts = _.map(deleteCascades, ({ title, commands }) => ({
title,
script: scriptToSql(driver, commands),
}));
// console.log('deleteCascadesScripts', deleteCascadesScripts);
if (getBoolSettingsValue('skipConfirm.tableDataSave', false) && !deleteCascadesScripts?.length) {
handleConfirmSql(sql);
} else {
showModal(ConfirmSqlModal, {
sql,
onConfirm: confirmedSql => handleConfirmSql(confirmedSql),
engine: driver.engine,
deleteCascadesScripts,
skipConfirmSettingKey: deleteCascadesScripts?.length ? null : 'skipConfirm.tableDataSave',
});
}
} }
} }

View File

@@ -22,3 +22,8 @@ export function subscribePermissionCompiler() {
export function setConfigForPermissions(config) { export function setConfigForPermissions(config) {
compiled = compilePermissions(config?.permissions || []); compiled = compilePermissions(config?.permissions || []);
} }
export function isAllowedDatabaseRunScript(databasePermissionRole) {
return !databasePermissionRole || databasePermissionRole == 'run_script';
}