@harperfast/harper-pro 5.0.0-alpha.2 → 5.0.0-alpha.4
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/core/.github/workflows/create-release.yaml +181 -0
- package/core/.github/workflows/notify-release-published.yaml +50 -0
- package/core/.github/workflows/publish-docker.yaml +174 -0
- package/core/.github/workflows/publish-npm.yaml +173 -0
- package/core/CONTRIBUTING.md +2 -0
- package/core/bin/harper.js +6 -0
- package/core/components/PluginModule.ts +0 -1
- package/core/components/componentLoader.ts +16 -4
- package/core/dev/sync-commits.js +18 -7
- package/core/integrationTests/server/operation-user-rbac.test.ts +484 -0
- package/core/package.json +4 -3
- package/core/resources/DatabaseTransaction.ts +2 -1
- package/core/resources/LMDBTransaction.ts +9 -4
- package/core/resources/RecordEncoder.ts +5 -3
- package/core/resources/RequestTarget.ts +1 -1
- package/core/resources/Resource.ts +1 -1
- package/core/resources/RocksTransactionLogStore.ts +106 -12
- package/core/resources/Table.ts +108 -114
- package/core/resources/dataLoader.ts +0 -1
- package/core/resources/databases.ts +1 -1
- package/core/resources/jsResource.ts +0 -2
- package/core/resources/registrationDeprecated.ts +8 -0
- package/core/resources/transactionBroadcast.ts +29 -25
- package/core/security/auth.ts +14 -1
- package/core/security/jsLoader.ts +9 -2
- package/core/security/permissionsTranslator.js +4 -0
- package/core/security/user.ts +20 -2
- package/core/server/REST.ts +29 -16
- package/core/server/Server.ts +6 -2
- package/core/server/http.ts +5 -7
- package/core/server/serverHelpers/Request.ts +5 -0
- package/core/server/serverHelpers/serverUtilities.ts +6 -2
- package/core/server/static.ts +0 -2
- package/core/unitTests/apiTests/cache-test.mjs +37 -0
- package/core/unitTests/commonTestErrors.js +4 -0
- package/core/unitTests/dataLayer/SQLSearch.test.js +2 -2
- package/core/unitTests/dataLayer/sql-update.test.js +2 -2
- package/core/unitTests/resources/auditLog.test.js +167 -0
- package/core/unitTests/resources/caching.test.js +79 -0
- package/core/unitTests/resources/permissions.test.js +7 -2
- package/core/unitTests/resources/txn-tracking.test.js +10 -4
- package/core/unitTests/resources/vectorIndex.test.js +1 -0
- package/core/unitTests/testApp/resources.js +30 -0
- package/core/unitTests/testApp/schema.graphql +5 -0
- package/core/unitTests/utility/operationPermissions.test.js +107 -0
- package/core/unitTests/utility/operation_authorization.test.js +130 -0
- package/core/unitTests/validation/role_validation.test.js +79 -0
- package/core/utility/common_utils.js +8 -4
- package/core/utility/errors/commonErrors.js +4 -0
- package/core/utility/hdbTerms.ts +1 -0
- package/core/utility/operationPermissions.ts +96 -0
- package/core/utility/operation_authorization.js +150 -49
- package/core/validation/role_validation.js +23 -0
- package/dist/bin/harper.js +1 -1
- package/dist/bin/harper.js.map +1 -1
- package/dist/cloneNode/cloneNode.js +55 -38
- package/dist/cloneNode/cloneNode.js.map +1 -1
- package/dist/core/bin/harper.js +2 -0
- package/dist/core/bin/harper.js.map +1 -1
- package/dist/core/components/ComponentV1.js +1 -0
- package/dist/core/components/ComponentV1.js.map +1 -1
- package/dist/core/components/EntryHandler.js +35 -1
- package/dist/core/components/EntryHandler.js.map +1 -1
- package/dist/core/components/PluginModule.js +1 -0
- package/dist/core/components/PluginModule.js.map +1 -1
- package/dist/core/components/Scope.js +2 -0
- package/dist/core/components/Scope.js.map +1 -1
- package/dist/core/components/componentLoader.js +12 -3
- package/dist/core/components/componentLoader.js.map +1 -1
- package/dist/core/components/status/api.js +1 -0
- package/dist/core/components/status/api.js.map +1 -1
- package/dist/core/components/status/crossThread.js +1 -0
- package/dist/core/components/status/crossThread.js.map +1 -1
- package/dist/core/config/RootConfigWatcher.js +34 -4
- package/dist/core/config/RootConfigWatcher.js.map +1 -1
- package/dist/core/dataLayer/harperBridge/ResourceBridge.js +1 -0
- package/dist/core/dataLayer/harperBridge/ResourceBridge.js.map +1 -1
- package/dist/core/resources/DatabaseTransaction.js +3 -1
- package/dist/core/resources/DatabaseTransaction.js.map +1 -1
- package/dist/core/resources/ErrorResource.js +1 -0
- package/dist/core/resources/ErrorResource.js.map +1 -1
- package/dist/core/resources/LMDBTransaction.js +10 -5
- package/dist/core/resources/LMDBTransaction.js.map +1 -1
- package/dist/core/resources/RecordEncoder.js +5 -3
- package/dist/core/resources/RecordEncoder.js.map +1 -1
- package/dist/core/resources/RequestTarget.js +3 -0
- package/dist/core/resources/RequestTarget.js.map +1 -1
- package/dist/core/resources/Resource.js +2 -1
- package/dist/core/resources/Resource.js.map +1 -1
- package/dist/core/resources/ResourceInterface.js +3 -0
- package/dist/core/resources/ResourceInterface.js.map +1 -1
- package/dist/core/resources/ResourceInterfaceV2.js +2 -0
- package/dist/core/resources/ResourceInterfaceV2.js.map +1 -1
- package/dist/core/resources/ResourceV2.js +3 -0
- package/dist/core/resources/ResourceV2.js.map +1 -1
- package/dist/core/resources/Resources.js +1 -0
- package/dist/core/resources/Resources.js.map +1 -1
- package/dist/core/resources/RocksIndexStore.js +1 -0
- package/dist/core/resources/RocksIndexStore.js.map +1 -1
- package/dist/core/resources/RocksTransactionLogStore.js +102 -12
- package/dist/core/resources/RocksTransactionLogStore.js.map +1 -1
- package/dist/core/resources/Table.js +100 -111
- package/dist/core/resources/Table.js.map +1 -1
- package/dist/core/resources/blob.js +1 -0
- package/dist/core/resources/blob.js.map +1 -1
- package/dist/core/resources/dataLoader.js +3 -2
- package/dist/core/resources/dataLoader.js.map +1 -1
- package/dist/core/resources/databases.js +1 -1
- package/dist/core/resources/databases.js.map +1 -1
- package/dist/core/resources/jsResource.js +2 -2
- package/dist/core/resources/jsResource.js.map +1 -1
- package/dist/core/resources/openApi.js +1 -0
- package/dist/core/resources/openApi.js.map +1 -1
- package/dist/core/resources/registrationDeprecated.js +11 -0
- package/dist/core/resources/registrationDeprecated.js.map +1 -0
- package/dist/core/resources/replayLogs.js +2 -0
- package/dist/core/resources/replayLogs.js.map +1 -1
- package/dist/core/resources/search.js +1 -0
- package/dist/core/resources/search.js.map +1 -1
- package/dist/core/resources/transactionBroadcast.js +30 -27
- package/dist/core/resources/transactionBroadcast.js.map +1 -1
- package/dist/core/security/auth.js +15 -1
- package/dist/core/security/auth.js.map +1 -1
- package/dist/core/security/jsLoader.js +10 -2
- package/dist/core/security/jsLoader.js.map +1 -1
- package/dist/core/security/permissionsTranslator.js +4 -0
- package/dist/core/security/permissionsTranslator.js.map +1 -1
- package/dist/core/security/user.js +16 -1
- package/dist/core/security/user.js.map +1 -1
- package/dist/core/server/REST.js +35 -18
- package/dist/core/server/REST.js.map +1 -1
- package/dist/core/server/Server.js +2 -0
- package/dist/core/server/Server.js.map +1 -1
- package/dist/core/server/http.js +5 -3
- package/dist/core/server/http.js.map +1 -1
- package/dist/core/server/operationsServer.js +2 -1
- package/dist/core/server/operationsServer.js.map +1 -1
- package/dist/core/server/serverHelpers/Request.js +2 -0
- package/dist/core/server/serverHelpers/Request.js.map +1 -1
- package/dist/core/server/serverHelpers/serverUtilities.js +3 -2
- package/dist/core/server/serverHelpers/serverUtilities.js.map +1 -1
- package/dist/core/server/static.js +1 -2
- package/dist/core/server/static.js.map +1 -1
- package/dist/core/utility/common_utils.js +7 -5
- package/dist/core/utility/common_utils.js.map +1 -1
- package/dist/core/utility/errors/commonErrors.js +3 -0
- package/dist/core/utility/errors/commonErrors.js.map +1 -1
- package/dist/core/utility/hdbTerms.js +1 -0
- package/dist/core/utility/hdbTerms.js.map +1 -1
- package/dist/core/utility/operationPermissions.js +101 -0
- package/dist/core/utility/operationPermissions.js.map +1 -0
- package/dist/core/utility/operation_authorization.js +83 -49
- package/dist/core/utility/operation_authorization.js.map +1 -1
- package/dist/core/validation/role_validation.js +19 -1
- package/dist/core/validation/role_validation.js.map +1 -1
- package/dist/licensing/usageLicensing.js +243 -0
- package/dist/licensing/usageLicensing.js.map +1 -0
- package/dist/licensing/validation.js +149 -0
- package/dist/licensing/validation.js.map +1 -0
- package/dist/replication/replicationConnection.js +90 -24
- package/dist/replication/replicationConnection.js.map +1 -1
- package/dist/replication/replicator.js +6 -2
- package/dist/replication/replicator.js.map +1 -1
- package/dist/replication/setNode.js +0 -1
- package/dist/replication/setNode.js.map +1 -1
- package/dist/security/certificate.js +206 -6
- package/dist/security/certificate.js.map +1 -1
- package/dist/security/keyService.js +58 -0
- package/dist/security/keyService.js.map +1 -0
- package/dist/security/sshKeyOperations.js +344 -0
- package/dist/security/sshKeyOperations.js.map +1 -0
- package/licensing/usageLicensing.ts +260 -0
- package/licensing/validation.ts +191 -0
- package/npm-shrinkwrap.json +509 -463
- package/package.json +6 -3
- package/replication/replicationConnection.ts +99 -31
- package/replication/replicator.ts +8 -3
- package/replication/setNode.ts +0 -1
- package/security/certificate.ts +259 -7
- package/security/keyService.ts +74 -0
- package/security/sshKeyOperations.ts +405 -0
- package/static/defaultConfig.yaml +2 -0
- package/studio/web/HDBDogOnly.svg +78 -0
- package/studio/web/assets/PPRadioGrotesk-Bold-DDaUYG8E.woff +0 -0
- package/studio/web/assets/fa-brands-400-CEJbCg16.woff +0 -0
- package/studio/web/assets/fa-brands-400-CSYNqBb_.ttf +0 -0
- package/studio/web/assets/fa-brands-400-DnkPfk3o.eot +0 -0
- package/studio/web/assets/fa-brands-400-UxlILjvJ.woff2 +0 -0
- package/studio/web/assets/fa-brands-400-cH1MgKbP.svg +3717 -0
- package/studio/web/assets/fa-regular-400-BhTwtT8w.eot +0 -0
- package/studio/web/assets/fa-regular-400-D1vz6WBx.ttf +0 -0
- package/studio/web/assets/fa-regular-400-DFnMcJPd.woff +0 -0
- package/studio/web/assets/fa-regular-400-DGzu1beS.woff2 +0 -0
- package/studio/web/assets/fa-regular-400-gwj8Pxq-.svg +801 -0
- package/studio/web/assets/fa-solid-900-B4ZZ7kfP.svg +5034 -0
- package/studio/web/assets/fa-solid-900-B6Axprfb.eot +0 -0
- package/studio/web/assets/fa-solid-900-BUswJgRo.woff2 +0 -0
- package/studio/web/assets/fa-solid-900-DOXgCApm.woff +0 -0
- package/studio/web/assets/fa-solid-900-mxuxnBEa.ttf +0 -0
- package/studio/web/assets/index-CjpELGtS.js +37 -0
- package/studio/web/assets/index-CjpELGtS.js.map +1 -0
- package/studio/web/assets/index-VoSl--bG.js +235 -0
- package/studio/web/assets/index-VoSl--bG.js.map +1 -0
- package/studio/web/assets/index-Y2g_iFpU.css +1 -0
- package/studio/web/assets/index-jiPwkrsB.css +1 -0
- package/studio/web/assets/index-o10iYrkU.js +2 -0
- package/studio/web/assets/index-o10iYrkU.js.map +1 -0
- package/studio/web/assets/index.lazy-D5hjT1pS.js +266 -0
- package/studio/web/assets/index.lazy-D5hjT1pS.js.map +1 -0
- package/studio/web/assets/profiler-B9Edexdi.js +2 -0
- package/studio/web/assets/profiler-B9Edexdi.js.map +1 -0
- package/studio/web/assets/react-redux-DsVOxD2E.js +6 -0
- package/studio/web/assets/react-redux-DsVOxD2E.js.map +1 -0
- package/studio/web/assets/startRecording-Bwd6AYRS.js +3 -0
- package/studio/web/assets/startRecording-Bwd6AYRS.js.map +1 -0
- package/studio/web/fabric-signup-background.webp +0 -0
- package/studio/web/fabric-signup-text.png +0 -0
- package/studio/web/favicon_purple.png +0 -0
- package/studio/web/github-icon.svg +15 -0
- package/studio/web/harper-fabric_black.png +0 -0
- package/studio/web/harper-fabric_white.png +0 -0
- package/studio/web/harper-studio_white.png +0 -0
- package/studio/web/index.html +16 -0
- package/studio/web/running.css +148 -0
- package/studio/web/running.html +147 -0
- package/studio/web/running.js +111 -0
|
@@ -21,6 +21,15 @@ describe('Audit log', () => {
|
|
|
21
21
|
subscription.on('data', (event) => {
|
|
22
22
|
events.push(event);
|
|
23
23
|
});
|
|
24
|
+
server.replication.mockRemoteMap = new Map([['local', 0]]);
|
|
25
|
+
server.replication.getIdOfRemoteNode = function (name) {
|
|
26
|
+
let id = server.replication.mockRemoteMap.get(name);
|
|
27
|
+
if (id === undefined) {
|
|
28
|
+
id = server.replication.mockRemoteMap.size;
|
|
29
|
+
server.replication.mockRemoteMap.set(name, id);
|
|
30
|
+
}
|
|
31
|
+
return id;
|
|
32
|
+
};
|
|
24
33
|
});
|
|
25
34
|
afterEach(function () {
|
|
26
35
|
setAuditRetention(60000);
|
|
@@ -48,6 +57,7 @@ describe('Audit log', () => {
|
|
|
48
57
|
for await (let entry of AuditedTable.getHistory()) {
|
|
49
58
|
results.push(entry);
|
|
50
59
|
}
|
|
60
|
+
|
|
51
61
|
assert.equal(results.length, 0);
|
|
52
62
|
assert.equal(AuditedTable.primaryStore.getEntry(1), undefined); // verify that the delete entry was removed
|
|
53
63
|
// verify that the twice-written entry was not removed
|
|
@@ -89,4 +99,161 @@ describe('Audit log', () => {
|
|
|
89
99
|
assert.equal(history[0].user, key.toString());
|
|
90
100
|
assert.deepEqual(history[0].value.id, key);
|
|
91
101
|
});
|
|
102
|
+
it('dynamically add new transaction logs to iterator', async function () {
|
|
103
|
+
if (!AuditedTable.auditStore.reusableIterable) return this.skip(); // only for rocksdb
|
|
104
|
+
|
|
105
|
+
// Create initial entries
|
|
106
|
+
await AuditedTable.put(10, { name: 'initial' });
|
|
107
|
+
await AuditedTable.put(11, { name: 'initial2' });
|
|
108
|
+
|
|
109
|
+
const results = [];
|
|
110
|
+
const iterator = AuditedTable.getHistory()[Symbol.asyncIterator]();
|
|
111
|
+
|
|
112
|
+
// Get first entry
|
|
113
|
+
let result = await iterator.next();
|
|
114
|
+
results.push(result.value);
|
|
115
|
+
|
|
116
|
+
// Emit a new transaction log event
|
|
117
|
+
AuditedTable.auditStore.rootStore.useLog('new-transaction-log');
|
|
118
|
+
await delay(20);
|
|
119
|
+
// Continue iterating - should include entries from new log if it has any
|
|
120
|
+
while (!(result = await iterator.next()).done) {
|
|
121
|
+
results.push(result.value);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Verify we got at least the initial entries
|
|
125
|
+
assert(results.length >= 2, 'Should have at least the initial entries');
|
|
126
|
+
});
|
|
127
|
+
it('cleanup listener when iterator completes naturally', async function () {
|
|
128
|
+
if (!AuditedTable.auditStore.reusableIterable) return this.skip(); // only for rocksdb
|
|
129
|
+
|
|
130
|
+
await AuditedTable.put(20, { name: 'test' });
|
|
131
|
+
|
|
132
|
+
const originalOn = AuditedTable.auditStore.rootStore.on.bind(AuditedTable.auditStore.rootStore);
|
|
133
|
+
const originalOff = AuditedTable.auditStore.rootStore.off.bind(AuditedTable.auditStore.rootStore);
|
|
134
|
+
let activeListener = null;
|
|
135
|
+
|
|
136
|
+
AuditedTable.auditStore.rootStore.on = function (event, listener) {
|
|
137
|
+
if (event === 'new-transaction-log') {
|
|
138
|
+
activeListener = listener;
|
|
139
|
+
}
|
|
140
|
+
return originalOn(event, listener);
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
AuditedTable.auditStore.rootStore.off = function (event, listener) {
|
|
144
|
+
if (event === 'new-transaction-log' && listener === activeListener) {
|
|
145
|
+
activeListener = null;
|
|
146
|
+
}
|
|
147
|
+
return originalOff(event, listener);
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
// Create iterator and let it complete
|
|
151
|
+
for await (const _entry of AuditedTable.getHistory()) {
|
|
152
|
+
// iterate through all
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Restore original methods
|
|
156
|
+
AuditedTable.auditStore.rootStore.on = originalOn;
|
|
157
|
+
AuditedTable.auditStore.rootStore.off = originalOff;
|
|
158
|
+
|
|
159
|
+
// Verify listener was cleaned up
|
|
160
|
+
assert.equal(activeListener, null, 'Listener should be cleaned up after completion');
|
|
161
|
+
});
|
|
162
|
+
it('cleanup listener when breaking from iteration', async function () {
|
|
163
|
+
if (!AuditedTable.auditStore.reusableIterable) return this.skip(); // only for rocksdb
|
|
164
|
+
|
|
165
|
+
await AuditedTable.put(30, { name: 'test1' });
|
|
166
|
+
await AuditedTable.put(31, { name: 'test2' });
|
|
167
|
+
await AuditedTable.put(32, { name: 'test3' });
|
|
168
|
+
|
|
169
|
+
// Track listener cleanup
|
|
170
|
+
const originalOn = AuditedTable.auditStore.rootStore.on.bind(AuditedTable.auditStore.rootStore);
|
|
171
|
+
const originalOff = AuditedTable.auditStore.rootStore.off.bind(AuditedTable.auditStore.rootStore);
|
|
172
|
+
let activeListener = null;
|
|
173
|
+
|
|
174
|
+
AuditedTable.auditStore.rootStore.on = function (event, listener) {
|
|
175
|
+
if (event === 'new-transaction-log') {
|
|
176
|
+
activeListener = listener;
|
|
177
|
+
}
|
|
178
|
+
return originalOn(event, listener);
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
AuditedTable.auditStore.rootStore.off = function (event, listener) {
|
|
182
|
+
if (event === 'new-transaction-log' && listener === activeListener) {
|
|
183
|
+
activeListener = null;
|
|
184
|
+
}
|
|
185
|
+
return originalOff(event, listener);
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
// Break early from iteration
|
|
189
|
+
let count = 0;
|
|
190
|
+
for await (const _entry of AuditedTable.getHistory()) {
|
|
191
|
+
if (++count >= 2) break;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Restore original methods
|
|
195
|
+
AuditedTable.auditStore.rootStore.on = originalOn;
|
|
196
|
+
AuditedTable.auditStore.rootStore.off = originalOff;
|
|
197
|
+
|
|
198
|
+
// Listener should be cleaned up after break
|
|
199
|
+
assert.equal(activeListener, null, 'Listener should be cleaned up after break');
|
|
200
|
+
});
|
|
201
|
+
it('exclude logs from new transaction log events', async function () {
|
|
202
|
+
if (!AuditedTable.auditStore.reusableIterable) return this.skip(); // only for rocksdb
|
|
203
|
+
await AuditedTable.put(40, { name: 'test' });
|
|
204
|
+
|
|
205
|
+
const excludedLog = 'excluded-log-' + Date.now();
|
|
206
|
+
const iterator = AuditedTable.auditStore.getRange({ excludeLogs: [excludedLog], start: 0 })[Symbol.iterator]();
|
|
207
|
+
|
|
208
|
+
// Start iteration
|
|
209
|
+
await iterator.next();
|
|
210
|
+
|
|
211
|
+
// Emit include log - should be include
|
|
212
|
+
let nodeId = AuditedTable.auditStore.ensureLogExists('new-transaction-log-2');
|
|
213
|
+
await delay(20);
|
|
214
|
+
await AuditedTable.put(41, { name: 'test' }, { nodeId });
|
|
215
|
+
// Emit excluded log - should be ignored
|
|
216
|
+
nodeId = AuditedTable.auditStore.ensureLogExists(excludedLog);
|
|
217
|
+
await delay(20);
|
|
218
|
+
|
|
219
|
+
await AuditedTable.put(42, { name: 'test' }, { nodeId });
|
|
220
|
+
|
|
221
|
+
let result = [];
|
|
222
|
+
// Finish iteration
|
|
223
|
+
let entry;
|
|
224
|
+
while (!(entry = await iterator.next()).done) {
|
|
225
|
+
result.push(entry.value);
|
|
226
|
+
}
|
|
227
|
+
assert(result.find((entry) => entry.recordId === 41));
|
|
228
|
+
//assert(!result.find((entry) => entry.recordId === 42));
|
|
229
|
+
assert(true, 'Should complete without including excluded log');
|
|
230
|
+
});
|
|
231
|
+
it('add and remove logs dynamically using iterator methods', async function () {
|
|
232
|
+
if (!AuditedTable.auditStore.reusableIterable) return this.skip(); // only for rocksdb
|
|
233
|
+
|
|
234
|
+
await AuditedTable.put(50, { name: 'test' });
|
|
235
|
+
|
|
236
|
+
const iterable = AuditedTable.auditStore.getRange({});
|
|
237
|
+
const iterator = iterable[Symbol.iterator]();
|
|
238
|
+
|
|
239
|
+
// Start iteration
|
|
240
|
+
iterator.next();
|
|
241
|
+
|
|
242
|
+
// Add a new log using the addLog method on the iterable
|
|
243
|
+
const newLogName = 'manual-log-' + Date.now();
|
|
244
|
+
AuditedTable.auditStore.ensureLogExists(newLogName);
|
|
245
|
+
|
|
246
|
+
// Verify the log was added to logByName
|
|
247
|
+
assert(AuditedTable.auditStore.logByName.has(newLogName), 'Log should be added to logByName');
|
|
248
|
+
|
|
249
|
+
// Remove the log using the removeLog method on the iterable
|
|
250
|
+
iterable.removeLog(newLogName);
|
|
251
|
+
|
|
252
|
+
// Continue iterating to completion
|
|
253
|
+
while (!(await iterator.next()).done) {
|
|
254
|
+
// continue
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
assert(true, 'Should complete successfully after adding and removing logs');
|
|
258
|
+
});
|
|
92
259
|
});
|
|
@@ -364,4 +364,83 @@ describe('Caching', () => {
|
|
|
364
364
|
timer = 0;
|
|
365
365
|
}
|
|
366
366
|
});
|
|
367
|
+
|
|
368
|
+
it('Extended class with sourcedFrom does not impact base class', async function () {
|
|
369
|
+
// Create a base table without a source
|
|
370
|
+
const BaseTable = table({
|
|
371
|
+
table: 'BaseTable',
|
|
372
|
+
database: 'test',
|
|
373
|
+
attributes: [{ name: 'id', isPrimaryKey: true }, { name: 'value' }],
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
// Create an extended class and give it a source
|
|
377
|
+
class ExtendedTable extends BaseTable {}
|
|
378
|
+
|
|
379
|
+
let extendedSourceCalls = 0;
|
|
380
|
+
ExtendedTable.sourcedFrom({
|
|
381
|
+
get(id) {
|
|
382
|
+
return new Promise((resolve) => {
|
|
383
|
+
extendedSourceCalls++;
|
|
384
|
+
resolve({
|
|
385
|
+
id,
|
|
386
|
+
value: 'extended-' + id,
|
|
387
|
+
});
|
|
388
|
+
});
|
|
389
|
+
},
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
// Verify the extended class has a source
|
|
393
|
+
assert(ExtendedTable.source);
|
|
394
|
+
assert.equal(typeof ExtendedTable.source.get, 'function');
|
|
395
|
+
|
|
396
|
+
// Verify the base class does NOT have a source
|
|
397
|
+
assert(!BaseTable.source);
|
|
398
|
+
|
|
399
|
+
// Test that the extended class uses its source
|
|
400
|
+
extendedSourceCalls = 0;
|
|
401
|
+
await ExtendedTable.invalidate(100);
|
|
402
|
+
const extendedResult = await ExtendedTable.get(100);
|
|
403
|
+
assert.equal(extendedResult.value, 'extended-100');
|
|
404
|
+
assert.equal(extendedSourceCalls, 1);
|
|
405
|
+
|
|
406
|
+
// Test that the base class doesn't call any source
|
|
407
|
+
await BaseTable.invalidate(101);
|
|
408
|
+
const baseResult = await BaseTable.get(101);
|
|
409
|
+
assert.equal(baseResult, undefined); // Should be undefined since there's no source
|
|
410
|
+
assert.equal(extendedSourceCalls, 1); // Should not have called extended source
|
|
411
|
+
|
|
412
|
+
// Create another extended class with a different source
|
|
413
|
+
class AnotherExtendedTable extends BaseTable {}
|
|
414
|
+
|
|
415
|
+
let anotherSourceCalls = 0;
|
|
416
|
+
AnotherExtendedTable.sourcedFrom({
|
|
417
|
+
get(id) {
|
|
418
|
+
return new Promise((resolve) => {
|
|
419
|
+
anotherSourceCalls++;
|
|
420
|
+
resolve({
|
|
421
|
+
id,
|
|
422
|
+
value: 'another-' + id,
|
|
423
|
+
});
|
|
424
|
+
});
|
|
425
|
+
},
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
// Verify each extended class has its own independent source
|
|
429
|
+
assert(AnotherExtendedTable.source);
|
|
430
|
+
assert(AnotherExtendedTable.source !== ExtendedTable.source);
|
|
431
|
+
|
|
432
|
+
// Test that each extended class uses its own source
|
|
433
|
+
await AnotherExtendedTable.invalidate(102);
|
|
434
|
+
const anotherResult = await AnotherExtendedTable.get(102);
|
|
435
|
+
assert.equal(anotherResult.value, 'another-102');
|
|
436
|
+
assert.equal(anotherSourceCalls, 1);
|
|
437
|
+
assert.equal(extendedSourceCalls, 1); // ExtendedTable source should not be called
|
|
438
|
+
|
|
439
|
+
// Verify ExtendedTable still uses its own source
|
|
440
|
+
await ExtendedTable.invalidate(103);
|
|
441
|
+
const extendedResult2 = await ExtendedTable.get(103);
|
|
442
|
+
assert.equal(extendedResult2.value, 'extended-103');
|
|
443
|
+
assert.equal(extendedSourceCalls, 2);
|
|
444
|
+
assert.equal(anotherSourceCalls, 1); // AnotherExtendedTable source should not be called
|
|
445
|
+
});
|
|
367
446
|
});
|
|
@@ -30,8 +30,13 @@ describe('Permissions through Resource API', () => {
|
|
|
30
30
|
],
|
|
31
31
|
});
|
|
32
32
|
for (let i = 0; i < 10; i++) {
|
|
33
|
-
RelatedTable.put({ id: 'related-id-' + i, name: 'related name-' + i });
|
|
34
|
-
TestTable.put({
|
|
33
|
+
await RelatedTable.put({ id: 'related-id-' + i, name: 'related name-' + i });
|
|
34
|
+
await TestTable.put({
|
|
35
|
+
id: 'id-' + i,
|
|
36
|
+
name: i > 0 ? 'name-' + i : null,
|
|
37
|
+
prop1: 'test',
|
|
38
|
+
relatedId: 'related-id-' + i,
|
|
39
|
+
});
|
|
35
40
|
}
|
|
36
41
|
restricted_user = {
|
|
37
42
|
role: {
|
|
@@ -2,9 +2,11 @@ require('../testUtils');
|
|
|
2
2
|
const assert = require('assert');
|
|
3
3
|
const { setupTestDBPath } = require('../testUtils');
|
|
4
4
|
const { setTxnExpiration } = require('#src/resources/DatabaseTransaction');
|
|
5
|
+
const { setTxnExpiration: setLMDBTxnExpiration } = require('#src/resources/LMDBTransaction');
|
|
5
6
|
const { setMainIsWorker } = require('#js/server/threads/manageThreads');
|
|
6
7
|
const { table } = require('#src/resources/databases');
|
|
7
8
|
const { setTimeout: delay } = require('node:timers/promises');
|
|
9
|
+
const { RocksDatabase } = require('@harperfast/rocksdb-js');
|
|
8
10
|
describe('Txn Expiration', () => {
|
|
9
11
|
let SlowResource,
|
|
10
12
|
performedDBInteractions = false;
|
|
@@ -31,15 +33,19 @@ describe('Txn Expiration', () => {
|
|
|
31
33
|
});
|
|
32
34
|
it('Slow txn will expire', async function () {
|
|
33
35
|
await SlowResource.put(3, { name: 'three' });
|
|
34
|
-
let trackedTxns =
|
|
36
|
+
let trackedTxns =
|
|
37
|
+
SlowResource.primaryStore instanceof RocksDatabase ? setTxnExpiration(20) : setLMDBTxnExpiration(20);
|
|
38
|
+
await delay(50);
|
|
35
39
|
let existingTxns = trackedTxns.size;
|
|
36
40
|
let result = SlowResource.get(3);
|
|
37
41
|
assert.equal(trackedTxns.size, existingTxns + 1);
|
|
38
42
|
const txns = Array.from(trackedTxns);
|
|
39
43
|
const lastTxn = txns[txns.length - 1];
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
44
|
+
if (SlowResource.primaryStore instanceof RocksDatabase) {
|
|
45
|
+
assert.equal(lastTxn.startedFrom.resourceName, 'SlowResource');
|
|
46
|
+
assert.equal(lastTxn.startedFrom.method, 'get');
|
|
47
|
+
assert.equal(lastTxn.timeout, 20);
|
|
48
|
+
}
|
|
43
49
|
await Promise.race([delay(50), result]);
|
|
44
50
|
assert(performedDBInteractions);
|
|
45
51
|
assert.equal(trackedTxns.size, existingTxns);
|
|
@@ -3,6 +3,7 @@ const { table } = require('#src/resources/databases');
|
|
|
3
3
|
const { HierarchicalNavigableSmallWorld } = require('#src/resources/indexes/HierarchicalNavigableSmallWorld');
|
|
4
4
|
|
|
5
5
|
describe('HierarchicalNavigableSmallWorld indexing', () => {
|
|
6
|
+
if (process.env.HARPER_STORAGE_ENGINE === 'lmdb') return; // don't try to test lmdb
|
|
6
7
|
let HNSWTest;
|
|
7
8
|
let testInstance = new HierarchicalNavigableSmallWorld();
|
|
8
9
|
let all = [];
|
|
@@ -163,6 +163,36 @@ tables.CacheOfResource.sourcedFrom({
|
|
|
163
163
|
},
|
|
164
164
|
});
|
|
165
165
|
|
|
166
|
+
// test transparent HTTP caching straight through
|
|
167
|
+
tables.CacheOfHttp.sourcedFrom({
|
|
168
|
+
async get(target) {
|
|
169
|
+
switch (target) {
|
|
170
|
+
case 'direct-fetch':
|
|
171
|
+
return fetch(`http://localhost:9926/FourProp/2`);
|
|
172
|
+
case 'fetch-body':
|
|
173
|
+
const response = await fetch(`http://localhost:9926/FourProp/2`);
|
|
174
|
+
return new Response(response.body, {
|
|
175
|
+
headers: {
|
|
176
|
+
'Content-Type': 'text/plain',
|
|
177
|
+
},
|
|
178
|
+
});
|
|
179
|
+
case 'created-response':
|
|
180
|
+
return new Response('test', {
|
|
181
|
+
headers: { 'x-custom-header': 'custom value', 'cache-control': 'max-age=10, s-maxage=20' },
|
|
182
|
+
});
|
|
183
|
+
case 'html-response':
|
|
184
|
+
return new Response('<html>test</html>', {
|
|
185
|
+
headers: { 'content-type': 'text/html' },
|
|
186
|
+
});
|
|
187
|
+
case 'headers-in-data':
|
|
188
|
+
return {
|
|
189
|
+
headers: { 'x-custom-header': 'custom value' },
|
|
190
|
+
name: 'test-sibling-to-headers',
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
},
|
|
194
|
+
});
|
|
195
|
+
|
|
166
196
|
export class FourPropWithHistory extends tables.FourProp {
|
|
167
197
|
async subscribe(options) {
|
|
168
198
|
let context = this.getContext();
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const assert = require('assert');
|
|
4
|
+
const terms = require('#src/utility/hdbTerms');
|
|
5
|
+
const { OPERATION_PERMISSION_GROUPS, expandOperationsPerms } = require('#src/utility/operationPermissions');
|
|
6
|
+
|
|
7
|
+
describe('operationPermissions', function () {
|
|
8
|
+
describe('OPERATION_PERMISSION_GROUPS', function () {
|
|
9
|
+
it('read_only contains expected read operations', function () {
|
|
10
|
+
const ops = OPERATION_PERMISSION_GROUPS.read_only;
|
|
11
|
+
assert.ok(ops.includes(terms.OPERATIONS_ENUM.SEARCH));
|
|
12
|
+
assert.ok(ops.includes(terms.OPERATIONS_ENUM.SQL));
|
|
13
|
+
assert.ok(ops.includes(terms.OPERATIONS_ENUM.DESCRIBE_ALL));
|
|
14
|
+
assert.ok(ops.includes(terms.OPERATIONS_ENUM.USER_INFO));
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('read_only does not contain write operations', function () {
|
|
18
|
+
const ops = OPERATION_PERMISSION_GROUPS.read_only;
|
|
19
|
+
assert.ok(!ops.includes(terms.OPERATIONS_ENUM.INSERT));
|
|
20
|
+
assert.ok(!ops.includes(terms.OPERATIONS_ENUM.UPDATE));
|
|
21
|
+
assert.ok(!ops.includes(terms.OPERATIONS_ENUM.DELETE));
|
|
22
|
+
assert.ok(!ops.includes(terms.OPERATIONS_ENUM.CSV_DATA_LOAD));
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('standard_user contains everything in read_only', function () {
|
|
26
|
+
const readOnly = new Set(OPERATION_PERMISSION_GROUPS.read_only);
|
|
27
|
+
for (const op of readOnly) {
|
|
28
|
+
assert.ok(OPERATION_PERMISSION_GROUPS.standard_user.includes(op), `standard_user missing read_only op: ${op}`);
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('standard_user contains data manipulation operations', function () {
|
|
33
|
+
const ops = OPERATION_PERMISSION_GROUPS.standard_user;
|
|
34
|
+
assert.ok(ops.includes(terms.OPERATIONS_ENUM.INSERT));
|
|
35
|
+
assert.ok(ops.includes(terms.OPERATIONS_ENUM.UPDATE));
|
|
36
|
+
assert.ok(ops.includes(terms.OPERATIONS_ENUM.UPSERT));
|
|
37
|
+
assert.ok(ops.includes(terms.OPERATIONS_ENUM.DELETE));
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('standard_user does not contain token management operations', function () {
|
|
41
|
+
const ops = OPERATION_PERMISSION_GROUPS.standard_user;
|
|
42
|
+
assert.ok(!ops.includes(terms.OPERATIONS_ENUM.CREATE_AUTHENTICATION_TOKENS));
|
|
43
|
+
assert.ok(!ops.includes(terms.OPERATIONS_ENUM.REFRESH_OPERATION_TOKEN));
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('standard_user does not contain schema DDL operations', function () {
|
|
47
|
+
const ops = OPERATION_PERMISSION_GROUPS.standard_user;
|
|
48
|
+
assert.ok(!ops.includes(terms.OPERATIONS_ENUM.CREATE_ATTRIBUTE));
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('admin_read contains elevated read operations', function () {
|
|
52
|
+
const ops = OPERATION_PERMISSION_GROUPS.admin_read;
|
|
53
|
+
assert.ok(ops.includes(terms.OPERATIONS_ENUM.GET_CONFIGURATION));
|
|
54
|
+
assert.ok(ops.includes(terms.OPERATIONS_ENUM.READ_LOG));
|
|
55
|
+
assert.ok(ops.includes(terms.OPERATIONS_ENUM.READ_AUDIT_LOG));
|
|
56
|
+
assert.ok(ops.includes(terms.OPERATIONS_ENUM.GET_CUSTOM_FUNCTIONS));
|
|
57
|
+
assert.ok(ops.includes(terms.OPERATIONS_ENUM.GET_COMPONENTS));
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('admin_read does not contain data write operations', function () {
|
|
61
|
+
const ops = OPERATION_PERMISSION_GROUPS.admin_read;
|
|
62
|
+
assert.ok(!ops.includes(terms.OPERATIONS_ENUM.INSERT));
|
|
63
|
+
assert.ok(!ops.includes(terms.OPERATIONS_ENUM.DELETE));
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
describe('expandOperationsPerms()', function () {
|
|
68
|
+
it('expands a group name to its member operations', function () {
|
|
69
|
+
const result = expandOperationsPerms(['read_only']);
|
|
70
|
+
assert.ok(result instanceof Set);
|
|
71
|
+
assert.ok(result.has(terms.OPERATIONS_ENUM.SEARCH));
|
|
72
|
+
assert.ok(result.has(terms.OPERATIONS_ENUM.SQL));
|
|
73
|
+
assert.ok(result.has(terms.OPERATIONS_ENUM.DESCRIBE_ALL));
|
|
74
|
+
assert.ok(result.has(terms.OPERATIONS_ENUM.USER_INFO));
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('group does not include write operations', function () {
|
|
78
|
+
const result = expandOperationsPerms(['read_only']);
|
|
79
|
+
assert.ok(!result.has(terms.OPERATIONS_ENUM.INSERT));
|
|
80
|
+
assert.ok(!result.has(terms.OPERATIONS_ENUM.UPDATE));
|
|
81
|
+
assert.ok(!result.has(terms.OPERATIONS_ENUM.DELETE));
|
|
82
|
+
assert.ok(!result.has(terms.OPERATIONS_ENUM.CSV_DATA_LOAD));
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('passes through an individual operation name directly', function () {
|
|
86
|
+
const result = expandOperationsPerms([terms.OPERATIONS_ENUM.RESTART]);
|
|
87
|
+
assert.ok(result.has(terms.OPERATIONS_ENUM.RESTART));
|
|
88
|
+
assert.equal(result.size, 1);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('combines a group and individual operations into a union set', function () {
|
|
92
|
+
const result = expandOperationsPerms([
|
|
93
|
+
'read_only',
|
|
94
|
+
terms.OPERATIONS_ENUM.RESTART,
|
|
95
|
+
terms.OPERATIONS_ENUM.LIST_USERS,
|
|
96
|
+
]);
|
|
97
|
+
assert.ok(result.has(terms.OPERATIONS_ENUM.SEARCH));
|
|
98
|
+
assert.ok(result.has(terms.OPERATIONS_ENUM.RESTART));
|
|
99
|
+
assert.ok(result.has(terms.OPERATIONS_ENUM.LIST_USERS));
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('returns an empty set for an empty array', function () {
|
|
103
|
+
const result = expandOperationsPerms([]);
|
|
104
|
+
assert.equal(result.size, 0);
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
});
|
|
@@ -16,8 +16,11 @@ const write = require('#js/dataLayer/insert');
|
|
|
16
16
|
const user = require('#src/security/user');
|
|
17
17
|
const alasql = require('alasql');
|
|
18
18
|
const search = require('#js/dataLayer/search');
|
|
19
|
+
const restart = require('#js/bin/restart');
|
|
20
|
+
const configUtils = require('#js/config/configUtils');
|
|
19
21
|
const jobs = require('#js/server/jobs/jobs');
|
|
20
22
|
const terms = require('#src/utility/hdbTerms');
|
|
23
|
+
|
|
21
24
|
const schema = require('#js/dataLayer/schema');
|
|
22
25
|
const PermissionResponseObject = require('#js/security/data_objects/PermissionResponseObject');
|
|
23
26
|
const PermissionTableResponseObject = require('#js/security/data_objects/PermissionTableResponseObject');
|
|
@@ -1490,3 +1493,130 @@ describe('Test operation_authorization', function () {
|
|
|
1490
1493
|
});
|
|
1491
1494
|
});
|
|
1492
1495
|
});
|
|
1496
|
+
|
|
1497
|
+
describe('Test operations permissions', function () {
|
|
1498
|
+
before(() => {
|
|
1499
|
+
global.hdb_schema = global.hdb_schema || {};
|
|
1500
|
+
testUtils.setGlobalSchema('id', TEST_SCHEMA, TEST_TABLE, TEST_ATTRIBUTES);
|
|
1501
|
+
});
|
|
1502
|
+
|
|
1503
|
+
after(() => {
|
|
1504
|
+
global.hdb_schema = undefined;
|
|
1505
|
+
});
|
|
1506
|
+
|
|
1507
|
+
// Helper: build a verifyPerms request JSON with operations set
|
|
1508
|
+
function makeOpUserRequest(operations, tablePerms = {}) {
|
|
1509
|
+
const req = getRequestJson(TEST_JSON);
|
|
1510
|
+
req.hdb_user.role.permission = {
|
|
1511
|
+
super_user: false,
|
|
1512
|
+
operations: operations,
|
|
1513
|
+
[TEST_SCHEMA]: {
|
|
1514
|
+
describe: true,
|
|
1515
|
+
tables: {
|
|
1516
|
+
[TEST_TABLE]: {
|
|
1517
|
+
describe: true,
|
|
1518
|
+
read: true,
|
|
1519
|
+
insert: false,
|
|
1520
|
+
update: false,
|
|
1521
|
+
delete: false,
|
|
1522
|
+
attribute_permissions: [],
|
|
1523
|
+
...tablePerms,
|
|
1524
|
+
},
|
|
1525
|
+
},
|
|
1526
|
+
},
|
|
1527
|
+
};
|
|
1528
|
+
return req;
|
|
1529
|
+
}
|
|
1530
|
+
|
|
1531
|
+
// Helper: build a request for a schema-less SU-only operation
|
|
1532
|
+
function makeOpUserSystemRequest(operations) {
|
|
1533
|
+
return {
|
|
1534
|
+
operation: 'restart',
|
|
1535
|
+
hdb_user: {
|
|
1536
|
+
active: true,
|
|
1537
|
+
role: {
|
|
1538
|
+
id: 'op-user-test-id',
|
|
1539
|
+
permission: {
|
|
1540
|
+
super_user: false,
|
|
1541
|
+
operations: operations,
|
|
1542
|
+
},
|
|
1543
|
+
role: 'op_test_role',
|
|
1544
|
+
__updatedtime__: (roleUpdatedTimeCounter += 1),
|
|
1545
|
+
},
|
|
1546
|
+
username: 'op_test_user',
|
|
1547
|
+
},
|
|
1548
|
+
};
|
|
1549
|
+
}
|
|
1550
|
+
|
|
1551
|
+
describe('verifyPerms() — operations allowlist', function () {
|
|
1552
|
+
it('(NOMINAL) op in read_only group with table READ perm — search allowed', function () {
|
|
1553
|
+
const req_json = makeOpUserRequest(['read_only'], { read: true });
|
|
1554
|
+
req_json.operation = terms.OPERATIONS_ENUM.SEARCH_BY_CONDITIONS;
|
|
1555
|
+
const result = op_auth.verifyPerms(req_json, search.searchByConditions.name);
|
|
1556
|
+
assert.equal(result, null);
|
|
1557
|
+
});
|
|
1558
|
+
|
|
1559
|
+
it('op NOT in operations list — insert blocked even with table perms', function () {
|
|
1560
|
+
const req_json = makeOpUserRequest(['read_only'], { insert: true });
|
|
1561
|
+
const result = op_auth.verifyPerms(req_json, write.insert.name);
|
|
1562
|
+
assert.notEqual(result, null);
|
|
1563
|
+
assert.equal(result.unauthorized_access.length, 1);
|
|
1564
|
+
// Error message uses the API name (terms.OPERATIONS_ENUM.INSERT = 'insert'), not the internal
|
|
1565
|
+
// function name (write.insert.name = 'insertData'), because that's what users put in operations.
|
|
1566
|
+
assert.ok(
|
|
1567
|
+
JSON.stringify(result).includes(TEST_OPERATION_AUTH_ERROR.OP_NOT_IN_OPERATIONS(terms.OPERATIONS_ENUM.INSERT))
|
|
1568
|
+
);
|
|
1569
|
+
});
|
|
1570
|
+
|
|
1571
|
+
it('SU-only op in operations — restart granted without super_user', function () {
|
|
1572
|
+
const req_json = makeOpUserSystemRequest([terms.OPERATIONS_ENUM.RESTART]);
|
|
1573
|
+
const result = op_auth.verifyPerms(req_json, restart.restart.name);
|
|
1574
|
+
assert.equal(result, null);
|
|
1575
|
+
});
|
|
1576
|
+
|
|
1577
|
+
it('SU-only op NOT in operations — restart denied', function () {
|
|
1578
|
+
const req_json = makeOpUserSystemRequest(['read_only']);
|
|
1579
|
+
const result = op_auth.verifyPerms(req_json, restart.restart.name);
|
|
1580
|
+
assert.notEqual(result, null);
|
|
1581
|
+
assert.equal(result.unauthorized_access.length, 1);
|
|
1582
|
+
});
|
|
1583
|
+
|
|
1584
|
+
it('get_configuration granted via operations (SU-only bypass)', function () {
|
|
1585
|
+
const req_json = makeOpUserSystemRequest([terms.OPERATIONS_ENUM.GET_CONFIGURATION]);
|
|
1586
|
+
req_json.operation = terms.OPERATIONS_ENUM.GET_CONFIGURATION;
|
|
1587
|
+
const result = op_auth.verifyPerms(req_json, configUtils.getConfiguration.name);
|
|
1588
|
+
assert.equal(result, null);
|
|
1589
|
+
});
|
|
1590
|
+
|
|
1591
|
+
it('dual gate — op in operations but table READ perm false — search denied at CRUD level', function () {
|
|
1592
|
+
const req_json = makeOpUserRequest(['read_only'], { read: false });
|
|
1593
|
+
req_json.operation = terms.OPERATIONS_ENUM.SEARCH;
|
|
1594
|
+
const result = op_auth.verifyPerms(req_json, search.search.name);
|
|
1595
|
+
assert.notEqual(result, null);
|
|
1596
|
+
});
|
|
1597
|
+
|
|
1598
|
+
it('no operations set — SU-only op still denied for non-SU user (no regression)', function () {
|
|
1599
|
+
const req_json = {
|
|
1600
|
+
operation: terms.OPERATIONS_ENUM.RESTART,
|
|
1601
|
+
hdb_user: {
|
|
1602
|
+
active: true,
|
|
1603
|
+
role: {
|
|
1604
|
+
id: 'plain-role-id',
|
|
1605
|
+
permission: { super_user: false },
|
|
1606
|
+
role: 'plain_role',
|
|
1607
|
+
__updatedtime__: (roleUpdatedTimeCounter += 1),
|
|
1608
|
+
},
|
|
1609
|
+
username: 'plain_user',
|
|
1610
|
+
},
|
|
1611
|
+
};
|
|
1612
|
+
const result = op_auth.verifyPerms(req_json, restart.restart.name);
|
|
1613
|
+
assert.notEqual(result, null);
|
|
1614
|
+
assert.equal(result.unauthorized_access.length, 1);
|
|
1615
|
+
});
|
|
1616
|
+
|
|
1617
|
+
it('no operations set — normal data op works as before (no regression)', function () {
|
|
1618
|
+
const result = op_auth.verifyPerms(TEST_JSON, write.insert.name);
|
|
1619
|
+
assert.equal(result, null);
|
|
1620
|
+
});
|
|
1621
|
+
});
|
|
1622
|
+
});
|