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 = ( shortcutsHandler(key)}> Copy @@ -243,6 +272,13 @@ function GraphEditor({ rule, setRule, ruleName, ruleMetaData, isCachingEnabled } isCachingEnabled={isCachingEnabled} /> )} + {securityFunctionModal && ( + setSecurityFunctionModal(false)} + /> + )} ) } 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 4672ede5..c883b319 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..5d969b05 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 = { @@ -161,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 = { @@ -211,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 b1c09dc5..067377d4 100644 --- a/src/mirage/fixtures.js +++ b/src/mirage/fixtures.js @@ -964,3 +964,17 @@ export const addonsConfig = { } } } + +export const securityFunctions = [ + { + id: "Function 1", + variables: ["variable1", "variable2"], + rule: { + rule: "allow" + } + }, + { + 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..b1698b57 --- /dev/null +++ b/src/operations/securityFunctions.js @@ -0,0 +1,78 @@ +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) => { + client.securityFunctions.fetchSecurityFunctions(projectId) + .then(functions => { + setSecurityFunctions(functions) + resolve() + }) + .catch(ex => reject(ex)) + }) +} + +export const saveSecurityFunction = (projectId, newConfig) => { + return new Promise((resolve, reject) => { + client.securityFunctions.setFunctionConfig(projectId, newConfig) + .then(({ queued }) => { + if (!queued) { + 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 }) + }) + .catch(error => reject(error)) + }) +} + +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) + .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", []) +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 new file mode 100644 index 00000000..952a9d94 --- /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, securityRuleGroups } from "../../constants"; +import Highlighter from "react-highlight-words"; +import EmptySearchResults from "../../components/utils/empty-search-results/EmptySearchResults"; +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"; + +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 = (id) => { + openSecurityRulesPage(projectID, securityRuleGroups.SECURITY_FUNCTIONS, id) + } + + 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 : `Saved 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.id) } } }} + locale={{ + emptyText: securityFunctions.length !== 0 ? + : + + }} + /> + + + {modalVisible && ( + {setModalVisible(false);setSelectedFunctionConfig()}} + /> + )} + + ); +}; + +export default SecurityFunctions; 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/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); } 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")