dbgate-api-premium 7.1.6 → 7.1.8-alpha.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +6 -6
- package/src/auth/storageAuthProvider.js +1 -1
- package/src/controllers/connections.js +55 -1
- package/src/controllers/jsldata.js +74 -1
- package/src/controllers/queryHistory.js +22 -6
- package/src/controllers/runners.js +21 -0
- package/src/controllers/storage.js +107 -0
- package/src/controllers/teamFiles.js +65 -16
- package/src/currentVersion.js +2 -2
- package/src/shell/runScript.js +1 -0
- package/src/storageModel.js +108 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dbgate-api-premium",
|
|
3
3
|
"main": "src/index.js",
|
|
4
|
-
"version": "7.1.
|
|
4
|
+
"version": "7.1.8-alpha.7",
|
|
5
5
|
"homepage": "https://www.dbgate.io/",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -30,11 +30,11 @@
|
|
|
30
30
|
"compare-versions": "^3.6.0",
|
|
31
31
|
"cors": "^2.8.5",
|
|
32
32
|
"cross-env": "^6.0.3",
|
|
33
|
-
"dbgate-datalib": "7.1.
|
|
33
|
+
"dbgate-datalib": "7.1.8-alpha.7",
|
|
34
34
|
"dbgate-query-splitter": "^4.12.0",
|
|
35
|
-
"dbgate-rest": "7.1.
|
|
36
|
-
"dbgate-sqltree": "7.1.
|
|
37
|
-
"dbgate-tools": "7.1.
|
|
35
|
+
"dbgate-rest": "7.1.8-alpha.7",
|
|
36
|
+
"dbgate-sqltree": "7.1.8-alpha.7",
|
|
37
|
+
"dbgate-tools": "7.1.8-alpha.7",
|
|
38
38
|
"debug": "^4.3.4",
|
|
39
39
|
"diff": "^5.0.0",
|
|
40
40
|
"diff2html": "^3.4.13",
|
|
@@ -88,7 +88,7 @@
|
|
|
88
88
|
"devDependencies": {
|
|
89
89
|
"@types/fs-extra": "^9.0.11",
|
|
90
90
|
"@types/lodash": "^4.14.149",
|
|
91
|
-
"dbgate-types": "7.1.
|
|
91
|
+
"dbgate-types": "7.1.8-alpha.7",
|
|
92
92
|
"env-cmd": "^10.1.0",
|
|
93
93
|
"jsdoc-to-markdown": "^9.0.5",
|
|
94
94
|
"node-loader": "^1.0.2",
|
|
@@ -592,7 +592,7 @@ function validateEmail(email) {
|
|
|
592
592
|
}
|
|
593
593
|
|
|
594
594
|
function extractEmailFromMsEntraPayload(payload) {
|
|
595
|
-
for (const field of ['email', 'upn', 'unique_name']) {
|
|
595
|
+
for (const field of ['email', 'preferred_username', 'upn', 'unique_name']) {
|
|
596
596
|
const value = payload[field];
|
|
597
597
|
if (value && validateEmail(value)) {
|
|
598
598
|
return value;
|
|
@@ -492,7 +492,61 @@ module.exports = {
|
|
|
492
492
|
return mask && !platformInfo.allowShellConnection ? maskConnection(res) : encryptConnection(res);
|
|
493
493
|
}
|
|
494
494
|
const res = await this.datastore.get(conid);
|
|
495
|
-
|
|
495
|
+
if (res) return res;
|
|
496
|
+
|
|
497
|
+
// In a forked runner-script child process, ask the parent for connections that may be
|
|
498
|
+
// volatile (in-memory only, e.g. ask-for-password). We only do this when
|
|
499
|
+
// there really is a parent (process.send exists) to avoid an infinite loop
|
|
500
|
+
// when the parent's own getCore falls through here.
|
|
501
|
+
// The check is intentionally narrow: only runner scripts pass
|
|
502
|
+
// --process-display-name script, so connect/session/ssh-forward subprocesses
|
|
503
|
+
// are not affected and continue to return null immediately.
|
|
504
|
+
if (process.send && processArgs.processDisplayName === 'script') {
|
|
505
|
+
const conn = await new Promise(resolve => {
|
|
506
|
+
let resolved = false;
|
|
507
|
+
|
|
508
|
+
const cleanup = () => {
|
|
509
|
+
process.removeListener('message', handler);
|
|
510
|
+
process.removeListener('disconnect', onDisconnect);
|
|
511
|
+
clearTimeout(timeout);
|
|
512
|
+
};
|
|
513
|
+
|
|
514
|
+
const settle = value => {
|
|
515
|
+
if (!resolved) {
|
|
516
|
+
resolved = true;
|
|
517
|
+
cleanup();
|
|
518
|
+
resolve(value);
|
|
519
|
+
}
|
|
520
|
+
};
|
|
521
|
+
|
|
522
|
+
const handler = message => {
|
|
523
|
+
if (message?.msgtype === 'volatile-connection-response' && message.conid === conid) {
|
|
524
|
+
settle(message.conn || null);
|
|
525
|
+
}
|
|
526
|
+
};
|
|
527
|
+
|
|
528
|
+
const onDisconnect = () => settle(null);
|
|
529
|
+
|
|
530
|
+
const timeout = setTimeout(() => settle(null), 5000);
|
|
531
|
+
// Don't let the timer alone keep the process alive if all other work is done
|
|
532
|
+
timeout.unref();
|
|
533
|
+
|
|
534
|
+
process.on('message', handler);
|
|
535
|
+
process.once('disconnect', onDisconnect);
|
|
536
|
+
|
|
537
|
+
try {
|
|
538
|
+
process.send({ msgtype: 'get-volatile-connection', conid });
|
|
539
|
+
} catch {
|
|
540
|
+
settle(null);
|
|
541
|
+
}
|
|
542
|
+
});
|
|
543
|
+
if (conn) {
|
|
544
|
+
volatileConnections[conn._id] = conn; // cache for subsequent calls
|
|
545
|
+
return conn;
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
return null;
|
|
496
550
|
},
|
|
497
551
|
|
|
498
552
|
get_meta: true,
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
const { filterName } = require('dbgate-tools');
|
|
1
|
+
const { filterName, getLogger, extractErrorLogData } = require('dbgate-tools');
|
|
2
|
+
const logger = getLogger('jsldata');
|
|
3
|
+
const { jsldir, archivedir } = require('../utility/directories');
|
|
2
4
|
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
3
6
|
const lineReader = require('line-reader');
|
|
4
7
|
const _ = require('lodash');
|
|
5
8
|
const { __ } = require('lodash/fp');
|
|
@@ -149,6 +152,10 @@ module.exports = {
|
|
|
149
152
|
|
|
150
153
|
getRows_meta: true,
|
|
151
154
|
async getRows({ jslid, offset, limit, filters, sort, formatterFunction }) {
|
|
155
|
+
const fileName = getJslFileName(jslid);
|
|
156
|
+
if (!fs.existsSync(fileName)) {
|
|
157
|
+
return [];
|
|
158
|
+
}
|
|
152
159
|
const datastore = await this.ensureDatastore(jslid, formatterFunction);
|
|
153
160
|
return datastore.getRows(offset, limit, _.isEmpty(filters) ? null : filters, _.isEmpty(sort) ? null : sort);
|
|
154
161
|
},
|
|
@@ -159,6 +166,72 @@ module.exports = {
|
|
|
159
166
|
return fs.existsSync(fileName);
|
|
160
167
|
},
|
|
161
168
|
|
|
169
|
+
streamRows_meta: {
|
|
170
|
+
method: 'get',
|
|
171
|
+
raw: true,
|
|
172
|
+
},
|
|
173
|
+
streamRows(req, res) {
|
|
174
|
+
const { jslid } = req.query;
|
|
175
|
+
if (!jslid) {
|
|
176
|
+
res.status(400).json({ apiErrorMessage: 'Missing jslid' });
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Reject file:// jslids — they resolve to arbitrary server-side paths
|
|
181
|
+
if (jslid.startsWith('file://')) {
|
|
182
|
+
res.status(403).json({ apiErrorMessage: 'Forbidden jslid scheme' });
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const fileName = getJslFileName(jslid);
|
|
187
|
+
|
|
188
|
+
if (!fs.existsSync(fileName)) {
|
|
189
|
+
res.status(404).json({ apiErrorMessage: 'File not found' });
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Dereference symlinks and normalize case (Windows) before the allow-list check.
|
|
194
|
+
// realpathSync is safe here because existsSync confirmed the file is present.
|
|
195
|
+
// path.resolve() alone cannot dereference symlinks, so a symlink inside an allowed
|
|
196
|
+
// root could otherwise point to an arbitrary external path.
|
|
197
|
+
const normalize = p => (process.platform === 'win32' ? p.toLowerCase() : p);
|
|
198
|
+
const resolveRoot = r => { try { return fs.realpathSync(r); } catch { return path.resolve(r); } };
|
|
199
|
+
|
|
200
|
+
let realFile;
|
|
201
|
+
try {
|
|
202
|
+
realFile = fs.realpathSync(fileName);
|
|
203
|
+
} catch {
|
|
204
|
+
res.status(403).json({ apiErrorMessage: 'Forbidden path' });
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const allowedRoots = [jsldir(), archivedir()].map(r => normalize(resolveRoot(r)) + path.sep);
|
|
209
|
+
const isAllowed = allowedRoots.some(root => normalize(realFile).startsWith(root));
|
|
210
|
+
if (!isAllowed) {
|
|
211
|
+
logger.warn({ jslid, realFile }, 'DBGM-00000 streamRows rejected path outside allowed roots');
|
|
212
|
+
res.status(403).json({ apiErrorMessage: 'Forbidden path' });
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
res.setHeader('Content-Type', 'application/x-ndjson');
|
|
216
|
+
res.setHeader('Cache-Control', 'no-cache');
|
|
217
|
+
const stream = fs.createReadStream(realFile, 'utf-8');
|
|
218
|
+
|
|
219
|
+
req.on('close', () => {
|
|
220
|
+
stream.destroy();
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
stream.on('error', err => {
|
|
224
|
+
logger.error(extractErrorLogData(err), 'DBGM-00000 Error streaming JSONL file');
|
|
225
|
+
if (!res.headersSent) {
|
|
226
|
+
res.status(500).json({ apiErrorMessage: 'Stream error' });
|
|
227
|
+
} else {
|
|
228
|
+
res.end();
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
stream.pipe(res);
|
|
233
|
+
},
|
|
234
|
+
|
|
162
235
|
getStats_meta: true,
|
|
163
236
|
getStats({ jslid }) {
|
|
164
237
|
const file = `${getJslFileName(jslid)}.stats`;
|
|
@@ -33,19 +33,35 @@ function readCore(reader, skip, limit, filter) {
|
|
|
33
33
|
});
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
async read({ skip, limit, filter }) {
|
|
36
|
+
function readJsonl({ skip, limit, filter }) {
|
|
37
|
+
return new Promise(async (resolve, reject) => {
|
|
39
38
|
const fileName = path.join(datadir(), 'query-history.jsonl');
|
|
40
39
|
// @ts-ignore
|
|
41
|
-
if (!(await fs.exists(fileName))) return [];
|
|
40
|
+
if (!(await fs.exists(fileName))) return resolve([]);
|
|
42
41
|
const reader = fsReverse(fileName);
|
|
43
42
|
const res = await readCore(reader, skip, limit, filter);
|
|
44
|
-
|
|
43
|
+
resolve(res);
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
module.exports = {
|
|
48
|
+
read_meta: true,
|
|
49
|
+
async read({ skip, limit, filter }, req) {
|
|
50
|
+
const storage = require('./storage');
|
|
51
|
+
const storageResult = await storage.readQueryHistory({ skip, limit, filter }, req);
|
|
52
|
+
if (storageResult) return storageResult;
|
|
53
|
+
return readJsonl({ skip, limit, filter });
|
|
45
54
|
},
|
|
46
55
|
|
|
47
56
|
write_meta: true,
|
|
48
|
-
async write({ data }) {
|
|
57
|
+
async write({ data }, req) {
|
|
58
|
+
const storage = require('./storage');
|
|
59
|
+
const written = await storage.writeQueryHistory({ data }, req);
|
|
60
|
+
if (written) {
|
|
61
|
+
socket.emit('query-history-changed');
|
|
62
|
+
return 'OK';
|
|
63
|
+
}
|
|
64
|
+
|
|
49
65
|
const fileName = path.join(datadir(), 'query-history.jsonl');
|
|
50
66
|
await fs.appendFile(fileName, JSON.stringify(data) + '\n');
|
|
51
67
|
socket.emit('query-history-changed');
|
|
@@ -196,6 +196,27 @@ module.exports = {
|
|
|
196
196
|
// @ts-ignore
|
|
197
197
|
const { msgtype } = message;
|
|
198
198
|
if (handleProcessCommunication(message, subprocess)) return;
|
|
199
|
+
if (msgtype === 'get-volatile-connection') {
|
|
200
|
+
const connections = require('./connections');
|
|
201
|
+
// @ts-ignore
|
|
202
|
+
const conid = message.conid;
|
|
203
|
+
if (!conid || typeof conid !== 'string') return;
|
|
204
|
+
const trySend = payload => {
|
|
205
|
+
if (!subprocess.connected) return;
|
|
206
|
+
try {
|
|
207
|
+
subprocess.send(payload);
|
|
208
|
+
} catch {
|
|
209
|
+
// child disconnected between the check and the send — ignore
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
connections.getCore({ conid }).then(conn => {
|
|
213
|
+
trySend({ msgtype: 'volatile-connection-response', conid, conn: conn?.unsaved ? conn : null });
|
|
214
|
+
}).catch(err => {
|
|
215
|
+
logger.error({ ...extractErrorLogData(err), conid }, 'DBGM-00000 Error resolving volatile connection for child process');
|
|
216
|
+
trySend({ msgtype: 'volatile-connection-response', conid, conn: null });
|
|
217
|
+
});
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
199
220
|
this[`handle_${msgtype}`](runid, message);
|
|
200
221
|
});
|
|
201
222
|
return _.pick(newOpened, ['runid']);
|
|
@@ -1282,4 +1282,111 @@ DbGate Team
|
|
|
1282
1282
|
|
|
1283
1283
|
return { success: true };
|
|
1284
1284
|
},
|
|
1285
|
+
|
|
1286
|
+
async readQueryHistory({ skip, limit, filter }, req) {
|
|
1287
|
+
if (!process.env.STORAGE_DATABASE) {
|
|
1288
|
+
return null;
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
const [conn, driver] = await getStorageConnection();
|
|
1292
|
+
if (!conn) return null;
|
|
1293
|
+
|
|
1294
|
+
const userId = req?.user?.userId;
|
|
1295
|
+
const roleId = req?.user?.roleId;
|
|
1296
|
+
|
|
1297
|
+
const conditions = [];
|
|
1298
|
+
|
|
1299
|
+
if (userId) {
|
|
1300
|
+
conditions.push({
|
|
1301
|
+
conditionType: 'binary',
|
|
1302
|
+
operator: '=',
|
|
1303
|
+
left: { exprType: 'column', columnName: 'user_id' },
|
|
1304
|
+
right: { exprType: 'value', value: userId },
|
|
1305
|
+
});
|
|
1306
|
+
} else {
|
|
1307
|
+
conditions.push({
|
|
1308
|
+
conditionType: 'binary',
|
|
1309
|
+
operator: '=',
|
|
1310
|
+
left: { exprType: 'column', columnName: 'role_id' },
|
|
1311
|
+
right: { exprType: 'value', value: roleId ?? -1 },
|
|
1312
|
+
});
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1315
|
+
if (filter) {
|
|
1316
|
+
conditions.push({
|
|
1317
|
+
conditionType: 'or',
|
|
1318
|
+
conditions: [
|
|
1319
|
+
{
|
|
1320
|
+
conditionType: 'like',
|
|
1321
|
+
left: { exprType: 'column', columnName: 'sql' },
|
|
1322
|
+
right: { exprType: 'value', value: `%${filter}%` },
|
|
1323
|
+
},
|
|
1324
|
+
{
|
|
1325
|
+
conditionType: 'like',
|
|
1326
|
+
left: { exprType: 'column', columnName: 'database' },
|
|
1327
|
+
right: { exprType: 'value', value: `%${filter}%` },
|
|
1328
|
+
},
|
|
1329
|
+
],
|
|
1330
|
+
});
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
const select = {
|
|
1334
|
+
commandType: 'select',
|
|
1335
|
+
from: {
|
|
1336
|
+
name: { pureName: 'query_history' },
|
|
1337
|
+
},
|
|
1338
|
+
columns: ['id', 'created', 'user_id', 'role_id', 'sql', 'conid', 'database'].map(columnName => ({
|
|
1339
|
+
exprType: 'column',
|
|
1340
|
+
columnName,
|
|
1341
|
+
})),
|
|
1342
|
+
orderBy: [{ exprType: 'column', columnName: 'id', direction: 'desc' }],
|
|
1343
|
+
};
|
|
1344
|
+
|
|
1345
|
+
if (conditions.length > 1) {
|
|
1346
|
+
select.where = { conditionType: 'and', conditions };
|
|
1347
|
+
} else {
|
|
1348
|
+
select.where = conditions[0];
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
if (limit || skip != null) {
|
|
1352
|
+
select.range = {
|
|
1353
|
+
limit,
|
|
1354
|
+
offset: skip ?? 0,
|
|
1355
|
+
};
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
const dmp = driver.createDumper();
|
|
1359
|
+
dumpSqlSelect(dmp, select);
|
|
1360
|
+
const resp = await driver.query(conn, dmp.s);
|
|
1361
|
+
|
|
1362
|
+
if (!resp || !resp.rows) return [];
|
|
1363
|
+
|
|
1364
|
+
return resp.rows.map(row => ({
|
|
1365
|
+
sql: row.sql,
|
|
1366
|
+
conid: row.conid,
|
|
1367
|
+
database: row.database,
|
|
1368
|
+
date: Number(row.created),
|
|
1369
|
+
}));
|
|
1370
|
+
},
|
|
1371
|
+
|
|
1372
|
+
async writeQueryHistory({ data }, req) {
|
|
1373
|
+
if (!process.env.STORAGE_DATABASE) {
|
|
1374
|
+
return false;
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
const userId = req?.user?.userId;
|
|
1378
|
+
const roleId = req?.user?.roleId;
|
|
1379
|
+
|
|
1380
|
+
await storageSqlCommandFmt(
|
|
1381
|
+
`^insert ^into ~query_history (~created, ~user_id, ~role_id, ~sql, ~conid, ~database) values (%v, %v, %v, %v, %v, %v)`,
|
|
1382
|
+
data.date || new Date().getTime(),
|
|
1383
|
+
userId || null,
|
|
1384
|
+
userId ? null : roleId ?? -1,
|
|
1385
|
+
data.sql || null,
|
|
1386
|
+
data.conid || null,
|
|
1387
|
+
data.database || null
|
|
1388
|
+
);
|
|
1389
|
+
|
|
1390
|
+
return true;
|
|
1391
|
+
},
|
|
1285
1392
|
};
|
|
@@ -83,15 +83,42 @@ module.exports = {
|
|
|
83
83
|
const readAll = hasPermission(`all-team-files/read`, loadedPermissions);
|
|
84
84
|
const writeAll = hasPermission(`all-team-files/write`, loadedPermissions);
|
|
85
85
|
const useAll = hasPermission(`all-team-files/use`, loadedPermissions);
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
86
|
+
const hasAllGlobal = readAll && writeAll && useAll;
|
|
87
|
+
const hasAnyGlobal = readAll || writeAll || useAll;
|
|
88
|
+
|
|
89
|
+
if (hasAllGlobal) {
|
|
90
|
+
// All global flags granted, no need to query per-file permissions
|
|
91
|
+
res = (await storageListAllTeamFiles()) || [];
|
|
92
|
+
res = res.map(item => ({ ...item, allow_read: true, allow_write: true, allow_use: true }));
|
|
93
|
+
} else if (hasAnyGlobal) {
|
|
94
|
+
// Partial global permissions - merge with per-file permissions
|
|
95
|
+
const userId = req?.user?.userId;
|
|
96
|
+
let perFileRes = [];
|
|
97
|
+
if (userId) {
|
|
98
|
+
perFileRes = (await storageListTeamFilesForUser(userId)) || [];
|
|
99
|
+
} else {
|
|
100
|
+
perFileRes = (await storageListTeamFilesForRole(getBuiltinRoleIdFromRequest(req))) || [];
|
|
101
|
+
}
|
|
102
|
+
const allFiles = (await storageListAllTeamFiles()) || [];
|
|
103
|
+
const perFileMap = {};
|
|
104
|
+
for (const f of perFileRes) {
|
|
105
|
+
perFileMap[f.id] = f;
|
|
106
|
+
}
|
|
107
|
+
res = allFiles.map(item => {
|
|
108
|
+
const perFile = perFileMap[item.id];
|
|
109
|
+
return {
|
|
110
|
+
...item,
|
|
111
|
+
allow_read: readAll || !!perFile?.allow_read,
|
|
112
|
+
allow_write: writeAll || !!perFile?.allow_write,
|
|
113
|
+
allow_use: useAll || !!perFile?.allow_use,
|
|
114
|
+
};
|
|
115
|
+
});
|
|
89
116
|
} else {
|
|
90
117
|
const userId = req?.user?.userId;
|
|
91
118
|
if (userId) {
|
|
92
|
-
res = await storageListTeamFilesForUser(userId);
|
|
119
|
+
res = (await storageListTeamFilesForUser(userId)) || [];
|
|
93
120
|
} else {
|
|
94
|
-
res = await storageListTeamFilesForRole(getBuiltinRoleIdFromRequest(req));
|
|
121
|
+
res = (await storageListTeamFilesForRole(getBuiltinRoleIdFromRequest(req))) || [];
|
|
95
122
|
}
|
|
96
123
|
}
|
|
97
124
|
return (
|
|
@@ -120,21 +147,43 @@ module.exports = {
|
|
|
120
147
|
const readAll = hasPermission(`all-team-files/read`, loadedPermissions);
|
|
121
148
|
const writeAll = hasPermission(`all-team-files/write`, loadedPermissions);
|
|
122
149
|
const useAll = hasPermission(`all-team-files/use`, loadedPermissions);
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
150
|
+
const hasAllGlobal = createAll && readAll && writeAll && useAll;
|
|
151
|
+
const hasAnyGlobal = readAll || writeAll || useAll || createAll;
|
|
152
|
+
|
|
153
|
+
if (hasAllGlobal) {
|
|
154
|
+
// All global flags granted, no need to query per-folder permissions
|
|
155
|
+
res = (await storageListAllTeamFolders()) || [];
|
|
156
|
+
res = res.map(item => ({ ...item, allow_create: true, allow_read: true, allow_write: true, allow_use: true }));
|
|
157
|
+
} else if (hasAnyGlobal) {
|
|
158
|
+
// Partial global permissions - merge with per-folder permissions
|
|
159
|
+
const userId = req?.user?.userId;
|
|
160
|
+
let perFolderRes = [];
|
|
161
|
+
if (userId) {
|
|
162
|
+
perFolderRes = (await storageListTeamFoldersForUser(userId)) || [];
|
|
163
|
+
} else {
|
|
164
|
+
perFolderRes = (await storageListTeamFoldersForRole(getBuiltinRoleIdFromRequest(req))) || [];
|
|
165
|
+
}
|
|
166
|
+
const allFolders = (await storageListAllTeamFolders()) || [];
|
|
167
|
+
const perFolderMap = {};
|
|
168
|
+
for (const f of perFolderRes) {
|
|
169
|
+
perFolderMap[f.id] = f;
|
|
170
|
+
}
|
|
171
|
+
res = allFolders.map(item => {
|
|
172
|
+
const perFolder = perFolderMap[item.id];
|
|
173
|
+
return {
|
|
174
|
+
...item,
|
|
175
|
+
allow_create: createAll || !!perFolder?.allow_create,
|
|
176
|
+
allow_read: readAll || !!perFolder?.allow_read,
|
|
177
|
+
allow_write: writeAll || !!perFolder?.allow_write,
|
|
178
|
+
allow_use: useAll || !!perFolder?.allow_use,
|
|
179
|
+
};
|
|
180
|
+
});
|
|
132
181
|
} else {
|
|
133
182
|
const userId = req?.user?.userId;
|
|
134
183
|
if (userId) {
|
|
135
|
-
res = await storageListTeamFoldersForUser(userId);
|
|
184
|
+
res = (await storageListTeamFoldersForUser(userId)) || [];
|
|
136
185
|
} else {
|
|
137
|
-
res = await storageListTeamFoldersForRole(getBuiltinRoleIdFromRequest(req));
|
|
186
|
+
res = (await storageListTeamFoldersForRole(getBuiltinRoleIdFromRequest(req))) || [];
|
|
138
187
|
}
|
|
139
188
|
}
|
|
140
189
|
return (
|
package/src/currentVersion.js
CHANGED
package/src/shell/runScript.js
CHANGED
package/src/storageModel.js
CHANGED
|
@@ -874,6 +874,114 @@ module.exports = {
|
|
|
874
874
|
}
|
|
875
875
|
]
|
|
876
876
|
},
|
|
877
|
+
{
|
|
878
|
+
"pureName": "query_history",
|
|
879
|
+
"columns": [
|
|
880
|
+
{
|
|
881
|
+
"pureName": "query_history",
|
|
882
|
+
"columnName": "id",
|
|
883
|
+
"dataType": "int",
|
|
884
|
+
"autoIncrement": true,
|
|
885
|
+
"notNull": true
|
|
886
|
+
},
|
|
887
|
+
{
|
|
888
|
+
"pureName": "query_history",
|
|
889
|
+
"columnName": "created",
|
|
890
|
+
"dataType": "bigint",
|
|
891
|
+
"notNull": true
|
|
892
|
+
},
|
|
893
|
+
{
|
|
894
|
+
"pureName": "query_history",
|
|
895
|
+
"columnName": "user_id",
|
|
896
|
+
"dataType": "int",
|
|
897
|
+
"notNull": false
|
|
898
|
+
},
|
|
899
|
+
{
|
|
900
|
+
"pureName": "query_history",
|
|
901
|
+
"columnName": "role_id",
|
|
902
|
+
"dataType": "int",
|
|
903
|
+
"notNull": false
|
|
904
|
+
},
|
|
905
|
+
{
|
|
906
|
+
"pureName": "query_history",
|
|
907
|
+
"columnName": "sql",
|
|
908
|
+
"dataType": "text",
|
|
909
|
+
"notNull": false
|
|
910
|
+
},
|
|
911
|
+
{
|
|
912
|
+
"pureName": "query_history",
|
|
913
|
+
"columnName": "conid",
|
|
914
|
+
"dataType": "varchar(100)",
|
|
915
|
+
"notNull": false
|
|
916
|
+
},
|
|
917
|
+
{
|
|
918
|
+
"pureName": "query_history",
|
|
919
|
+
"columnName": "database",
|
|
920
|
+
"dataType": "varchar(200)",
|
|
921
|
+
"notNull": false
|
|
922
|
+
}
|
|
923
|
+
],
|
|
924
|
+
"foreignKeys": [
|
|
925
|
+
{
|
|
926
|
+
"constraintType": "foreignKey",
|
|
927
|
+
"constraintName": "FK_query_history_user_id",
|
|
928
|
+
"pureName": "query_history",
|
|
929
|
+
"refTableName": "users",
|
|
930
|
+
"deleteAction": "CASCADE",
|
|
931
|
+
"columns": [
|
|
932
|
+
{
|
|
933
|
+
"columnName": "user_id",
|
|
934
|
+
"refColumnName": "id"
|
|
935
|
+
}
|
|
936
|
+
]
|
|
937
|
+
},
|
|
938
|
+
{
|
|
939
|
+
"constraintType": "foreignKey",
|
|
940
|
+
"constraintName": "FK_query_history_role_id",
|
|
941
|
+
"pureName": "query_history",
|
|
942
|
+
"refTableName": "roles",
|
|
943
|
+
"deleteAction": "CASCADE",
|
|
944
|
+
"columns": [
|
|
945
|
+
{
|
|
946
|
+
"columnName": "role_id",
|
|
947
|
+
"refColumnName": "id"
|
|
948
|
+
}
|
|
949
|
+
]
|
|
950
|
+
}
|
|
951
|
+
],
|
|
952
|
+
"indexes": [
|
|
953
|
+
{
|
|
954
|
+
"constraintName": "idx_query_history_user_id",
|
|
955
|
+
"pureName": "query_history",
|
|
956
|
+
"constraintType": "index",
|
|
957
|
+
"columns": [
|
|
958
|
+
{
|
|
959
|
+
"columnName": "user_id"
|
|
960
|
+
}
|
|
961
|
+
]
|
|
962
|
+
},
|
|
963
|
+
{
|
|
964
|
+
"constraintName": "idx_query_history_role_id",
|
|
965
|
+
"pureName": "query_history",
|
|
966
|
+
"constraintType": "index",
|
|
967
|
+
"columns": [
|
|
968
|
+
{
|
|
969
|
+
"columnName": "role_id"
|
|
970
|
+
}
|
|
971
|
+
]
|
|
972
|
+
}
|
|
973
|
+
],
|
|
974
|
+
"primaryKey": {
|
|
975
|
+
"pureName": "query_history",
|
|
976
|
+
"constraintType": "primaryKey",
|
|
977
|
+
"constraintName": "PK_query_history",
|
|
978
|
+
"columns": [
|
|
979
|
+
{
|
|
980
|
+
"columnName": "id"
|
|
981
|
+
}
|
|
982
|
+
]
|
|
983
|
+
}
|
|
984
|
+
},
|
|
877
985
|
{
|
|
878
986
|
"pureName": "roles",
|
|
879
987
|
"columns": [
|