From 2c0793eb17e1b408663cccd9cf86394ad19db1b9 Mon Sep 17 00:00:00 2001 From: shubham4443 Date: Thu, 4 Feb 2021 17:31:59 +0530 Subject: [PATCH 1/5] WIP: finished with security functions page --- .../function-form/FunctionForm.jsx | 64 ++++++++ src/components/sidenav/Sidenav.jsx | 3 + src/constants.js | 3 +- src/mirage/fixtures.js | 11 ++ src/mirage/server.js | 5 + src/operations/securityFunctions.js | 45 ++++++ .../security-functions/SecurityFunctions.jsx | 153 ++++++++++++++++++ src/routes/ProjectPages.jsx | 2 + src/services/securityFunctions.js | 49 ++++++ src/services/service.js | 2 + 10 files changed, 336 insertions(+), 1 deletion(-) create mode 100644 src/components/security-functions/function-form/FunctionForm.jsx create mode 100644 src/operations/securityFunctions.js create mode 100644 src/pages/security-functions/SecurityFunctions.jsx create mode 100644 src/services/securityFunctions.js diff --git a/src/components/security-functions/function-form/FunctionForm.jsx b/src/components/security-functions/function-form/FunctionForm.jsx new file mode 100644 index 00000000..223529a0 --- /dev/null +++ b/src/components/security-functions/function-form/FunctionForm.jsx @@ -0,0 +1,64 @@ +import React from "react" +import { Modal, Input, Form, Row, Col, Button } from 'antd'; +import FormItemLabel from "../../form-item-label/FormItemLabel" +import { MinusCircleFilled, PlusOutlined } from "@ant-design/icons"; + +const FunctionForm = ({ handleSubmit, handleCancel, initialValues }) => { + const [form] = Form.useForm(); + + const onSubmit = e => { + form.validateFields().then(values => { + handleSubmit(values).then(() => { + handleCancel(); + form.resetFields(); + }) + }); + } + + const formInitialValues = initialValues ? initialValues : { variables: [""] } + + return ( + +
+ + + + + + + {(fields, { add, remove }) => ( +
+ {fields.map((field => ( + + + + + + + {fields.length > 1 && + remove(field.name)} /> + } + + )))} + +
+ )} +
+ +
+ ); +} + +export default FunctionForm; + diff --git a/src/components/sidenav/Sidenav.jsx b/src/components/sidenav/Sidenav.jsx index 4672ede5..ffca7760 100644 --- a/src/components/sidenav/Sidenav.jsx +++ b/src/components/sidenav/Sidenav.jsx @@ -105,6 +105,9 @@ const Sidenav = (props) => { + + + diff --git a/src/constants.js b/src/constants.js index 203450f1..13767428 100644 --- a/src/constants.js +++ b/src/constants.js @@ -74,7 +74,8 @@ export const projectModules = { INTEGRATIONS: "integrations", EXPLORER: "explorer", SETTINGS: "settings", - SECURITY_RULES: "security-rules" + SECURITY_RULES: "security-rules", + SECURITY_FUNCTIONS: "security-functions" } export const moduleResources = { diff --git a/src/mirage/fixtures.js b/src/mirage/fixtures.js index b1c09dc5..c401e571 100644 --- a/src/mirage/fixtures.js +++ b/src/mirage/fixtures.js @@ -964,3 +964,14 @@ export const addonsConfig = { } } } + +export const securityFunctions = [ + { + id: "Function 1", + variables: ["variable1", "variable2"] + }, + { + id: "Function 2", + variables: ["variable1", "variable2"] + } +] diff --git a/src/mirage/server.js b/src/mirage/server.js index 71f05716..a0a7599b 100644 --- a/src/mirage/server.js +++ b/src/mirage/server.js @@ -145,6 +145,11 @@ export function makeServer({ environment = "development" } = {}) { this.post("/config/integrations", () => respondOk()) this.delete("/config/integrations/:integrationId", () => respondOk()) + // SecurityFunctions endpoints + this.get("/config/projects/:projectId/security/function", () => respondOk({ result: fixtures.securityFunctions })) + this.post("/config/projects/:projectId/security/function/:id", () => respondOk()) + this.delete("/config/projects/:projectId/security/function/:id", () => respondOk()) + // Addons this.get("/config/add-ons/rabbitmq/rabbitmq", () => respondOk({ result: [fixtures.addonsConfig.rabbitmq] })) this.get("/config/add-ons/redis/redis", () => respondOk({ result: [fixtures.addonsConfig.redis] })) diff --git a/src/operations/securityFunctions.js b/src/operations/securityFunctions.js new file mode 100644 index 00000000..feef5538 --- /dev/null +++ b/src/operations/securityFunctions.js @@ -0,0 +1,45 @@ +import { set, get } from "automate-redux"; +import client from "../client"; +import store from "../store"; + +export const loadSecurityFunctions = (projectId) => { + return new Promise((resolve, reject) => { + client.securityFunctions.fetchSecurityFunctions(projectId) + .then(functions => { + setSecurityFunctions(functions) + resolve() + }) + .catch(ex => reject(ex)) + }) +} + +export const saveSecurityFunction = (projectId, config) => { + return new Promise((resolve, reject) => { + client.securityFunctions.setFunctionConfig(projectId, config) + .then(({ queued }) => { + if (!queued) { + const newSecurityFunctions = getSecurityFunctions(store.getState()).concat(config) + setSecurityFunctions(newSecurityFunctions) + } + resolve({ queued }) + }) + .catch(error => reject(error)) + }) +} + +export const deleteSecurityFunction = (projectId, functionId) => { + return new Promise((resolve, reject) => { + client.securityFunctions.deleteFunctionConfig(projectId, functionId) + .then(({ queued }) => { + if (!queued) { + const newSecurityFunctions = getSecurityFunctions(store.getState()).filter(item => item.id !== functionId) + setSecurityFunctions(newSecurityFunctions) + } + resolve({ queued }) + }) + .catch(error => reject(error)) + }) +} + +export const getSecurityFunctions = (state) => get(state, "securityFunctions", []) +const setSecurityFunctions = (functions) => store.dispatch(set("securityFunctions", functions)) \ No newline at end of file diff --git a/src/pages/security-functions/SecurityFunctions.jsx b/src/pages/security-functions/SecurityFunctions.jsx new file mode 100644 index 00000000..60d6d1d3 --- /dev/null +++ b/src/pages/security-functions/SecurityFunctions.jsx @@ -0,0 +1,153 @@ +import { Button, Input, Popconfirm, Table, Tag, Empty } from "antd"; +import React, { useState, useEffect } from "react"; +import { useParams } from "react-router-dom"; +import Sidenav from "../../components/sidenav/Sidenav"; +import Topbar from "../../components/topbar/Topbar"; +import { projectModules, actionQueuedMessage } from "../../constants"; +import Highlighter from "react-highlight-words"; +import EmptySearchResults from "../../components/utils/empty-search-results/EmptySearchResults"; +import { decrementPendingRequests, incrementPendingRequests, notify } from "../../utils"; +import { deleteSecurityFunction, getSecurityFunctions, loadSecurityFunctions, saveSecurityFunction } from "../../operations/securityFunctions"; +import { useSelector } from "react-redux"; +import FunctionForm from "../../components/security-functions/function-form/FunctionForm"; + +const SecurityFunctions = () => { + // Router params + const { projectID } = useParams(); + + // Global state + const securityFunctions = useSelector(state => getSecurityFunctions(state)) + + // Component state + const [searchText, setSearchText] = useState('') + const [modalVisible, setModalVisible] = useState(false) + const [selectedFunctionConfig, setSelectedFunctionConfig] = useState() + + // Derived state + const filteredSecurityFunctions = securityFunctions.length !== 0 ? securityFunctions.filter(item => { + return item.id.toLowerCase().includes(searchText.toLowerCase()); + }) : [] + + useEffect(() => { + if (projectID) { + incrementPendingRequests() + loadSecurityFunctions(projectID) + .catch(error => notify("error", error.title, error.msg.length === 0 ? "Failed to get security functions" : error.msg)) + .finally(() => decrementPendingRequests()) + } + }, [projectID]) + + // Handlers + const handleViewClick = (name) => { + console.log(name) + } + + const handleEditClick = (config) => { + setSelectedFunctionConfig(config) + setModalVisible(true) + } + + const handleDelete = (functionId) => { + incrementPendingRequests() + deleteSecurityFunction(projectID, functionId) + .then(({ queued }) => notify("success", "Success", queued ? actionQueuedMessage : "Removed security function successfully")) + .catch(error => notify("error", error.title, error.msg.length === 0 ? "Failed to delete security function" : error.msg)) + .finally(() => decrementPendingRequests()) + } + + const handleSubmit = (values) => { + return new Promise((resolve, reject) => { + incrementPendingRequests() + saveSecurityFunction(projectID, values) + .then(({ queued }) => { + notify("success", "Success", queued ? actionQueuedMessage : `Added security function successfully`) + resolve() + }) + .catch(error => { + notify("error", error.title, error.msg.length === 0 ? "Failed to set security function" : error.msg) + reject() + }) + .finally(() => decrementPendingRequests()) + }) + } + + const tableColumns = [ + { + title: 'Name', + dataIndex: 'id', + key: 'id', + render: (value) => { + return + } + }, + { + title: "Variables", + dataIndex: 'variables', + key: "variables", + render: (value) => value.map(item => {item}) + }, + { + title: 'Actions', + key: 'actions', + className: 'column-actions', + render: (_, record) => ( + + handleViewClick(record.id)}>View + { + handleEditClick(record) + e.stopPropagation() + }}>Edit +
e.stopPropagation()}> + { + handleDelete(record.id) + }}> + Delete + +
+
+ ) + } + ] + + return ( + + + +
+
+
+

Global security function

+
+ setSearchText(e.target.value)} /> + +
+
+ { return { onClick: event => { handleViewClick(record.name) } } }} + locale={{ + emptyText: securityFunctions.length !== 0 ? + : + + }} + /> + + + {modalVisible && ( + setModalVisible(false)} + /> + )} + + ); +}; + +export default SecurityFunctions; diff --git a/src/routes/ProjectPages.jsx b/src/routes/ProjectPages.jsx index 762a99e7..6a89b0e6 100644 --- a/src/routes/ProjectPages.jsx +++ b/src/routes/ProjectPages.jsx @@ -59,6 +59,7 @@ import CacheOverview from '../pages/cache/overview/Overview'; import CacheIndex from '../pages/cache/Index'; import ConfigureRabbitMQ from "../pages/settings/add-ons/rabbit-mq/RabbitMQ"; import ConfigureRedis from "../pages/settings/add-ons/redis/ConfigureRedis"; +import SecurityFunctions from "../pages/security-functions/SecurityFunctions"; // import AddOns from "../pages/settings/add-ons/AddOns"; @@ -140,6 +141,7 @@ function ProjectPages() { + ) } diff --git a/src/services/securityFunctions.js b/src/services/securityFunctions.js new file mode 100644 index 00000000..488f04f6 --- /dev/null +++ b/src/services/securityFunctions.js @@ -0,0 +1,49 @@ +class SecurityFunctions { + constructor(client) { + this.client = client + } + + fetchSecurityFunctions(projectId) { + return new Promise((resolve, reject) => { + this.client.getJSON(`/v1/config/projects/${projectId}/security/function`) + .then(({ status, data }) => { + if (status < 200 || status >= 300) { + reject(data.error) + return + } + resolve(data.result ? data.result : []) + }) + .catch(ex => reject(ex.toString())) + }) + } + + setFunctionConfig(projectId, config) { + return new Promise((resolve, reject) => { + this.client.postJSON(`/v1/config/projects/${projectId}/security/function/${config.id}`, config) + .then(({ status, data }) => { + if (status < 200 || status >= 300) { + reject({title: data.error, msg: data.rawError}) + return + } + resolve({ queued: status === 202 }) + }) + .catch(ex => reject({title: "Failed to set security function", msg: ex.message})) + }) + } + + deleteFunctionConfig(projectId, functionId) { + return new Promise((resolve, reject) => { + this.client.delete(`/v1/config/projects/${projectId}/security/function/${functionId}`) + .then(({ status, data }) => { + if (status < 200 || status >= 300) { + reject({title: data.error, msg: data.rawError}) + return + } + resolve({ queued: status === 202 }) + }) + .catch(ex => reject({title: "Failed to delete security-function", msg: ex.message})) + }) + } +} + +export default SecurityFunctions; \ No newline at end of file diff --git a/src/services/service.js b/src/services/service.js index f624c3f3..bd8e8471 100644 --- a/src/services/service.js +++ b/src/services/service.js @@ -17,6 +17,7 @@ import Secrets from "./secrets" import Cluster from "./cluster"; import Integrations from "./integrations"; import Addons from './addons'; +import SecurityFunctions from "./securityFunctions"; const API = SpaceAPI.API const cond = SpaceAPI.cond @@ -41,6 +42,7 @@ class Service { this.cluster = new Cluster(this.client) this.integrations = new Integrations(this.client, this.spaceAPIClient) this.addons = new Addons(this.client) + this.securityFunctions = new SecurityFunctions(this.client) if (token) this.client.setToken(token); } From 04440f5aac0acab96bcefc4873d05a2069b73fa6 Mon Sep 17 00:00:00 2001 From: shubham4443 Date: Fri, 5 Feb 2021 18:25:19 +0530 Subject: [PATCH 2/5] finished wih security function integration in security builder --- .../configure-rule/ConfigureRule.jsx | 52 +++++++++++++++++-- .../graph-editor/GraphEditor.jsx | 40 +++++++++++++- src/constants.js | 7 ++- src/mirage/fixtures.js | 5 +- src/operations/securityFunctions.js | 41 +++++++++++++-- src/operations/securityRuleBuilder.js | 9 +++- .../security-functions/SecurityFunctions.jsx | 14 ++--- src/pages/security-rules/RulesEditor.jsx | 7 ++- src/utils.js | 3 ++ 9 files changed, 157 insertions(+), 21 deletions(-) diff --git a/src/components/security-rules/configure-rule/ConfigureRule.jsx b/src/components/security-rules/configure-rule/ConfigureRule.jsx index a2974b73..30e5e953 100644 --- a/src/components/security-rules/configure-rule/ConfigureRule.jsx +++ b/src/components/security-rules/configure-rule/ConfigureRule.jsx @@ -24,6 +24,7 @@ import { getCollectionSchema, getDbConfigs, getTrackedCollections } from '../../ import { securityRuleGroups } from '../../../constants'; import JSONCodeMirror from '../../json-code-mirror/JSONCodeMirror'; import AntCodeMirror from "../../ant-code-mirror/AntCodeMirror"; +import { getSecurityFunctions } from '../../../operations/securityFunctions'; const { Option } = Select; @@ -155,7 +156,12 @@ const isTypeOfFieldsString = (fields) => { return typeof fields === "string" } -const rules = ['allow', 'deny', 'authenticated', 'match', 'and', 'or', 'query', 'webhook', 'force', 'remove', 'encrypt', 'decrypt', 'hash']; +const getSecurityFunctionVariables = (securityFunctions, functionId) => { + const index = securityFunctions.findIndex(item => item.id === functionId) + return index === -1 ? [] : securityFunctions[index].variables +} + +const rules = ['allow', 'deny', 'authenticated', 'match', 'and', 'or', 'query', 'webhook', 'force', 'remove', 'encrypt', 'decrypt', 'hash', 'Function']; const ConfigureRule = (props) => { // form @@ -165,12 +171,13 @@ const ConfigureRule = (props) => { const [col, setCol] = useState(''); // Derived properties - const { rule, type, f1, f2, error, fields, field, value, url, store, outputFormat, claims, requestTemplate, db, cache } = props.selectedRule; + const { rule, type, f1, f2, error, fields, field, value, url, store, outputFormat, claims, requestTemplate, db, cache, securityFunctionName, fnBlockVariables } = props.selectedRule; const dbConfigs = useSelector(state => getDbConfigs(state)) const dbList = Object.keys(dbConfigs) const [selectedDb, setSelectedDb] = useState(db); const data = useSelector(state => getTrackedCollections(state, selectedDb)) const collectionSchemaString = useSelector(state => getCollectionSchema(state, props.ruleMetaData.group, props.ruleMetaData.id)) + const securityFunctions = useSelector(state => getSecurityFunctions(state)) // Handlers const handleSelectDatabase = (value) => setSelectedDb(value); @@ -215,6 +222,8 @@ const ConfigureRule = (props) => { delete values["applyTransformations"] break; + case "Function": + break; } delete values.errorMsg; @@ -277,6 +286,7 @@ const ConfigureRule = (props) => { autoCompleteOptions = { args: { data: true } } break; case securityRuleGroups.INGRESS_ROUTES: + case securityRuleGroups.SECURITY_FUNCTIONS: const query = { path: true, pathArray: true, @@ -312,7 +322,9 @@ const ConfigureRule = (props) => { error, cacheResponse: cache ? true : false, cacheTTL: cache && cache.ttl !== undefined && cache.ttl !== null ? cache.ttl : undefined, - cacheInstantInvalidate: cache && cache.instantInvalidate !== undefined && cache.instantInvalidate !== null ? cache.instantInvalidate : undefined + cacheInstantInvalidate: cache && cache.instantInvalidate !== undefined && cache.instantInvalidate !== null ? cache.instantInvalidate : undefined, + securityFunctionName, + fnBlockVariables } if (formInitialValues.type === "object") { @@ -685,6 +697,40 @@ const ConfigureRule = (props) => { + form.getFieldValue('rule') === 'Function'} + > + + + + + + {() => ( + + {form.getFieldValue("securityFunctionName") && ( + <> + + {getSecurityFunctionVariables(securityFunctions, form.getFieldValue("securityFunctionName")).map(item => ( + + + + + + + + + + + ))} + + )} + + )} + + diff --git a/src/components/security-rules/graph-editor/GraphEditor.jsx b/src/components/security-rules/graph-editor/GraphEditor.jsx index f0bc2e0d..6c2adeaa 100644 --- a/src/components/security-rules/graph-editor/GraphEditor.jsx +++ b/src/components/security-rules/graph-editor/GraphEditor.jsx @@ -4,8 +4,12 @@ import dotProp from 'dot-prop-immutable'; import KeyboardEventHandler from 'react-keyboard-event-handler'; import ConfigureRule from '../configure-rule/ConfigureRule'; import SecurityRulesGraph from "../security-rules-graph/SecurityRulesGraph"; -import { saveObjectToLocalStorage, getObjectFromLocalStorage } from "../../../utils"; +import { saveObjectToLocalStorage, getObjectFromLocalStorage, incrementPendingRequests, notify, decrementPendingRequests } from "../../../utils"; import generateGraph from "./generateGraph"; +import { securityRuleGroups, actionQueuedMessage } from "../../../constants"; +import FunctionForm from "../../security-functions/function-form/FunctionForm"; +import { useSelector } from "react-redux"; +import { getSecurityFunction, saveSecurityFunction } from "../../../operations/securityFunctions"; const LOCAL_STORAGE_RULE_KEY = "securityRuleBuilder:copiedRule" @@ -19,13 +23,17 @@ const getDoubleClickedRuleObject = (rule, ruleKey) => { return dotProp.get(rule, getStrippedKey(ruleKey), {}) } -function GraphEditor({ rule, setRule, ruleName, ruleMetaData, isCachingEnabled }) { +function GraphEditor({ rule, setRule, ruleName, ruleMetaData, isCachingEnabled, projectId }) { const { ruleType } = ruleMetaData + // Global state + const securityFunctionConfig = useSelector(state => getSecurityFunction(state, ruleName)) + // Component state const [selectedNodeId, setselectedNodeId] = useState(); const [doubleClickedNodeId, setDoubleClickedNodeId] = useState(""); const [network, setNetwork] = useState(); + const [securityFunctionModal, setSecurityFunctionModal] = useState(false); useEffect(() => { function handleResize() { @@ -69,6 +77,10 @@ function GraphEditor({ rule, setRule, ruleName, ruleMetaData, isCachingEnabled } }, doubleClick: function (event) { const nodeId = event.nodes[0]; + if (ruleType === securityRuleGroups.SECURITY_FUNCTIONS) { + setSecurityFunctionModal(true); + return; + } if (nodeId && nodeId.startsWith("root")) { message.error("Operation not allowed. Only rule blocks (blue ones) can be double clicked to configure them") return @@ -185,6 +197,23 @@ function GraphEditor({ rule, setRule, ruleName, ruleMetaData, isCachingEnabled } setRule(dotProp.set(rule, getStrippedKey(doubleClickedNodeId), values)); }; + // On security function submit + const handleSecurityFunctionSubmit = (values) => { + return new Promise((resolve, reject) => { + incrementPendingRequests() + saveSecurityFunction(projectId, values) + .then(({ queued }) => { + notify("success", "Success", queued ? actionQueuedMessage : `Modified security function successfully`) + resolve() + }) + .catch(error => { + notify("error", error.title, error.msg.length === 0 ? "Failed to set security function" : error.msg) + reject() + }) + .finally(() => decrementPendingRequests()) + }) + } + const menu = ( shortcutsHandler(key)}> Copy @@ -243,6 +272,13 @@ function GraphEditor({ rule, setRule, ruleName, ruleMetaData, isCachingEnabled } isCachingEnabled={isCachingEnabled} /> )} + {securityFunctionModal && ( + setSecurityFunctionModal(false)} + /> + )} ) } diff --git a/src/constants.js b/src/constants.js index 13767428..5d969b05 100644 --- a/src/constants.js +++ b/src/constants.js @@ -162,7 +162,8 @@ export const securityRuleGroups = { EVENTING: "eventing", EVENTING_FILTERS: "eventing-filters", REMOTE_SERVICES: "remote-services", - INGRESS_ROUTES: "ingress-routes" + INGRESS_ROUTES: "ingress-routes", + SECURITY_FUNCTIONS: "security-functions" } export const defaultDBRules = { @@ -212,6 +213,10 @@ export const defaultPreparedQueryRule = { rule: "allow" } +export const defaultSecurityFunctionRule = { + rule: "allow" +} + export const deploymentStatuses = { PENDING: "PENDING", SUCCEEDED: "SUCCEEDED", diff --git a/src/mirage/fixtures.js b/src/mirage/fixtures.js index c401e571..067377d4 100644 --- a/src/mirage/fixtures.js +++ b/src/mirage/fixtures.js @@ -968,7 +968,10 @@ export const addonsConfig = { export const securityFunctions = [ { id: "Function 1", - variables: ["variable1", "variable2"] + variables: ["variable1", "variable2"], + rule: { + rule: "allow" + } }, { id: "Function 2", diff --git a/src/operations/securityFunctions.js b/src/operations/securityFunctions.js index feef5538..b1698b57 100644 --- a/src/operations/securityFunctions.js +++ b/src/operations/securityFunctions.js @@ -1,6 +1,7 @@ import { set, get } from "automate-redux"; import client from "../client"; import store from "../store"; +import { upsertArray } from "../utils"; export const loadSecurityFunctions = (projectId) => { return new Promise((resolve, reject) => { @@ -13,12 +14,14 @@ export const loadSecurityFunctions = (projectId) => { }) } -export const saveSecurityFunction = (projectId, config) => { +export const saveSecurityFunction = (projectId, newConfig) => { return new Promise((resolve, reject) => { - client.securityFunctions.setFunctionConfig(projectId, config) + client.securityFunctions.setFunctionConfig(projectId, newConfig) .then(({ queued }) => { if (!queued) { - const newSecurityFunctions = getSecurityFunctions(store.getState()).concat(config) + const securityFunctions = getSecurityFunctions(store.getState()) + const oldConfig = getSecurityFunction(store.getState(), newConfig.id); + const newSecurityFunctions = upsertArray(securityFunctions, obj => obj.id === newConfig.id, () => ({...oldConfig, ...newConfig})) setSecurityFunctions(newSecurityFunctions) } resolve({ queued }) @@ -27,6 +30,21 @@ export const saveSecurityFunction = (projectId, config) => { }) } +export const saveSecurityFunctionRule = (projectId, functionId, rule) => { + return new Promise((resolve, reject) => { + const oldConfig = getSecurityFunction(store.getState(), functionId) + const newConfig = Object.assign({}, oldConfig, {}, { rule }) + client.securityFunctions.setFunctionConfig(projectId, newConfig) + .then(({ queued }) => { + if (!queued) { + setSecurityFunctionRule(functionId, rule) + } + resolve({ queued }) + }) + .catch(error => reject(error)) + }) +} + export const deleteSecurityFunction = (projectId, functionId) => { return new Promise((resolve, reject) => { client.securityFunctions.deleteFunctionConfig(projectId, functionId) @@ -41,5 +59,20 @@ export const deleteSecurityFunction = (projectId, functionId) => { }) } -export const getSecurityFunctions = (state) => get(state, "securityFunctions", []) +export const getSecurityFunctions = (state) => get(state, "securityFunctions", []) +export const getSecurityFunctionRule = (state, id) => { + const securityFunctions = getSecurityFunctions(state) + const index = securityFunctions.findIndex(obj => obj.id === id) + return get(securityFunctions[index], "rule", {}) +} +export const getSecurityFunction = (state, functionId) => { + const securityFunctions = getSecurityFunctions(state) + const index = securityFunctions.findIndex(obj => obj.id === functionId) + return (index === -1) ? {} : securityFunctions[index] +} +export const setSecurityFunctionRule = (functionId, rule) => { + const securityFunctions = getSecurityFunctions(store.getState()) + const newSecurityFunctions = securityFunctions.map(obj => obj.id === functionId ? Object.assign({}, obj, { rule }) : obj) + setSecurityFunctions(newSecurityFunctions) +} const setSecurityFunctions = (functions) => store.dispatch(set("securityFunctions", functions)) \ No newline at end of file diff --git a/src/operations/securityRuleBuilder.js b/src/operations/securityRuleBuilder.js index a5c0e534..01284bc0 100644 --- a/src/operations/securityRuleBuilder.js +++ b/src/operations/securityRuleBuilder.js @@ -5,9 +5,10 @@ import { loadFileStoreRules, getFileStoreSecurityRule, saveFileStoreSecurityRule import { loadRemoteServices, getRemoteEndpointSecurityRule, saveRemoteServiceEndpointRule } from "./remoteServices" import { loadIngressRoutes, getIngressRouteSecurityRule, getIngressRouteURL, saveIngressRouteRule } from "./ingressRoutes" import { loadCacheConfig } from "./cache" +import { getSecurityFunctionRule, loadSecurityFunctions, saveSecurityFunctionRule } from "./securityFunctions" export const loadSecurityRules = (projectId, ruleType, ruleId) => { - const promises = [loadDbConfig(projectId), loadDbSchemas(projectId), loadCacheConfig()] + const promises = [loadDbConfig(projectId), loadDbSchemas(projectId), loadCacheConfig(), loadSecurityFunctions(projectId)] switch (ruleType) { case securityRuleGroups.DB_COLLECTIONS: @@ -54,6 +55,9 @@ export const getSecurityRuleInfo = (state, ruleType, id, group) => { const ingressSecurityRule = getIngressRouteSecurityRule(state, id) const url = getIngressRouteURL(state, id) return { rule: ingressSecurityRule, name: url } + case securityRuleGroups.SECURITY_FUNCTIONS: + const securityFunctionRule = getSecurityFunctionRule(state, id) + return { rule: securityFunctionRule, name: id } default: return { rule: {}, name: "" } } @@ -84,6 +88,9 @@ export const saveSecurityRule = (projectId, ruleType, id, group, rule) => { case securityRuleGroups.INGRESS_ROUTES: req = saveIngressRouteRule(projectId, id, rule) break + case securityRuleGroups.SECURITY_FUNCTIONS: + req = saveSecurityFunctionRule(projectId, id, rule) + break; } req diff --git a/src/pages/security-functions/SecurityFunctions.jsx b/src/pages/security-functions/SecurityFunctions.jsx index 60d6d1d3..952a9d94 100644 --- a/src/pages/security-functions/SecurityFunctions.jsx +++ b/src/pages/security-functions/SecurityFunctions.jsx @@ -3,10 +3,10 @@ import React, { useState, useEffect } from "react"; import { useParams } from "react-router-dom"; import Sidenav from "../../components/sidenav/Sidenav"; import Topbar from "../../components/topbar/Topbar"; -import { projectModules, actionQueuedMessage } from "../../constants"; +import { projectModules, actionQueuedMessage, securityRuleGroups } from "../../constants"; import Highlighter from "react-highlight-words"; import EmptySearchResults from "../../components/utils/empty-search-results/EmptySearchResults"; -import { decrementPendingRequests, incrementPendingRequests, notify } from "../../utils"; +import { decrementPendingRequests, incrementPendingRequests, notify, openSecurityRulesPage } from "../../utils"; import { deleteSecurityFunction, getSecurityFunctions, loadSecurityFunctions, saveSecurityFunction } from "../../operations/securityFunctions"; import { useSelector } from "react-redux"; import FunctionForm from "../../components/security-functions/function-form/FunctionForm"; @@ -38,8 +38,8 @@ const SecurityFunctions = () => { }, [projectID]) // Handlers - const handleViewClick = (name) => { - console.log(name) + const handleViewClick = (id) => { + openSecurityRulesPage(projectID, securityRuleGroups.SECURITY_FUNCTIONS, id) } const handleEditClick = (config) => { @@ -60,7 +60,7 @@ const SecurityFunctions = () => { incrementPendingRequests() saveSecurityFunction(projectID, values) .then(({ queued }) => { - notify("success", "Success", queued ? actionQueuedMessage : `Added security function successfully`) + notify("success", "Success", queued ? actionQueuedMessage : `Saved security function successfully`) resolve() }) .catch(error => { @@ -130,7 +130,7 @@ const SecurityFunctions = () => {
{ return { onClick: event => { handleViewClick(record.name) } } }} + onRow={(record) => { return { onClick: event => { handleViewClick(record.id) } } }} locale={{ emptyText: securityFunctions.length !== 0 ? : @@ -143,7 +143,7 @@ const SecurityFunctions = () => { setModalVisible(false)} + handleCancel={() => {setModalVisible(false);setSelectedFunctionConfig()}} /> )} diff --git a/src/pages/security-rules/RulesEditor.jsx b/src/pages/security-rules/RulesEditor.jsx index bb28a4e0..b01fc92f 100644 --- a/src/pages/security-rules/RulesEditor.jsx +++ b/src/pages/security-rules/RulesEditor.jsx @@ -16,7 +16,7 @@ import GraphEditor from "../../components/security-rules/graph-editor/GraphEdito import JSONEditor from "../../components/security-rules/json-editor/JSONEditor"; import useDeepCompareEffect from 'use-deep-compare-effect'; import { getSecurityRuleInfo, loadSecurityRules, saveSecurityRule } from '../../operations/securityRuleBuilder'; -import { securityRuleGroups, defaultDBRules, defaultPreparedQueryRule, defaultEventRule, defaultFileRule, defaultEndpointRule, defaultIngressRoutingRule, actionQueuedMessage, defaultEventFilterRule } from '../../constants'; +import { securityRuleGroups, defaultDBRules, defaultPreparedQueryRule, defaultEventRule, defaultFileRule, defaultEndpointRule, defaultIngressRoutingRule, actionQueuedMessage, defaultEventFilterRule, defaultSecurityFunctionRule } from '../../constants'; import { getCacheConfig } from '../../operations/cache'; const RulesEditor = () => { @@ -94,6 +94,9 @@ const RulesEditor = () => { case securityRuleGroups.INGRESS_ROUTES: setRule(defaultIngressRoutingRule) break; + case securityRuleGroups.SECURITY_FUNCTIONS: + setRule(defaultSecurityFunctionRule) + break; } } @@ -175,7 +178,7 @@ const RulesEditor = () => {
- +
diff --git a/src/utils.js b/src/utils.js index a59514bf..d2e9561c 100644 --- a/src/utils.js +++ b/src/utils.js @@ -18,6 +18,7 @@ import { setEventingSecurityRule } from './operations/eventing' import { setFileStoreSecurityRule } from './operations/fileStore' import { loadClusterEnv, refreshClusterTokenIfPresent, loadPermissions, isLoggedIn, getPermisions, getLoginURL } from './operations/cluster' import { useSelector } from 'react-redux' +import { setSecurityFunctionRule } from './operations/securityFunctions' const mysqlSvg = require(`./assets/mysqlSmall.svg`) const postgresSvg = require(`./assets/postgresSmall.svg`) @@ -383,6 +384,8 @@ const registerSecurityRulesBroadCastListener = () => { case securityRuleGroups.INGRESS_ROUTES: setIngressRouteRule(id, rule) break + case securityRuleGroups.SECURITY_FUNCTIONS: + setSecurityFunctionRule(id, rule) } } notify("success", "Success", queued ? actionQueuedMessage : "Saved security rules successfully") From 4dac2b29af1361058c035a73379a0db8dd1d9472 Mon Sep 17 00:00:00 2001 From: shubham4443 Date: Sat, 6 Feb 2021 17:36:43 +0530 Subject: [PATCH 3/5] fixed issues in security function --- .../security-functions/function-form/FunctionForm.jsx | 11 +++++------ .../security-rules/configure-rule/ConfigureRule.jsx | 6 +++--- .../security-rules/graph-editor/GraphEditor.jsx | 2 +- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/components/security-functions/function-form/FunctionForm.jsx b/src/components/security-functions/function-form/FunctionForm.jsx index 223529a0..d4777bf5 100644 --- a/src/components/security-functions/function-form/FunctionForm.jsx +++ b/src/components/security-functions/function-form/FunctionForm.jsx @@ -8,6 +8,7 @@ const FunctionForm = ({ handleSubmit, handleCancel, initialValues }) => { const onSubmit = e => { form.validateFields().then(values => { + values.variables = values.variables ? values.variables : [] handleSubmit(values).then(() => { handleCancel(); form.resetFields(); @@ -15,8 +16,6 @@ const FunctionForm = ({ handleSubmit, handleCancel, initialValues }) => { }); } - const formInitialValues = initialValues ? initialValues : { variables: [""] } - return ( { onCancel={handleCancel} onOk={onSubmit} > -
+ @@ -46,12 +45,12 @@ const FunctionForm = ({ handleSubmit, handleCancel, initialValues }) => { - {fields.length > 1 &&
+ remove(field.name)} /> - } + )))} - + )} diff --git a/src/components/security-rules/configure-rule/ConfigureRule.jsx b/src/components/security-rules/configure-rule/ConfigureRule.jsx index 30e5e953..1dc61319 100644 --- a/src/components/security-rules/configure-rule/ConfigureRule.jsx +++ b/src/components/security-rules/configure-rule/ConfigureRule.jsx @@ -161,7 +161,7 @@ const getSecurityFunctionVariables = (securityFunctions, functionId) => { return index === -1 ? [] : securityFunctions[index].variables } -const rules = ['allow', 'deny', 'authenticated', 'match', 'and', 'or', 'query', 'webhook', 'force', 'remove', 'encrypt', 'decrypt', 'hash', 'Function']; +const rules = ['allow', 'deny', 'authenticated', 'match', 'and', 'or', 'query', 'webhook', 'force', 'remove', 'encrypt', 'decrypt', 'hash', 'function']; const ConfigureRule = (props) => { // form @@ -222,7 +222,7 @@ const ConfigureRule = (props) => { delete values["applyTransformations"] break; - case "Function": + case "function": break; } @@ -699,7 +699,7 @@ const ConfigureRule = (props) => { form.getFieldValue('rule') === 'Function'} + condition={() => form.getFieldValue('rule') === 'function'} > diff --git a/src/components/security-rules/graph-editor/GraphEditor.jsx b/src/components/security-rules/graph-editor/GraphEditor.jsx index 6c2adeaa..2e24c428 100644 --- a/src/components/security-rules/graph-editor/GraphEditor.jsx +++ b/src/components/security-rules/graph-editor/GraphEditor.jsx @@ -77,7 +77,7 @@ function GraphEditor({ rule, setRule, ruleName, ruleMetaData, isCachingEnabled, }, doubleClick: function (event) { const nodeId = event.nodes[0]; - if (ruleType === securityRuleGroups.SECURITY_FUNCTIONS) { + if (nodeId && nodeId.startsWith("root") && ruleType === securityRuleGroups.SECURITY_FUNCTIONS) { setSecurityFunctionModal(true); return; } From 14d45866994b205a384947d3417e27316012f4f7 Mon Sep 17 00:00:00 2001 From: shubham4443 Date: Sat, 6 Feb 2021 20:44:27 +0530 Subject: [PATCH 4/5] added support for variable --- .../security-rules/configure-rule/ConfigureRule.jsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/security-rules/configure-rule/ConfigureRule.jsx b/src/components/security-rules/configure-rule/ConfigureRule.jsx index 1dc61319..ad2b0142 100644 --- a/src/components/security-rules/configure-rule/ConfigureRule.jsx +++ b/src/components/security-rules/configure-rule/ConfigureRule.jsx @@ -719,9 +719,9 @@ const ConfigureRule = (props) => { - - - + + + ))} From 1b14e50ed619e7582665581f0c852ae256069299 Mon Sep 17 00:00:00 2001 From: shubham4443 Date: Fri, 26 Feb 2021 19:26:01 +0530 Subject: [PATCH 5/5] changed icon and displayed function name on the rule block --- src/components/security-rules/graph-editor/generateGraph.js | 6 ++++++ src/components/sidenav/Sidenav.jsx | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/components/security-rules/graph-editor/generateGraph.js b/src/components/security-rules/graph-editor/generateGraph.js index a48772a9..047c9a96 100644 --- a/src/components/security-rules/graph-editor/generateGraph.js +++ b/src/components/security-rules/graph-editor/generateGraph.js @@ -8,6 +8,7 @@ const mergeGraph = (graph1, graph2) => { } const convertRuleToGraph = (rule, id, parentId) => { + console.log("RULEE", rule) let graph = { nodes: [], edges: [] } const isRootBlock = !parentId.includes(".") @@ -19,6 +20,11 @@ const convertRuleToGraph = (rule, id, parentId) => { } return graph } + if (rule.rule === "function") { + graph.nodes.push({ id: id, label: rule.securityFunctionName, group: "rule" }) + graph.edges.push({ from: parentId, to: id }) + return graph + } graph.nodes.push({ id: id, label: rule.rule, group: "rule" }) graph.edges.push({ from: parentId, to: id }) diff --git a/src/components/sidenav/Sidenav.jsx b/src/components/sidenav/Sidenav.jsx index ffca7760..c883b319 100644 --- a/src/components/sidenav/Sidenav.jsx +++ b/src/components/sidenav/Sidenav.jsx @@ -106,7 +106,7 @@ const Sidenav = (props) => { - +