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..d4777bf5
--- /dev/null
+++ b/src/components/security-functions/function-form/FunctionForm.jsx
@@ -0,0 +1,63 @@
+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 => {
+ values.variables = values.variables ? values.variables : []
+ handleSubmit(values).then(() => {
+ handleCancel();
+ form.resetFields();
+ })
+ });
+ }
+
+ return (
+
+
+
+
+
+
+ {(fields, { add, remove }) => (
+
+ {fields.map((field => (
+
+
+
+
+
+
+
+ remove(field.name)} />
+
+
+ )))}
+
+
+ )}
+
+
+
+ );
+}
+
+export default FunctionForm;
+
diff --git a/src/components/security-rules/configure-rule/ConfigureRule.jsx b/src/components/security-rules/configure-rule/ConfigureRule.jsx
index a2974b73..ad2b0142 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..2e24c428 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 (nodeId && nodeId.startsWith("root") && 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 = (