From c4244104a9fc55dff006483002cca801bae2b8d6 Mon Sep 17 00:00:00 2001 From: Matteo Sessa Date: Thu, 12 Jan 2017 17:40:28 +1100 Subject: [PATCH 1/8] Basic react wrapper component for josdejong/jsoneditor. Integration with Secrets page --- app/components/Secrets/Secrets.jsx | 154 +++++++++++++++------------ app/components/shared/JsonEditor.jsx | 88 +++++++++++++++ docker-compose.yaml | 3 + package.json | 2 + run-docker-compose-dev | 1 + webpack.config.js | 15 ++- 6 files changed, 189 insertions(+), 74 deletions(-) create mode 100644 app/components/shared/JsonEditor.jsx diff --git a/app/components/Secrets/Secrets.jsx b/app/components/Secrets/Secrets.jsx index a0562fb..294ac79 100644 --- a/app/components/Secrets/Secrets.jsx +++ b/app/components/Secrets/Secrets.jsx @@ -13,6 +13,7 @@ import FlatButton from 'material-ui/FlatButton'; import TextField from 'material-ui/TextField'; import { green500, green400, red500, red300, yellow500, white } from 'material-ui/styles/colors.js' import axios from 'axios'; +import JsonEditor from '../shared/JsonEditor.jsx'; const copyEvent = new CustomEvent("snackbar", { detail: { @@ -49,7 +50,8 @@ class Secrets extends React.Component { 'renderList', 'renderNamespace', 'clickSecret', - 'secretChanged', + 'secretChangedTextEditor', + 'secretChangedJsonEditor', 'updateSecret', 'renderEditDialog', 'renderNewKeyDialog', @@ -115,35 +117,21 @@ class Secrets extends React.Component { }) } - secretChanged(e, v) { - if (this.state.useRootKey) { - //If root key is in place, set as json object to properly escape special characters - let tmp = {}; - tmp = _.set(tmp, `${this.state.rootKey}`, v); - this.state.focusSecret = tmp; - } else { + secretChangedJsonEditor(v, syntaxCheckOk) { + console.log(`syntax: ${syntaxCheckOk} , secret: ${v}`); + if (syntaxCheckOk && v) { + this.setState({disableSubmit: false}); this.state.focusSecret = v; - + } else { + this.setState({disableSubmit: true}); } } - checkValidJson() { - try { - if (this.state.useRootKey) { - JSON.parse(JSON.stringify(this.state.focusSecret)); - } else { - JSON.parse(this.state.focusSecret); - } - this.setState({ - errorMessage: '' - }) - return true; - } catch (e) { - this.setState({ - errorMessage: `Invalid JSON` - }) - return false; - } + secretChangedTextEditor(e, v) { + this.setState({disableSubmit: false}); + let tmp = {}; + _.set(tmp, `${this.state.rootKey}`, v); + this.state.focusSecret = tmp; } renderEditDialog() { @@ -153,28 +141,47 @@ class Secrets extends React.Component { ]; let submitUpdate = () => { - if (!this.checkValidJson()) return; this.updateSecret(false); this.setState({ openEditModal: false }); } + var objectIsBasicRootKey = _.size(this.state.focusSecret) == 1 && this.state.focusSecret.hasOwnProperty(this.state.rootKey); + var content; + + if (objectIsBasicRootKey && this.state.useRootKey) { + var title = `Editing ${this.state.namespace}${this.state.focusKey} with specified root key`; + content = ( + + ); + } else { + var title = `Editing ${this.state.namespace}${this.state.focusKey}`; + content = ( + + ); + } return ( this.setState({ openEditModal: false })} autoScrollBodyContent={true} > - + {content}
{this.state.errorMessage}
); @@ -219,40 +226,52 @@ class Secrets extends React.Component { }); return; } - if (!this.checkValidJson()) return; + console.log(this.state.focusSecret); this.updateSecret(true); this.setState({ openNewKeyModal: false, errorMessage: '' }); } const actions = [ this.setState({ openNewKeyModal: false, errorMessage: '' })} />, - + ]; var rootKeyInfo; + var content; if (this.state.useRootKey) { rootKeyInfo = "Current Root Key: " + this.state.rootKey; + var content = ( + + ); } else { - rootKeyInfo = "No Root Key set. Value must be JSON."; + content = ( + + ); } return ( this.setState({ openNewKeyModal: false, errorMessage: '' })} autoScrollBodyContent={true} > - this.setState({ focusKey: v })} /> - + this.setState({ focusKey: v })} /> + {content}
{this.state.errorMessage}
{rootKeyInfo}
@@ -322,28 +341,15 @@ class Secrets extends React.Component { let fullKey = `${this.state.namespace}${key}`; axios.get(`/secret?vaultaddr=${encodeURI(window.localStorage.getItem("vaultUrl"))}&secret=${encodeURI(fullKey)}&token=${encodeURI(window.localStorage.getItem("vaultAccessToken"))}`) .then((resp) => { - let val = this.state.useRootKey ? _.get(resp, `data.${this.state.rootKey}`) : resp.data; - if (val === undefined) { - this.setState({ - errorMessage: `No value exists under the root key '${this.state.rootKey}'.`, - focusSecret: '', - disableSubmit: true, - openEditModal: true, - disableTextField: true, - listBackends: false - }); - } else { - val = typeof val == 'object' ? JSON.stringify(val) : val; - this.setState({ - errorMessage: '', - disableSubmit: false, - disableTextField: false, - openEditModal: true, - focusKey: key, - focusSecret: val, - listBackends: false - }); - } + this.setState({ + errorMessage: '', + disableSubmit: false, + disableTextField: false, + openEditModal: true, + focusKey: key, + focusSecret: resp.data, + listBackends: false + }); }) .catch((err) => { console.error(err.stack); @@ -449,12 +455,18 @@ class Secrets extends React.Component {

Secrets

Here you can view, update, and delete keys stored in your Vault. Just remember, deleting keys cannot be undone!

this.setState({ openNewKeyModal: true, focusKey: '', focusSecret: '', errorMessage: '' })} /> + onTouchTap={() => this.setState({ + disableSubmit: true, + openNewKeyModal: true, + focusKey: '', + focusSecret: '', + errorMessage: '' + })} />
{this.renderNamespace()}
{this.renderList()} diff --git a/app/components/shared/JsonEditor.jsx b/app/components/shared/JsonEditor.jsx new file mode 100644 index 0000000..e6c76e4 --- /dev/null +++ b/app/components/shared/JsonEditor.jsx @@ -0,0 +1,88 @@ +import React, { PropTypes } from 'react'; +import { browserHistory } from 'react-router'; +import JSONEditor from 'jsoneditor'; +import 'jsoneditor/src/css/reset.css'; +import 'jsoneditor/src/css/jsoneditor.css'; +import 'jsoneditor/src/css/menu.css'; +import 'jsoneditor/src/css/searchbox.css'; +import 'jsoneditor/src/css/contextmenu.css'; + +function isValid(value) { + return value !== '' && value !== undefined && value !== null; +}; + +class JsonEditor extends React.Component { + static propTypes = { + rootName: PropTypes.string, + value: PropTypes.any, + mode: PropTypes.oneOf(['tree', 'form', 'text']), + onChange: PropTypes.func, + }; + + static defaultProps = { + rootName: '', + value: '', + mode: 'tree', + onChange: () => {} + }; + + state = { + hasValue: false, + }; + + constructor(props) { + super(props); + if (typeof JSONEditor === undefined) { + throw new Error('JSONEditor is undefined!'); + } + } + + handleInputChange = () => { + try { + this.setState({hasValue: isValid(this._jsoneditor.get())}); + if (this.props.onChange) { + this.props.onChange(this._jsoneditor.get(), this.state.hasValue); + } + } catch (e) { + this.props.onChange(null, false); + } + } + + componentDidMount() { + const { + onChange, + value, + } = this.props; + + var container = this.editorEl;//ReactDOM.findDOMNode(this); + var options = { + name: this.props.rootName, + mode: this.props.mode, + modes: ['tree', 'code'], + onChange: this.handleInputChange, + }; + this._jsoneditor = new JSONEditor(container, options, value); + this.setState({hasValue: true}); + this._jsoneditor.focus(); + + + } + + componentWillReceiveProps(nextProps) { + if (nextProps.rootName !== this.props.rootName) { + this._jsoneditor.setName(nextProps.rootName); + } + } + + componentWillUnmount() { + this._jsoneditor.destroy(); + } + + render() { + return ( +
{ this.editorEl = c; }} /> + ); + } +} + +export default JsonEditor; \ No newline at end of file diff --git a/docker-compose.yaml b/docker-compose.yaml index d40f432..0671c6f 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -19,6 +19,9 @@ services: volumes: - .:/app - /app/node_modules + environment: + VAULT_URL_DEFAULT: http://vault:8200 + VAULT_AUTH_DEFAULT: USERNAMEPASSWORD webpack: build: . diff --git a/package.json b/package.json index 3619de0..08c295e 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "babel-preset-react": "^6.16.0", "babel-preset-stage-2": "^6.18.0", "css-loader": "^0.25.0", + "url-loader": "^0.5.7", "extract-text-webpack-plugin": "^1.0.1", "material-ui": "^0.16.4", "postcss-loader": "^1.1.0", @@ -53,6 +54,7 @@ "hcl-to-json": "0.0.4", "lodash": "^4.16.6", "material-ui": "^0.16.1", + "jsoneditor": "^5.5.11", "react": "^15.4.0", "react-dom": "^15.4.0", "react-router": "^3.0.0", diff --git a/run-docker-compose-dev b/run-docker-compose-dev index f97af15..1b74f02 100755 --- a/run-docker-compose-dev +++ b/run-docker-compose-dev @@ -19,6 +19,7 @@ exec_in_vault vault status exec_in_vault vault auth-enable userpass exec_in_vault vault policy-write admin /app/admin.hcl exec_in_vault vault write auth/userpass/users/test password=test policies=admin +exec_in_vault vault write secret/test somekey=somedata echo "------------- Vault Root Token -------------" docker-compose logs vault | grep 'Root Token:' | tail -n 1 diff --git a/webpack.config.js b/webpack.config.js index 2508c48..edc7b30 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -21,11 +21,19 @@ module.exports = { exclude: 'node_modules' }, { test: /\.json$/i, - loader: 'json', + loader: 'json-loader', exclude: 'node_modules' + }, { + test: /\.svg$/, + loader: 'url' + }, { + test: /\.css$/, + loader: ExtractTextPlugin.extract('css?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]!postcss-loader'), + exclude: /node_modules/ }, { test: /\.css$/, - loader: ExtractTextPlugin.extract('css?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]!postcss-loader') + loader: ExtractTextPlugin.extract('css!postcss-loader'), + include: /node_modules/ }] }, postcss: [ autoprefixer({ browsers: ['last 2 versions'] }) ], @@ -43,6 +51,7 @@ module.exports = { // comments: false, // sourceMap: false // }), - new ExtractTextPlugin("styles.css") + new ExtractTextPlugin("styles.css"), + new webpack.IgnorePlugin(/regenerator|nodent|js-beautify/, /ajv/) ] }; From 9d72a82890d316219848c3bdf6453a3f180d542e Mon Sep 17 00:00:00 2001 From: Matteo Sessa Date: Fri, 13 Jan 2017 11:13:51 +1100 Subject: [PATCH 2/8] Small fix --- app/components/Secrets/Secrets.jsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/components/Secrets/Secrets.jsx b/app/components/Secrets/Secrets.jsx index 294ac79..0c91cfb 100644 --- a/app/components/Secrets/Secrets.jsx +++ b/app/components/Secrets/Secrets.jsx @@ -118,10 +118,8 @@ class Secrets extends React.Component { } secretChangedJsonEditor(v, syntaxCheckOk) { - console.log(`syntax: ${syntaxCheckOk} , secret: ${v}`); if (syntaxCheckOk && v) { - this.setState({disableSubmit: false}); - this.state.focusSecret = v; + this.setState({disableSubmit: false, focusSecret: v}); } else { this.setState({disableSubmit: true}); } From 4d1a9c19addfb5849e06719f8926ae2a03259c97 Mon Sep 17 00:00:00 2001 From: Matteo Sessa Date: Fri, 13 Jan 2017 11:22:07 +1100 Subject: [PATCH 3/8] Json editor intergation in policies page with schema valitation --- app/components/Policies/Manage.jsx | 163 ++++++++++-------- .../Policies/vault-policy-schema.json | 50 ++++++ app/components/shared/JsonEditor.jsx | 34 ++-- package.json | 1 + src/policies.js | 13 +- 5 files changed, 166 insertions(+), 95 deletions(-) create mode 100644 app/components/Policies/vault-policy-schema.json diff --git a/app/components/Policies/Manage.jsx b/app/components/Policies/Manage.jsx index 2ba526c..6059ac3 100644 --- a/app/components/Policies/Manage.jsx +++ b/app/components/Policies/Manage.jsx @@ -9,6 +9,9 @@ import Dialog from 'material-ui/Dialog'; import TextField from 'material-ui/TextField'; import IconButton from 'material-ui/IconButton'; import FontIcon from 'material-ui/FontIcon'; +import JsonEditor from '../shared/JsonEditor.jsx'; +import hcltojson from 'hcl-to-json' +import jsonschema from './vault-policy-schema.json' export default class Manage extends React.Component { constructor(props) { @@ -17,11 +20,13 @@ export default class Manage extends React.Component { openEditModal: false, openNewPolicyModal: false, newPolicyErrorMessage: '', + newPolicyNameErrorMessage: '', openDeleteModal: false, - editingPolicy: -1, + focusPolicy: -1, deletingPolicy: '', policies: [], currentPolicy: '', + disableSubmit: false, errorMessage: '', forbidden: false, buttonColor: 'lightgrey' @@ -45,23 +50,27 @@ export default class Manage extends React.Component { this.listPolicies(); } - updatePolicy() { - let policyName = this.state.editingPolicy; + updatePolicy(policyName, isNewPolicy) { axios.put(`/policy?vaultaddr=${encodeURI(window.localStorage.getItem("vaultUrl"))}&policy=${encodeURI(policyName)}&token=${encodeURI(window.localStorage.getItem("vaultAccessToken"))}`, { "Policy": this.state.currentPolicy }) .then((resp) => { - // Custom future logic on success - this.setState({ - errorMessage: '' - }); + if (isNewPolicy) { + let policies = this.state.policies; + policies.push({ name: policyName }); + this.setState({ + policies: policies + }); + } }) .catch((err) => { console.error(err.stack); - this.setState({ - errorMessage: err.response.data - }); + if(err.response.data.errors){ + this.setState({ + errorMessage: err.response.data.errors.join('
') + }); + } }) - + this.setState({ openNewPolicyModal: false }); this.setState({ openEditModal: false }); } @@ -93,86 +102,78 @@ export default class Manage extends React.Component { renderEditDialog() { const actions = [ this.setState({ openEditModal: false })} />, - this.updatePolicy()} /> + this.updatePolicy(this.state.focusPolicy, false)} /> ]; - let policyChanged = (e, v, ) => { - this.state.currentPolicy = e.target.value; + let policyChanged = (v, syntaxCheckOk, schemaCheckOk) => { + if (syntaxCheckOk && schemaCheckOk && v) { + this.setState({disableSubmit: false, currentPolicy: v}); + } else { + this.setState({disableSubmit: true}); + } }; return ( this.setState({ openEditModal: false })} autoScrollBodyContent={true} > - policyChanged(e, v)} - name="editingText" multiLine={true} - autoFocus - defaultValue={this.state.currentPolicy} - fullWidth={true} /> + ); } renderNewPolicyDialog() { const MISSING_POLICY_ERROR = "Policy cannot be empty."; - const DUPLICATE_POLICY_ERROR = `Policy ${this.state.newPolicy.name} already exists.`; + const DUPLICATE_POLICY_ERROR = `Policy ${this.state.focusPolicy} already exists.`; let validateAndSubmit = () => { - if (this.state.newPolicy.name === '') { + if (this.state.focusPolicy === '') { this.setState({ newPolicyErrorMessage: MISSING_POLICY_ERROR }); return; } - if (_.filter(this.state.policies, x => x.name === this.state.newPolicy.name).length > 0) { + if (_.filter(this.state.policies, x => x.name === this.state.focusPolicy).length > 0) { this.setState({ newPolicyErrorMessage: DUPLICATE_POLICY_ERROR }); return; } - - axios.put(`/policy?vaultaddr=${encodeURI(window.localStorage.getItem("vaultUrl"))}&policy=${encodeURI(this.state.newPolicy.name)}&token=${encodeURI(window.localStorage.getItem("vaultAccessToken"))}`, { "Policy": this.state.currentPolicy }) - .then((resp) => { - let policies = this.state.policies; - policies.push({ name: this.state.newPolicy.name }); - this.setState({ - policies: policies, - errorMessage: '' - }); - }) - .catch((err) => { - console.error(err.stack); - this.setState({ - errorMessage: err.response.data - }); - }) - - this.setState({ openNewPolicyModal: false }); + this.updatePolicy(this.state.focusPolicy, true); } const actions = [ this.setState({ openNewPolicyModal: false, newPolicyErrorMessage: '' })} />, - + ]; - let setNewPolicy = (e, v) => { - let currentPolicy = this.state.newPolicy; - if (e.target.name === "newName") { - currentPolicy.name = v; - } else if (e.target.name === "newRules") { - currentPolicy.rules = v; + let setNewPolicy = (v, syntaxCheckOk, schemaCheckOk) => { + if (syntaxCheckOk && schemaCheckOk && v) { + this.setState({disableSubmit: false, currentPolicy: v}); + } else { + this.setState({disableSubmit: true}); + } + } + + let validatePolicyName = (event, v) => { + var pattern = /^[^\/&]+$/; + if (v.match(pattern)) { + this.setState({newPolicyNameErrorMessage: '', focusPolicy: v}); + } else { + this.setState({newPolicyNameErrorMessage: 'Policy name contains illegal characters'}); } - this.setState({ - newPolicy: currentPolicy - }); } @@ -186,14 +187,21 @@ export default class Manage extends React.Component { autoScrollBodyContent={true} autoDetectWindowHeight={true} > - setNewPolicy(e, v)} /> setNewPolicy(e, v)} /> + hintText="Name" + errorText={this.state.newPolicyNameErrorMessage} + onChange={validatePolicyName} + /> +
{this.state.newPolicyErrorMessage}
); @@ -224,18 +232,25 @@ export default class Manage extends React.Component { axios.get(`/policy?vaultaddr=${encodeURI(window.localStorage.getItem("vaultUrl"))}&policy=${encodeURI(policyName)}&token=${encodeURI(window.localStorage.getItem("vaultAccessToken"))}`) .then((resp) => { let rules = resp.data.data.rules; + let rules_obj; // Attempt to parse into JSON incase a stringified JSON was sent try { - rules = JSON.parse(rules) + rules_obj = JSON.parse(rules); } catch (e) { } - let val = typeof rules == 'object' ? JSON.stringify(rules, null, 4) : rules; + if (!rules_obj) { + // Previous parse failed, attempt HCL to JSON conversion + rules_obj = hcltojson(rules); + } - this.setState({ - openEditModal: true, - editingPolicy: policyName, - currentPolicy: val - }); + if(rules_obj) { + this.setState({ + openEditModal: true, + focusPolicy: policyName, + currentPolicy: rules_obj, + disableSubmit: true, + }); + } }) .catch((err) => { console.error(err.stack); @@ -251,11 +266,12 @@ export default class Manage extends React.Component { errorMessage: 'An error occurred.' }); } else { + debugger; let policies = this.state.policies; - let policyToDelete = _.find(policies, (policyToDelete) => { return policyToDelete.name === policyName }); å + let policyToDelete = _.find(policies, (policyToDelete) => { return policyToDelete.name === policyName }); policies = _.pull(policies, policyToDelete); this.setState({ - secrets: policies, + policies: policies, errorMessage: '' }); } @@ -317,7 +333,16 @@ export default class Manage extends React.Component { backgroundColor={this.state.buttonColor} hoverColor={green400} labelStyle={{ color: white }} - onTouchTap={() => this.setState({ openNewPolicyModal: true, newPolicy: { name: '', value: '' } })} />} + onTouchTap={() => this.setState({ + openNewPolicyModal: true, + errorMessage: '', + newPolicyErrorMessage: '', + newPolicyNameErrorMessage: '', + disableSubmit: true, + focusPolicy: '', + currentPolicy: { path: { 'sample/path' : { capabilities: ['read'] }} } + + })} />} {this.state.errorMessage &&
diff --git a/app/components/Policies/vault-policy-schema.json b/app/components/Policies/vault-policy-schema.json new file mode 100644 index 0000000..068142a --- /dev/null +++ b/app/components/Policies/vault-policy-schema.json @@ -0,0 +1,50 @@ +{ + "type": "object", + "required": [ "path" ], + "properties": { + "path": { + "type": "object", + "minProperties": 1, + "additionalProperties": false, + "patternProperties": { + "^[^\/].*$": { + "type": "object", + "additionalProperties": false, + "anyOf": [ + {"required": ["capabilities"]}, + {"required": ["policy"]} + ], + "properties": { + "capabilities" : { + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": { + "type": "string", + "enum": [ + "create", + "read", + "update", + "delete", + "list", + "sudo", + "deny" + ] + } + }, + "policy" : { + "type": "string", + "enum": [ + "read", + "write", + "sudo", + "deny" + ] + } + } + + } + } + } + } +} diff --git a/app/components/shared/JsonEditor.jsx b/app/components/shared/JsonEditor.jsx index e6c76e4..08382f7 100644 --- a/app/components/shared/JsonEditor.jsx +++ b/app/components/shared/JsonEditor.jsx @@ -15,69 +15,73 @@ class JsonEditor extends React.Component { static propTypes = { rootName: PropTypes.string, value: PropTypes.any, - mode: PropTypes.oneOf(['tree', 'form', 'text']), + mode: PropTypes.oneOf(['tree', 'code']), + schema: PropTypes.object, onChange: PropTypes.func, }; - + static defaultProps = { rootName: '', value: '', mode: 'tree', + schema: null, onChange: () => {} }; - + state = { hasValue: false, }; - + constructor(props) { super(props); if (typeof JSONEditor === undefined) { throw new Error('JSONEditor is undefined!'); } } - + handleInputChange = () => { try { this.setState({hasValue: isValid(this._jsoneditor.get())}); if (this.props.onChange) { - this.props.onChange(this._jsoneditor.get(), this.state.hasValue); + let schemaCheck = true; + if (this.props.schema) { + schemaCheck = this._jsoneditor.validateSchema(this._jsoneditor.get()); + } + this.props.onChange(this._jsoneditor.get(), this.state.hasValue, schemaCheck); } } catch (e) { - this.props.onChange(null, false); + this.props.onChange(null, false, false); } } componentDidMount() { - const { - onChange, - value, - } = this.props; var container = this.editorEl;//ReactDOM.findDOMNode(this); var options = { name: this.props.rootName, mode: this.props.mode, modes: ['tree', 'code'], + schema: this.props.schema, onChange: this.handleInputChange, }; - this._jsoneditor = new JSONEditor(container, options, value); + + this._jsoneditor = new JSONEditor(container, options, this.props.value); this.setState({hasValue: true}); this._jsoneditor.focus(); } - + componentWillReceiveProps(nextProps) { if (nextProps.rootName !== this.props.rootName) { this._jsoneditor.setName(nextProps.rootName); } } - + componentWillUnmount() { this._jsoneditor.destroy(); } - + render() { return (
{ this.editorEl = c; }} /> diff --git a/package.json b/package.json index 08c295e..e10339c 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "babel-preset-stage-2": "^6.18.0", "css-loader": "^0.25.0", "url-loader": "^0.5.7", + "json-loader": "^0.5.4", "extract-text-webpack-plugin": "^1.0.1", "material-ui": "^0.16.4", "postcss-loader": "^1.1.0", diff --git a/src/policies.js b/src/policies.js index c0e14d1..418b155 100644 --- a/src/policies.js +++ b/src/policies.js @@ -48,16 +48,8 @@ exports.updatePolicy = function (req, res) { //API requires an escaped JSON let policy = _.get(req, "body.Policy"); - // Attempt to parse into JSON incase a stringified JSON was sent - try { - policy = JSON.parse(policy) - } catch (e) { } - - //If the user passed in an HCL document, convert to stringified JSON as required by the API - let rules = typeof policy == 'object' ? JSON.stringify(policy) : JSON.stringify(hcltojson(policy)); - let body = { - rules: rules + rules: JSON.stringify(policy) }; axios.put(`${vaultAddr}${endpoint}`, body, config) @@ -65,8 +57,7 @@ exports.updatePolicy = function (req, res) { res.json(resp.data); }) .catch((err) => { - console.error(err.stack); - res.status(err.response.status).send(err.response); + res.status(err.response.status).send(err.response.data); }); } From 279c01c09b7cd11b47c1e3d7bc19ece0caa32916 Mon Sep 17 00:00:00 2001 From: Matteo Sessa Date: Fri, 13 Jan 2017 11:31:45 +1100 Subject: [PATCH 4/8] Cosmetic fixes --- app/components/Policies/Manage.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/components/Policies/Manage.jsx b/app/components/Policies/Manage.jsx index 6059ac3..dfcdf6a 100644 --- a/app/components/Policies/Manage.jsx +++ b/app/components/Policies/Manage.jsx @@ -249,6 +249,7 @@ export default class Manage extends React.Component { focusPolicy: policyName, currentPolicy: rules_obj, disableSubmit: true, + errorMessage: '', }); } }) @@ -341,7 +342,6 @@ export default class Manage extends React.Component { disableSubmit: true, focusPolicy: '', currentPolicy: { path: { 'sample/path' : { capabilities: ['read'] }} } - })} />} {this.state.errorMessage &&
From cf25662f055fe05bdad23408f444aa5e30710e7c Mon Sep 17 00:00:00 2001 From: Matteo Sessa Date: Fri, 13 Jan 2017 11:36:07 +1100 Subject: [PATCH 5/8] Policiy names are always lowercase --- app/components/Policies/Manage.jsx | 1 + 1 file changed, 1 insertion(+) diff --git a/app/components/Policies/Manage.jsx b/app/components/Policies/Manage.jsx index dfcdf6a..6b94bae 100644 --- a/app/components/Policies/Manage.jsx +++ b/app/components/Policies/Manage.jsx @@ -169,6 +169,7 @@ export default class Manage extends React.Component { let validatePolicyName = (event, v) => { var pattern = /^[^\/&]+$/; + v = v.toLowerCase(); if (v.match(pattern)) { this.setState({newPolicyNameErrorMessage: '', focusPolicy: v}); } else { From 11418b6bfc8236f7b108e2e06ddae89954226e0b Mon Sep 17 00:00:00 2001 From: Matteo Sessa Date: Fri, 13 Jan 2017 11:57:45 +1100 Subject: [PATCH 6/8] Remove debugger line --- app/components/Policies/Manage.jsx | 1 - 1 file changed, 1 deletion(-) diff --git a/app/components/Policies/Manage.jsx b/app/components/Policies/Manage.jsx index 6b94bae..21e2a6c 100644 --- a/app/components/Policies/Manage.jsx +++ b/app/components/Policies/Manage.jsx @@ -268,7 +268,6 @@ export default class Manage extends React.Component { errorMessage: 'An error occurred.' }); } else { - debugger; let policies = this.state.policies; let policyToDelete = _.find(policies, (policyToDelete) => { return policyToDelete.name === policyName }); policies = _.pull(policies, policyToDelete); From ec435d690262825bf271a416671e8f9ec579068c Mon Sep 17 00:00:00 2001 From: Matteo Sessa Date: Fri, 13 Jan 2017 12:40:07 +1100 Subject: [PATCH 7/8] 4 space indent --- app/components/shared/JsonEditor.jsx | 127 +++++++++++++-------------- 1 file changed, 62 insertions(+), 65 deletions(-) diff --git a/app/components/shared/JsonEditor.jsx b/app/components/shared/JsonEditor.jsx index 08382f7..f141d45 100644 --- a/app/components/shared/JsonEditor.jsx +++ b/app/components/shared/JsonEditor.jsx @@ -8,85 +8,82 @@ import 'jsoneditor/src/css/searchbox.css'; import 'jsoneditor/src/css/contextmenu.css'; function isValid(value) { - return value !== '' && value !== undefined && value !== null; + return value !== '' && value !== undefined && value !== null; }; class JsonEditor extends React.Component { - static propTypes = { - rootName: PropTypes.string, - value: PropTypes.any, - mode: PropTypes.oneOf(['tree', 'code']), - schema: PropTypes.object, - onChange: PropTypes.func, - }; + static propTypes = { + rootName: PropTypes.string, + value: PropTypes.any, + mode: PropTypes.oneOf(['tree', 'code']), + schema: PropTypes.object, + onChange: PropTypes.func, + }; - static defaultProps = { - rootName: '', - value: '', - mode: 'tree', - schema: null, - onChange: () => {} - }; + static defaultProps = { + rootName: '', + value: '', + mode: 'tree', + schema: null, + onChange: () => {} + }; - state = { - hasValue: false, - }; + state = { + hasValue: false, + }; - constructor(props) { - super(props); - if (typeof JSONEditor === undefined) { - throw new Error('JSONEditor is undefined!'); + constructor(props) { + super(props); + if (typeof JSONEditor === undefined) { + throw new Error('JSONEditor is undefined!'); + } } - } - handleInputChange = () => { - try { - this.setState({hasValue: isValid(this._jsoneditor.get())}); - if (this.props.onChange) { - let schemaCheck = true; - if (this.props.schema) { - schemaCheck = this._jsoneditor.validateSchema(this._jsoneditor.get()); - } - this.props.onChange(this._jsoneditor.get(), this.state.hasValue, schemaCheck); + handleInputChange = () => { + try { + this.setState({hasValue: isValid(this._jsoneditor.get())}); + if (this.props.onChange) { + let schemaCheck = true; + if (this.props.schema) { + schemaCheck = this._jsoneditor.validateSchema(this._jsoneditor.get()); + } + this.props.onChange(this._jsoneditor.get(), this.state.hasValue, schemaCheck); + } + } catch (e) { + this.props.onChange(null, false, false); } - } catch (e) { - this.props.onChange(null, false, false); } - } - - componentDidMount() { - - var container = this.editorEl;//ReactDOM.findDOMNode(this); - var options = { - name: this.props.rootName, - mode: this.props.mode, - modes: ['tree', 'code'], - schema: this.props.schema, - onChange: this.handleInputChange, - }; - this._jsoneditor = new JSONEditor(container, options, this.props.value); - this.setState({hasValue: true}); - this._jsoneditor.focus(); - - - } + componentDidMount() { + var container = this.editorEl;//ReactDOM.findDOMNode(this); + var options = { + name: this.props.rootName, + mode: this.props.mode, + modes: ['tree', 'code'], + schema: this.props.schema, + onChange: this.handleInputChange, + }; - componentWillReceiveProps(nextProps) { - if (nextProps.rootName !== this.props.rootName) { - this._jsoneditor.setName(nextProps.rootName); + this._jsoneditor = new JSONEditor(container, options, this.props.value); + this.setState({hasValue: true}); + this._jsoneditor.focus(); } - } - componentWillUnmount() { - this._jsoneditor.destroy(); - } + componentWillReceiveProps(nextProps) { + if (nextProps.rootName !== this.props.rootName) { + this._jsoneditor.setName(nextProps.rootName); + } + } - render() { - return ( -
{ this.editorEl = c; }} /> - ); - } + componentWillUnmount() { + this._jsoneditor.destroy(); + } + + render() { + return ( +
{ this.editorEl = c; }} /> + ); + } } -export default JsonEditor; \ No newline at end of file +export default JsonEditor; From 1740081d84155585c05f3ce3342317591c6ace1f Mon Sep 17 00:00:00 2001 From: Matteo Sessa Date: Fri, 13 Jan 2017 12:45:41 +1100 Subject: [PATCH 8/8] Deduplicate state change code --- app/components/Policies/Manage.jsx | 29 +++++++++++------------------ 1 file changed, 11 insertions(+), 18 deletions(-) diff --git a/app/components/Policies/Manage.jsx b/app/components/Policies/Manage.jsx index 21e2a6c..89506d1 100644 --- a/app/components/Policies/Manage.jsx +++ b/app/components/Policies/Manage.jsx @@ -36,6 +36,7 @@ export default class Manage extends React.Component { this, 'updatePolicy', 'listPolicies', + 'policyChangeSetState', 'renderEditDialog', 'renderNewPolicyDialog', 'renderDeleteConfirmationDialog', @@ -99,20 +100,20 @@ export default class Manage extends React.Component { }); } + policyChangeSetState(v, syntaxCheckOk, schemaCheckOk) { + if (syntaxCheckOk && schemaCheckOk && v) { + this.setState({disableSubmit: false, currentPolicy: v}); + } else { + this.setState({disableSubmit: true}); + } + } + renderEditDialog() { const actions = [ this.setState({ openEditModal: false })} />, this.updatePolicy(this.state.focusPolicy, false)} /> ]; - let policyChanged = (v, syntaxCheckOk, schemaCheckOk) => { - if (syntaxCheckOk && schemaCheckOk && v) { - this.setState({disableSubmit: false, currentPolicy: v}); - } else { - this.setState({disableSubmit: true}); - } - }; - return ( ); @@ -159,14 +160,6 @@ export default class Manage extends React.Component { ]; - let setNewPolicy = (v, syntaxCheckOk, schemaCheckOk) => { - if (syntaxCheckOk && schemaCheckOk && v) { - this.setState({disableSubmit: false, currentPolicy: v}); - } else { - this.setState({disableSubmit: true}); - } - } - let validatePolicyName = (event, v) => { var pattern = /^[^\/&]+$/; v = v.toLowerCase(); @@ -201,7 +194,7 @@ export default class Manage extends React.Component { value={this.state.currentPolicy} mode={'code'} schema={jsonschema} - onChange={setNewPolicy} + onChange={this.policyChangeSetState} />
{this.state.newPolicyErrorMessage}