dbgate-api-premium 6.6.0 → 6.6.2

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.
Files changed (62) hide show
  1. package/package.json +6 -6
  2. package/src/auth/authProvider.js +14 -2
  3. package/src/auth/storageAuthProvider.js +89 -22
  4. package/src/controllers/archive.js +1 -1
  5. package/src/controllers/auth.js +3 -2
  6. package/src/controllers/cloud.js +1 -1
  7. package/src/controllers/config.js +8 -5
  8. package/src/controllers/connections.js +12 -11
  9. package/src/controllers/databaseConnections.js +148 -83
  10. package/src/controllers/files.js +49 -19
  11. package/src/controllers/plugins.js +7 -4
  12. package/src/controllers/runners.js +10 -6
  13. package/src/controllers/scheduler.js +4 -3
  14. package/src/controllers/serverConnections.js +69 -14
  15. package/src/controllers/sessions.js +8 -5
  16. package/src/controllers/storage.js +81 -51
  17. package/src/controllers/storageDb.js +118 -4
  18. package/src/controllers/uploads.js +2 -2
  19. package/src/currentVersion.js +2 -2
  20. package/src/index.js +36 -5
  21. package/src/main.js +59 -20
  22. package/src/proc/databaseConnectionProcess.js +45 -13
  23. package/src/proc/serverConnectionProcess.js +32 -6
  24. package/src/proc/sessionProcess.js +2 -2
  25. package/src/proc/sshForwardProcess.js +1 -1
  26. package/src/shell/archiveWriter.js +1 -1
  27. package/src/shell/copyStream.js +1 -1
  28. package/src/shell/executeQuery.js +3 -3
  29. package/src/shell/importDatabase.js +3 -3
  30. package/src/shell/jsonLinesReader.js +1 -1
  31. package/src/shell/jsonLinesWriter.js +1 -1
  32. package/src/shell/jsonReader.js +1 -1
  33. package/src/shell/jsonWriter.js +1 -1
  34. package/src/shell/loadDatabase.js +2 -2
  35. package/src/shell/modifyJsonLinesReader.js +1 -1
  36. package/src/shell/queryReader.js +1 -1
  37. package/src/shell/requirePlugin.js +6 -1
  38. package/src/shell/runScript.js +1 -1
  39. package/src/shell/sqlDataWriter.js +1 -1
  40. package/src/shell/tableReader.js +3 -3
  41. package/src/shell/tableWriter.js +1 -1
  42. package/src/shell/unzipDirectory.js +4 -4
  43. package/src/shell/zipDirectory.js +3 -3
  44. package/src/shell/zipJsonLinesData.js +3 -3
  45. package/src/storageModel.js +726 -105
  46. package/src/utility/DatastoreProxy.js +3 -3
  47. package/src/utility/JsonLinesDatastore.js +4 -2
  48. package/src/utility/appLogStore.js +119 -0
  49. package/src/utility/auditlog.js +1 -1
  50. package/src/utility/authProxy.js +4 -4
  51. package/src/utility/checkLicense.js +10 -4
  52. package/src/utility/childProcessChecker.js +1 -1
  53. package/src/utility/cloudIntf.js +5 -5
  54. package/src/utility/cloudUpgrade.js +4 -4
  55. package/src/utility/connectUtility.js +1 -1
  56. package/src/utility/directories.js +2 -2
  57. package/src/utility/extractSingleFileFromZip.js +3 -3
  58. package/src/utility/hasPermission.js +286 -71
  59. package/src/utility/loadModelTransform.js +1 -1
  60. package/src/utility/sshTunnel.js +7 -7
  61. package/src/utility/sshTunnelProxy.js +1 -1
  62. package/src/utility/useController.js +3 -3
@@ -1,96 +1,303 @@
1
- const { compilePermissions, testPermission } = require('dbgate-tools');
1
+ const { compilePermissions, testPermission, getPermissionsCacheKey } = require('dbgate-tools');
2
2
  const _ = require('lodash');
3
3
  const { getAuthProviderFromReq } = require('../auth/authProvider');
4
4
 
5
5
  const cachedPermissions = {};
6
6
 
7
- function hasPermission(tested, req) {
7
+ async function loadPermissionsFromRequest(req) {
8
+ const authProvider = getAuthProviderFromReq(req);
8
9
  if (!req) {
9
- // request object not available, allow all
10
- return true;
10
+ return null;
11
11
  }
12
12
 
13
- const permissions = getAuthProviderFromReq(req).getCurrentPermissions(req);
13
+ const loadedPermissions = await authProvider.getCurrentPermissions(req);
14
+ return loadedPermissions;
15
+ }
14
16
 
15
- if (!cachedPermissions[permissions]) {
16
- cachedPermissions[permissions] = compilePermissions(permissions);
17
+ function hasPermission(tested, loadedPermissions) {
18
+ if (!loadedPermissions) {
19
+ // not available, allow all
20
+ return true;
17
21
  }
18
22
 
19
- return testPermission(tested, cachedPermissions[permissions]);
20
-
21
- // const { user } = (req && req.auth) || {};
22
- // const { login } = (process.env.OAUTH_PERMISSIONS && req && req.user) || {};
23
- // const key = user || login || '';
24
- // const logins = getLogins();
23
+ const permissionsKey = getPermissionsCacheKey(loadedPermissions);
24
+ if (!cachedPermissions[permissionsKey]) {
25
+ cachedPermissions[permissionsKey] = compilePermissions(loadedPermissions);
26
+ }
25
27
 
26
- // if (!userPermissions[key]) {
27
- // if (logins) {
28
- // const login = logins.find(x => x.login == user);
29
- // userPermissions[key] = compilePermissions(login ? login.permissions : null);
30
- // } else {
31
- // userPermissions[key] = compilePermissions(process.env.PERMISSIONS);
32
- // }
33
- // }
34
- // return testPermission(tested, userPermissions[key]);
28
+ return testPermission(tested, cachedPermissions[permissionsKey]);
35
29
  }
36
30
 
37
- // let loginsCache = null;
38
- // let loginsLoaded = false;
39
-
40
- // function getLogins() {
41
- // if (loginsLoaded) {
42
- // return loginsCache;
43
- // }
44
-
45
- // const res = [];
46
- // if (process.env.LOGIN && process.env.PASSWORD) {
47
- // res.push({
48
- // login: process.env.LOGIN,
49
- // password: process.env.PASSWORD,
50
- // permissions: process.env.PERMISSIONS,
51
- // });
52
- // }
53
- // if (process.env.LOGINS) {
54
- // const logins = _.compact(process.env.LOGINS.split(',').map(x => x.trim()));
55
- // for (const login of logins) {
56
- // const password = process.env[`LOGIN_PASSWORD_${login}`];
57
- // const permissions = process.env[`LOGIN_PERMISSIONS_${login}`];
58
- // if (password) {
59
- // res.push({
60
- // login,
61
- // password,
62
- // permissions,
63
- // });
64
- // }
65
- // }
66
- // } else if (process.env.OAUTH_PERMISSIONS) {
67
- // const login_permission_keys = Object.keys(process.env).filter(key => _.startsWith(key, 'LOGIN_PERMISSIONS_'));
68
- // for (const permissions_key of login_permission_keys) {
69
- // const login = permissions_key.replace('LOGIN_PERMISSIONS_', '');
70
- // const permissions = process.env[permissions_key];
71
- // userPermissions[login] = compilePermissions(permissions);
72
- // }
73
- // }
74
-
75
- // loginsCache = res.length > 0 ? res : null;
76
- // loginsLoaded = true;
77
- // return loginsCache;
78
- // }
79
-
80
- function connectionHasPermission(connection, req) {
31
+ function connectionHasPermission(connection, loadedPermissions) {
81
32
  if (!connection) {
82
33
  return true;
83
34
  }
84
35
  if (_.isString(connection)) {
85
- return hasPermission(`connections/${connection}`, req);
36
+ return hasPermission(`connections/${connection}`, loadedPermissions);
86
37
  } else {
87
- return hasPermission(`connections/${connection._id}`, req);
38
+ return hasPermission(`connections/${connection._id}`, loadedPermissions);
39
+ }
40
+ }
41
+
42
+ async function testConnectionPermission(connection, req, loadedPermissions) {
43
+ if (!loadedPermissions) {
44
+ loadedPermissions = await loadPermissionsFromRequest(req);
45
+ }
46
+ if (process.env.STORAGE_DATABASE) {
47
+ if (hasPermission(`all-connections`, loadedPermissions)) {
48
+ return;
49
+ }
50
+ const conid = _.isString(connection) ? connection : connection?._id;
51
+ if (hasPermission('internal-storage', loadedPermissions) && conid == '__storage') {
52
+ return;
53
+ }
54
+ const authProvider = getAuthProviderFromReq(req);
55
+ if (!req) {
56
+ return;
57
+ }
58
+ if (!(await authProvider.checkCurrentConnectionPermission(req, conid))) {
59
+ throw new Error('DBGM-00263 Connection permission not granted');
60
+ }
61
+ } else {
62
+ if (!connectionHasPermission(connection, loadedPermissions)) {
63
+ throw new Error('DBGM-00264 Connection permission not granted');
64
+ }
65
+ }
66
+ }
67
+
68
+ async function loadDatabasePermissionsFromRequest(req) {
69
+ const authProvider = getAuthProviderFromReq(req);
70
+ if (!req) {
71
+ return null;
72
+ }
73
+
74
+ const databasePermissions = await authProvider.getCurrentDatabasePermissions(req);
75
+ return databasePermissions;
76
+ }
77
+
78
+ async function loadTablePermissionsFromRequest(req) {
79
+ const authProvider = getAuthProviderFromReq(req);
80
+ if (!req) {
81
+ return null;
82
+ }
83
+
84
+ const tablePermissions = await authProvider.getCurrentTablePermissions(req);
85
+ return tablePermissions;
86
+ }
87
+
88
+ function matchDatabasePermissionRow(conid, database, permissionRow) {
89
+ if (permissionRow.connection_id) {
90
+ if (conid != permissionRow.connection_id) {
91
+ return false;
92
+ }
93
+ }
94
+ if (permissionRow.database_names_list) {
95
+ const items = permissionRow.database_names_list.split('\n');
96
+ if (!items.find(item => item.trim()?.toLowerCase() === database?.toLowerCase())) {
97
+ return false;
98
+ }
99
+ }
100
+ if (permissionRow.database_names_regex) {
101
+ const regex = new RegExp(permissionRow.database_names_regex, 'i');
102
+ if (!regex.test(database)) {
103
+ return false;
104
+ }
105
+ }
106
+ return true;
107
+ }
108
+
109
+ function matchTablePermissionRow(objectTypeField, schemaName, pureName, permissionRow) {
110
+ if (permissionRow.table_names_list) {
111
+ const items = permissionRow.table_names_list.split('\n');
112
+ if (!items.find(item => item.trim()?.toLowerCase() === pureName?.toLowerCase())) {
113
+ return false;
114
+ }
115
+ }
116
+ if (permissionRow.table_names_regex) {
117
+ const regex = new RegExp(permissionRow.table_names_regex, 'i');
118
+ if (!regex.test(pureName)) {
119
+ return false;
120
+ }
121
+ }
122
+ if (permissionRow.schema_names_list) {
123
+ const items = permissionRow.schema_names_list.split('\n');
124
+ if (!items.find(item => item.trim()?.toLowerCase() === schemaName?.toLowerCase())) {
125
+ return false;
126
+ }
127
+ }
128
+ if (permissionRow.schema_names_regex) {
129
+ const regex = new RegExp(permissionRow.schema_names_regex, 'i');
130
+ if (!regex.test(schemaName)) {
131
+ return false;
132
+ }
133
+ }
134
+
135
+ return true;
136
+ }
137
+
138
+ const DATABASE_ROLE_ID_NAMES = {
139
+ '-1': 'view',
140
+ '-2': 'read_content',
141
+ '-3': 'write_data',
142
+ '-4': 'run_script',
143
+ '-5': 'deny',
144
+ };
145
+
146
+ function getDatabaseRoleLevelIndex(roleName) {
147
+ if (!roleName) {
148
+ return 6;
149
+ }
150
+ if (roleName == 'run_script') {
151
+ return 5;
152
+ }
153
+ if (roleName == 'write_data') {
154
+ return 4;
155
+ }
156
+ if (roleName == 'read_content') {
157
+ return 3;
158
+ }
159
+ if (roleName == 'view') {
160
+ return 2;
161
+ }
162
+ if (roleName == 'deny') {
163
+ return 1;
164
+ }
165
+ return 6;
166
+ }
167
+
168
+ function getTablePermissionRoleLevelIndex(roleName) {
169
+ if (!roleName) {
170
+ return 6;
171
+ }
172
+ if (roleName == 'run_script') {
173
+ return 5;
174
+ }
175
+ if (roleName == 'create_update_delete') {
176
+ return 4;
177
+ }
178
+ if (roleName == 'update_only') {
179
+ return 3;
180
+ }
181
+ if (roleName == 'read') {
182
+ return 2;
183
+ }
184
+ if (roleName == 'deny') {
185
+ return 1;
88
186
  }
187
+ return 6;
89
188
  }
90
189
 
91
- function testConnectionPermission(connection, req) {
92
- if (!connectionHasPermission(connection, req)) {
93
- throw new Error('Connection permission not granted');
190
+ function getDatabasePermissionRole(conid, database, loadedDatabasePermissions) {
191
+ let res = 'deny';
192
+ for (const permissionRow of loadedDatabasePermissions) {
193
+ if (!matchDatabasePermissionRow(conid, database, permissionRow)) {
194
+ continue;
195
+ }
196
+ res = DATABASE_ROLE_ID_NAMES[permissionRow.database_permission_role_id];
197
+ }
198
+ return res;
199
+ }
200
+
201
+ const TABLE_ROLE_ID_NAMES = {
202
+ '-1': 'read',
203
+ '-2': 'update_only',
204
+ '-3': 'create_update_delete',
205
+ '-4': 'run_script',
206
+ '-5': 'deny',
207
+ };
208
+
209
+ const TABLE_SCOPE_ID_NAMES = {
210
+ '-1': 'all_objects',
211
+ '-2': 'tables',
212
+ '-3': 'views',
213
+ '-4': 'tables_views_collections',
214
+ '-5': 'procedures',
215
+ '-6': 'functions',
216
+ '-7': 'triggers',
217
+ '-8': 'sql_objects',
218
+ '-9': 'collections',
219
+ };
220
+
221
+ function getTablePermissionRole(
222
+ conid,
223
+ database,
224
+ objectTypeField,
225
+ schemaName,
226
+ pureName,
227
+ loadedTablePermissions,
228
+ databasePermissionRole
229
+ ) {
230
+ let res =
231
+ databasePermissionRole == 'read_content'
232
+ ? 'read'
233
+ : databasePermissionRole == 'write_data'
234
+ ? 'create_update_delete'
235
+ : databasePermissionRole == 'run_script'
236
+ ? 'run_script'
237
+ : 'deny';
238
+ for (const permissionRow of loadedTablePermissions) {
239
+ if (!matchDatabasePermissionRow(conid, database, permissionRow)) {
240
+ continue;
241
+ }
242
+ if (!matchTablePermissionRow(objectTypeField, schemaName, pureName, permissionRow)) {
243
+ continue;
244
+ }
245
+ const scope = TABLE_SCOPE_ID_NAMES[permissionRow.table_permission_scope_id];
246
+ switch (scope) {
247
+ case 'tables':
248
+ if (objectTypeField != 'tables') continue;
249
+ break;
250
+ case 'views':
251
+ if (objectTypeField != 'views') continue;
252
+ break;
253
+ case 'tables_views_collections':
254
+ if (objectTypeField != 'tables' && objectTypeField != 'views' && objectTypeField != 'collections') continue;
255
+ break;
256
+ case 'procedures':
257
+ if (objectTypeField != 'procedures') continue;
258
+ break;
259
+ case 'functions':
260
+ if (objectTypeField != 'functions') continue;
261
+ break;
262
+ case 'triggers':
263
+ if (objectTypeField != 'triggers') continue;
264
+ break;
265
+ case 'sql_objects':
266
+ if (objectTypeField != 'procedures' && objectTypeField != 'functions' && objectTypeField != 'triggers')
267
+ continue;
268
+ break;
269
+ case 'collections':
270
+ if (objectTypeField != 'collections') continue;
271
+ break;
272
+ }
273
+ res = TABLE_ROLE_ID_NAMES[permissionRow.table_permission_role_id];
274
+ }
275
+ return res;
276
+ }
277
+
278
+ async function testStandardPermission(permission, req, loadedPermissions) {
279
+ if (!loadedPermissions) {
280
+ loadedPermissions = await loadPermissionsFromRequest(req);
281
+ }
282
+ if (!hasPermission(permission, loadedPermissions)) {
283
+ throw new Error('DBGM-00265 Permission not granted');
284
+ }
285
+ }
286
+
287
+ async function testDatabaseRolePermission(conid, database, requiredRole, req) {
288
+ if (!process.env.STORAGE_DATABASE) {
289
+ return;
290
+ }
291
+ const loadedPermissions = await loadPermissionsFromRequest(req);
292
+ if (hasPermission(`all-databases`, loadedPermissions)) {
293
+ return;
294
+ }
295
+ const databasePermissions = await loadDatabasePermissionsFromRequest(req);
296
+ const role = getDatabasePermissionRole(conid, database, databasePermissions);
297
+ const requiredIndex = getDatabaseRoleLevelIndex(requiredRole);
298
+ const roleIndex = getDatabaseRoleLevelIndex(role);
299
+ if (roleIndex < requiredIndex) {
300
+ throw new Error('DBGM-00266 Permission not granted');
94
301
  }
95
302
  }
96
303
 
@@ -98,4 +305,12 @@ module.exports = {
98
305
  hasPermission,
99
306
  connectionHasPermission,
100
307
  testConnectionPermission,
308
+ loadPermissionsFromRequest,
309
+ loadDatabasePermissionsFromRequest,
310
+ loadTablePermissionsFromRequest,
311
+ getDatabasePermissionRole,
312
+ getTablePermissionRole,
313
+ testStandardPermission,
314
+ testDatabaseRolePermission,
315
+ getTablePermissionRoleLevelIndex,
101
316
  };
@@ -28,7 +28,7 @@ async function loadModelTransform(file) {
28
28
  }
29
29
  return null;
30
30
  } catch (err) {
31
- logger.error(extractErrorLogData(err), `Error loading model transform ${file}`);
31
+ logger.error(extractErrorLogData(err), `DBGM-00173 Error loading model transform ${file}`);
32
32
  return null;
33
33
  }
34
34
  }
@@ -40,7 +40,7 @@ function callForwardProcess(connection, tunnelConfig, tunnelCacheKey) {
40
40
  tunnelConfig,
41
41
  });
42
42
  } catch (err) {
43
- logger.error(extractErrorLogData(err), 'Error connecting SSH');
43
+ logger.error(extractErrorLogData(err), 'DBGM-00174 Error connecting SSH');
44
44
  }
45
45
  return new Promise((resolve, reject) => {
46
46
  let promiseHandled = false;
@@ -57,18 +57,18 @@ function callForwardProcess(connection, tunnelConfig, tunnelCacheKey) {
57
57
  }
58
58
  });
59
59
  subprocess.on('exit', code => {
60
- logger.info(`SSH forward process exited with code ${code}`);
60
+ logger.info(`DBGM-00090 SSH forward process exited with code ${code}`);
61
61
  delete sshTunnelCache[tunnelCacheKey];
62
62
  if (!promiseHandled) {
63
63
  reject(
64
64
  new Error(
65
- 'SSH forward process exited, try to change "Local host address for SSH connections" in Settings/Connections'
65
+ 'DBGM-00091 SSH forward process exited, try to change "Local host address for SSH connections" in Settings/Connections'
66
66
  )
67
67
  );
68
68
  }
69
69
  });
70
70
  subprocess.on('error', error => {
71
- logger.error(extractErrorLogData(error), 'SSH forward process error');
71
+ logger.error(extractErrorLogData(error), 'DBGM-00092 SSH forward process error');
72
72
  delete sshTunnelCache[tunnelCacheKey];
73
73
  if (!promiseHandled) {
74
74
  reject(error);
@@ -97,13 +97,13 @@ async function getSshTunnel(connection) {
97
97
  };
98
98
  try {
99
99
  logger.info(
100
- `Creating SSH tunnel to ${connection.sshHost}-${connection.server}:${connection.port}, using local port ${localPort}`
100
+ `DBGM-00093 Creating SSH tunnel to ${connection.sshHost}-${connection.server}:${connection.port}, using local port ${localPort}`
101
101
  );
102
102
 
103
103
  const subprocess = await callForwardProcess(connection, tunnelConfig, tunnelCacheKey);
104
104
 
105
105
  logger.info(
106
- `Created SSH tunnel to ${connection.sshHost}-${connection.server}:${connection.port}, using local port ${localPort}`
106
+ `DBGM-00094 Created SSH tunnel to ${connection.sshHost}-${connection.server}:${connection.port}, using local port ${localPort}`
107
107
  );
108
108
 
109
109
  sshTunnelCache[tunnelCacheKey] = {
@@ -114,7 +114,7 @@ async function getSshTunnel(connection) {
114
114
  };
115
115
  return sshTunnelCache[tunnelCacheKey];
116
116
  } catch (err) {
117
- logger.error(extractErrorLogData(err), 'Error creating SSH tunnel:');
117
+ logger.error(extractErrorLogData(err), 'DBGM-00095 Error creating SSH tunnel:');
118
118
  // error is not cached
119
119
  return {
120
120
  state: 'error',
@@ -10,7 +10,7 @@ async function handleGetSshTunnelRequest({ msgid, connection }, subprocess) {
10
10
  try {
11
11
  subprocess.send({ msgtype: 'getsshtunnel-response', msgid, response });
12
12
  } catch (err) {
13
- logger.error(extractErrorLogData(err), 'Error sending to SSH tunnel');
13
+ logger.error(extractErrorLogData(err), 'DBGM-00175 Error sending to SSH tunnel');
14
14
  }
15
15
  }
16
16
 
@@ -12,11 +12,11 @@ module.exports = function useController(app, electron, route, controller) {
12
12
  const router = express.Router();
13
13
 
14
14
  if (controller._init) {
15
- logger.info(`Calling init controller for controller ${route}`);
15
+ logger.info(`DBGM-00096 Calling init controller for controller ${route}`);
16
16
  try {
17
17
  controller._init();
18
18
  } catch (err) {
19
- logger.error(extractErrorLogData(err), `Error initializing controller, exiting application`);
19
+ logger.error(extractErrorLogData(err), `DBGM-00097 Error initializing controller, exiting application`);
20
20
  process.exit(1);
21
21
  }
22
22
  }
@@ -78,7 +78,7 @@ module.exports = function useController(app, electron, route, controller) {
78
78
  const data = await controller[key]({ ...req.body, ...req.query }, req);
79
79
  res.json(data);
80
80
  } catch (err) {
81
- logger.error(extractErrorLogData(err), `Error when processing route ${route}/${key}`);
81
+ logger.error(extractErrorLogData(err), `DBGM-00176 Error when processing route ${route}/${key}`);
82
82
  if (err instanceof MissingCredentialsError) {
83
83
  res.json({
84
84
  missingCredentials: true,