diff --git a/.env b/.env
index ac324e6a..ff6b1b54 100644
--- a/.env
+++ b/.env
@@ -2,6 +2,7 @@
ORG_ID=Shuffle
ENVIRONMENT_NAME=Shuffle
+
# Remote github config for first load
SHUFFLE_DOWNLOAD_WORKFLOW_LOCATION=
SHUFFLE_DOWNLOAD_WORKFLOW_USERNAME=
@@ -60,10 +61,10 @@ SHUFFLE_ELASTIC=true
# DATABASE CONFIGURATIONS
DATASTORE_EMULATOR_HOST=shuffle-database:8000
-#SHUFFLE_OPENSEARCH_URL=https://shuffle-opensearch:9200
-SHUFFLE_OPENSEARCH_URL=http://shuffle-opensearch:9200
-SHUFFLE_OPENSEARCH_USERNAME=
-SHUFFLE_OPENSEARCH_PASSWORD=
+#SHUFFLE_OPENSEARCH_URL=http://shuffle-opensearch:9200
+SHUFFLE_OPENSEARCH_URL=https://shuffle-opensearch:9200
+SHUFFLE_OPENSEARCH_USERNAME=admin
+SHUFFLE_OPENSEARCH_PASSWORD=admin
SHUFFLE_OPENSEARCH_CERTIFICATE_FILE=
SHUFFLE_OPENSEARCH_APIKEY=
SHUFFLE_OPENSEARCH_CLOUDID=
diff --git a/.github/install-guide.md b/.github/install-guide.md
index 0864a172..360bc6a8 100644
--- a/.github/install-guide.md
+++ b/.github/install-guide.md
@@ -10,18 +10,19 @@ The Docker setup is done with docker-compose
1. Make sure you have [Docker](https://docs.docker.com/get-docker/) and [docker-compose](https://docs.docker.com/compose/install/) installed.
2. Download Shuffle
-```
+```bash
git clone https://github.com/frikky/Shuffle
cd Shuffle
```
3. Fix prerequisites for the Opensearch database (Elasticsearch):
-```
-sudo chown -R 1000:1000 shuffle-database # Required for Opensearch
+```bash
+mkdir shuffle-database
+sudo chown -R 1000:1000 shuffle-database
```
4. Run docker-compose.
-```
+```bash
docker-compose up -d
```
@@ -38,20 +39,20 @@ This step is for setting up with Docker on windows from scratch.
4. Open the .env file and change the line with "OUTER_HOSTNAME" to contain your IP:
-```
+```bash
OUTER_HOSTNAME=YOUR.IP.HERE
```
6. Run docker-compose
-```
-docker compose up -d
+```bash
+docker-compose up -d
```
### Configurations (proxies, default users etc.)
https://shuffler.io/docs/configuration
### After installation
-1. After installation, go to http://localhost:3001/adminsetup (or your servername - https is on port 3443)
+1. After installation, go to http://localhost:3001 (or your servername - https is on port 3443)
2. Now set up your admin account (username & password). Shuffle doesn't have a default username and password.
3. Sign in with the same Username & Password! Go to /apps and see if you have any apps yet. If not - you may need to [configure proxies](https://shuffler.io/docs/configuration#production_readiness)
4. Check out https://shuffler.io/docs/configuration as it has a lot of useful information to get started
diff --git a/README.md b/README.md
index 01fd94b1..d4a13732 100644
--- a/README.md
+++ b/README.md
@@ -6,7 +6,7 @@ Shuffle Automation
- is an automation platform for and by the community, focusing on accessibility for anyone to automate. Security operations is complex, but it doesn't have to be.
+[Shuffle](https://shuffler.io) is an automation platform for and by the community, focusing on accessibility for anyone to automate. Security operations is complex, but it doesn't have to be.
[_Key Features_](https://shuffler.io/docs/features) —
[_Community & Support_](https://discord.gg/B2CBzUm) —
diff --git a/backend/app_sdk/app_base.py b/backend/app_sdk/app_base.py
index f6a5a83e..75cceffb 100644
--- a/backend/app_sdk/app_base.py
+++ b/backend/app_sdk/app_base.py
@@ -3,6 +3,7 @@ import copy
import sys
import re
import time
+import base64
import json
import liquid
import logging
@@ -14,17 +15,122 @@ import requests
import http.client
import urllib.parse
import jinja2
+from io import StringIO as StringBuffer
from io import BytesIO
-from liquid import Liquid
+from liquid import Liquid, defaults
runtime = os.getenv("SHUFFLE_SWARM_CONFIG", "")
+###
+###
+###
+#### Filters for liquidpy
+###
+###
+###
+
+defaults.MODE = 'wild'
+defaults.FROM_FILE = False
+from liquid.filters.manager import FilterManager
+from liquid.filters.standard import standard_filter_manager
+
+shuffle_filters = FilterManager()
+for key, value in standard_filter_manager.filters.items():
+ shuffle_filters.filters[key] = value
+
+#@shuffle_filters.register
+#def plus(a, b):
+# try:
+# a = int(a)
+# except:
+# a = 0
+#
+# try:
+# b = int(b)
+# except:
+# b = 0
+#
+# return standard_filter_manager.filters["plus"](a, b)
+#
+#@shuffle_filters.register
+#def minus(a, b):
+# a = int(a)
+# b = int(b)
+# return standard_filter_manager.filters["minus"](a, b)
+#
+#@shuffle_filters.register
+#def multiply(a, b):
+# a = int(a)
+# b = int(b)
+# return standard_filter_manager.filters["multiply"](a, b)
+#
+#@shuffle_filters.register
+#def divide(a, b):
+# a = int(a)
+# b = int(b)
+# return standard_filter_manager.filters["divide"](a, b)
+
+@shuffle_filters.register
+def md5(a):
+ a = str(a)
+ return hashlib.md5(a.encode('utf-8')).hexdigest()
+
+@shuffle_filters.register
+def sha256(a):
+ a = str(a)
+ return hashlib.sha256(str(a).encode("utf-8")).hexdigest()
+
+@shuffle_filters.register
+def md5_base64(a):
+ a = str(a)
+ foundhash = hashlib.md5(a.encode('utf-8')).hexdigest()
+ return base64.b64encode(foundhash.encode('utf-8'))
+
+@shuffle_filters.register
+def base64_encode(a):
+ a = str(a)
+ try:
+ return base64.b64encode(a.encode('utf-8')).decode()
+ except:
+ return base64.b64encode(a).decode()
+
+@shuffle_filters.register
+def base64_decode(a):
+ a = str(a)
+ try:
+ return base64.b64decode(a).decode()
+ except:
+ return base64.b64decode(a)
+
+#print(standard_filter_manager.filters)
+#print(shuffle_filters.filters)
+#print(Liquid("{{ '10' | plus: 1}}", filters=shuffle_filters.filters).render())
+#print(Liquid("{{ '10' | minus: 1}}", filters=shuffle_filters.filters).render())
+#print(Liquid("{{ asd | size }}", filters=shuffle_filters.filters).render())
+#print(Liquid("{{ 'asd' | md5 }}", filters=shuffle_filters.filters).render())
+#print(Liquid("{{ 'asd' | sha256 }}", filters=shuffle_filters.filters).render())
+#print(Liquid("{{ 'asd' | md5_base64 | base64_decode }}", filters=shuffle_filters.filters).render())
+
+###
+###
+###
+###
+###
+###
+###
+
class AppBase:
__version__ = None
app_name = None
def __init__(self, redis=None, logger=None, console_logger=None):#, docker_client=None):
self.logger = logger if logger is not None else logging.getLogger("AppBaseLogger")
+ self.log_capture_string = StringBuffer()
+ ch = logging.StreamHandler(self.log_capture_string)
+ formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
+ ch.setFormatter(formatter)
+ logger.addHandler(ch)
+
self.redis=redis
self.console_logger = logger if logger is not None else logging.getLogger("AppBaseLogger")
@@ -139,15 +245,15 @@ class AppBase:
try:
new_input = input_data.split()
except Exception as e:
- self.logger.info(f"[ERROR] Failed to run magic parser (1): {e}")
+ self.logger.info(f"[ERROR] Failed to run magic parser during split (1): {e}")
return input_data
# Won't ever touch this one?
- if isinstance(input_data, list) or isinstance(input_data, object):
+ if isinstance(new_input, list) or isinstance(new_input, object):
try:
return json.dumps(new_input)
except Exception as e:
- self.logger.info(f"[ERROR] Failed to run magic parser: {e}")
+ self.logger.info(f"[ERROR] Failed to run magic parser (3): {e}")
return new_input
@@ -165,12 +271,13 @@ class AppBase:
else:
self.logger.warning(f"[ERROR] Magic output not defined.")
except Exception as e:
- self.logger.warning(f"[ERROR] Failed to run magic autoparser: {e}")
+ self.logger.warning(f"[ERROR] Failed to run magic autoparser (send result): {e}")
pass
# Try it with some magic
self.logger.info(f"""[DEBUG] Inside Send result with status {action_result["status"]}""")
+ #if isinstance(action_result,
# FIXME: Add cleanup of parameters to not send to frontend here
params = {}
@@ -188,11 +295,64 @@ class AppBase:
self.logger.info(f"[DEBUG] Before last stream result")
url = "%s%s" % (self.base_url, stream_path)
self.logger.info("[INFO] URL FOR RESULT (URL): %s" % url)
+
try:
- ret = requests.post(url, headers=headers, json=action_result)
- #self.logger.info(f"[DEBUG] Result: {ret.status_code}")
- #if ret.status_code != 200:
- # self.logger.info(f"[DEBUG] Shuffle Response: {ret.text}")
+ log_contents = self.log_capture_string.getvalue()
+ #print("RESULTS: %s" % log_contents)
+ self.logger.info("[WARNING] Got logs of length {len(log_contents)}")
+ if len(action_result["action"]["parameters"]) == 0:
+ action_result["action"]["parameters"] = []
+
+ param_found = False
+ for param in action_result["action"]["parameters"]:
+ if param["name"] == "shuffle_action_logs":
+ param_found = True
+ break
+
+ if not param_found:
+ action_result["action"]["parameters"].append({
+ "name": "shuffle_action_logs",
+ "value": log_contents,
+ })
+
+ except Exception as e:
+ print(f"Failed adding parameter: {e}")
+
+ # FIXME: Adding retries here.
+ try:
+ finished = False
+ for i in range (0, 5):
+ try:
+ ret = requests.post(url, headers=headers, json=action_result, timeout=10)
+
+ self.logger.info(f"[DEBUG] Result: {ret.status_code} (break on 200)")
+ if ret.status_code == 200 or ret.status_code == 201:
+ finished = True
+ break
+ else:
+ self.logger.info(f"[DEBUG] RESP: {ret.text}")
+
+ except (requests.exceptions.RequestException, TimeoutError) as e:
+ time.sleep(5)
+ continue
+ except requests.exceptions.ConnectionError as e:
+ time.sleep(5)
+ continue
+ except http.client.RemoteDisconnected as e:
+ time.sleep(5)
+ continue
+ except urllib3.exceptions.ProtocolError as e:
+ time.sleep(5)
+ continue
+
+ time.sleep(5)
+
+ if not finished:
+ # Not sure why this would work tho :)
+ action_result["status"] = "FAILURE"
+ action_result["result"] = f"POST error: {e}"
+ self.logger.info(f"[DEBUG] Before typeerror stream result: {e}")
+ ret = requests.post("%s%s" % (self.base_url, stream_path), headers=headers, json=action_result)
self.logger.info(f"""[DEBUG] Successful request result request: Status= {ret.status_code} & Response= {ret.text}. Action status: {action_result["status"]}""")
except requests.exceptions.ConnectionError as e:
@@ -821,7 +981,7 @@ class AppBase:
returns = []
for item in value:
self.logger.info("VALUE: %s" % item)
- if len(item) != 36:
+ if len(item) != 36 and not item.startswith("file_"):
self.logger.info("Bad length for file value %s" % item)
continue
#return {
@@ -924,6 +1084,7 @@ class AppBase:
#return value.json()
return {"success": False}
+ # Wrapper for set_files
def set_file(self, infiles):
return self.set_files(infiles)
@@ -1010,19 +1171,23 @@ class AppBase:
}
# Simple validation of parameters in general
+ replace_params = False
try:
tmp_parameters = action["parameters"]
+ for param in tmp_parameters:
+ if param["value"] == "SHUFFLE_AUTO_REMOVED":
+ replace_params = True
except KeyError:
action["parameters"] = []
except TypeError:
pass
self.action = copy.deepcopy(action)
- self.logger.info("[DEBUG] Sending starting action result (EXECUTING)")
+ self.logger.info(f"[DEBUG] Sending starting action result (EXECUTING). Param replace: {replace_params}")
headers = {
"Content-Type": "application/json",
- "Authorization": "Bearer %s" % self.authorization
+ "Authorization": f"Bearer {self.authorization}"
}
if len(self.action) == 0:
@@ -1118,6 +1283,32 @@ class AppBase:
self.full_execution = fullexecution
+
+ try:
+ if replace_params == True:
+ for inner_action in self.full_execution["workflow"]["actions"]:
+ self.logger.info("[DEBUG] ID: %s vs %s" % (inner_action["id"], self.action["id"]))
+
+ # In case of some kind of magic, we're just doing params
+ if inner_action["id"] == self.action["id"]:
+ self.logger.info("FOUND!")
+
+ if isinstance(self.action, str):
+ self.logger.info("Params is in string object for self.action?")
+ else:
+ self.action["parameters"] = inner_action["parameters"]
+ self.action_result["action"]["parameters"] = inner_action["parameters"]
+
+ if isinstance(self.original_action, str):
+ self.logger.info("Params for original actions is in string object?")
+ else:
+ self.original_action["parameters"] = inner_action["parameters"]
+
+ break
+
+ except Exception as e:
+ self.logger.info(f"[WARNING] Failed in replace params action parsing: {e}")
+
self.logger.info("[DEBUG] AFTER FULLEXEC stream result (init)")
# Gets the value at the parenthesis level you want
@@ -1489,6 +1680,11 @@ class AppBase:
newvalue = []
firstitem = actualitem[0][0]
seconditem = actualitem[0][1]
+ if isinstance(firstitem, int):
+ firstitem = str(firstitem)
+ if isinstance(seconditem, int):
+ seconditem = str(seconditem)
+
print("[DEBUG] ACTUAL PARSED: %s" % actualitem)
# Means it's a single item -> continue
@@ -1509,17 +1705,23 @@ class AppBase:
newvalue, is_loop = (tmpitem, parsersplit[outercnt+1:])
else:
print("[INFO] In ELSE - handling %s and %s" % (firstitem, seconditem))
- if firstitem.lower() == "max" or firstitem.lower() == "last" or firstitem.lower() == "end":
- firstitem = len(basejson)-1
- elif firstitem.lower() == "min" or firstitem.lower() == "first":
- firstitem = 0
+ if isinstance(firstitem, str):
+ if firstitem.lower() == "max" or firstitem.lower() == "last" or firstitem.lower() == "end":
+ firstitem = len(basejson)-1
+ elif firstitem.lower() == "min" or firstitem.lower() == "first":
+ firstitem = 0
+ else:
+ firstitem = int(firstitem)
else:
firstitem = int(firstitem)
- if seconditem.lower() == "max" or seconditem.lower() == "last" or firstitem.lower() == "end":
- seconditem = len(basejson)-1
- elif seconditem.lower() == "min" or seconditem.lower() == "first":
- seconditem = 0
+ if isinstance(seconditem, str):
+ if seconditem.lower() == "max" or seconditem.lower() == "last" or firstitem.lower() == "end":
+ seconditem = len(basejson)-1
+ elif seconditem.lower() == "min" or seconditem.lower() == "first":
+ seconditem = 0
+ else:
+ seconditem = int(seconditem)
else:
seconditem = int(seconditem)
@@ -1704,7 +1906,7 @@ class AppBase:
basejson = json.loads(baseresult)
except json.decoder.JSONDecodeError as e:
try:
- baseresult = baseresult.replace("\'", "\"")
+ #baseresult = baseresult.replace("\'", "\"")
basejson = json.loads(baseresult)
except json.decoder.JSONDecodeError as e:
print("Parser issue with JSON: %s" % e)
@@ -1768,9 +1970,10 @@ class AppBase:
# return template
#self.logger.info(globals())
- self.logger.info("[DEBUG] Running liquid with data of length %d" % len(template))
+ if len(template) > 100:
+ self.logger.info("[DEBUG] Running liquid with data of length %d" % len(template))
#self.logger.info(f"[DEBUG] Data: {template}")
- run = Liquid(template, mode="wild", from_file=False)
+ run = Liquid(template, mode="wild", from_file=False, filters=shuffle_filters.filters)
# Can't handle self yet (?)
ret = run.render(**globals())
@@ -1822,8 +2025,8 @@ class AppBase:
self.action_result["status"] = "FAILURE"
data = {
"success": False,
- "input": template,
"reason": f"Failed to parse LiquidPy: {error_msg}",
+ "input": template,
}
try:
self.action_result["result"] = json.dumps(data)
@@ -1945,7 +2148,11 @@ class AppBase:
except:
self.logger.info("Error in initial replacement of escaped dollar!")
- #self.logger.info("POST input value: %s" % parameter["value"])
+ # Basic fix in case variant isn't set
+ try:
+ self.logger.info("[DEBUG] Parameter variant: %s" % parameter["variant"])
+ except:
+ parameter["variant"] = "STATIC_VALUE"
# Regex to find all the things
if parameter["variant"] == "STATIC_VALUE":
@@ -2176,7 +2383,7 @@ class AppBase:
return True
else:
print("[DEBUG] Condition: can't handle %s yet. Setting to true" % check)
-
+
return False
def check_branch_conditions(action, fullexecution, self):
@@ -2187,21 +2394,48 @@ class AppBase:
except KeyError:
return True, ""
+
+ available_checks = [
+ "=",
+ "equals",
+ "!=",
+ "does not equal",
+ ">",
+ "larger than",
+ "<",
+ "less than",
+ ">=",
+ "<=",
+ "startswith",
+ "endswith",
+ "contains",
+ "contains_any_of",
+ "re",
+ "matches regex",
+ ]
+
relevantbranches = []
+ correct_branches = 0
+ matching_branches = 0
for branch in fullexecution["workflow"]["branches"]:
if branch["destination_id"] != action["id"]:
continue
+ matching_branches += 1
# Remove anything without a condition
try:
if (branch["conditions"]) == 0 or branch["conditions"] == None:
+ correct_branches += 1
continue
except KeyError:
+ correct_branches += 1
continue
self.logger.info("[DEBUG] Relevant conditions: %s" % branch["conditions"])
successful_conditions = []
failed_conditions = []
+ successful_conditions = 0
+ total_conditions = len(branch["conditions"])
for condition in branch["conditions"]:
self.logger.info("[DEBUG] Getting condition value of %s" % condition)
@@ -2209,6 +2443,7 @@ class AppBase:
sourcevalue = condition["source"]["value"]
check, sourcevalue, is_loop = parse_params(action, fullexecution, condition["source"], self)
if check:
+ continue
return False, {"success": False, "reason": "Failed condition (1): %s %s %s because %s" % (sourcevalue, condition["condition"]["value"], destinationvalue, check)}
#sourcevalue = sourcevalue.encode("utf-8")
@@ -2217,28 +2452,11 @@ class AppBase:
check, destinationvalue, is_loop = parse_params(action, fullexecution, condition["destination"], self)
if check:
+ continue
return False, {"success": False, "reason": "Failed condition (2): %s %s %s because %s" % (sourcevalue, condition["condition"]["value"], destinationvalue, check)}
#destinationvalue = destinationvalue.encode("utf-8")
destinationvalue = parse_wrapper_start(destinationvalue, self)
- available_checks = [
- "=",
- "equals",
- "!=",
- "does not equal",
- ">",
- "larger than",
- "<",
- "less than",
- ">=",
- "<=",
- "startswith",
- "endswith",
- "contains",
- "contains_any_of",
- "re",
- "matches regex",
- ]
if not condition["condition"]["value"] in available_checks:
self.logger.warning("Skipping %s %s %s because %s is invalid." % (sourcevalue, condition["condition"]["value"], destinationvalue, condition["condition"]["value"]))
@@ -2255,22 +2473,58 @@ class AppBase:
except KeyError:
pass
- if not validation:
- self.logger.info("Failed condition check for %s %s %s." % (sourcevalue, condition["condition"]["value"], destinationvalue))
- return False, {"success": False, "reason": "Failed condition (3): %s %s %s" % (sourcevalue, condition["condition"]["value"], destinationvalue)}
+ if validation == True:
+ successful_conditions += 1
+ #if not validation:
+ # self.logger.info("Failed condition check for %s %s %s." % (sourcevalue, condition["condition"]["value"], destinationvalue))
+ # return False, {"success": False, "reason": "Failed condition (3): %s %s %s" % (sourcevalue, condition["condition"]["value"], destinationvalue)}
- # Make a general parser here, at least to get param["name"] = param["value"] in maparameter[string]string
- #for condition in branch.conditons:
+ self.logger.info("CONDITIONS VS SUCCESS: %d vs %d" % (total_conditions, successful_conditions))
+
+ if total_conditions == successful_conditions:
+ correct_branches += 1
+ if matching_branches == 0:
+ return True, ""
+
+ if matching_branches > 0 and correct_branches > 0:
+ return True, ""
+
+ self.logger.info("[DEBUG] Correct branches vs matching branches: %d vs %d" % (correct_branches, matching_branches))
+ return False, {"success": False, "reason": "Minimum of one branch's conditions must be correct to continue. Total: %d of %d" % (correct_branches, matching_branches)}
+
+ #Correct branches vs matching branches: 1 vs 1
+ #if
return True, ""
+ #
+ #
+ #
+ #
+ # CONT
+ # CONT
+ # CONT
+ # CONT
+ # CONT
+ # CONT
+ # CONT
+ # CONT
+ # CONT
+ # CONT
+ # CONT
+ # CONT
+ # CONT
+ #
+ #
+ #
+ #
# THE START IS ACTUALLY RIGHT HERE :O
# Checks whether conditions are met, otherwise set
branchcheck, tmpresult = check_branch_conditions(action, fullexecution, self)
- if isinstance(tmpresult, object) or isinstance(tmpresult, list):
+ if isinstance(tmpresult, object) or isinstance(tmpresult, list) or isinstance(tmpresult, dict):
self.logger.info("[DEBUG] Fixing branch return as object -> string")
try:
#tmpresult = tmpresult.replace("'", "\"")
@@ -2278,19 +2532,14 @@ class AppBase:
except json.decoder.JSONDecodeError as e:
self.logger.info(f"[WARNING] Failed condition parsing {tmpresult} to string")
+ # IF branches fail: Exit!
if not branchcheck:
self.logger.info("Failed one or more branch conditions.")
self.action_result["result"] = tmpresult
self.action_result["status"] = "SKIPPED"
- try:
- ret = requests.post("%s%s" % (self.base_url, stream_path), headers=headers, json=self.action_result)
- self.logger.info("Result: %d" % ret.status_code)
- if ret.status_code != 200:
- self.logger.info(ret.text)
- except requests.exceptions.ConnectionError as e:
- self.logger.exception(e)
+ self.action_result["completed_at"] = int(time.time())
- self.logger.info("\n\n[DEBUG] RETURNING BECAUSE A BRANCH FAILED: %s\n\n" % tmpresult)
+ self.send_result(self.action_result, headers, stream_path)
return
# Replace name cus there might be issues
@@ -2612,7 +2861,13 @@ class AppBase:
for i in range(0, curminlength):
tmpitem = json.loads(json.dumps(parameter["value"]))
for key, value in replacements.items():
- replacement = json.dumps(json.loads(value)[i])
+ replacement = value
+ try:
+ replacement = json.dumps(json.loads(value)[i])
+ except IndexError as e:
+ self.logger.info(f"[ERROR] Failed handling value parsing with index: {e}")
+ pass
+
if replacement.startswith("\"") and replacement.endswith("\""):
replacement = replacement[1:len(replacement)-1]
#except json.decoder.JSONDecodeError as e:
@@ -2667,13 +2922,20 @@ class AppBase:
multi_parameters[parameter["name"]] = resultarray
else:
# Parses things like int(value)
- self.logger.info("[DEBUG] Normal parsing (not looping)")#with data %s" % value)
+ #self.logger.info("[DEBUG] Normal parsing (not looping)")#with data %s" % value)
# This part has fucked over so many random JSON usages because of weird paranthesis parsing
value = parse_wrapper_start(value, self)
#self.logger.info("[DEBUG] Post return: %s" % value)
#self.logger.info("POST data value: %s" % value)
+
+ try:
+ if str(value).startswith("b'") and str(value).endswith("'"):
+ value = value[2:-1]
+ except Exception as e:
+ print(f"Value rawbytes Exception: {e}")
+
params[parameter["name"]] = value
multi_parameters[parameter["name"]] = value
@@ -2803,7 +3065,7 @@ class AppBase:
except Exception as e:
self.logger.warning("[ERROR] Failed to parse coroutine value for old app: {e}")
- self.logger.info("\n[INFO] Returned from execution with types %s" % type(newres))
+ self.logger.info("\n[INFO] Returned from execution with type(s) %s" % type(newres))
#self.logger.info("\n[INFO] Returned from execution with %s of types %s" % (newres, type(newres)))#, newres)
if isinstance(newres, tuple):
self.logger.info(f"[INFO] Handling return as tuple: {newres}")
@@ -2946,6 +3208,11 @@ class AppBase:
# Send the result :)
self.send_result(self.action_result, headers, stream_path)
+ try:
+ self.log_capture_string.close()
+ except:
+ pass
+
return
@classmethod
diff --git a/backend/app_sdk/build.sh b/backend/app_sdk/build.sh
index 198177c4..4cfe3e2c 100644
--- a/backend/app_sdk/build.sh
+++ b/backend/app_sdk/build.sh
@@ -3,7 +3,7 @@
### DEFAULT
NAME=shuffle-app_sdk
-VERSION=0.9.50
+VERSION=0.9.61
docker rmi docker.pkg.github.com/frikky/shuffle/$NAME:$VERSION --force
docker build . -f Dockerfile -t frikky/shuffle:app_sdk -t frikky/$NAME:$VERSION -t docker.pkg.github.com/frikky/shuffle/$NAME:$VERSION -t ghcr.io/frikky/$NAME:$VERSION -t ghcr.io/frikky/$NAME:nightly
@@ -19,17 +19,17 @@ docker push ghcr.io/frikky/$NAME:nightly
docker push ghcr.io/frikky/$NAME:latest
#### KALI ###
-NAME=shuffle-app_sdk_kali
-docker build . -f Dockerfile_kali -t frikky/shuffle:app_sdk_kali -t frikky/$NAME:$VERSION -t docker.pkg.github.com/frikky/shuffle/$NAME:$VERSION -t ghcr.io/frikky/$NAME:$VERSION
-
-docker push frikky/shuffle:app_sdk_kali
-docker push ghcr.io/frikky/$NAME:$VERSION
-docker push ghcr.io/frikky/$NAME:nightly
+#NAME=shuffle-app_sdk_kali
+#docker build . -f Dockerfile_kali -t frikky/shuffle:app_sdk_kali -t frikky/$NAME:$VERSION -t docker.pkg.github.com/frikky/shuffle/$NAME:$VERSION -t ghcr.io/frikky/$NAME:$VERSION
+#
+#docker push frikky/shuffle:app_sdk_kali
+#docker push ghcr.io/frikky/$NAME:$VERSION
+#docker push ghcr.io/frikky/$NAME:nightly
### BLACKARCH ###
-NAME=shuffle-app_sdk_blackarch
-docker build . -f Dockerfile_blackarch -t frikky/shuffle:app_sdk_blackarch -t frikky/$NAME:$VERSION -t docker.pkg.github.com/frikky/shuffle/$NAME:$VERSION -t ghcr.io/frikky/$NAME:$VERSION
-
-docker push frikky/shuffle:app_sdk_blackarch
-docker push ghcr.io/frikky/$NAME:$VERSION
-docker push ghcr.io/frikky/$NAME:nightly
+#NAME=shuffle-app_sdk_blackarch
+#docker build . -f Dockerfile_blackarch -t frikky/shuffle:app_sdk_blackarch -t frikky/$NAME:$VERSION -t docker.pkg.github.com/frikky/shuffle/$NAME:$VERSION -t ghcr.io/frikky/$NAME:$VERSION
+#
+#docker push frikky/shuffle:app_sdk_blackarch
+#docker push ghcr.io/frikky/$NAME:$VERSION
+#docker push ghcr.io/frikky/$NAME:nightly
diff --git a/backend/go-app/docker.go b/backend/go-app/docker.go
index 9b535c72..25acc65a 100644
--- a/backend/go-app/docker.go
+++ b/backend/go-app/docker.go
@@ -14,6 +14,7 @@ import (
"encoding/json"
"errors"
"fmt"
+
//"github.com/docker/docker"
"github.com/docker/docker/api/types"
//"github.com/docker/docker/api/types/container"
@@ -417,7 +418,7 @@ func stopWebhook(image string, identifier string) error {
// Starts a new webhook
func handleStopHookDocker(resp http.ResponseWriter, request *http.Request) {
- cors := handleCors(resp, request)
+ cors := shuffle.HandleCors(resp, request)
if cors {
return
}
@@ -502,7 +503,7 @@ var webhook = `{
// Starts a new webhook
func handleDeleteHookDocker(resp http.ResponseWriter, request *http.Request) {
ctx := context.Background()
- cors := handleCors(resp, request)
+ cors := shuffle.HandleCors(resp, request)
if cors {
return
}
@@ -619,7 +620,7 @@ func hookTest() {
//https://stackoverflow.com/questions/23935141/how-to-copy-docker-images-from-one-host-to-another-without-using-a-repository
func getDockerImage(resp http.ResponseWriter, request *http.Request) {
- cors := handleCors(resp, request)
+ cors := shuffle.HandleCors(resp, request)
if cors {
return
}
diff --git a/backend/go-app/go.mod b/backend/go-app/go.mod
index 0eb5e3b3..a0289ae9 100644
--- a/backend/go-app/go.mod
+++ b/backend/go-app/go.mod
@@ -1,34 +1,35 @@
module main
-go 1.15
+go 1.16
-replace github.com/shuffle/shuffle-shared => ../../../../git/shuffle-shared
+//replace github.com/shuffle/shuffle-shared => ../../../shuffle-shared
//replace github.com/frikky/kin-openapi => ../../../../git/kin-openapi
//replace github.com/frikky/go-elasticsearch => ../../../../git/go-elasticsearch
require (
cloud.google.com/go/datastore v1.6.0
- cloud.google.com/go/pubsub v1.17.0
+ cloud.google.com/go/iam v0.1.1 // indirect
+ cloud.google.com/go/pubsub v1.17.1
cloud.google.com/go/storage v1.18.2
github.com/basgys/goxml2json v1.1.0
github.com/carlescere/scheduler v0.0.0-20170109141437-ee74d2f83d82
- github.com/docker/docker v20.10.9+incompatible
+ github.com/docker/docker v20.10.12+incompatible
github.com/frikky/kin-openapi v0.41.0
- github.com/fsouza/go-dockerclient v1.7.4
+ github.com/fsouza/go-dockerclient v1.7.7
github.com/ghodss/yaml v1.0.0
github.com/go-git/go-billy/v5 v5.3.1
github.com/go-git/go-git/v5 v5.4.2
github.com/gorilla/mux v1.8.0
- github.com/h2non/filetype v1.1.1
+ github.com/h2non/filetype v1.1.3
+ github.com/nirasan/go-oauth-pkce-code-verifier v0.0.0-20170819232839-0fbfe93532da // indirect
github.com/satori/go.uuid v1.2.0
- github.com/shuffle/shuffle-shared v0.1.79
- github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect
+ github.com/shuffle/shuffle-shared v0.2.7
go4.org v0.0.0-20201209231011-d4a079459e60 // indirect
- golang.org/x/crypto v0.0.0-20210921155107-089bfa567519
- google.golang.org/api v0.58.0
+ golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce
+ google.golang.org/api v0.65.0
google.golang.org/appengine v1.6.7
- google.golang.org/grpc v1.41.0
+ google.golang.org/grpc v1.43.0
gopkg.in/src-d/go-git.v4 v4.13.1
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
)
diff --git a/backend/go-app/main.go b/backend/go-app/main.go
index 9e9473c5..a8b52797 100644
--- a/backend/go-app/main.go
+++ b/backend/go-app/main.go
@@ -1,14 +1,17 @@
package main
import (
+ uuid "github.com/satori/go.uuid"
"github.com/shuffle/shuffle-shared"
"bufio"
"bytes"
"context"
"crypto/md5"
+
//"crypto/tls"
//"crypto/x509"
+ "encoding/base64"
"encoding/hex"
"encoding/json"
"errors"
@@ -16,11 +19,13 @@ import (
"io"
"io/ioutil"
"log"
+ "math/rand"
"net/http"
"net/url"
"os"
"os/exec"
"path/filepath"
+
//"regexp"
"strings"
"time"
@@ -49,12 +54,14 @@ import (
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/storage/memory"
+
+ //cv "github.com/nirasan/go-oauth-pkce-code-verifier"
+
//githttp "gopkg.in/src-d/go-git.v4/plumbing/transport/http"
// Random
xj "github.com/basgys/goxml2json"
newscheduler "github.com/carlescere/scheduler"
- "github.com/satori/go.uuid"
"golang.org/x/crypto/bcrypt"
"gopkg.in/yaml.v3"
@@ -706,7 +713,7 @@ func createNewUser(username, password, role, apikey string, org shuffle.OrgMini)
}
func handleRegister(resp http.ResponseWriter, request *http.Request) {
- cors := handleCors(resp, request)
+ cors := shuffle.HandleCors(resp, request)
if cors {
return
}
@@ -835,7 +842,7 @@ func handleCookie(request *http.Request) bool {
}
func handleInfo(resp http.ResponseWriter, request *http.Request) {
- cors := handleCors(resp, request)
+ cors := shuffle.HandleCors(resp, request)
if cors {
return
}
@@ -1118,7 +1125,7 @@ func increaseStatisticsField(ctx context.Context, fieldname, id string, amount i
// FIXME - forward this to emails or whatever CRM system in use
func handleContact(resp http.ResponseWriter, request *http.Request) {
- cors := handleCors(resp, request)
+ cors := shuffle.HandleCors(resp, request)
if cors {
return
}
@@ -1163,8 +1170,17 @@ func handleContact(resp http.ResponseWriter, request *http.Request) {
resp.Write([]byte(fmt.Sprintf(`{"success": true, "message": "Thanks for reaching out. We will contact you soon!"}`)))
}
+func verifier() (*shuffle.CodeVerifier, error) {
+ r := rand.New(rand.NewSource(time.Now().UnixNano()))
+ b := make([]byte, 32, 32)
+ for i := 0; i < 32; i++ {
+ b[i] = byte(r.Intn(255))
+ }
+ return shuffle.CreateCodeVerifierFromBytes(b)
+}
+
func checkAdminLogin(resp http.ResponseWriter, request *http.Request) {
- cors := handleCors(resp, request)
+ cors := shuffle.HandleCors(resp, request)
if cors {
return
}
@@ -1186,14 +1202,74 @@ func checkAdminLogin(resp http.ResponseWriter, request *http.Request) {
return
}
- //ssoUrl = org.SSOConfig.SOSOEntrypoint
- redirectUri := shuffle.SSOUrl
+ baseSSOUrl := ""
+ handled := []string{}
+ for _, user := range users {
+ if shuffle.ArrayContains(handled, user.ActiveOrg.Id) {
+ continue
+ }
+
+ handled = append(handled, user.ActiveOrg.Id)
+ org, err := shuffle.GetOrg(ctx, user.ActiveOrg.Id)
+ if err != nil {
+ log.Printf("[WARNING] Error getting org in admin check: %s", err)
+ continue
+ }
+
+ // No childorg setup, only parent org
+ if len(org.ManagerOrgs) > 0 || len(org.CreatorOrg) > 0 {
+ continue
+ }
+
+ // Should run calculations
+ if len(org.SSOConfig.OpenIdAuthorization) > 0 {
+ log.Printf("[DEBUG] Found OpenID url (PKCE). Extra redirect check: %s", request.URL.String())
+ baseSSOUrl = org.SSOConfig.OpenIdAuthorization
+
+ codeChallenge := uuid.NewV4().String()
+ //h.Write([]byte(v.Value))
+ verifier, verifiererr := verifier()
+ if verifiererr == nil {
+ codeChallenge = verifier.Value
+ }
+
+ //log.Printf("[DEBUG] Got challenge value %s (pre state)", codeChallenge)
+
+ // https://192.168.55.222:3443/api/v1/login_openid
+ //location := strings.Split(request.URL.String(), "/")
+ //redirectUrl := url.QueryEscape("http://localhost:5001/api/v1/login_openid")
+ redirectUrl := url.QueryEscape(fmt.Sprintf("http://%s/api/v1/login_openid", request.Host))
+ if strings.Contains(request.Host, "shuffle-backend") && !strings.Contains(os.Getenv("BASE_URL"), "shuffle-backend") {
+ redirectUrl = url.QueryEscape(fmt.Sprintf("%s/api/v1/login_openid", os.Getenv("BASE_URL")))
+ }
+
+ state := base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("org=%s&challenge=%s&redirect=%s", org.Id, codeChallenge, redirectUrl)))
+
+ // has to happen after initial value is stored
+ if verifiererr == nil {
+ codeChallenge = verifier.CodeChallengeS256()
+ }
+
+ //log.Printf("[DEBUG] Got challenge value %s (POST state)", codeChallenge)
+
+ baseSSOUrl += fmt.Sprintf("?client_id=%s&response_type=code&scope=openid&redirect_uri=%s&state=%s&code_challenge_method=S256&code_challenge=%s", org.SSOConfig.OpenIdClientId, redirectUrl, state, codeChallenge)
+ break
+ }
+
+ if len(org.SSOConfig.SSOEntrypoint) > 0 {
+ log.Printf("[DEBUG] Found SAML SSO url")
+ baseSSOUrl = org.SSOConfig.SSOEntrypoint
+ break
+ }
+ }
+
+ //log.Printf("[DEBUG] OpenID URL: %s", baseSSOUrl)
resp.WriteHeader(200)
- resp.Write([]byte(fmt.Sprintf(`{"success": true, "reason": "redirect", "sso_url": "%s"}`, redirectUri)))
+ resp.Write([]byte(fmt.Sprintf(`{"success": true, "reason": "redirect", "sso_url": "%s"}`, baseSSOUrl)))
}
func handleLogin(resp http.ResponseWriter, request *http.Request) {
- cors := handleCors(resp, request)
+ cors := shuffle.HandleCors(resp, request)
if cors {
return
}
@@ -1418,8 +1494,12 @@ func fixUserOrg(ctx context.Context, user *shuffle.User) *shuffle.User {
}
// Used for testing only. Shouldn't impact production.
-func handleCors(resp http.ResponseWriter, request *http.Request) bool {
- allowedOrigins := "http://localhost:3000"
+/*
+func shuffle.HandleCors(resp http.ResponseWriter, request *http.Request) bool {
+ // Used for Codespace dev
+ allowedOrigins := "https://frikky-shuffle-5gvr4xx62w64-3000.githubpreview.dev"
+ //origin := request.Header["Origin"]
+ //log.Printf("Origin: %s", origin)
//allowedOrigins := "http://localhost:3002"
resp.Header().Set("Vary", "Origin")
@@ -1436,6 +1516,7 @@ func handleCors(resp http.ResponseWriter, request *http.Request) bool {
return false
}
+*/
func parseWorkflowParameters(resp http.ResponseWriter, request *http.Request) (map[string]interface{}, error) {
body, err := ioutil.ReadAll(request.Body)
@@ -1580,7 +1661,7 @@ func SearchNested(obj interface{}, key string) (interface{}, bool) {
}
func handleSetHook(resp http.ResponseWriter, request *http.Request) {
- cors := handleCors(resp, request)
+ cors := shuffle.HandleCors(resp, request)
if cors {
return
}
@@ -1768,7 +1849,7 @@ func verifyHook(hook shuffle.Hook) (bool, string) {
}
func setSpecificSchedule(resp http.ResponseWriter, request *http.Request) {
- cors := handleCors(resp, request)
+ cors := shuffle.HandleCors(resp, request)
if cors {
return
}
@@ -1828,7 +1909,7 @@ func setSpecificSchedule(resp http.ResponseWriter, request *http.Request) {
}
func getSpecificWebhook(resp http.ResponseWriter, request *http.Request) {
- cors := handleCors(resp, request)
+ cors := shuffle.HandleCors(resp, request)
if cors {
return
}
@@ -1880,7 +1961,7 @@ func getSpecificWebhook(resp http.ResponseWriter, request *http.Request) {
// Starts a new webhook
func handleDeleteSchedule(resp http.ResponseWriter, request *http.Request) {
- cors := handleCors(resp, request)
+ cors := shuffle.HandleCors(resp, request)
if cors {
return
}
@@ -1935,7 +2016,7 @@ func handleDeleteSchedule(resp http.ResponseWriter, request *http.Request) {
// Starts a new webhook
func handleNewSchedule(resp http.ResponseWriter, request *http.Request) {
- cors := handleCors(resp, request)
+ cors := shuffle.HandleCors(resp, request)
if cors {
return
}
@@ -2229,7 +2310,7 @@ func getSpecificSchedule(resp http.ResponseWriter, request *http.Request) {
return
}
- cors := handleCors(resp, request)
+ cors := shuffle.HandleCors(resp, request)
if cors {
return
}
@@ -2295,7 +2376,7 @@ func loadYaml(fileLocation string) (ApiYaml, error) {
// This should ALWAYS come from an OUTPUT
func executeSchedule(resp http.ResponseWriter, request *http.Request) {
- cors := handleCors(resp, request)
+ cors := shuffle.HandleCors(resp, request)
if cors {
return
}
@@ -2788,7 +2869,7 @@ type Result struct {
// r.HandleFunc("/api/v1/docs/{key}", getDocs).Methods("GET", "OPTIONS")
func getOpenapi(resp http.ResponseWriter, request *http.Request) {
- cors := handleCors(resp, request)
+ cors := shuffle.HandleCors(resp, request)
if cors {
return
}
@@ -3254,7 +3335,7 @@ func buildSwaggerApp(resp http.ResponseWriter, body []byte, user shuffle.User) {
// Creates an app from the app builder
func verifySwagger(resp http.ResponseWriter, request *http.Request) {
- cors := handleCors(resp, request)
+ cors := shuffle.HandleCors(resp, request)
if cors {
return
}
@@ -3316,11 +3397,6 @@ func createFs(basepath, pathname string) (billy.Filesystem, error) {
return err
}
- //if strings.Contains(path, "yaml") {
- // log.Printf("PATH: %s -> %s", path, fullpath)
- // //log.Printf("DATA: %s", string(srcData))
- //}
-
dst, err := fs.Create(fullpath)
if err != nil {
log.Printf("Dst error: %s", err)
@@ -3863,7 +3939,7 @@ func runInitEs(ctx context.Context) {
}
for _, schedule := range schedules {
- if schedule.Environment == "cloud" {
+ if strings.ToLower(schedule.Environment) == "cloud" {
log.Printf("Skipping cloud schedule")
continue
}
@@ -4999,7 +5075,7 @@ func handleStopCloudSync(syncUrl string, org shuffle.Org) (*shuffle.Org, error)
This is here to both enable and disable cloud sync features for an organization
*/
func handleCloudSetup(resp http.ResponseWriter, request *http.Request) {
- cors := handleCors(resp, request)
+ cors := shuffle.HandleCors(resp, request)
if cors {
return
}
@@ -5101,8 +5177,19 @@ func handleCloudSetup(resp http.ResponseWriter, request *http.Request) {
_, err = handleStopCloudSync(syncPath, *org)
if err != nil {
+ ret := shuffle.ResultChecker{
+ Success: false,
+ Reason: fmt.Sprintf("%s", err),
+ }
+
resp.WriteHeader(401)
- resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err)))
+ b, err := json.Marshal(ret)
+ if err != nil {
+ resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err)))
+ return
+ }
+
+ resp.Write(b)
} else {
resp.WriteHeader(200)
resp.Write([]byte(fmt.Sprintf(`{"success": true, "reason": "Successfully disabled cloud sync for org."}`)))
@@ -5687,10 +5774,15 @@ func initHandlers() {
dbclient, err = datastore.NewClient(ctx, gceProject, option.WithGRPCDialOption(grpc.WithNoProxy()))
if err != nil {
if elasticConfig == "" {
- log.Fatalf("[ERROR] Database client error during init: %s. Env: SHUFFLE_ELASTIC=false", err)
+ log.Printf("[ERROR] Database client error during init: %s. Env: SHUFFLE_ELASTIC=false", err)
} else {
- log.Printf("[DEBUG] Database client error during init: %s. Here for backwards compatibility: not critical.", err)
+ if !strings.Contains(fmt.Sprintf("%s", err), "find default credentials") {
+ log.Printf("[DEBUG] Database client error info during init: %s. Here for backwards compatibility: not critical.", err)
+ }
+ dbclient = &datastore.Client{}
}
+ } else {
+ //log.Printf("Database client initiated: %s", dbclient)
}
for {
@@ -5783,10 +5875,13 @@ func initHandlers() {
r.HandleFunc("/api/v1/apps/authentication/{appauthId}/config", shuffle.SetAuthenticationConfig).Methods("POST", "OPTIONS")
r.HandleFunc("/api/v1/apps/authentication/{appauthId}", shuffle.DeleteAppAuthentication).Methods("DELETE", "OPTIONS")
- // Related to
+ // Related to NFT things
r.HandleFunc("/api/v1/workflows/collections/load", shuffle.LoadCollections).Methods("POST", "OPTIONS")
r.HandleFunc("/api/v1/workflows/collections/{key}", shuffle.HandleGetCollection).Methods("GET", "OPTIONS")
+ // Related to use-cases that are not directly workflows.
+ r.HandleFunc("/api/v1/workflows/usecases", shuffle.LoadUsecases).Methods("GET", "OPTIONS")
+
// Legacy app things
r.HandleFunc("/api/v1/workflows/apps/validate", validateAppInput).Methods("POST", "OPTIONS")
r.HandleFunc("/api/v1/workflows/apps", getWorkflowApps).Methods("GET", "OPTIONS")
@@ -5868,7 +5963,8 @@ func initHandlers() {
// Docker orborus specific - downloads an image
r.HandleFunc("/api/v1/get_docker_image", getDockerImage).Methods("POST", "OPTIONS")
r.HandleFunc("/api/v1/migrate_database", migrateDatabase).Methods("POST", "OPTIONS")
- r.HandleFunc("/api/v1/login_sso", shuffle.HandleSSO).Methods("POST", "OPTIONS")
+ r.HandleFunc("/api/v1/login_sso", shuffle.HandleSSO).Methods("GET", "POST", "OPTIONS")
+ r.HandleFunc("/api/v1/login_openid", shuffle.HandleOpenId).Methods("GET", "OPTIONS")
// Important for email, IDS etc. Create this by:
// PS: For cloud, this has to use cloud storage.
@@ -5887,6 +5983,9 @@ func initHandlers() {
r.HandleFunc("/api/v1/notifications/clear", shuffle.HandleClearNotifications).Methods("GET", "OPTIONS")
r.HandleFunc("/api/v1/notifications/{notificationId}/markasread", shuffle.HandleMarkAsRead).Methods("GET", "OPTIONS")
//r.HandleFunc("/api/v1/notifications/{notificationId}/markasread", shuffle.HandleMarkAsRead).Methods("GET", "OPTIONS")
+ r.HandleFunc("/api/v1/users/notifications", shuffle.HandleGetNotifications).Methods("GET", "OPTIONS")
+ r.HandleFunc("/api/v1/users/notifications/clear", shuffle.HandleClearNotifications).Methods("GET", "OPTIONS")
+ r.HandleFunc("/api/v1/users/notifications/{notificationId}/markasread", shuffle.HandleMarkAsRead).Methods("GET", "OPTIONS")
http.Handle("/", r)
}
diff --git a/backend/go-app/walkoff.go b/backend/go-app/walkoff.go
index c05c380d..27ca1615 100644
--- a/backend/go-app/walkoff.go
+++ b/backend/go-app/walkoff.go
@@ -21,6 +21,7 @@ import (
"github.com/docker/docker/api/types"
dockerclient "github.com/docker/docker/client"
+
//gyaml "github.com/ghodss/yaml"
"github.com/h2non/filetype"
@@ -34,6 +35,7 @@ import (
"github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/storage/memory"
http2 "gopkg.in/src-d/go-git.v4/plumbing/transport/http"
+
//"github.com/gorilla/websocket"
//"google.golang.org/appengine"
//"google.golang.org/appengine/memcache"
@@ -142,7 +144,7 @@ func createSchedule(ctx context.Context, scheduleId, workflowId, name, startNode
}
func handleGetWorkflowqueueConfirm(resp http.ResponseWriter, request *http.Request) {
- cors := handleCors(resp, request)
+ cors := shuffle.HandleCors(resp, request)
if cors {
return
}
@@ -243,7 +245,7 @@ func handleGetWorkflowqueueConfirm(resp http.ResponseWriter, request *http.Reque
// FIXME: Authenticate this one? Can org ID be auth enough?
// (especially since we have a default: shuffle)
func handleGetWorkflowqueue(resp http.ResponseWriter, request *http.Request) {
- cors := handleCors(resp, request)
+ cors := shuffle.HandleCors(resp, request)
if cors {
return
}
@@ -335,7 +337,7 @@ func handleGetWorkflowqueue(resp http.ResponseWriter, request *http.Request) {
}
func handleGetStreamResults(resp http.ResponseWriter, request *http.Request) {
- cors := handleCors(resp, request)
+ cors := shuffle.HandleCors(resp, request)
if cors {
return
}
@@ -393,7 +395,7 @@ func handleGetStreamResults(resp http.ResponseWriter, request *http.Request) {
}
func handleWorkflowQueue(resp http.ResponseWriter, request *http.Request) {
- cors := handleCors(resp, request)
+ cors := shuffle.HandleCors(resp, request)
if cors {
return
}
@@ -666,7 +668,7 @@ func handleExecutionStatistics(execution shuffle.WorkflowExecution) {
}
func deleteWorkflow(resp http.ResponseWriter, request *http.Request) {
- cors := handleCors(resp, request)
+ cors := shuffle.HandleCors(resp, request)
if cors {
return
}
@@ -1034,7 +1036,7 @@ func cloudExecuteAction(execution shuffle.WorkflowExecution) error {
}
func executeWorkflow(resp http.ResponseWriter, request *http.Request) {
- cors := handleCors(resp, request)
+ cors := shuffle.HandleCors(resp, request)
if cors {
return
}
@@ -1131,7 +1133,7 @@ func executeWorkflow(resp http.ResponseWriter, request *http.Request) {
}
func stopSchedule(resp http.ResponseWriter, request *http.Request) {
- cors := handleCors(resp, request)
+ cors := shuffle.HandleCors(resp, request)
if cors {
return
}
@@ -1281,7 +1283,7 @@ func stopSchedule(resp http.ResponseWriter, request *http.Request) {
}
func stopScheduleGCP(resp http.ResponseWriter, request *http.Request) {
- cors := handleCors(resp, request)
+ cors := shuffle.HandleCors(resp, request)
if cors {
return
}
@@ -1390,7 +1392,7 @@ func deleteSchedule(ctx context.Context, id string) error {
}
func scheduleWorkflow(resp http.ResponseWriter, request *http.Request) {
- cors := handleCors(resp, request)
+ cors := shuffle.HandleCors(resp, request)
if cors {
return
}
@@ -1641,7 +1643,7 @@ func setExampleresult(ctx context.Context, result shuffle.AppExecutionExample) e
}
func getWorkflowApps(resp http.ResponseWriter, request *http.Request) {
- cors := handleCors(resp, request)
+ cors := shuffle.HandleCors(resp, request)
if cors {
return
}
@@ -1714,7 +1716,7 @@ func handleGetfile(resp http.ResponseWriter, request *http.Request) ([]byte, err
// Basically a search for apps that aren't activated yet
func getSpecificApps(resp http.ResponseWriter, request *http.Request) {
- cors := handleCors(resp, request)
+ cors := shuffle.HandleCors(resp, request)
if cors {
return
}
@@ -1789,7 +1791,7 @@ func getSpecificApps(resp http.ResponseWriter, request *http.Request) {
}
func validateAppInput(resp http.ResponseWriter, request *http.Request) {
- cors := handleCors(resp, request)
+ cors := shuffle.HandleCors(resp, request)
if cors {
return
}
@@ -1910,7 +1912,7 @@ func loadGithubWorkflows(url, username, password, userId, branch, orgId string)
}
func loadSpecificWorkflows(resp http.ResponseWriter, request *http.Request) {
- cors := handleCors(resp, request)
+ cors := shuffle.HandleCors(resp, request)
if cors {
return
}
@@ -1972,7 +1974,7 @@ func loadSpecificWorkflows(resp http.ResponseWriter, request *http.Request) {
}
func handleAppHotloadRequest(resp http.ResponseWriter, request *http.Request) {
- cors := handleCors(resp, request)
+ cors := shuffle.HandleCors(resp, request)
if cors {
return
}
@@ -2290,7 +2292,7 @@ func iterateWorkflowGithubFolders(fs billy.Filesystem, dir []os.FileInfo, extra
}
func setNewWorkflowApp(resp http.ResponseWriter, request *http.Request) {
- cors := handleCors(resp, request)
+ cors := shuffle.HandleCors(resp, request)
if cors {
return
}
diff --git a/backend/tests/files.sh b/backend/tests/files.sh
index 64417941..91171924 100755
--- a/backend/tests/files.sh
+++ b/backend/tests/files.sh
@@ -3,7 +3,7 @@
#curl http://localhost:5001/api/v1/files/create -H "Authorization: Bearer db0373c6-1083-4dec-a05d-3ba73f02ccd4" -d '{"filename": "file.txt", "org_id": "b199646b-16d2-456d-9fd6-b9972e929466", "workflow_id": "global"}'
#
#echo
-#curl http://localhost:5001/api/v1/files/e19cffe4-e2da-47e9-809e-904f5cb03687/upload -H "Authorization: Bearer db0373c6-1083-4dec-a05d-3ba73f02ccd4" -F 'shuffle_file=@files.sh'
+curl http://localhost:5001/api/v1/apps/upload -H "Authorization: Bearer db0373c6-1083-4dec-a05d-3ba73f02ccd4" -F 'shuffle_file=@files.sh'
#
#curl http://localhost:5001/api/v1/files/1915981b-b897-4db1-8a2e-44bc34cead3b/content -H "Authorization: Bearer db0373c6-1083-4dec-a05d-3ba73f02ccd4"
#curl http://localhost:5001/api/v1/files/e19cffe4-e2da-47e9-809e-904f5cb03687 -H "Authorization: Bearer db0373c6-1083-4dec-a05d-3ba73f02ccd4"
diff --git a/docker-compose.yml b/docker-compose.yml
index d5c27571..02e07e9a 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -48,7 +48,6 @@ services:
- /var/run/docker.sock:/var/run/docker.sock
environment:
- SHUFFLE_WORKER_VERSION=nightly
- - ORG_ID=${ORG_ID}
- ENVIRONMENT_NAME=${ENVIRONMENT_NAME}
- BASE_URL=http://${OUTER_HOSTNAME}:${BACKEND_PORT}
- DOCKER_API_VERSION=1.40
@@ -64,13 +63,12 @@ services:
- SHUFFLE_SWARM_CONFIG=runn
restart: unless-stopped
opensearch:
- image: opensearchproject/opensearch:1.2.3
+ image: opensearchproject/opensearch:1.2.4
hostname: shuffle-opensearch
container_name: shuffle-opensearch
environment:
- bootstrap.memory_lock=true
- - "OPENSEARCH_JAVA_OPTS=-Xms1024m -Xmx1024m" # minimum and maximum Java heap size, recommend setting both to 50% of system RAM
- - plugins.security.disabled=true
+ - "OPENSEARCH_JAVA_OPTS=-Xms2048m -Xmx2048m" # minimum and maximum Java heap size, recommend setting both to 50% of system RAM
- cluster.routing.allocation.disk.threshold_enabled=false
- cluster.name=shuffle-cluster
- node.name=shuffle-opensearch
@@ -96,4 +94,3 @@ networks:
driver: bridge
#driver: overlay
-#driver: bridge
diff --git a/frontend/package.json b/frontend/package.json
index 5766f14f..e7ea8871 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -1,10 +1,11 @@
{
"name": "shuffler",
"homepage": "https://shuffler.io",
- "version": "0.9.50",
+ "version": "0.9.61",
"private": true,
"dependencies": {
"@babel/core": "^7.15.8",
+ "@emotion/is-prop-valid": "^1.1.1",
"@emotion/react": "^11.7.0",
"@emotion/styled": "^11.6.0",
"@material-ui/core": "^4.5.2",
@@ -66,6 +67,7 @@
"react-scripts": "^4.0.1",
"react-shepherd": "^3.3.6",
"reactstrap": "^7.1.0",
+ "reaviz": "^12.1.0",
"shellwords": "^0.1.1",
"simplebar": "^4.2.3",
"styled-components": "^4.4.0",
diff --git a/frontend/public/images/detectionframework.png b/frontend/public/images/detectionframework.png
new file mode 100644
index 00000000..30da65a5
Binary files /dev/null and b/frontend/public/images/detectionframework.png differ
diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx
index d27cdc39..424eb9d5 100644
--- a/frontend/src/App.jsx
+++ b/frontend/src/App.jsx
@@ -15,7 +15,7 @@ import theme from "./theme";
import Apps from "./views/Apps";
import AppCreator from "./views/AppCreator";
-import Dashboard from "./views/Dashboard";
+import Dashboard from "./views/Dashboard.jsx";
import AdminSetup from "./views/AdminSetup";
import Admin from "./views/Admin";
import Docs from "./views/Docs";
@@ -49,6 +49,12 @@ if (window.location.port === "3000") {
//globalUrl = "http://localhost:5002"
}
+if (globalUrl.includes("githubpreview.dev")) {
+ //globalUrl = globalUrl.replace("3000", "5001")
+ globalUrl = "https://frikky-shuffle-5gvr4xx62w64-5001.githubpreview.dev"
+}
+console.log("global: ", globalUrl)
+
const App = (message, props) => {
const [userdata, setUserData] = useState({});
@@ -70,7 +76,7 @@ const App = (message, props) => {
checkLogin();
setDataset(true);
}
- });
+ }, []);
if (
isLoaded &&
@@ -78,17 +84,19 @@ const App = (message, props) => {
!window.location.pathname.startsWith("/login") &&
!window.location.pathname.startsWith("/docs") &&
!window.location.pathname.startsWith("/detectionframework") &&
- !window.location.pathname.startsWith("/adminsetup")
+ !window.location.pathname.startsWith("/adminsetup") &&
+ !window.location.pathname.startsWith("/usecases")
) {
window.location = "/login";
}
const getUserNotifications = () => {
- fetch(`${globalUrl}/api/v1/notifications`, {
+ fetch(`${globalUrl}/api/v1/users/notifications`, {
credentials: "include",
headers: {
"Content-Type": "application/json",
},
+ cors: "cors",
})
.then((response) => response.json())
.then((responseJson) => {
@@ -109,7 +117,7 @@ const App = (message, props) => {
const checkLogin = () => {
var baseurl = globalUrl;
- fetch(baseurl + "/api/v1/users/getinfo", {
+ fetch(`${globalUrl}/api/v1/getinfo`, {
credentials: "include",
headers: {
"Content-Type": "application/json",
@@ -399,7 +407,7 @@ const App = (message, props) => {
/>
{
{action.must_activate ? (
diff --git a/frontend/src/components/DetectionFramework.jsx b/frontend/src/components/DetectionFramework.jsx
index e66d09b7..2d7fc528 100644
--- a/frontend/src/components/DetectionFramework.jsx
+++ b/frontend/src/components/DetectionFramework.jsx
@@ -1499,7 +1499,7 @@ const Framework = (props) => {
/>
: