@rockcarver/frodo-lib 0.11.0
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/.eslintrc +32 -0
- package/.github/ISSUE_TEMPLATE/bug_report.md +30 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
- package/.github/README.md +121 -0
- package/.github/workflows/pipeline.yml +287 -0
- package/.prettierrc +6 -0
- package/CHANGELOG.md +512 -0
- package/CODE_OF_CONDUCT.md +128 -0
- package/LICENSE +21 -0
- package/README.md +8 -0
- package/docs/CONTRIBUTE.md +96 -0
- package/docs/PIPELINE.md +169 -0
- package/docs/images/npm_versioning_guidelines.png +0 -0
- package/docs/images/release_pipeline.png +0 -0
- package/jsconfig.json +6 -0
- package/package.json +95 -0
- package/resources/sampleEntitiesFile.json +8 -0
- package/resources/sampleEnvFile.env +2 -0
- package/src/api/AuthenticateApi.js +33 -0
- package/src/api/BaseApi.js +242 -0
- package/src/api/CirclesOfTrustApi.js +87 -0
- package/src/api/EmailTemplateApi.js +37 -0
- package/src/api/IdmConfigApi.js +88 -0
- package/src/api/LogApi.js +45 -0
- package/src/api/ManagedObjectApi.js +62 -0
- package/src/api/OAuth2ClientApi.js +69 -0
- package/src/api/OAuth2OIDCApi.js +73 -0
- package/src/api/OAuth2ProviderApi.js +32 -0
- package/src/api/RealmApi.js +99 -0
- package/src/api/Saml2Api.js +176 -0
- package/src/api/ScriptApi.js +84 -0
- package/src/api/SecretsApi.js +151 -0
- package/src/api/ServerInfoApi.js +41 -0
- package/src/api/SocialIdentityProvidersApi.js +114 -0
- package/src/api/StartupApi.js +45 -0
- package/src/api/ThemeApi.js +181 -0
- package/src/api/TreeApi.js +207 -0
- package/src/api/VariablesApi.js +104 -0
- package/src/api/utils/ApiUtils.js +77 -0
- package/src/api/utils/ApiUtils.test.js +96 -0
- package/src/api/utils/Base64.js +62 -0
- package/src/index.js +32 -0
- package/src/index.test.js +13 -0
- package/src/ops/AdminOps.js +901 -0
- package/src/ops/AuthenticateOps.js +342 -0
- package/src/ops/CirclesOfTrustOps.js +350 -0
- package/src/ops/ConnectionProfileOps.js +254 -0
- package/src/ops/EmailTemplateOps.js +326 -0
- package/src/ops/IdmOps.js +227 -0
- package/src/ops/IdpOps.js +342 -0
- package/src/ops/JourneyOps.js +2026 -0
- package/src/ops/LogOps.js +357 -0
- package/src/ops/ManagedObjectOps.js +34 -0
- package/src/ops/OAuth2ClientOps.js +151 -0
- package/src/ops/OrganizationOps.js +85 -0
- package/src/ops/RealmOps.js +139 -0
- package/src/ops/SamlOps.js +541 -0
- package/src/ops/ScriptOps.js +211 -0
- package/src/ops/SecretsOps.js +288 -0
- package/src/ops/StartupOps.js +114 -0
- package/src/ops/ThemeOps.js +379 -0
- package/src/ops/VariablesOps.js +185 -0
- package/src/ops/templates/OAuth2ClientTemplate.json +270 -0
- package/src/ops/templates/OrgModelUserAttributesTemplate.json +149 -0
- package/src/ops/templates/cloud/GenericExtensionAttributesTemplate.json +392 -0
- package/src/ops/templates/cloud/managed.json +4119 -0
- package/src/ops/utils/Console.js +434 -0
- package/src/ops/utils/DataProtection.js +92 -0
- package/src/ops/utils/DataProtection.test.js +28 -0
- package/src/ops/utils/ExportImportUtils.js +146 -0
- package/src/ops/utils/ExportImportUtils.test.js +119 -0
- package/src/ops/utils/OpsUtils.js +76 -0
- package/src/ops/utils/Wordwrap.js +11 -0
- package/src/storage/SessionStorage.js +45 -0
- package/src/storage/StaticStorage.js +15 -0
- package/test/e2e/journey/baseline/ForgottenUsername.journey.json +216 -0
- package/test/e2e/journey/baseline/Login.journey.json +205 -0
- package/test/e2e/journey/baseline/PasswordGrant.journey.json +139 -0
- package/test/e2e/journey/baseline/ProgressiveProfile.journey.json +198 -0
- package/test/e2e/journey/baseline/Registration.journey.json +249 -0
- package/test/e2e/journey/baseline/ResetPassword.journey.json +268 -0
- package/test/e2e/journey/baseline/UpdatePassword.journey.json +323 -0
- package/test/e2e/journey/baseline/allAlphaJourneys.journeys.json +1520 -0
- package/test/e2e/journey/delete/ForgottenUsername.journey.json +216 -0
- package/test/e2e/journey/delete/Login.journey.json +205 -0
- package/test/e2e/journey/delete/PasswordGrant.journey.json +139 -0
- package/test/e2e/journey/delete/ProgressiveProfile.journey.json +198 -0
- package/test/e2e/journey/delete/Registration.journey.json +249 -0
- package/test/e2e/journey/delete/ResetPassword.journey.json +268 -0
- package/test/e2e/journey/delete/UpdatePassword.journey.json +323 -0
- package/test/e2e/journey/delete/deleteMe.journey.json +230 -0
- package/test/e2e/journey/list/Disabled.journey.json +43 -0
- package/test/e2e/journey/list/ForgottenUsername.journey.json +216 -0
- package/test/e2e/journey/list/Login.journey.json +205 -0
- package/test/e2e/journey/list/PasswordGrant.journey.json +139 -0
- package/test/e2e/journey/list/ProgressiveProfile.journey.json +198 -0
- package/test/e2e/journey/list/Registration.journey.json +249 -0
- package/test/e2e/journey/list/ResetPassword.journey.json +268 -0
- package/test/e2e/journey/list/UpdatePassword.journey.json +323 -0
- package/test/e2e/setup.js +107 -0
- package/test/e2e/theme/baseline/Contrast.theme.json +95 -0
- package/test/e2e/theme/baseline/Highlander.theme.json +95 -0
- package/test/e2e/theme/baseline/Robroy.theme.json +95 -0
- package/test/e2e/theme/baseline/Starter-Theme.theme.json +94 -0
- package/test/e2e/theme/baseline/Zardoz.theme.json +95 -0
- package/test/e2e/theme/import/Contrast.theme.json +95 -0
- package/test/e2e/theme/import/Highlander.theme.json +95 -0
- package/test/e2e/theme/import/Robroy.theme.json +95 -0
- package/test/e2e/theme/import/Starter-Theme.theme.json +94 -0
- package/test/e2e/theme/import/Zardoz.default.theme.json +95 -0
- package/test/fs_tmp/.gitkeep +2 -0
- package/test/global/setup.js +65 -0
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
import { SingleBar, Presets } from 'cli-progress';
|
|
2
|
+
import { createSpinner } from 'nanospinner';
|
|
3
|
+
import Table from 'cli-table3';
|
|
4
|
+
// eslint-disable-next-line no-unused-vars
|
|
5
|
+
import * as colors from '@colors/colors';
|
|
6
|
+
import storage from '../../storage/SessionStorage.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Output a data message
|
|
10
|
+
* @param {Object} message the message
|
|
11
|
+
*/
|
|
12
|
+
function data(message) {
|
|
13
|
+
switch (typeof message) {
|
|
14
|
+
case 'object':
|
|
15
|
+
console.dir(message, { depth: 3 });
|
|
16
|
+
break;
|
|
17
|
+
default:
|
|
18
|
+
console.log(message);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Output a text message
|
|
24
|
+
* @param {Object} message the message
|
|
25
|
+
*/
|
|
26
|
+
function text(message) {
|
|
27
|
+
switch (typeof message) {
|
|
28
|
+
case 'object':
|
|
29
|
+
console.dir(message, { depth: 3 });
|
|
30
|
+
break;
|
|
31
|
+
default:
|
|
32
|
+
console.error(message);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Output an info message
|
|
38
|
+
* @param {Object} message the message
|
|
39
|
+
*/
|
|
40
|
+
function info(message) {
|
|
41
|
+
switch (typeof message) {
|
|
42
|
+
case 'object':
|
|
43
|
+
console.dir(message, { depth: 3 });
|
|
44
|
+
break;
|
|
45
|
+
default:
|
|
46
|
+
console.error(message.brightCyan);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Output a warn message
|
|
52
|
+
* @param {Object} message the message
|
|
53
|
+
*/
|
|
54
|
+
function warn(message) {
|
|
55
|
+
switch (typeof message) {
|
|
56
|
+
case 'object':
|
|
57
|
+
console.dir(message, { depth: 3 });
|
|
58
|
+
break;
|
|
59
|
+
default:
|
|
60
|
+
console.error(message.yellow);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Output an error message
|
|
66
|
+
* @param {Object} message the message
|
|
67
|
+
*/
|
|
68
|
+
function error(message) {
|
|
69
|
+
switch (typeof message) {
|
|
70
|
+
case 'object':
|
|
71
|
+
console.dir(message, { depth: 4 });
|
|
72
|
+
break;
|
|
73
|
+
default:
|
|
74
|
+
console.error(message.brightRed);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Prints a string message to console
|
|
80
|
+
*
|
|
81
|
+
* @param {string} message The string message to print
|
|
82
|
+
* @param {string} [type=text] "text", "info", "warn", "error" or "data". All but
|
|
83
|
+
* type="data" will be written to stderr.
|
|
84
|
+
* @param {boolean} [newline=true] Whether to add a new at the end of message
|
|
85
|
+
*
|
|
86
|
+
*/
|
|
87
|
+
export function printMessage(message, type = 'text', newline = true) {
|
|
88
|
+
// if (storage.session.getItem('scriptFriendly')) {
|
|
89
|
+
switch (type) {
|
|
90
|
+
case 'data':
|
|
91
|
+
if (newline) {
|
|
92
|
+
data(message);
|
|
93
|
+
} else {
|
|
94
|
+
process.stdout.write(message);
|
|
95
|
+
}
|
|
96
|
+
break;
|
|
97
|
+
case 'text':
|
|
98
|
+
if (newline) {
|
|
99
|
+
text(message);
|
|
100
|
+
} else {
|
|
101
|
+
process.stderr.write(message);
|
|
102
|
+
}
|
|
103
|
+
break;
|
|
104
|
+
case 'info':
|
|
105
|
+
if (newline) {
|
|
106
|
+
info(message);
|
|
107
|
+
} else {
|
|
108
|
+
process.stderr.write(message.brightCyan);
|
|
109
|
+
}
|
|
110
|
+
break;
|
|
111
|
+
case 'warn':
|
|
112
|
+
if (newline) {
|
|
113
|
+
warn(message);
|
|
114
|
+
} else {
|
|
115
|
+
process.stderr.write(message.yellow);
|
|
116
|
+
}
|
|
117
|
+
break;
|
|
118
|
+
case 'error':
|
|
119
|
+
if (newline) {
|
|
120
|
+
error(message);
|
|
121
|
+
} else {
|
|
122
|
+
process.stderr.write(message.brightRed);
|
|
123
|
+
}
|
|
124
|
+
break;
|
|
125
|
+
default:
|
|
126
|
+
if (newline) {
|
|
127
|
+
error(message);
|
|
128
|
+
} else {
|
|
129
|
+
process.stderr.write(message.bgBrightRed);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Creates a progress bar on stderr
|
|
136
|
+
*
|
|
137
|
+
* Example:
|
|
138
|
+
* [========================================] 100% | 49/49 | Analyzing journey - transactional_auth
|
|
139
|
+
*
|
|
140
|
+
* @param {Number} total The total number of entries to track progress for
|
|
141
|
+
* @param {String} message optional progress bar message
|
|
142
|
+
* @param {Object} options progress bar configuration options
|
|
143
|
+
*
|
|
144
|
+
*/
|
|
145
|
+
export function createProgressBar(
|
|
146
|
+
total,
|
|
147
|
+
message = null,
|
|
148
|
+
options = {
|
|
149
|
+
format: '[{bar}] {percentage}% | {value}/{total} | {data}',
|
|
150
|
+
noTTYOutput: true,
|
|
151
|
+
}
|
|
152
|
+
) {
|
|
153
|
+
let opt = options;
|
|
154
|
+
if (message == null) {
|
|
155
|
+
opt = {
|
|
156
|
+
format: '[{bar}] {percentage}% | {value}/{total}',
|
|
157
|
+
noTTYOutput: true,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
let pBar = storage.session.getItem('progressBar');
|
|
161
|
+
if (!pBar) pBar = new SingleBar(opt, Presets.legacy); // create only when needed
|
|
162
|
+
pBar.start(total, 0, {
|
|
163
|
+
data: message,
|
|
164
|
+
});
|
|
165
|
+
storage.session.setItem('progressBar', pBar);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Updates the progress bar by 1
|
|
170
|
+
* @param {string} message optional message to update the progress bar
|
|
171
|
+
*
|
|
172
|
+
*/
|
|
173
|
+
export function updateProgressBar(message = null) {
|
|
174
|
+
const pBar = storage.session.getItem('progressBar');
|
|
175
|
+
if (message)
|
|
176
|
+
pBar.increment({
|
|
177
|
+
data: message,
|
|
178
|
+
});
|
|
179
|
+
else pBar.increment();
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Stop and hide the progress bar
|
|
184
|
+
* @param {*} message optional message to update the progress bar
|
|
185
|
+
*/
|
|
186
|
+
export function stopProgressBar(message = null) {
|
|
187
|
+
const pBar = storage.session.getItem('progressBar');
|
|
188
|
+
if (message)
|
|
189
|
+
pBar.update({
|
|
190
|
+
data: message,
|
|
191
|
+
});
|
|
192
|
+
pBar.stop();
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Create the spinner
|
|
197
|
+
* @param {String} message optional spinner message
|
|
198
|
+
*/
|
|
199
|
+
export function showSpinner(message) {
|
|
200
|
+
const spinner = createSpinner(message).start();
|
|
201
|
+
storage.session.setItem('Spinner', spinner);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Stop the spinner
|
|
206
|
+
* @param {String} message optional message to update the spinner
|
|
207
|
+
*/
|
|
208
|
+
export function stopSpinner(message = null) {
|
|
209
|
+
const spinner = storage.session.getItem('Spinner');
|
|
210
|
+
if (spinner) {
|
|
211
|
+
let options = {};
|
|
212
|
+
if (message) options = { text: message };
|
|
213
|
+
spinner.stop(options);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Succeed the spinner. Stop and render success checkmark with optional message.
|
|
219
|
+
* @param {String} message optional message to update the spinner
|
|
220
|
+
*/
|
|
221
|
+
export function succeedSpinner(message = null) {
|
|
222
|
+
const spinner = storage.session.getItem('Spinner');
|
|
223
|
+
if (spinner) {
|
|
224
|
+
let options = {};
|
|
225
|
+
if (message) options = { text: message };
|
|
226
|
+
spinner.success(options);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Warn the spinner
|
|
232
|
+
* @param {String} message optional message to update the spinner
|
|
233
|
+
*/
|
|
234
|
+
export function warnSpinner(message = null) {
|
|
235
|
+
const spinner = storage.session.getItem('Spinner');
|
|
236
|
+
if (spinner) {
|
|
237
|
+
let options = {};
|
|
238
|
+
if (message) options = { text: message };
|
|
239
|
+
spinner.warn(options);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Fail the spinner
|
|
245
|
+
* @param {String} message optional message to update the spinner
|
|
246
|
+
*/
|
|
247
|
+
export function failSpinner(message = null) {
|
|
248
|
+
const spinner = storage.session.getItem('Spinner');
|
|
249
|
+
if (spinner) {
|
|
250
|
+
let options = {};
|
|
251
|
+
if (message) options = { text: message };
|
|
252
|
+
spinner.error(options);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Spin the spinner
|
|
258
|
+
* @param {String} message optional message to update the spinner
|
|
259
|
+
*/
|
|
260
|
+
export function spinSpinner(message = null) {
|
|
261
|
+
const spinner = storage.session.getItem('Spinner');
|
|
262
|
+
if (spinner) {
|
|
263
|
+
let options = {};
|
|
264
|
+
if (message) options = { text: message };
|
|
265
|
+
spinner.update(options);
|
|
266
|
+
spinner.spin();
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Create an empty table
|
|
272
|
+
* @param {[String]} head header row as an array of strings
|
|
273
|
+
* @returns {CliTable3} an empty table
|
|
274
|
+
*/
|
|
275
|
+
export function createTable(head) {
|
|
276
|
+
return new Table({
|
|
277
|
+
head,
|
|
278
|
+
chars: {
|
|
279
|
+
top: '',
|
|
280
|
+
'top-mid': '',
|
|
281
|
+
'top-left': '',
|
|
282
|
+
'top-right': '',
|
|
283
|
+
bottom: '',
|
|
284
|
+
'bottom-mid': '',
|
|
285
|
+
'bottom-left': '',
|
|
286
|
+
'bottom-right': '',
|
|
287
|
+
left: '',
|
|
288
|
+
'left-mid': '',
|
|
289
|
+
mid: '',
|
|
290
|
+
'mid-mid': '',
|
|
291
|
+
right: '',
|
|
292
|
+
'right-mid': '',
|
|
293
|
+
},
|
|
294
|
+
style: { 'padding-left': 0, 'padding-right': 0, head: ['brightCyan'] },
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Create a new key/value table
|
|
300
|
+
* @returns {CliTable3} an empty key/value table
|
|
301
|
+
*/
|
|
302
|
+
export function createKeyValueTable() {
|
|
303
|
+
return new Table({
|
|
304
|
+
chars: {
|
|
305
|
+
top: '',
|
|
306
|
+
'top-mid': '',
|
|
307
|
+
'top-left': '',
|
|
308
|
+
'top-right': '',
|
|
309
|
+
bottom: '',
|
|
310
|
+
'bottom-mid': '',
|
|
311
|
+
'bottom-left': '',
|
|
312
|
+
'bottom-right': '',
|
|
313
|
+
left: '',
|
|
314
|
+
'left-mid': '',
|
|
315
|
+
mid: '',
|
|
316
|
+
'mid-mid': '',
|
|
317
|
+
right: '',
|
|
318
|
+
'right-mid': '',
|
|
319
|
+
},
|
|
320
|
+
style: { 'padding-left': 0, 'padding-right': 0 },
|
|
321
|
+
wordWrap: true,
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Helper function to determine the total depth of an object
|
|
327
|
+
* @param {Object} object input object
|
|
328
|
+
* @returns {Number} total depth of the input object
|
|
329
|
+
*/
|
|
330
|
+
function getObjectDepth(object) {
|
|
331
|
+
return Object(object) === object
|
|
332
|
+
? 1 + Math.max(-1, ...Object.values(object).map(getObjectDepth))
|
|
333
|
+
: 0;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Helper function to determine if an object has values
|
|
338
|
+
* @param {Object} object input object
|
|
339
|
+
* @returns {boolean} true of the object or any of its sub-objects contain values, false otherwise
|
|
340
|
+
*/
|
|
341
|
+
function hasValues(object) {
|
|
342
|
+
let has = false;
|
|
343
|
+
const keys = Object.keys(object);
|
|
344
|
+
for (const key of keys) {
|
|
345
|
+
if (Object(object[key]) !== object[key]) {
|
|
346
|
+
return true;
|
|
347
|
+
}
|
|
348
|
+
has = has || hasValues(object[key]);
|
|
349
|
+
}
|
|
350
|
+
return has;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Helper function (recursive) to add rows to an object table
|
|
355
|
+
* @param {Object} object object to render
|
|
356
|
+
* @param {Number} depth total depth of initial object
|
|
357
|
+
* @param {Number} level current level
|
|
358
|
+
* @param {CliTable3} table the object table to add the rows to
|
|
359
|
+
* @returns the updated object table
|
|
360
|
+
*/
|
|
361
|
+
function addRows(object, depth, level, table, keyMap) {
|
|
362
|
+
const space = ' ';
|
|
363
|
+
const keys = Object.keys(object);
|
|
364
|
+
for (const key of keys) {
|
|
365
|
+
if (Object(object[key]) !== object[key]) {
|
|
366
|
+
if (level === 1) {
|
|
367
|
+
table.push([
|
|
368
|
+
keyMap[key] ? keyMap[key].brightCyan : key.brightCyan,
|
|
369
|
+
object[key],
|
|
370
|
+
]);
|
|
371
|
+
} else {
|
|
372
|
+
table.push([
|
|
373
|
+
{
|
|
374
|
+
hAlign: 'right',
|
|
375
|
+
content: keyMap[key] ? keyMap[key].gray : key.gray,
|
|
376
|
+
},
|
|
377
|
+
object[key],
|
|
378
|
+
]);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
for (const key of keys) {
|
|
383
|
+
if (Object(object[key]) === object[key]) {
|
|
384
|
+
// only print header if there are any values below
|
|
385
|
+
if (hasValues(object[key])) {
|
|
386
|
+
let indention = new Array(level).fill(space).join('');
|
|
387
|
+
if (level < 3) indention = `\n${indention}`;
|
|
388
|
+
table.push([
|
|
389
|
+
indention.concat(
|
|
390
|
+
keyMap[key] ? keyMap[key].brightCyan : key.brightCyan
|
|
391
|
+
),
|
|
392
|
+
'',
|
|
393
|
+
]);
|
|
394
|
+
}
|
|
395
|
+
// eslint-disable-next-line no-param-reassign
|
|
396
|
+
table = addRows(object[key], depth, level + 1, table, keyMap);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
return table;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Create and populate an object table from any JSON object. Use for describe commands.
|
|
404
|
+
* @param {Object} object JSON object to create
|
|
405
|
+
* @returns {CliTable3} a table that can be printed to the console
|
|
406
|
+
*/
|
|
407
|
+
export function createObjectTable(object, keyMap = {}) {
|
|
408
|
+
// eslint-disable-next-line no-param-reassign
|
|
409
|
+
const depth = getObjectDepth(object);
|
|
410
|
+
// eslint-disable-next-line no-param-reassign
|
|
411
|
+
const level = 0;
|
|
412
|
+
// eslint-disable-next-line no-param-reassign
|
|
413
|
+
const table = new Table({
|
|
414
|
+
chars: {
|
|
415
|
+
top: '',
|
|
416
|
+
'top-mid': '',
|
|
417
|
+
'top-left': '',
|
|
418
|
+
'top-right': '',
|
|
419
|
+
bottom: '',
|
|
420
|
+
'bottom-mid': '',
|
|
421
|
+
'bottom-left': '',
|
|
422
|
+
'bottom-right': '',
|
|
423
|
+
left: '',
|
|
424
|
+
'left-mid': '',
|
|
425
|
+
mid: '',
|
|
426
|
+
'mid-mid': '',
|
|
427
|
+
right: '',
|
|
428
|
+
'right-mid': '',
|
|
429
|
+
},
|
|
430
|
+
style: { 'padding-left': 0, 'padding-right': 0, head: ['brightCyan'] },
|
|
431
|
+
});
|
|
432
|
+
addRows(object, depth, level + 1, table, keyMap);
|
|
433
|
+
return table;
|
|
434
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Data is stored in base64 format. Initially it was binary data
|
|
3
|
+
* Format used in this encryption module.
|
|
4
|
+
* inspired by AndiDittrich
|
|
5
|
+
* +--------------------+-----------------------+----------------+----------------+
|
|
6
|
+
* | SALT | Initialization Vector | Auth Tag | Payload |
|
|
7
|
+
* | Used to derive key | AES GCM XOR Init | Data Integrity | Encrypted Data |
|
|
8
|
+
* | 64 Bytes, random | 16 Bytes, random | 16 Bytes | (N-96) Bytes |
|
|
9
|
+
* +--------------------+-----------------------+----------------+----------------+
|
|
10
|
+
* This module doesn't take care of data persistence, it's assumed the consuming method/class/package will do so.
|
|
11
|
+
*/
|
|
12
|
+
import fs, { promises as fsp } from 'fs';
|
|
13
|
+
import crypto from 'crypto';
|
|
14
|
+
import { homedir } from 'os';
|
|
15
|
+
import { promisify } from 'util';
|
|
16
|
+
import { printMessage } from './Console.js';
|
|
17
|
+
|
|
18
|
+
const scrypt = promisify(crypto.scrypt);
|
|
19
|
+
// using WeakMaps for added security since it gets garbage collected
|
|
20
|
+
const _masterKey = new WeakMap();
|
|
21
|
+
const _nonce = new WeakMap();
|
|
22
|
+
const _salt = new WeakMap();
|
|
23
|
+
const _key = new WeakMap();
|
|
24
|
+
const _encrypt = new WeakMap();
|
|
25
|
+
|
|
26
|
+
class DataProtection {
|
|
27
|
+
constructor() {
|
|
28
|
+
const masterKeyPath = () => `${homedir()}/.frodo/masterkey.key`;
|
|
29
|
+
// Generates a master key in base64 format. This master key will be used to derive the key for encryption. this key should be protected by an HSM or KMS
|
|
30
|
+
_masterKey.set(this, async () => {
|
|
31
|
+
try {
|
|
32
|
+
if (!fs.existsSync(masterKeyPath())) {
|
|
33
|
+
const masterKey = crypto.randomBytes(32).toString('base64');
|
|
34
|
+
await fsp.writeFile(masterKeyPath(), masterKey);
|
|
35
|
+
}
|
|
36
|
+
return await fsp.readFile(masterKeyPath(), 'utf8');
|
|
37
|
+
} catch (err) {
|
|
38
|
+
printMessage(err.message, 'error');
|
|
39
|
+
return '';
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// the nonce for AES GCM
|
|
44
|
+
_nonce.set(this, () => crypto.randomBytes(16));
|
|
45
|
+
|
|
46
|
+
// The salt
|
|
47
|
+
_salt.set(this, () => crypto.randomBytes(64));
|
|
48
|
+
|
|
49
|
+
// The function that derives the key, this supports sync and async operations
|
|
50
|
+
_key.set(
|
|
51
|
+
this,
|
|
52
|
+
// eslint-disable-next-line no-return-await
|
|
53
|
+
async (masterKey, salt) => await scrypt(masterKey, salt, 32)
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
// private method to encrypt and return encrypted data. cleaner code
|
|
57
|
+
_encrypt.set(this, (key, nonce, data, salt) => {
|
|
58
|
+
const cipher = crypto.createCipheriv('aes-256-gcm', key, nonce);
|
|
59
|
+
const encrypted = Buffer.concat([
|
|
60
|
+
cipher.update(JSON.stringify(data), 'utf8'),
|
|
61
|
+
cipher.final(),
|
|
62
|
+
]);
|
|
63
|
+
const tag = cipher.getAuthTag();
|
|
64
|
+
const buffer = Buffer.concat([salt, nonce, tag, encrypted]);
|
|
65
|
+
return buffer.toString('base64');
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async encrypt(data) {
|
|
70
|
+
const nonce = _nonce.get(this)();
|
|
71
|
+
const salt = _salt.get(this)();
|
|
72
|
+
const masterKey = await _masterKey.get(this)();
|
|
73
|
+
const key = await _key.get(this)(masterKey, salt);
|
|
74
|
+
return _encrypt.get(this)(key, nonce, data, salt);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async decrypt(data) {
|
|
78
|
+
const buffer = Buffer.from(data.toString(), 'base64');
|
|
79
|
+
const salt = buffer.slice(0, 64);
|
|
80
|
+
const nonce = buffer.slice(64, 80);
|
|
81
|
+
const tag = buffer.slice(80, 96);
|
|
82
|
+
const encrypted = buffer.slice(96);
|
|
83
|
+
const masterKey = await _masterKey.get(this)();
|
|
84
|
+
const key = await _key.get(this)(masterKey, salt);
|
|
85
|
+
const decipher = crypto.createDecipheriv('aes-256-gcm', key, nonce);
|
|
86
|
+
decipher.setAuthTag(tag);
|
|
87
|
+
return JSON.parse(
|
|
88
|
+
decipher.update(encrypted, 'binary', 'utf8') + decipher.final('utf8')
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
export default DataProtection;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import DataProtection from './DataProtection.js';
|
|
2
|
+
|
|
3
|
+
test('DataProtection to encrypt', async () => {
|
|
4
|
+
// Note this test checks that encyption happned not that encryption is correct
|
|
5
|
+
// this test relys on other tests to proove the likelyhood of successful encryption
|
|
6
|
+
// Arrange
|
|
7
|
+
const dp = new DataProtection();
|
|
8
|
+
const EXPECTED =
|
|
9
|
+
'aMLtCqK1b+d3d88DDKrmIV7A6pifP77IItLKX7N7/UTOPxf8YCQWHCpTrmNnM5wNXue8HllEFIS+sxXRb20oCb4HImpbQM0so5DrHIqcIlF5LYDKjvzBOz1PdlclhIuIV+Gr8M3GRbNkQxXJuUZ4th5ISLpOjM+k8bDAlnHsRx5LLlbLFnAKq8Pu9DaTYUkZABOCOjfkoTb6re1p9c7xE2pAe213';
|
|
10
|
+
const originalString =
|
|
11
|
+
'Go not to the Elves for counsel, for they will say both no and yes.';
|
|
12
|
+
// Act
|
|
13
|
+
const RESULT = await dp.encrypt(originalString);
|
|
14
|
+
// Assert
|
|
15
|
+
expect(RESULT.length).toBe(EXPECTED.length);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test('DataProtection to decrypt', async () => {
|
|
19
|
+
// Arrange
|
|
20
|
+
const dp = new DataProtection();
|
|
21
|
+
const originalString =
|
|
22
|
+
'Go not to the Elves for counsel, for they will say both no and yes.';
|
|
23
|
+
// Act
|
|
24
|
+
const encrypted = await dp.encrypt(originalString);
|
|
25
|
+
const RESULT = await dp.decrypt(encrypted);
|
|
26
|
+
// Assert
|
|
27
|
+
expect(RESULT).toBe(originalString);
|
|
28
|
+
});
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import slugify from 'slugify';
|
|
3
|
+
import storage from '../../storage/SessionStorage.js';
|
|
4
|
+
import { FRODO_METADATA_ID } from '../../storage/StaticStorage.js';
|
|
5
|
+
import {
|
|
6
|
+
encode,
|
|
7
|
+
decode,
|
|
8
|
+
encodeBase64Url,
|
|
9
|
+
decodeBase64Url,
|
|
10
|
+
} from '../../api/utils/Base64.js';
|
|
11
|
+
import { printMessage } from './Console.js';
|
|
12
|
+
|
|
13
|
+
export function getCurrentTimestamp() {
|
|
14
|
+
const ts = new Date();
|
|
15
|
+
return ts.toISOString();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function getMetadata() {
|
|
19
|
+
const metadata = {
|
|
20
|
+
origin: storage.session.getTenant(),
|
|
21
|
+
exportedBy: storage.session.getUsername(),
|
|
22
|
+
exportDate: getCurrentTimestamp(),
|
|
23
|
+
exportTool: FRODO_METADATA_ID,
|
|
24
|
+
exportToolVersion: storage.session.getFrodoVersion(),
|
|
25
|
+
};
|
|
26
|
+
return metadata;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/*
|
|
30
|
+
* Output str in title case
|
|
31
|
+
*
|
|
32
|
+
* e.g.: 'ALL UPPERCASE AND all lowercase' = 'All Uppercase And All Lowercase'
|
|
33
|
+
*/
|
|
34
|
+
export function titleCase(input) {
|
|
35
|
+
const str = input.toString();
|
|
36
|
+
const splitStr = str.toLowerCase().split(' ');
|
|
37
|
+
for (let i = 0; i < splitStr.length; i += 1) {
|
|
38
|
+
splitStr[i] = splitStr[i].charAt(0).toUpperCase() + splitStr[i].slice(1);
|
|
39
|
+
}
|
|
40
|
+
return splitStr.join(' ');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function getRealmString() {
|
|
44
|
+
const realm = storage.session.getRealm();
|
|
45
|
+
return realm
|
|
46
|
+
.split('/')
|
|
47
|
+
.reduce((result, item) => `${result}${titleCase(item)}`, '');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function convertBase64TextToArray(b64text) {
|
|
51
|
+
let arrayOut = [];
|
|
52
|
+
let plainText = decode(b64text);
|
|
53
|
+
plainText = plainText.replace(/\t/g, ' ');
|
|
54
|
+
arrayOut = plainText.split('\n');
|
|
55
|
+
return arrayOut;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function convertBase64UrlTextToArray(b64UTF8Text) {
|
|
59
|
+
let arrayOut = [];
|
|
60
|
+
let plainText = decodeBase64Url(b64UTF8Text);
|
|
61
|
+
plainText = plainText.replace(/\t/g, ' ');
|
|
62
|
+
arrayOut = plainText.split('\n');
|
|
63
|
+
return arrayOut;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function convertTextArrayToBase64(textArray) {
|
|
67
|
+
const joinedText = textArray.join('\n');
|
|
68
|
+
const b64encodedScript = encode(joinedText);
|
|
69
|
+
return b64encodedScript;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function convertTextArrayToBase64Url(textArray) {
|
|
73
|
+
const joinedText = textArray.join('\n');
|
|
74
|
+
const b64encodedScript = encodeBase64Url(joinedText);
|
|
75
|
+
return b64encodedScript;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// eslint-disable-next-line no-unused-vars
|
|
79
|
+
export function validateImport(metadata) {
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// eslint-disable-next-line no-unused-vars
|
|
84
|
+
export function checkTargetCompatibility(type, source, target) {
|
|
85
|
+
// console.log(`source ${source}, target ${target}`);
|
|
86
|
+
// compatibilityKeys[type].forEach((element) => {
|
|
87
|
+
// if (source[element] != target[element]) {
|
|
88
|
+
// console.warn(`${element} in ${type} mismatch between source and target`);
|
|
89
|
+
// }
|
|
90
|
+
// });
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function getTypedFilename(name, type, suffix = 'json') {
|
|
94
|
+
const slug = slugify(name.replace(/^http(s?):\/\//, ''));
|
|
95
|
+
return `${slug}.${type}.${suffix}`;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function saveToFile(type, data, identifier, filename) {
|
|
99
|
+
const exportData = {};
|
|
100
|
+
exportData.meta = getMetadata();
|
|
101
|
+
exportData[type] = {};
|
|
102
|
+
if (Array.isArray(data)) {
|
|
103
|
+
data.forEach((element) => {
|
|
104
|
+
exportData[type][element[identifier]] = element;
|
|
105
|
+
});
|
|
106
|
+
} else {
|
|
107
|
+
exportData[type][data[identifier]] = data;
|
|
108
|
+
}
|
|
109
|
+
fs.writeFile(filename, JSON.stringify(exportData, null, 2), (err) => {
|
|
110
|
+
if (err) {
|
|
111
|
+
return printMessage(`ERROR - can't save ${type} to file`, 'error');
|
|
112
|
+
}
|
|
113
|
+
return '';
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Save JSON object to file
|
|
119
|
+
* @param {Object} data data object
|
|
120
|
+
* @param {String} filename file name
|
|
121
|
+
*/
|
|
122
|
+
export function saveJsonToFile(data, filename) {
|
|
123
|
+
const exportData = data;
|
|
124
|
+
exportData.meta = getMetadata();
|
|
125
|
+
fs.writeFile(filename, JSON.stringify(exportData, null, 2), (err) => {
|
|
126
|
+
if (err) {
|
|
127
|
+
return printMessage(`ERROR - can't save ${filename}`, 'error');
|
|
128
|
+
}
|
|
129
|
+
return '';
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Save text data to file
|
|
135
|
+
* @param {String} data text data
|
|
136
|
+
* @param {String} filename file name
|
|
137
|
+
*/
|
|
138
|
+
export function saveTextToFile(data, filename) {
|
|
139
|
+
fs.writeFile(filename, data, (err) => {
|
|
140
|
+
if (err) {
|
|
141
|
+
printMessage(`ERROR - can't save ${filename}`, 'error');
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
return true;
|
|
145
|
+
});
|
|
146
|
+
}
|