🔒 Added a way to hide the secret settings once they are set

issue https://github.com/TryGhost/Team/issues/621
This commit is contained in:
Thibaut Patel 2021-04-16 17:05:16 +02:00 committed by Daniel Lockyer
parent 0d312d3e00
commit e29a62aadb
No known key found for this signature in database
GPG Key ID: FFBC6FA2A6F6ABC1
5 changed files with 108 additions and 9 deletions

View File

@ -26,12 +26,14 @@ module.exports = {
}));
}
// CASE: omit core settings unless internal request
if (!frame.options.context.internal) {
// CASE: omit core settings unless internal request
settings = _.filter(settings, (setting) => {
const isCore = setting.group === 'core';
return !isCore;
});
// CASE: omit secret settings unless internal request
settings = settings.map(settingsService.hideValueIfSecret);
}
return settings;
@ -70,6 +72,8 @@ module.exports = {
}));
}
setting = settingsService.hideValueIfSecret(setting);
return {
[frame.options.key]: setting
};
@ -217,8 +221,8 @@ module.exports = {
async query(frame) {
const stripeConnectIntegrationToken = frame.data.settings.find(setting => setting.key === 'stripe_connect_integration_token');
// The `stripe_connect_integration_token` "setting" is only used to set the `stripe_connect_*` settings.
const settings = frame.data.settings.filter((setting) => {
// The `stripe_connect_integration_token` "setting" is only used to set the `stripe_connect_*` settings.
return ![
'stripe_connect_integration_token',
'stripe_connect_publishable_key',
@ -226,7 +230,9 @@ module.exports = {
'stripe_connect_livemode',
'stripe_connect_account_id',
'stripe_connect_display_name'
].includes(setting.key);
].includes(setting.key)
// Remove obfuscated settings
&& !(setting.value === settingsService.obfuscatedSetting && settingsService.isSecretSetting(setting));
});
const getSetting = setting => settingsCache.get(setting.key, {resolve: false});

View File

@ -24,12 +24,14 @@ module.exports = {
}));
}
// CASE: omit core settings unless internal request
if (!frame.options.context.internal) {
// CASE: omit core settings unless internal request
settings = _.filter(settings, (setting) => {
const isCore = setting.group === 'core';
return !isCore;
});
// CASE: omit secret settings unless internal request
settings = settings.map(settingsService.hideValueIfSecret);
}
return settings;
@ -68,6 +70,8 @@ module.exports = {
}));
}
setting = settingsService.hideValueIfSecret(setting);
return {
[frame.options.key]: setting
};
@ -108,7 +112,10 @@ module.exports = {
}
frame.data.settings = _.reject(frame.data.settings, (setting) => {
return setting.key === 'type';
return setting.key === 'type'
// Remove obfuscated settings
|| (setting.value === settingsService.obfuscatedSetting
&& settingsService.isSecretSetting(setting));
});
const errors = [];

View File

@ -26,12 +26,14 @@ module.exports = {
}));
}
// CASE: omit core settings unless internal request
if (!frame.options.context.internal) {
// CASE: omit core settings unless internal request
settings = _.filter(settings, (setting) => {
const isCore = setting.group === 'core';
return !isCore;
});
// CASE: omit secret settings unless internal request
settings = settings.map(settingsService.hideValueIfSecret);
}
return settings;
@ -70,6 +72,8 @@ module.exports = {
}));
}
setting = settingsService.hideValueIfSecret(setting);
return {
[frame.options.key]: setting
};
@ -217,8 +221,8 @@ module.exports = {
async query(frame) {
const stripeConnectIntegrationToken = frame.data.settings.find(setting => setting.key === 'stripe_connect_integration_token');
// The `stripe_connect_integration_token` "setting" is only used to set the `stripe_connect_*` settings.
const settings = frame.data.settings.filter((setting) => {
// The `stripe_connect_integration_token` "setting" is only used to set the `stripe_connect_*` settings.
return ![
'stripe_connect_integration_token',
'stripe_connect_publishable_key',
@ -226,7 +230,9 @@ module.exports = {
'stripe_connect_livemode',
'stripe_connect_account_id',
'stripe_connect_display_name'
].includes(setting.key);
].includes(setting.key)
// Remove obfuscated settings
&& !(setting.value === settingsService.obfuscatedSetting && settingsService.isSecretSetting(setting));
});
const getSetting = setting => settingsCache.get(setting.key, {resolve: false});

View File

@ -5,6 +5,22 @@
const models = require('../../models');
const SettingsCache = require('./cache');
// The string returned when a setting is set as write-only
const obfuscatedSetting = '••••••••';
// The function used to decide whether a setting is write-only
function isSecretSetting(setting) {
return /secret/.test(setting.key);
}
// The function that obfuscates a write-only setting
function hideValueIfSecret(setting) {
if (setting.value && isSecretSetting(setting)) {
return {...setting, value: obfuscatedSetting};
}
return setting;
}
module.exports = {
async init() {
const settingsCollection = await models.Settings.populateDefaults();
@ -44,5 +60,9 @@ module.exports = {
value: currentRoutesHash
}], {context: {internal: true}});
}
}
},
obfuscatedSetting,
isSecretSetting,
hideValueIfSecret
};

View File

@ -225,6 +225,66 @@ describe('Settings API (canary)', function () {
.expect(403);
});
it('Can\'t read secret setting', function (done) {
const key = 'stripe_secret_key';
request
.get(localUtils.API.getApiQuery(`settings/${key}/`))
.set('Origin', config.get('url'))
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200)
.end(function (err, res) {
if (err) {
return done(err);
}
const json = res.body;
should.exist(json);
should.exist(json.settings);
json.settings.length.should.eql(1);
json.settings[0].key.should.eql('stripe_secret_key');
should(json.settings[0].value).be.null();
done();
});
});
it('Can\'t read secret setting', function (done) {
const key = 'stripe_secret_key';
request.put(localUtils.API.getApiQuery('settings/'))
.set('Origin', config.get('url'))
.send({
settings: [{
key,
value: 'sk_test_4eC39HqLyjWDarjtT1zdp7dc'
}]
})
.then(() => {
request
.get(localUtils.API.getApiQuery(`settings/${key}/`))
.set('Origin', config.get('url'))
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200)
.end(function (err, res) {
if (err) {
return done(err);
}
const json = res.body;
should.exist(json);
should.exist(json.settings);
json.settings.length.should.eql(1);
json.settings[0].key.should.eql('stripe_secret_key');
json.settings[0].value.should.eql('••••••••');
done();
});
});
});
it('Can\'t read permalinks', function (done) {
request.get(localUtils.API.getApiQuery('settings/permalinks/'))
.set('Origin', config.get('url'))