0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-02-03 23:00:14 -05:00

Updated serializers/model layer validation using settings type

refs https://github.com/TryGhost/Ghost/issues/10318

- Updates `boolean` serialization in v2/canary serializers to apply only for `boolean` type settings
- Updates `boolean` transformation in model layer `format`/`parse` to check on `boolean` type setting
- Removes error thrown on Read-only setting for settings edit endpoint
- Updates v2/canary input serializers to remove any Read-only settings (using RO flag) to avoid edits
- Added type/group mappings in the importer when pre-migration settings table import data is present
- Updates tests
This commit is contained in:
Rish 2020-06-30 17:34:56 +05:30 committed by Rishabh Garg
parent 8a50a3e9c9
commit d5f68dbbc5
11 changed files with 216 additions and 112 deletions

View file

@ -1,6 +1,7 @@
const _ = require('lodash');
const url = require('./utils/url');
const typeGroupMapper = require('./utils/settings-type-group-mapper');
const typeGroupMapper = require('../../../../shared/serializers/input/utils/settings-filter-type-group-mapper');
const settingsCache = require('../../../../../services/settings/cache');
module.exports = {
browse(apiConfig, frame) {
@ -38,11 +39,22 @@ module.exports = {
if (_.isString(frame.data)) {
frame.data = {settings: [{key: frame.data, value: frame.options}]};
}
const settings = settingsCache.getAll();
// Ignore and drop all values with Read-only flag
frame.data.settings = frame.data.settings.filter((setting) => {
const settingFlagsStr = settings[setting.key] ? settings[setting.key].flags : '';
const settingFlagsArr = settingFlagsStr ? settingFlagsStr.split(',') : [];
return !settingFlagsArr.includes('RO');
});
frame.data.settings.forEach((setting) => {
// CASE: transform objects/arrays into string (we store stringified objects in the db)
// @TODO: This belongs into the model layer. We should stringify before saving and parse when fetching from db.
// @TODO: Fix when dropping v0.1
const settingType = settings[setting.key] ? settings[setting.key].type : '';
//TODO: Needs to be removed once we get rid of all `object` type settings
if (_.isObject(setting.value)) {
setting.value = JSON.stringify(setting.value);
}
@ -50,12 +62,12 @@ module.exports = {
// @TODO: handle these transformations in a centralised API place (these rules should apply for ALL resources)
// CASE: Ensure we won't forward strings, otherwise model events or model interactions can fail
if (setting.value === '0' || setting.value === '1') {
if (settingType === 'boolean' && (setting.value === '0' || setting.value === '1')) {
setting.value = !!+setting.value;
}
// CASE: Ensure we won't forward strings, otherwise model events or model interactions can fail
if (setting.value === 'false' || setting.value === 'true') {
if (settingType === 'boolean' && (setting.value === 'false' || setting.value === 'true')) {
setting.value = setting.value === 'true';
}

View file

@ -1,7 +1,7 @@
const Promise = require('bluebird');
const _ = require('lodash');
const {i18n} = require('../../../../../lib/common');
const {BadRequestError, NotFoundError} = require('@tryghost/errors');
const {NotFoundError} = require('@tryghost/errors');
module.exports = {
read(apiConfig, frame) {
@ -19,15 +19,7 @@ module.exports = {
const errors = [];
_.each(frame.data.settings, (setting) => {
if (setting.key === 'active_theme') {
// @NOTE: active theme has to be changed via theme endpoints
errors.push(
new BadRequestError({
message: i18n.t('errors.api.settings.activeThemeSetViaAPI.error'),
help: i18n.t('errors.api.settings.activeThemeSetViaAPI.help')
})
);
} else if (setting.key === 'ghost_head' || setting.key === 'ghost_foot') {
if (setting.key === 'ghost_head' || setting.key === 'ghost_foot') {
// @NOTE: was removed https://github.com/TryGhost/Ghost/issues/10373
errors.push(new NotFoundError({
message: i18n.t('errors.api.settings.problemFindingSetting', {

View file

@ -0,0 +1,57 @@
// NOTE: mapping is based on maping present in migration - 3.22/04-populate-settings-groups-and-flags
const keyGroupMapping = {
members_public_key: 'core',
members_private_key: 'core',
members_email_auth_secret: 'core',
db_hash: 'core',
next_update_check: 'core',
notifications: 'core',
session_secret: 'core',
theme_session_secret: 'core',
ghost_public_key: 'core',
ghost_private_key: 'core',
title: 'site',
description: 'site',
logo: 'site',
cover_image: 'site',
icon: 'site',
accent_color: 'site',
lang: 'site',
timezone: 'site',
codeinjection_head: 'site',
codeinjection_foot: 'site',
facebook: 'site',
twitter: 'site',
navigation: 'site',
secondary_navigation: 'site',
meta_title: 'site',
meta_description: 'site',
og_image: 'site',
og_title: 'site',
og_description: 'site',
twitter_image: 'site',
twitter_title: 'site',
twitter_description: 'site',
active_theme: 'theme',
is_private: 'private',
password: 'private',
public_hash: 'private',
amp: 'amp',
labs: 'labs',
slack: 'slack',
unsplash: 'unsplash',
shared_views: 'views',
bulk_email_settings: 'email',
default_content_visibility: 'members',
members_subscription_settings: 'members',
stripe_connect_integration: 'members',
portal_name: 'portal',
portal_button: 'portal',
portal_plans: 'portal'
};
const mapKeyToGroup = (key) => {
return keyGroupMapping[key];
};
module.exports = mapKeyToGroup;

View file

@ -0,0 +1,66 @@
// NOTE: mapping is based on maping present in migration - 3.22/07-update-type-for-settings
const keyTypeMapping = {
db_hash: 'string',
session_secret: 'string',
theme_session_secret: 'string',
ghost_public_key: 'string',
ghost_private_key: 'string',
title: 'string',
description: 'string',
logo: 'string',
cover_image: 'string',
icon: 'string',
accent_color: 'string',
lang: 'string',
timezone: 'string',
codeinjection_head: 'string',
codeinjection_foot: 'string',
facebook: 'string',
twitter: 'string',
meta_title: 'string',
meta_description: 'string',
og_image: 'string',
og_title: 'string',
og_description: 'string',
twitter_image: 'string',
twitter_title: 'string',
twitter_description: 'string',
active_theme: 'string',
password: 'string',
public_hash: 'string',
members_public_key: 'string',
members_private_key: 'string',
members_email_auth_secret: 'string',
default_content_visibility: 'string',
members_from_address: 'string',
stripe_product_name: 'string',
stripe_secret_key: 'string',
stripe_publishable_key: 'string',
stripe_connect_publishable_key: 'string',
stripe_connect_secret_key: 'string',
stripe_connect_display_name: 'string',
stripe_connect_account_id: 'string',
notifications: 'array',
navigation: 'array',
secondary_navigation: 'array',
slack: 'array',
shared_views: 'array',
portal_plans: 'array',
stripe_plans: 'array',
next_update_check: 'number',
amp: 'boolean',
is_private: 'boolean',
members_allow_free_signup: 'boolean',
portal_name: 'boolean',
portal_button: 'boolean',
stripe_connect_livemode: 'boolean',
labs: 'object',
unsplash: 'object',
bulk_email_settings: 'object'
};
const mapKeyToType = (key) => {
return keyTypeMapping[key];
};
module.exports = mapKeyToType;

View file

@ -1,6 +1,7 @@
const _ = require('lodash');
const url = require('./utils/url');
const typeGroupMapper = require('./utils/settings-type-group-mapper');
const typeGroupMapper = require('../../../../shared/serializers/input/utils/settings-filter-type-group-mapper');
const settingsCache = require('../../../../../services/settings/cache');
module.exports = {
browse(apiConfig, frame) {
@ -35,23 +36,30 @@ module.exports = {
frame.data = {settings: [{key: frame.data, value: frame.options}]};
}
const settings = settingsCache.getAll();
// Ignore and drop all values with Read-only flag
frame.data.settings = frame.data.settings.filter((setting) => {
const settingFlagsStr = settings[setting.key] ? settings[setting.key].flags : '';
const settingFlagsArr = settingFlagsStr ? settingFlagsStr.split(',') : [];
return !settingFlagsArr.includes('RO');
});
frame.data.settings.forEach((setting) => {
// CASE: transform objects/arrays into string (we store stringified objects in the db)
// @TODO: This belongs into the model layer. We should stringify before saving and parse when fetching from db.
// @TODO: Fix when dropping v0.1
const settingType = settings[setting.key] ? settings[setting.key].type : '';
// TODO: Needs to be removed once we get rid of all `object` type settings
if (_.isObject(setting.value)) {
setting.value = JSON.stringify(setting.value);
}
// @TODO: handle these transformations in a centralised API place (these rules should apply for ALL resources)
// CASE: Ensure we won't forward strings, otherwise model events or model interactions can fail
if (setting.value === '0' || setting.value === '1') {
// CASE: Ensure we won't forward strings for booleans, otherwise model events or model interactions can fail
if (settingType === 'boolean' && (setting.value === '0' || setting.value === '1')) {
setting.value = !!+setting.value;
}
// CASE: Ensure we won't forward strings, otherwise model events or model interactions can fail
if (setting.value === 'false' || setting.value === 'true') {
// CASE: Ensure we won't forward strings for booleans, otherwise model events or model interactions can fail
if (settingType === 'boolean' && (setting.value === 'false' || setting.value === 'true')) {
setting.value = setting.value === 'true';
}

View file

@ -1,45 +0,0 @@
const typeGroupMapping = {
core: [
'core'
],
blog: [
'site',
'amp',
'labs',
'slack',
'unsplash',
'views'
],
theme: [
'theme'
],
members: [
'members'
],
private: [
'private'
],
portal: [
'portal'
],
bulk_email: [
'email'
],
site: [
'site'
]
};
const mapTypeToGroup = (typeOptions) => {
const types = typeOptions.split(',');
const mappedTypes = types.map((type) => {
const sanitizedType = type ? type.trim() : null;
return typeGroupMapping[sanitizedType];
}).filter(type => !!type);
return mappedTypes.join(',');
};
module.exports = mapTypeToGroup;

View file

@ -133,10 +133,22 @@ Settings = ghostBookshelf.Model.extend({
format() {
const attrs = ghostBookshelf.Model.prototype.format.apply(this, arguments);
const settingType = attrs.type;
// @NOTE: type TEXT will transform boolean to "0"
if (_.isBoolean(attrs.value)) {
attrs.value = attrs.value.toString();
if (settingType === 'boolean') {
// CASE: Ensure we won't forward strings, otherwise model events or model interactions can fail
if (attrs.value === '0' || attrs.value === '1') {
attrs.value = !!+attrs.value;
}
// CASE: Ensure we won't forward strings, otherwise model events or model interactions can fail
if (attrs.value === 'false' || attrs.value === 'true') {
attrs.value = JSON.parse(attrs.value);
}
if (_.isBoolean(attrs.value)) {
attrs.value = attrs.value.toString();
}
}
return attrs;
@ -145,13 +157,14 @@ Settings = ghostBookshelf.Model.extend({
parse() {
const attrs = ghostBookshelf.Model.prototype.parse.apply(this, arguments);
// transform "0" to false
// transform "false" to false
if (attrs.value === '0' || attrs.value === '1') {
// transform "0" to false for boolean type
const settingType = attrs.type;
if (settingType === 'boolean' && (attrs.value === '0' || attrs.value === '1')) {
attrs.value = !!+attrs.value;
}
if (attrs.value === 'false' || attrs.value === 'true') {
// transform "false" to false for boolean type
if (settingType === 'boolean' && (attrs.value === 'false' || attrs.value === 'true')) {
attrs.value = JSON.parse(attrs.value);
}

View file

@ -1,5 +1,5 @@
const should = require('should');
const mapper = require('../../../../../../../../core//server/api/canary/utils/serializers/input/utils/settings-type-group-mapper');
const mapper = require('../../../../../../../../core/server/api/shared/serializers/input/utils/settings-filter-type-group-mapper');
describe('Unit: canary/utils/serializers/input/utils/settings-type-group-mapper', function () {
describe('browse', function () {

View file

@ -48,9 +48,10 @@ describe('Unit: models/settings', function () {
][step - 1]();
});
return models.Settings.edit({
return models.Settings.add({
key: 'description',
value: 'added value'
value: 'added value',
type: 'string'
})
.then(() => {
eventSpy.calledTwice.should.be.true();
@ -188,22 +189,22 @@ describe('Unit: models/settings', function () {
it('ensure correct parsing when fetching from db', function () {
const setting = models.Settings.forge();
let returns = setting.parse({key: 'is_private', value: 'false'});
let returns = setting.parse({key: 'is_private', value: 'false', type: 'boolean'});
should.equal(returns.value, false);
returns = setting.parse({key: 'is_private', value: false});
returns = setting.parse({key: 'is_private', value: false, type: 'boolean'});
should.equal(returns.value, false);
returns = setting.parse({key: 'is_private', value: true});
returns = setting.parse({key: 'is_private', value: true, type: 'boolean'});
should.equal(returns.value, true);
returns = setting.parse({key: 'is_private', value: 'true'});
returns = setting.parse({key: 'is_private', value: 'true', type: 'boolean'});
should.equal(returns.value, true);
returns = setting.parse({key: 'is_private', value: '0'});
returns = setting.parse({key: 'is_private', value: '0', type: 'boolean'});
should.equal(returns.value, false);
returns = setting.parse({key: 'is_private', value: '1'});
returns = setting.parse({key: 'is_private', value: '1', type: 'boolean'});
should.equal(returns.value, true);
returns = setting.parse({key: 'something', value: 'null'});

View file

@ -2208,7 +2208,7 @@
"id": "5c2ca6e0e015a6761618240d",
"key": "db_hash",
"value": "04a3a271-634b-44d8-842e-49286b38a162",
"type": "core",
"type": "string",
"created_at": "2019-01-02T11:56:16.000Z",
"created_by": "1",
"updated_at": "2019-01-02T11:56:16.000Z",
@ -2218,7 +2218,7 @@
"id": "5c2ca6e0e015a6761618240e",
"key": "next_update_check",
"value": null,
"type": "core",
"type": "number",
"created_at": "2019-01-02T11:56:16.000Z",
"created_by": "1",
"updated_at": "2019-01-02T11:56:16.000Z",
@ -2228,7 +2228,7 @@
"id": "5c2ca6e0e015a6761618240f",
"key": "notifications",
"value": "[]",
"type": "core",
"type": "array",
"created_at": "2019-01-02T11:56:16.000Z",
"created_by": "1",
"updated_at": "2019-01-02T11:56:16.000Z",
@ -2238,7 +2238,7 @@
"id": "5c2ca6e0e015a67616182410",
"key": "session_secret",
"value": "e255c011755a47cba690cb5089e2c05f488ab0e6d682d44809f622e25864e980",
"type": "core",
"type": "string",
"created_at": "2019-01-02T11:56:16.000Z",
"created_by": "1",
"updated_at": "2019-01-02T11:56:16.000Z",
@ -2248,7 +2248,7 @@
"id": "5c2ca6e0e015a67616182411",
"key": "title",
"value": "Ghost",
"type": "site",
"type": "string",
"created_at": "2019-01-02T11:56:16.000Z",
"created_by": "1",
"updated_at": "2019-01-02T11:56:16.000Z",
@ -2258,7 +2258,7 @@
"id": "5c2ca6e0e015a67616182412",
"key": "description",
"value": "The professional publishing platform",
"type": "site",
"type": "string",
"created_at": "2019-01-02T11:56:16.000Z",
"created_by": "1",
"updated_at": "2019-01-02T11:56:16.000Z",
@ -2268,7 +2268,7 @@
"id": "5c2ca6e0e015a67616182413",
"key": "logo",
"value": "https://static.ghost.org/v1.0.0/images/ghost-logo.svg",
"type": "site",
"type": "string",
"created_at": "2019-01-02T11:56:16.000Z",
"created_by": "1",
"updated_at": "2019-01-02T11:56:16.000Z",
@ -2278,7 +2278,7 @@
"id": "5c2ca6e0e015a67616182414",
"key": "cover_image",
"value": "https://static.ghost.org/v1.0.0/images/blog-cover.jpg",
"type": "site",
"type": "string",
"created_at": "2019-01-02T11:56:16.000Z",
"created_by": "1",
"updated_at": "2019-01-02T11:56:16.000Z",
@ -2288,7 +2288,7 @@
"id": "5c2ca6e0e015a67616182415",
"key": "icon",
"value": "",
"type": "site",
"type": "string",
"created_at": "2019-01-02T11:56:16.000Z",
"created_by": "1",
"updated_at": "2019-01-02T11:56:16.000Z",
@ -2298,7 +2298,7 @@
"id": "5c2ca6e0e015a67616182416",
"key": "lang",
"value": "en",
"type": "site",
"type": "string",
"created_at": "2019-01-02T11:56:16.000Z",
"created_by": "1",
"updated_at": "2019-01-02T11:56:16.000Z",
@ -2308,7 +2308,7 @@
"id": "5c2ca6e0e015a67616182417",
"key": "timezone",
"value": "Etc/UTC",
"type": "site",
"type": "string",
"created_at": "2019-01-02T11:56:16.000Z",
"created_by": "1",
"updated_at": "2019-01-02T11:56:16.000Z",
@ -2318,7 +2318,7 @@
"id": "5c2ca6e0e015a6761618241a",
"key": "amp",
"value": "true",
"type": "amp",
"type": "boolean",
"created_at": "2019-01-02T11:56:16.000Z",
"created_by": "1",
"updated_at": "2019-01-02T11:56:16.000Z",
@ -2328,7 +2328,7 @@
"id": "5c2ca6e0e015a6761618241b",
"key": "ghost_head",
"value": "",
"type": "site",
"type": "string",
"created_at": "2019-01-02T11:56:16.000Z",
"created_by": "1",
"updated_at": "2019-01-02T11:56:16.000Z",
@ -2338,7 +2338,7 @@
"id": "5c2ca6e0e015a6761618241c",
"key": "ghost_foot",
"value": "",
"type": "site",
"type": "string",
"created_at": "2019-01-02T11:56:16.000Z",
"created_by": "1",
"updated_at": "2019-01-02T11:56:16.000Z",
@ -2348,7 +2348,7 @@
"id": "5c2ca6e0e015a6761618241d",
"key": "facebook",
"value": "ghost",
"type": "site",
"type": "string",
"created_at": "2019-01-02T11:56:16.000Z",
"created_by": "1",
"updated_at": "2019-01-02T11:56:16.000Z",
@ -2358,7 +2358,7 @@
"id": "5c2ca6e0e015a6761618241e",
"key": "twitter",
"value": "tryghost",
"type": "site",
"type": "string",
"created_at": "2019-01-02T11:56:16.000Z",
"created_by": "1",
"updated_at": "2019-01-02T11:56:16.000Z",
@ -2368,7 +2368,7 @@
"id": "5c2ca6e0e015a6761618241f",
"key": "labs",
"value": "{\"publicAPI\": true}",
"type": "labs",
"type": "object",
"created_at": "2019-01-02T11:56:16.000Z",
"created_by": "1",
"updated_at": "2019-01-02T11:56:16.000Z",
@ -2378,7 +2378,7 @@
"id": "5c2ca6e0e015a67616182420",
"key": "navigation",
"value": "[{\"label\":\"Home\", \"url\":\"/\"},{\"label\":\"Tag\", \"url\":\"/tag/getting-started/\"}, {\"label\":\"Author\", \"url\":\"/author/ghost/\"},{\"label\":\"Help\", \"url\":\"https://ghost.org/docs/\"}]",
"type": "site",
"type": "array",
"created_at": "2019-01-02T11:56:16.000Z",
"created_by": "1",
"updated_at": "2019-01-02T11:56:16.000Z",
@ -2388,7 +2388,7 @@
"id": "5c2ca6e0e015a67616182421",
"key": "slack",
"value": "[{\"url\":\"\", \"username\":\"Ghost\"}]",
"type": "slack",
"type": "array",
"created_at": "2019-01-02T11:56:16.000Z",
"created_by": "1",
"updated_at": "2019-01-02T11:56:16.000Z",
@ -2398,7 +2398,7 @@
"id": "5c2ca6e0e015a67616182422",
"key": "unsplash",
"value": "{\"isActive\": true}",
"type": "unsplash",
"type": "object",
"created_at": "2019-01-02T11:56:16.000Z",
"created_by": "1",
"updated_at": "2019-01-02T11:56:16.000Z",
@ -2408,7 +2408,7 @@
"id": "5c2ca6e0e015a67616182423",
"key": "active_theme",
"value": "casper",
"type": "theme",
"type": "string",
"created_at": "2019-01-02T11:56:16.000Z",
"created_by": "1",
"updated_at": "2019-01-02T11:56:16.000Z",
@ -2418,7 +2418,7 @@
"id": "5c2ca6e0e015a67616182424",
"key": "active_apps",
"value": "[]",
"type": "app",
"type": "string",
"created_at": "2019-01-02T11:56:16.000Z",
"created_by": "1",
"updated_at": "2019-01-02T11:56:16.000Z",
@ -2428,7 +2428,7 @@
"id": "5c2ca6e0e015a67616182425",
"key": "installed_apps",
"value": "[]",
"type": "app",
"type": "string",
"created_at": "2019-01-02T11:56:16.000Z",
"created_by": "1",
"updated_at": "2019-01-02T11:56:16.000Z",
@ -2438,7 +2438,7 @@
"id": "5c2ca6e0e015a67616182426",
"key": "is_private",
"value": "false",
"type": "private",
"type": "boolean",
"created_at": "2019-01-02T11:56:16.000Z",
"created_by": "1",
"updated_at": "2019-01-02T11:56:16.000Z",
@ -2448,7 +2448,7 @@
"id": "5c2ca6e0e015a67616182427",
"key": "password",
"value": "",
"type": "private",
"type": "string",
"created_at": "2019-01-02T11:56:16.000Z",
"created_by": "1",
"updated_at": "2019-01-02T11:56:16.000Z",
@ -2458,7 +2458,7 @@
"id": "5c2ca6e0e015a67616182428",
"key": "public_hash",
"value": "a7cb7ad9a319203bc4f1d1a90c4116",
"type": "private",
"type": "string",
"created_at": "2019-01-02T11:56:16.000Z",
"created_by": "1",
"updated_at": "2019-01-02T11:56:16.000Z",
@ -2468,7 +2468,7 @@
"id": "5c2ca6e0e015a67616182429",
"key": "members_public_key",
"value": "-----BEGIN RSA PUBLIC KEY-----\nMIGJAoGBAJ/6gCIMJyUZsR+lwV/ObX1mYSt7MFcy7LcPeDvepSruyabB9Z98Tit6Npfr79cc\nvxt95S8oUxXqRKNQgrDyJG6NJLMx9rcU2OMmdLAG5wJVlBz0D1eOwIaGKDzjZL9B42QIJPsy\nFQiNntWGYgiIPjpUT2u4rnsV0ATo9TB/bk3xAgMBAAE=\n-----END RSA PUBLIC KEY-----\n",
"type": "core",
"type": "string",
"created_at": "2019-01-02T11:56:16.000Z",
"created_by": "1",
"updated_at": "2019-01-02T11:56:16.000Z",
@ -2478,7 +2478,7 @@
"id": "5c2ca6e0e015a6761618242a",
"key": "members_private_key",
"value": "-----BEGIN RSA PRIVATE KEY-----\nMIICXAIBAAKBgQCf+oAiDCclGbEfpcFfzm19ZmErezBXMuy3D3g73qUq7smmwfWffE4rejaX\n6+/XHL8bfeUvKFMV6kSjUIKw8iRujSSzMfa3FNjjJnSwBucCVZQc9A9XjsCGhig842S/QeNk\nCCT7MhUIjZ7VhmIIiD46VE9ruK57FdAE6PUwf25N8QIDAQABAoGAZm70Gljju6qusg/lOJ4p\nlzC1qSywsDTIQxKhrtwJr+rDrYXl6x+hwc74I+CLapZae5Tp6X8NbCvblSKY/AmfbzEw4QnQ\nybQC5WVC/VPl6jWYIodpwUM7RdUNYqWMiY07XnSwL2ejXszW3MO3NPGqh1irp0U4RwutaRfV\n4D6xDMECQQDh4PdiYJetxrruQBg3u5E4VaX/M1xzRGqo4jCdaO3e4+M1WymcPGmHJC3cypzg\n0dEdkhD9KH1AziSEjEeu/9kZAkEAtU/V+nAsdxsUby6LMBIB+3y6DcQwwygobfwkD2A+3qVQ\nTHxNwh6kL2Xif9U9d4+w1XQE8kwmZsKoQ9z8PC2+mQJAHuGi8NBD7H4/EFOy++uo7wrGpx1e\nhmPUMUK7Ysn1u4NsjN7p0XJw+wj3PDh3OkV1UZWmvPXMKhAE7ho/sq1IAQJATURloyGUwXln\n3u3N4UF7WMpRm7ZFNZXyjNSMJYVVpZp7uuyqUpSuUYiw2ttsI3y31m9oAD4Vi2tfO/R8BcVU\n2QJBAM4tqJaEy4jQ4aa9XEfU0/LU1TwZbeHZJ8Y+pDarHfYiHShQ2JKlMDAeh/DNqD0VEpeI\nSeO5Hxbv/56NVT+pNQE=\n-----END RSA PRIVATE KEY-----\n",
"type": "core",
"type": "string",
"created_at": "2019-01-02T11:56:16.000Z",
"created_by": "1",
"updated_at": "2019-01-02T11:56:16.000Z",