Skip to content

Commit 4eba049

Browse files
authored
Encrypt auth information in the database. Closes #1653 (#1664)
* Encrypt auth information in the database. Closes #1653 * Add tests for utils
1 parent c0edc3e commit 4eba049

File tree

3 files changed

+151
-7
lines changed

3 files changed

+151
-7
lines changed

src/common/utils.js

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,66 @@
7878
};
7979
};
8080

81+
const splitObj = function (obj, nestedKeys) {
82+
const selected = {};
83+
const remaining = deepCopy(obj);
84+
nestedKeys.forEach(keys => {
85+
const value = deepGet(obj, keys);
86+
deepSet(selected, keys, value);
87+
deepDelete(remaining, keys);
88+
});
89+
90+
return [selected, remaining];
91+
};
92+
93+
const deepDelete = function (obj, nestedKeys) {
94+
const allButLast = nestedKeys.slice(0, nestedKeys.length - 1);
95+
const nestedObj = deepGet(obj, allButLast);
96+
const lastKey = nestedKeys.slice().pop();
97+
delete nestedObj[lastKey];
98+
};
99+
100+
const deepGet = function (obj, nestedKeys) {
101+
return nestedKeys.reduce((value, key) => value[key], obj);
102+
};
103+
104+
const deepSet = function (obj, nestedKeys, value) {
105+
const allButLast = nestedKeys.slice(0, nestedKeys.length-1);
106+
const nestedObj = createNestedObjs(obj, allButLast);
107+
const lastKey = nestedKeys[nestedKeys.length - 1];
108+
nestedObj[lastKey] = value;
109+
};
110+
111+
const createNestedObjs = function (obj, nestedKeys) {
112+
nestedKeys.forEach(key => {
113+
if (!obj[key]) {
114+
obj[key] = {};
115+
}
116+
obj = obj[key];
117+
});
118+
return obj;
119+
};
120+
121+
const deepCopy = v => JSON.parse(JSON.stringify(v));
122+
123+
const deepExtend = function (obj1, obj2) {
124+
Object.entries(obj2).forEach(entry => {
125+
const [key, value] = entry;
126+
const mergeRequired = typeof obj1[key] === 'object' &&
127+
typeof value === 'object';
128+
129+
if (mergeRequired) {
130+
deepExtend(obj1[key], value);
131+
} else {
132+
obj1[key] = value;
133+
}
134+
});
135+
return obj1;
136+
};
137+
81138
return {
139+
deepExtend,
140+
splitObj,
82141
resolveCarriageReturns,
83142
abbr,
84143
withTimeout,

src/common/viz/ConfigDialog.js

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@
22
define([
33
'q',
44
'js/Dialogs/PluginConfig/PluginConfigDialog',
5+
'deepforge/utils',
56
'text!js/Dialogs/PluginConfig/templates/PluginConfigDialog.html',
67
'css!./ConfigDialog.css'
78
], function(
89
Q,
910
PluginConfigDialog,
11+
utils,
1012
pluginConfigDialogTemplate,
1113
) {
1214
var SECTION_DATA_KEY = 'section',
@@ -90,31 +92,60 @@ define([
9092

9193
this._dialog.modal('hide');
9294
if (saveConfig) {
93-
this.saveConfigForUser(config);
95+
this.saveConfig(config);
9496
}
9597
return callback(config);
9698
};
9799

98-
ConfigDialog.prototype.saveConfigForUser = async function (config) {
100+
ConfigDialog.prototype.saveConfig = async function (config) {
101+
const authKeys = this.getAuthenticationKeys(this._pluginMetadata)
102+
.map(keys => [this._pluginMetadata.id].concat(keys));
103+
const [secretConfig, publicConfig] = utils.splitObj(config, authKeys);
104+
105+
await this.saveUserData({Dialog: {__secrets__: secretConfig}}, true);
106+
await this.saveUserData({Dialog: publicConfig});
107+
};
108+
109+
ConfigDialog.prototype.saveUserData = async function (config, encrypt=false) {
99110
const opts = {
100111
method: 'PATCH',
101112
credentials: 'same-origin',
102113
headers: {
103114
'Content-Type': 'application/json'
104115
},
105-
body: JSON.stringify({Dialog: config})
116+
body: JSON.stringify(config)
106117
};
107-
await fetch('/api/user/data', opts);
118+
await fetch(`/api/user/data?${encrypt ? 'encrypt=1' : ''}`, opts);
119+
};
120+
121+
ConfigDialog.prototype.getAuthenticationKeys = function (metadata) {
122+
const keys = [];
123+
metadata.configStructure.forEach(config => {
124+
if (config.isAuth) {
125+
keys.push([config.name]);
126+
} else if (config.valueType === 'dict') {
127+
const nestedKeys = config.valueItems
128+
.flatMap(c => this.getAuthenticationKeys(c))
129+
.map(key => [config.name, 'config'].concat(key));
130+
131+
keys.push(...nestedKeys);
132+
}
133+
});
134+
135+
return keys;
108136
};
109137

110138
ConfigDialog.prototype.getSavedConfig = async function () {
111139
const opts = {
112140
method: 'GET',
113141
credentials: 'same-origin'
114142
};
115-
const response = await fetch('/api/user/data', opts);
116-
const userData = await response.json();
117-
return userData.Dialog;
143+
let response = await fetch('/api/user/data/Dialog/__secrets__?decrypt=true', opts);
144+
const secrets = await response.json();
145+
146+
response = await fetch('/api/user/data/Dialog', opts);
147+
const publicData = await response.json();
148+
return utils.deepExtend(publicData, secrets);
118149
};
119150

120151
ConfigDialog.prototype._getAllConfigValues = function () {

test/unit/common/utils.spec.js

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
describe('common/utils', function() {
2+
const assert = require('assert');
3+
const utils = require('../../../src/common/utils');
4+
5+
describe('splitObj', function() {
6+
it('should set key/value in "selected" obj', function() {
7+
const [selected] = utils.splitObj({a: 1}, [['a']]);
8+
assert.equal(selected.a, 1);
9+
});
10+
11+
it('should set nested key/value in "selected" obj', function() {
12+
const [selected] = utils.splitObj({a: {b: 1}}, [['a', 'b']]);
13+
assert.equal(selected.a.b, 1);
14+
});
15+
16+
it('should rm keys from "remaining" obj', function() {
17+
const [,remaining] = utils.splitObj({a: 1}, [['a']]);
18+
assert.equal(remaining.a, undefined);
19+
});
20+
21+
it('should rm nested keys from "remaining" obj', function() {
22+
const [,remaining] = utils.splitObj({a: {b: 1}}, [['a', 'b']]);
23+
assert.equal(remaining.a.b, undefined);
24+
});
25+
});
26+
27+
describe('deepExtend', function() {
28+
it('should copy primitive vals', function() {
29+
const merged = utils.deepExtend({}, {a: 1});
30+
assert.equal(merged.a, 1);
31+
});
32+
33+
it('should overwrite existing primitive vals', function() {
34+
const merged = utils.deepExtend({a: 2}, {a: 1});
35+
assert.equal(merged.a, 1);
36+
});
37+
38+
it('should create nested objects as needed vals', function() {
39+
const merged = utils.deepExtend({}, {a: {b: 1}});
40+
assert.equal(merged.a.b, 1);
41+
});
42+
43+
it('should copy nested primitive vals', function() {
44+
const merged = utils.deepExtend({a: {}}, {a: {b: 1}});
45+
assert.equal(merged.a.b, 1);
46+
});
47+
48+
it('should merge nested objects', function() {
49+
const merged = utils.deepExtend({a: {c: 2}}, {a: {b: 1}});
50+
assert.equal(merged.a.b, 1);
51+
assert.equal(merged.a.c, 2);
52+
});
53+
});
54+
});

0 commit comments

Comments
 (0)