diff --git a/.env b/.env
old mode 100644
new mode 100755
index 307c9d35..07deaf4a
--- a/.env
+++ b/.env
@@ -2,6 +2,9 @@
ORG_ID=Shuffle
ENVIRONMENT_NAME=Shuffle
+# Sanitize liquid.py input
+LIQUID_SANITIZE_INPUT=true
+
# Remote github config for first load
SHUFFLE_DOWNLOAD_WORKFLOW_LOCATION=
@@ -55,14 +58,28 @@ SHUFFLE_ORBORUS_STARTUP_DELAY= # Used for setting up a startup delay for Orbor
SHUFFLE_BASE_IMAGE_NAME=shuffle
SHUFFLE_BASE_IMAGE_REGISTRY=ghcr.io
-SHUFFLE_BASE_IMAGE_TAG_SUFFIX="-1.0.0"
+SHUFFLE_BASE_IMAGE_TAG_SUFFIX="-1.1.0"
+
+## shuffle_memcached (for distributed caching)
+## shuffle_SWARM_CONFIG (run vs not run)
+## shuffle_Scale_Replicas (workers/node)
+## shuffle_App_Replicas (apps/node)
+
+SHUFFLE_SWARM_BRIDGE_DEFAULT_MTU=1500 # 1500 by default
+# The eth0 interface inside a container corresponds
+# to the virtual Ethernet interface that connects
+# the container to the docker0
+SHUFFLE_SWARM_BRIDGE_DEFAULT_INTERFACE=eth0
# Used for auto-cleanup of containers. REALLY important at scale.
SHUFFLE_CONTAINER_AUTO_CLEANUP=false
SHUFFLE_ELASTIC=true
SHUFFLE_LOGS_DISABLED=false
-SHUFFLE_CHAT_DISABLED=false # Controls support chat
+SHUFFLE_CHAT_DISABLED=false # Controls support chat
SHUFFLE_RERUN_SCHEDULE=300
+SHUFFLE_DISABLE_RERUN_AND_ABORT=false
+SHUFFLE_WORKER_SERVER_URL= # Definition in case Worker & Orborus is talking to the wrong server
+SHUFFLE_ORBORUS_PULL_TIME= # Definition in case Orborus is pulling too often/not often enough
# DATABASE CONFIGURATIONS
DATASTORE_EMULATOR_HOST=shuffle-database:8000
diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md
old mode 100644
new mode 100755
diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
old mode 100644
new mode 100755
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
old mode 100644
new mode 100755
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
old mode 100644
new mode 100755
diff --git a/.github/install-aws.md b/.github/install-aws.md
new file mode 100644
index 00000000..35bc140b
--- /dev/null
+++ b/.github/install-aws.md
@@ -0,0 +1,38 @@
+## Install Shuffle on AWS
+
+First, you need to create your own VPC for the same range of private IP addresses.
+
+**To create a VPC in AWS, follow these steps**
+
+1. Sign in to the AWS Management Console and open the Amazon VPC console at https://console.aws.amazon.com/vpc/.
+2. In the top navigation bar, choose the region in which you want to create the VPC.
+3. In the navigation pane, choose Your VPCs & Choose Create VPC.
+4. Enter a name for your VPC in the Name tag field & Choose VPC and more option.
+5. Specify the IPv4 CIDR block for your VPC. The CIDR block is the range of IP addresses that will be available for use within your VPC. You can specify any CIDR block that is:
+ - Between a /16 and /28 netmask (inclusive)
+ - Not currently in use
+6. Specify the AZs, Number of public subnets & Number of private subnets.
+
+
+
+7. Choose Yes, Create VPC.
+
+Your VPC will be created and will appear in the list of Your VPCs. By default, a VPC includes a default security group and a default network ACL. You can customize your VPC by adding subnets, security groups, network ACLs, and other resources.
+
+
+
+**To create an EC2 instance in AWS, follow these steps:**
+
+1. Sign in to the AWS Management Console and open the Amazon EC2 console at https://console.aws.amazon.com/ec2/.
+2. In the top navigation bar, choose the region in which you want to create the instance.
+3. In the navigation pane, choose Instances and Choose Launch Instance.
+4. On the Choose an Amazon Machine Image (AMI) page, choose an AMI. An AMI is a template that contains the software configuration (operating system, application server, and applications) for your instance.
+5. On the Choose an Instance Type page, choose the hardware configuration of your instance.
+6. On the Select an existing key pair or create a new key pair dialog box, choose an existing key pair or create a new one.
+7. On the network settings page and click on edit and select your VPC & subnet.
+8. On the Configure Security Group page, configure the security group for your instance. A security group acts as a virtual firewall for your instance to control inbound and outbound traffic.
+
+
+
+9. On the Add Storage page, add storage to your instance.
+10. Review your instance launch details and choose Launch.
diff --git a/.github/install-guide.html b/.github/install-guide.html
old mode 100644
new mode 100755
diff --git a/.github/install-guide.md b/.github/install-guide.md
old mode 100644
new mode 100755
index 73550fca..48b3689e
--- a/.github/install-guide.md
+++ b/.github/install-guide.md
@@ -1,7 +1,7 @@
# Installation guide
Installation of Shuffle is currently only available in docker. Looking for how to update Shuffle? Check the [updating guide](https://shuffler.io/docs/configuration#updating_shuffle)
-This document outlines a an introduction environment which is not scalable. [Read here](https://shuffler.io/docs/configuration#production_readiness) for information on production readiness. This also includes system requirements and configurations for Swarm or K8s.
+This document outlines a an introduction environment which is not scalable. [Read here](https://shuffler.io/docs/configuration#production_readiness) for information on production readiness. This also includes system requirements and configurations for Swarm or Kubernetes.
# Docker - *nix
The Docker setup is done with docker-compose
@@ -19,6 +19,7 @@ cd Shuffle
```bash
mkdir shuffle-database
sudo chown -R 1000:1000 shuffle-database
+# IF you get an error using 'chown', add the user first with 'sudo useradd opensearch'
```
4. Run docker-compose.
@@ -26,7 +27,12 @@ sudo chown -R 1000:1000 shuffle-database
docker-compose up -d
```
-When you're done, skip to the "After installation" step below.
+5. Recommended for Opensearch to work well
+```bash
+sudo sysctl -w vm.max_map_count=262144 # https://www.elastic.co/guide/en/elasticsearch/reference/current/vm-max-map-count.html
+```
+
+When you're done, skip to the [After installation](#after-installation) step below.
## Windows with WSL
This step is for setting up with Docker on windows from scratch.
@@ -57,7 +63,7 @@ https://shuffler.io/docs/configuration
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
-
+
### Useful info
* Check out [getting started](https://shuffler.io/docs/getting_started)
@@ -85,7 +91,7 @@ npm start
## Backend - Golang
http://localhost:5001 - REST API - requires [>=go1.13](https://golang.org/dl/)
```bash
-export SHUFFLE_OPENSEARCH_URL="http://localhost:9200"
+export SHUFFLE_OPENSEARCH_URL="https://localhost:9200"
export SHUFFLE_ELASTIC=true
export SHUFFLE_OPENSEARCH_USERNAME=admin
export SHUFFLE_OPENSEARCH_PASSWORD=admin
diff --git a/.github/push_nightly.sh b/.github/push_nightly.sh
index 92d5320b..b3660193 100644
--- a/.github/push_nightly.sh
+++ b/.github/push_nightly.sh
@@ -2,40 +2,79 @@
# Done manually for now since GHCR isn't being pushed to easily with the current Github action CI. Nightly = Latest IF we run hotfixes on latest
### Pull latest from ghcr CI/CD
-#docker pull ghcr.io/shuffle/shuffle-app_sdk:nightly
-#docker pull ghcr.io/shuffle/shuffle-worker:nightly
-#docker pull ghcr.io/shuffle/shuffle-orborus:nightly
-#docker pull ghcr.io/shuffle/shuffle-frontend:nightly
+docker pull ghcr.io/shuffle/shuffle-app_sdk:nightly
+docker pull ghcr.io/shuffle/shuffle-worker:nightly
+docker pull ghcr.io/shuffle/shuffle-orborus:nightly
+docker pull ghcr.io/shuffle/shuffle-frontend:nightly
#docker pull ghcr.io/shuffle/shuffle-backend:nightly
+#
+### NIGHTLY releases
+docker tag ghcr.io/shuffle/shuffle-app_sdk:nightly ghcr.io/frikky/shuffle-app_sdk:nightly
+docker tag ghcr.io/shuffle/shuffle-worker:nightly ghcr.io/frikky/shuffle-worker:nightly
+docker tag ghcr.io/shuffle/shuffle-orborus:nightly ghcr.io/frikky/shuffle-orborus:nightly
+docker tag ghcr.io/shuffle/shuffle-frontend:nightly ghcr.io/frikky/shuffle-frontend:nightly
+docker tag ghcr.io/shuffle/shuffle-backend:nightly ghcr.io/frikky/shuffle-backend:nightly
+
+docker push ghcr.io/frikky/shuffle-app_sdk:nightly
+docker push ghcr.io/frikky/shuffle-worker:nightly
+docker push ghcr.io/frikky/shuffle-orborus:nightly
+docker push ghcr.io/frikky/shuffle-frontend:nightly
+docker push ghcr.io/frikky/shuffle-backend:nightly
### LATEST releases:
-#docker tag ghcr.io/shuffle/shuffle-app_sdk:nightly ghcr.io/shuffle/shuffle-app_sdk:latest
-#docker tag ghcr.io/shuffle/shuffle-worker:nightly ghcr.io/shuffle/shuffle-worker:latest
-#docker tag ghcr.io/shuffle/shuffle-orborus:nightly ghcr.io/shuffle/shuffle-orborus:latest
-#docker tag ghcr.io/shuffle/shuffle-frontend:nightly ghcr.io/shuffle/shuffle-frontend:latest
-#docker tag ghcr.io/shuffle/shuffle-backend:nightly ghcr.io/shuffle/shuffle-backend:latest
-#
-#docker push ghcr.io/shuffle/shuffle-app_sdk:latest
-#docker push ghcr.io/shuffle/shuffle-worker:latest
-#docker push ghcr.io/shuffle/shuffle-orborus:latest
-#docker push ghcr.io/shuffle/shuffle-frontend:latest
-#docker push ghcr.io/shuffle/shuffle-backend:latest
+## shuffle/shuffle
+docker tag ghcr.io/shuffle/shuffle-app_sdk:nightly ghcr.io/shuffle/shuffle-app_sdk:latest
+docker tag ghcr.io/shuffle/shuffle-worker:nightly ghcr.io/shuffle/shuffle-worker:latest
+docker tag ghcr.io/shuffle/shuffle-orborus:nightly ghcr.io/shuffle/shuffle-orborus:latest
+docker tag ghcr.io/shuffle/shuffle-frontend:nightly ghcr.io/shuffle/shuffle-frontend:latest
+docker tag ghcr.io/shuffle/shuffle-backend:nightly ghcr.io/shuffle/shuffle-backend:latest
+
+docker push ghcr.io/shuffle/shuffle-app_sdk:latest
+docker push ghcr.io/shuffle/shuffle-worker:latest
+docker push ghcr.io/shuffle/shuffle-orborus:latest
+docker push ghcr.io/shuffle/shuffle-frontend:latest
+docker push ghcr.io/shuffle/shuffle-backend:latest
+
+## frikky/shuffle
+docker tag ghcr.io/shuffle/shuffle-app_sdk:nightly ghcr.io/frikky/shuffle-app_sdk:latest
+docker tag ghcr.io/shuffle/shuffle-worker:nightly ghcr.io/frikky/shuffle-worker:latest
+docker tag ghcr.io/shuffle/shuffle-orborus:nightly ghcr.io/frikky/shuffle-orborus:latest
+docker tag ghcr.io/shuffle/shuffle-frontend:nightly ghcr.io/frikky/shuffle-frontend:latest
+docker tag ghcr.io/shuffle/shuffle-backend:nightly ghcr.io/frikky/shuffle-backend:latest
+
+docker push ghcr.io/frikky/shuffle-app_sdk:latest
+docker push ghcr.io/frikky/shuffle-worker:latest
+docker push ghcr.io/frikky/shuffle-orborus:latest
+docker push ghcr.io/frikky/shuffle-frontend:latest
+docker push ghcr.io/frikky/shuffle-backend:latest
### 1.1.0 releases:
-#docker tag ghcr.io/shuffle/shuffle-app_sdk:nightly ghcr.io/shuffle/shuffle-app_sdk:1.1.0
-#docker tag ghcr.io/shuffle/shuffle-worker:nightly ghcr.io/shuffle/shuffle-worker:1.1.0
-#docker tag ghcr.io/shuffle/shuffle-orborus:nightly ghcr.io/shuffle/shuffle-orborus:1.1.0
-#docker tag ghcr.io/shuffle/shuffle-frontend:nightly ghcr.io/shuffle/shuffle-frontend:1.1.0
-#docker tag ghcr.io/shuffle/shuffle-backend:nightly ghcr.io/shuffle/shuffle-backend:1.1.0
-#
-#docker push ghcr.io/shuffle/shuffle-app_sdk:1.1.0
-#docker push ghcr.io/shuffle/shuffle-worker:1.1.0
-#docker push ghcr.io/shuffle/shuffle-orborus:1.1.0
-#docker push ghcr.io/shuffle/shuffle-frontend:1.1.0
-#docker push ghcr.io/shuffle/shuffle-backend:1.1.0
+## shuffle/shuffle
+docker tag ghcr.io/shuffle/shuffle-app_sdk:nightly ghcr.io/shuffle/shuffle-app_sdk:1.1.0
+docker tag ghcr.io/shuffle/shuffle-worker:nightly ghcr.io/shuffle/shuffle-worker:1.1.0
+docker tag ghcr.io/shuffle/shuffle-orborus:nightly ghcr.io/shuffle/shuffle-orborus:1.1.0
+docker tag ghcr.io/shuffle/shuffle-frontend:nightly ghcr.io/shuffle/shuffle-frontend:1.1.0
+docker tag ghcr.io/shuffle/shuffle-backend:nightly ghcr.io/shuffle/shuffle-backend:1.1.0
+docker push ghcr.io/shuffle/shuffle-app_sdk:1.1.0
+docker push ghcr.io/shuffle/shuffle-worker:1.1.0
+docker push ghcr.io/shuffle/shuffle-orborus:1.1.0
+docker push ghcr.io/shuffle/shuffle-frontend:1.1.0
+docker push ghcr.io/shuffle/shuffle-backend:1.1.0
+## frikky/shuffle
+docker tag ghcr.io/shuffle/shuffle-app_sdk:nightly ghcr.io/frikky/shuffle-app_sdk:1.1.0
+docker tag ghcr.io/shuffle/shuffle-worker:nightly ghcr.io/frikky/shuffle-worker:1.1.0
+docker tag ghcr.io/shuffle/shuffle-orborus:nightly ghcr.io/frikky/shuffle-orborus:1.1.0
+docker tag ghcr.io/shuffle/shuffle-frontend:nightly ghcr.io/frikky/shuffle-frontend:1.1.0
+docker tag ghcr.io/shuffle/shuffle-backend:nightly ghcr.io/frikky/shuffle-backend:1.1.0
+
+docker push ghcr.io/frikky/shuffle-app_sdk:1.1.0
+docker push ghcr.io/frikky/shuffle-worker:1.1.0
+docker push ghcr.io/frikky/shuffle-orborus:1.1.0
+docker push ghcr.io/frikky/shuffle-frontend:1.1.0
+docker push ghcr.io/frikky/shuffle-backend:1.1.0
### Manage worker-scale upload (Requires auth)
# This is supposed to be unavailable, and only be downloadable by customers
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
old mode 100644
new mode 100755
diff --git a/.github/workflows/docker-build.yaml b/.github/workflows/docker-build.yaml
deleted file mode 100644
index 716b42db..00000000
--- a/.github/workflows/docker-build.yaml
+++ /dev/null
@@ -1,66 +0,0 @@
-name: docker-build
-
-on:
- push:
- branches: launch
-jobs:
- main:
- runs-on: ubuntu-latest
- continue-on-error: ${{ matrix.experimental }}
- strategy:
- fail-fast: false
- matrix:
- include:
- - app: frontend
- path: frontend
- version: 1.0.0
- experimental: true
- - app: backend
- path: backend
- version: 1.0.0
- experimental: false
- - app: orborus
- path: functions/onprem/orborus
- version: 1.0.0
- experimental: false
- - app: database
- path: backend/database
- version: 1.0.0
- experimental: false
- steps:
- -
- name: Checkout
- uses: actions/checkout@v2
- -
- name: Set up QEMU
- uses: docker/setup-qemu-action@v1
- -
- name: Set up Docker Buildx
- uses: docker/setup-buildx-action@v1
- -
- name: Login to DockerHub
- uses: docker/login-action@v1
- with:
- username: ${{ secrets.DOCKERHUB_USERNAME }}
- password: ${{ secrets.DOCKERHUB_TOKEN }}
-# Use below configuration for ghcr.io
-# with:
-# registry: ghcr.io
-# username: ${{ github.repository_owner }}
-# password: ${{ secrets.CR_PAT }}
- -
- name: Build and push
- id: docker_build
- uses: docker/build-push-action@v2
- env:
- BUILDX_NO_DEFAULT_LOAD: true
- with:
- context: ${{ matrix.path }}/
- file: ${{ matrix.path }}/Dockerfile
- platforms: linux/amd64,linux/arm64
- #,linux/386 - no node image I guess?
- push: true
- tags: ${{ secrets.DOCKERHUB_USERNAME }}/${{ matrix.app }}:${{ matrix.version }}
- -
- name: Image digest
- run: echo ${{ steps.docker_build.outputs.digest }}
diff --git a/.github/workflows/dockerbuild.yaml b/.github/workflows/dockerbuild.yaml
index 0594f24b..4ef90c7a 100644
--- a/.github/workflows/dockerbuild.yaml
+++ b/.github/workflows/dockerbuild.yaml
@@ -2,7 +2,7 @@ name: dockerbuild
on:
push:
- branches: 1.2.0
+ branches: [main, 1.3.0]
jobs:
main:
runs-on: ubuntu-latest
diff --git a/.github/workflows/project_automation.yml b/.github/workflows/project_automation.yml
new file mode 100644
index 00000000..0e3bf945
--- /dev/null
+++ b/.github/workflows/project_automation.yml
@@ -0,0 +1,16 @@
+name: Automation - Add all new issues to roadmap project
+
+on:
+ issues:
+ types:
+ - opened
+
+jobs:
+ add-to-project:
+ name: Add issue to project
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/add-to-project@v0.5.0
+ with:
+ project-url: https://github.com/orgs/Shuffle/projects/8
+ github-token: ${{ secrets.ADD_TO_PROJECT_PAT }}
diff --git a/.github/workflows/snyk-container-analysis.yml b/.github/workflows/snyk-container-analysis.yml
old mode 100644
new mode 100755
diff --git a/.github/workflows/snyk-infrastructure-analysis.yml b/.github/workflows/snyk-infrastructure-analysis.yml
old mode 100644
new mode 100755
diff --git a/.gitignore b/.gitignore
old mode 100644
new mode 100755
diff --git a/LICENSE b/LICENSE
old mode 100644
new mode 100755
diff --git a/README.md b/README.md
old mode 100644
new mode 100755
index 797dd530..b62fac68
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
-[](https://shuffler.io)
+[](https://shuffler.io)
Shuffle Automation
diff --git a/SECURITY.md b/SECURITY.md
old mode 100644
new mode 100755
diff --git a/backend/Dockerfile b/backend/Dockerfile
old mode 100644
new mode 100755
diff --git a/backend/README.md b/backend/README.md
old mode 100644
new mode 100755
diff --git a/backend/app_gen/LICENSE b/backend/app_gen/LICENSE
old mode 100644
new mode 100755
diff --git a/backend/app_gen/openapi-parsers/misp.py b/backend/app_gen/openapi-parsers/misp.py
old mode 100644
new mode 100755
diff --git a/backend/app_gen/openapi-parsers/swimlane.py b/backend/app_gen/openapi-parsers/swimlane.py
old mode 100644
new mode 100755
diff --git a/backend/app_gen/openapi/README.md b/backend/app_gen/openapi/README.md
old mode 100644
new mode 100755
diff --git a/backend/app_gen/openapi/baseline/Dockerfile b/backend/app_gen/openapi/baseline/Dockerfile
old mode 100644
new mode 100755
diff --git a/backend/app_gen/openapi/baseline/requirements.txt b/backend/app_gen/openapi/baseline/requirements.txt
old mode 100644
new mode 100755
diff --git a/backend/app_gen/openapi/test.go b/backend/app_gen/openapi/test.go
old mode 100644
new mode 100755
diff --git a/backend/app_gen/openapi/testGCP.go b/backend/app_gen/openapi/testGCP.go
old mode 100644
new mode 100755
diff --git a/backend/app_gen/python-lib/README.md b/backend/app_gen/python-lib/README.md
old mode 100644
new mode 100755
diff --git a/backend/app_gen/python-lib/baseline/Dockerfile b/backend/app_gen/python-lib/baseline/Dockerfile
old mode 100644
new mode 100755
diff --git a/backend/app_gen/python-lib/baseline/docker-compose.yml b/backend/app_gen/python-lib/baseline/docker-compose.yml
old mode 100644
new mode 100755
diff --git a/backend/app_gen/python-lib/baseline/env.txt b/backend/app_gen/python-lib/baseline/env.txt
old mode 100644
new mode 100755
diff --git a/backend/app_gen/python-lib/baseline/requirements.txt b/backend/app_gen/python-lib/baseline/requirements.txt
old mode 100644
new mode 100755
diff --git a/backend/app_gen/python-lib/generator.py b/backend/app_gen/python-lib/generator.py
old mode 100644
new mode 100755
diff --git a/backend/app_gen/python-lib/requirements.txt b/backend/app_gen/python-lib/requirements.txt
old mode 100644
new mode 100755
diff --git a/backend/app_sdk/Dockerfile b/backend/app_sdk/Dockerfile
old mode 100644
new mode 100755
index 25c9aa40..d3709032
--- a/backend/app_sdk/Dockerfile
+++ b/backend/app_sdk/Dockerfile
@@ -1,5 +1,6 @@
#FROM python:3.9.1-alpine as base
FROM python:3.10.0-alpine as base
+#FROM python:3.11.3-alpine as base
FROM base as builder
RUN apk --no-cache add --update alpine-sdk libffi libffi-dev musl-dev openssl-dev tzdata coreutils
diff --git a/backend/app_sdk/Dockerfile_blackarch b/backend/app_sdk/Dockerfile_blackarch
old mode 100644
new mode 100755
diff --git a/backend/app_sdk/Dockerfile_kali b/backend/app_sdk/Dockerfile_kali
old mode 100644
new mode 100755
diff --git a/backend/app_sdk/LICENSE b/backend/app_sdk/LICENSE
old mode 100644
new mode 100755
diff --git a/backend/app_sdk/README.md b/backend/app_sdk/README.md
old mode 100644
new mode 100755
diff --git a/backend/app_sdk/__init__.py b/backend/app_sdk/__init__.py
old mode 100644
new mode 100755
diff --git a/backend/app_sdk/app_base.py b/backend/app_sdk/app_base.py
old mode 100644
new mode 100755
index 2f23755f..b0ffee23
--- a/backend/app_sdk/app_base.py
+++ b/backend/app_sdk/app_base.py
@@ -18,6 +18,10 @@ import urllib.parse
import jinja2
import datetime
import dateutil
+
+import threading
+import concurrent.futures
+
from io import StringIO as StringBuffer
from io import BytesIO
from liquid import Liquid, defaults
@@ -138,13 +142,47 @@ def escape(a):
a = str(a)
return json_escape(a)
+@shuffle_filters.register
+def neat_json(a):
+ try:
+ a = json.loads(a)
+ except:
+ pass
+
+ return json.dumps(a, indent=4, sort_keys=True)
+
@shuffle_filters.register
def flatten(a):
a = list(a)
- flat_list = [a for xs in xss for a in xs]
+ flat_list = [a for xs in a for a in xs]
return flat_list
+@shuffle_filters.register
+def last(a):
+ try:
+ a = json.loads(a)
+ except:
+ pass
+
+ if len(a) == 0:
+ return ""
+
+ return a[-1]
+
+@shuffle_filters.register
+def first(a):
+ try:
+ a = json.loads(a)
+ except:
+ pass
+
+ if len(a) == 0:
+ return ""
+
+ return a[0]
+
+
@shuffle_filters.register
def csv_parse(a):
a = str(a)
@@ -186,9 +224,37 @@ def csv_parse(a):
allitems.append(fullitem)
- return allitems
+ try:
+ return json.dumps(allitems)
+ except:
+ print("[ERROR] Failed dumping from JSON in csv parse")
+ return allitems
+
+@shuffle_filters.register
+def parse_csv(a):
+ return csv_parse(a)
+
+@shuffle_filters.register
+def format_csv(a):
+ return csv_parse(a)
+
+@shuffle_filters.register
+def csv_format(a):
+ return csv_parse(a)@standard_filter_manager.register
+
+@shuffle_filters.register
+def split(base, sep):
+ if not sep:
+ try:
+ return json.dumps(list(base))
+ except:
+ return list(base)
+
+ try:
+ return json.dumps(base.split(sep))
+ except:
+ return base.split(sep)
-#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())
@@ -205,6 +271,7 @@ def csv_parse(a):
###
###
+
class AppBase:
__version__ = None
app_name = None
@@ -327,13 +394,14 @@ class AppBase:
new_input = fixed_return
except Exception as e:
- self.logger.info(f"[ERROR] Failed to run magic parser (2): {e}")
+ # Not used anymore
+ #self.logger.info(f"[ERROR] Failed to run magic parser (2): {e}")
return input_data
try:
new_input = input_data.split()
except Exception as e:
- self.logger.info(f"[ERROR] Failed to run magic parser during split (1): {e}")
+ self.logger.info(f"[ERROR] Failed to run parser during split (1): {e}")
return input_data
# Won't ever touch this one?
@@ -389,9 +457,11 @@ class AppBase:
else:
self.logger.warning(f"[WARNING] Magic output not defined.")
except KeyError as e:
- self.logger.warning(f"[DEBUG] Failed to run magic autoparser (send result) - keyerror: {e}")
+ #self.logger.warning(f"[DEBUG] Failed to run magic autoparser (send result) - keyerror: {e}")
+ pass
except Exception as e:
- self.logger.warning(f"[DEBUG] Failed to run magic autoparser (send result): {e}")
+ #self.logger.warning(f"[DEBUG] Failed to run magic autoparser (send result): {e}")
+ pass
# Try it with some magic
@@ -401,15 +471,6 @@ class AppBase:
# FIXME: Add cleanup of parameters to not send to frontend here
params = {}
- #action = action_result["action"]
- #try:
- # for item in action["authentication"]:
- # for action["parameters"]
- # self.logger.info("AUTH: ", key, value)
- # params[item["key"]] = item["value"]
- #except KeyError:
- # self.logger.info("No authentication specified!")
- # pass
# I wonder if this actually works
self.logger.info(f"[DEBUG] Before last stream result")
@@ -417,7 +478,7 @@ class AppBase:
self.logger.info(f"[INFO] URL FOR RESULT (URL): {url}")
try:
- log_contents = "disabled: add env SHUFFLE_LOGS_DISABLED=true to Orborus to re-enable logs for apps"
+ log_contents = "disabled: add env SHUFFLE_LOGS_DISABLED=true to Orborus to re-enable logs for apps. Can not be enabled natively in Cloud except in Hybrid mode."
if not os.getenv("SHUFFLE_LOGS_DISABLED") == "true":
log_contents = self.log_capture_string.getvalue()
@@ -446,9 +507,9 @@ class AppBase:
finished = False
for i in range (0, 10):
try:
- ret = requests.post(url, headers=headers, json=action_result, timeout=10)
+ ret = requests.post(url, headers=headers, json=action_result, timeout=10, verify=False)
- self.logger.info(f"[DEBUG] Result: {ret.status_code} (break on 200)")
+ self.logger.info(f"[DEBUG] Result: {ret.status_code} (break on 200 or 201)")
if ret.status_code == 200 or ret.status_code == 201:
finished = True
break
@@ -493,7 +554,7 @@ class AppBase:
action_result["status"] = "FAILURE"
action_result["result"] = json.dumps({"success": False, "reason": "POST error: Failed connecting to %s over 10 retries to the backend" % url})
self.logger.info(f"[DEBUG] Before typeerror stream result - NOT finished after 10 requests")
- ret = requests.post("%s%s" % (self.base_url, stream_path), headers=headers, json=action_result)
+ ret = requests.post("%s%s" % (self.base_url, stream_path), headers=headers, json=action_result, verify=False)
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:
@@ -503,7 +564,7 @@ class AppBase:
action_result["result"] = json.dumps({"success": False, "reason": "Typeerror when sending to backend URL %s" % url})
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)
+ ret = requests.post("%s%s" % (self.base_url, stream_path), headers=headers, json=action_result, verify=False)
#self.logger.info(f"[DEBUG] Result: {ret.status_code}")
#if ret.status_code != 200:
# pr
@@ -632,7 +693,7 @@ class AppBase:
#self.logger.info(f"RET: {ret.text}")
#self.logger.info(f"ID: {ret.status_code}")
url = f"{self.url}/api/v1/orgs/{org_id}/validate_app_values"
- ret = requests.post(url, json=data)
+ ret = requests.post(url, json=data, verify=False)
if ret.status_code == 200:
json_value = ret.json()
if len(json_value["found"]) > 0:
@@ -971,11 +1032,9 @@ class AppBase:
for subparams in param_multiplier:
#self.logger.info(f"SUBPARAMS IN MULTI: {subparams}")
try:
- #tmp = await func(**subparams)
while True:
try:
- #tmp = await func(**subparams)
tmp = func(**subparams)
break
except TypeError as e:
@@ -988,7 +1047,7 @@ class AppBase:
try:
del subparams[field]
- self.logger.info("Removed field invalid field %s" % field)
+ self.logger.info("Removed invalid field %s (1)" % field)
except KeyError:
break
else:
@@ -1015,6 +1074,7 @@ class AppBase:
# An attempt at decomposing coroutine results
+ # Backwards compatibility
try:
if asyncio.iscoroutine(tmp):
self.logger.info("[DEBUG] In coroutine (2)")
@@ -1028,7 +1088,8 @@ class AppBase:
tmp = asyncio.run(parse_value(tmp))
else:
- self.logger.info("[DEBUG] Not in coroutine (2)")
+ #self.logger.info("[DEBUG] Not in coroutine (2)")
+ pass
except Exception as e:
self.logger.warning("[ERROR] Failed to parse coroutine value for old app: {e}")
@@ -1102,10 +1163,11 @@ class AppBase:
get_path = "/api/v1/files/namespaces/%s?execution_id=%s&ids=true" % (category, self.full_execution["execution_id"])
headers = {
- "Authorization": "Bearer %s" % self.authorization
+ "Authorization": "Bearer %s" % self.authorization,
+ "User-Agent": "Shuffle 1.1.0",
}
- ret = requests.get("%s%s" % (self.url, get_path), headers=headers)
+ ret = requests.get("%s%s" % (self.url, get_path), headers=headers, verify=False)
return ret.json()
#if ret1.status_code != 200:
# return {
@@ -1127,10 +1189,11 @@ class AppBase:
get_path = "/api/v1/files/namespaces/%s?execution_id=%s" % (namespace, self.full_execution["execution_id"])
headers = {
- "Authorization": "Bearer %s" % self.authorization
+ "Authorization": "Bearer %s" % self.authorization,
+ "User-Agent": "Shuffle 1.1.0",
}
- ret1 = requests.get("%s%s" % (self.url, get_path), headers=headers)
+ ret1 = requests.get("%s%s" % (self.url, get_path), headers=headers, verify=False)
if ret1.status_code != 200:
return None
@@ -1189,10 +1252,11 @@ class AppBase:
get_path = "/api/v1/files/%s?execution_id=%s" % (item, full_execution["execution_id"])
headers = {
"Content-Type": "application/json",
- "Authorization": "Bearer %s" % self.authorization
+ "Authorization": "Bearer %s" % self.authorization,
+ "User-Agent": "Shuffle 1.1.0",
}
- ret1 = requests.get("%s%s" % (self.url, get_path), headers=headers)
+ ret1 = requests.get("%s%s" % (self.url, get_path), headers=headers, verify=False)
self.logger.info("RET1 (file get): %s" % ret1.text)
if ret1.status_code != 200:
returns.append({
@@ -1203,7 +1267,7 @@ class AppBase:
continue
content_path = "/api/v1/files/%s/content?execution_id=%s" % (item, full_execution["execution_id"])
- ret2 = requests.get("%s%s" % (self.url, content_path), headers=headers)
+ ret2 = requests.get("%s%s" % (self.url, content_path), headers=headers, verify=False)
self.logger.info("RET2 (file get) done")
if ret2.status_code == 200:
tmpdata = ret1.json()
@@ -1239,7 +1303,7 @@ class AppBase:
"value": str(value),
}
- response = requests.post(url, json=data)
+ response = requests.post(url, json=data, verify=False)
try:
allvalues = response.json()
allvalues["key"] = key
@@ -1261,7 +1325,7 @@ class AppBase:
"key": key,
}
- value = requests.post(url, json=data)
+ value = requests.post(url, json=data, verify=False)
try:
allvalues = value.json()
self.logger.info("VAL1: ", allvalues)
@@ -1291,7 +1355,8 @@ class AppBase:
org_id = full_execution["workflow"]["execution_org"]["id"]
headers = {
"Content-Type": "application/json",
- "Authorization": "Bearer %s" % self.authorization
+ "Authorization": "Bearer %s" % self.authorization,
+ "User-Agent": "Shuffle 1.1.0",
}
if not isinstance(infiles, list):
@@ -1314,7 +1379,7 @@ class AppBase:
self.logger.info(f"KeyError in file setup: {e}")
pass
- ret = requests.post("%s%s" % (self.url, create_path), headers=headers, json=data)
+ ret = requests.post("%s%s" % (self.url, create_path), headers=headers, json=data, verify=False)
#self.logger.info(f"Ret CREATE: {ret.text}")
cur_id = ""
if ret.status_code == 200:
@@ -1337,6 +1402,7 @@ class AppBase:
new_headers = {
"Authorization": f"Bearer {self.authorization}",
+ "User-Agent": "Shuffle 1.1.0",
}
upload_path = "/api/v1/files/%s/upload?execution_id=%s" % (cur_id, full_execution["execution_id"])
@@ -1345,7 +1411,7 @@ class AppBase:
files={"shuffle_file": (filename, curfile["data"])}
#open(filename,'rb')}
- ret = requests.post("%s%s" % (self.url, upload_path), files=files, headers=new_headers)
+ ret = requests.post("%s%s" % (self.url, upload_path), files=files, headers=new_headers, verify=False)
self.logger.info("Ret UPLOAD: %s" % ret.text)
self.logger.info("Ret2 UPLOAD: %d" % ret.status_code)
@@ -1383,7 +1449,8 @@ class AppBase:
headers = {
"Content-Type": "application/json",
- "Authorization": f"Bearer {self.authorization}"
+ "Authorization": f"Bearer {self.authorization}",
+ "User-Agent": "Shuffle 1.1.0",
}
if len(self.action) == 0:
@@ -1411,7 +1478,7 @@ class AppBase:
# FIXME: Shouldn't skip this, but it's good for minimzing API calls
#try:
- # ret = requests.post("%s%s" % (self.base_url, stream_path), headers=headers, json=action_result)
+ # ret = requests.post("%s%s" % (self.base_url, stream_path), headers=headers, json=action_result, verify=False)
# self.logger.info("Workflow: %d" % ret.status_code)
# if ret.status_code != 200:
# self.logger.info(ret.text)
@@ -1426,7 +1493,7 @@ class AppBase:
# If found, we get the full results list from backend
fullexecution = {}
if isinstance(self.full_execution, str) and len(self.full_execution) == 0:
- self.logger.info("[DEBUG] NO EXECUTION - LOADING!")
+ #self.logger.info("[DEBUG] NO EXECUTION - LOADING!")
try:
failed = False
rettext = ""
@@ -1440,7 +1507,8 @@ class AppBase:
ret = requests.post(
"%s/api/v1/streams/results" % (self.base_url),
headers=headers,
- json=tmpdata
+ json=tmpdata,
+ verify=False
)
if ret.status_code == 200:
@@ -1448,12 +1516,13 @@ class AppBase:
failed = False
break
- elif ret.status_code == 500:
+ #elif ret.status_code == 500 or ret.status_code == 400:
+ elif ret.status_code >= 400:
self.logger.info("[ERROR] (fails: %d) Error in app with status code %d for results (1). RETRYING because results can't be handled" % (i+1, ret.status_code))
rettext = ret.text
failed = True
- time.sleep(10)
+ time.sleep(8)
continue
else:
@@ -1461,6 +1530,7 @@ class AppBase:
rettext = ret.text
failed = True
+ time.sleep(8)
break
if failed:
@@ -1497,6 +1567,13 @@ class AppBase:
self.full_execution = fullexecution
+ #try:
+ # if "backend_url" in self.full_execution:
+ # self.url = self.full_execution["backend_url"]
+ # self.base_url = self.full_execution["backend_url"]
+ #except KeyError:
+ # pass
+
try:
if replace_params == True:
for inner_action in self.full_execution["workflow"]["actions"]:
@@ -1522,7 +1599,7 @@ class AppBase:
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)")
+ self.logger.info(f"[DEBUG] AFTER FULLEXEC stream result (init): {self.current_execution_id}")
# Gets the value at the parenthesis level you want
def parse_nested_param(string, level):
@@ -1708,7 +1785,7 @@ class AppBase:
# Because liquid can handle ALL of this now.
# Implemented for >0.9.25
- self.logger.info("[DEBUG] Skipping parser because use of its been deprecated >0.9.25 due to Liquid implementation")
+ #self.logger.info("[DEBUG] Skipping parser because use of its been deprecated >0.9.25 due to Liquid implementation")
return data, False
wrappers = ["int", "number", "lower", "upper", "trim", "strip", "split", "parse", "len", "length", "lenght", "join", "replace"]
@@ -1761,7 +1838,7 @@ class AppBase:
else:
parse_string = inner_result
- print("PARSE STRING: %s" % parse_string)
+ #print("PARSE STRING: %s" % parse_string)
return parse_string, True
# Looks for parantheses to grab special cases within a string, e.g:
@@ -1900,7 +1977,7 @@ class AppBase:
if isinstance(seconditem, int):
seconditem = str(seconditem)
- print("[DEBUG] ACTUAL PARSED: %s" % actualitem)
+ #print("[DEBUG] ACTUAL PARSED: %s" % actualitem)
# Means it's a single item -> continue
if seconditem == "":
@@ -2022,13 +2099,13 @@ class AppBase:
actionname_lower = parsersplit[0][1:].lower()
#Actionname: Start_node
- print(f"\n[INFO] Actionname: {actionname_lower}")
+ #print(f"\n[INFO] Actionname: {actionname_lower}")
# 1. Find the action
baseresult = ""
appendresult = ""
- print("[INFO] Parsersplit length: %d" % len(parsersplit))
+ #print("[INFO] Parsersplit length: %d" % len(parsersplit))
if (actionname_lower.startswith("exec ") or actionname_lower.startswith("webhook ") or actionname_lower.startswith("schedule ") or actionname_lower.startswith("userinput ") or actionname_lower.startswith("email_trigger ") or actionname_lower.startswith("trigger ")) and len(parsersplit) == 1:
record = False
for char in actionname_lower:
@@ -2071,7 +2148,6 @@ class AppBase:
print("[DEBUG] No results to get values from.")
baseresult = "$" + parsersplit[0][1:]
- print("[DEBUG] BEFORE VARIABLES!")
if len(baseresult) == 0:
try:
for variable in execution_data["workflow"]["workflow_variables"]:
@@ -2082,13 +2158,12 @@ class AppBase:
break
except KeyError as e:
- print("[INFO] KeyError wf variables: %s" % e)
+ #print("[INFO] KeyError wf variables: %s" % e)
pass
except TypeError as e:
- print("[INFO] TypeError wf variables: %s" % e)
+ #print("[INFO] TypeError wf variables: %s" % e)
pass
- print("[DEBUG] BEFORE EXECUTION VAR")
if len(baseresult) == 0:
try:
for variable in execution_data["execution_variables"]:
@@ -2106,14 +2181,14 @@ class AppBase:
except KeyError as error:
print(f"[DEBUG] KeyError in JSON: {error}")
- print(f"[INFO] After first trycatch. Baseresult")#, baseresult)
+ #print(f"[INFO] After first trycatch. Baseresult")#, baseresult)
# 2. Find the JSON data
# Returns if there isn't any JSON in the base ($nodename)
if len(baseresult) == 0:
return ""+appendresult, False
- print("[INFO] After second return")
+ #print("[INFO] After second return")
# Returns if the result is JUST something like $nodename, not $nodename.value
if len(parsersplit) == 1:
returndata = str(baseresult)+str(appendresult)
@@ -2124,7 +2199,7 @@ class AppBase:
baseresult = baseresult.replace(" False", " false,")
# Tries to actually read it as JSON with some stupid formatting
- print("[INFO] After third parser return - Formatted")#, baseresult)
+ #print("[INFO] After third parser return - Formatted")#, baseresult)
basejson = {}
try:
basejson = json.loads(baseresult)
@@ -2148,7 +2223,6 @@ class AppBase:
print("[WARNING] Parseditem issue: %s" % e)
pass
- print("[DEBUG] DATA: (%s) %s" % (type(data), data))
if is_loop:
print("[DEBUG] DATA IS A LOOP - SHOULD WRAP")
if parsersplit[-1] == "#":
@@ -2160,7 +2234,6 @@ class AppBase:
parseditem = "${%s%s}$" % (parsersplit[-1], json.dumps(data))
- print("[DEBUG] Before last return with %s" % appendresult)
returndata = str(parseditem)+str(appendresult)
# New in 0.8.97: Don't return items without lists
@@ -2409,9 +2482,11 @@ class AppBase:
newvalue[key] = recurse_cleanup_script(value)
except json.decoder.JSONDecodeError as e:
- print(f"[WARNING] Failed JSON replacement for OpenAPI keys (3) {e}")
+ # Since here the data isn't at all JSON compatible..?
+ # Seems to happen with newlines in variables being parsed in as strings?
+ print(f"[ERROR] Failed JSON replacement for OpenAPI keys (3) {e}. Value: {data}")
except Exception as e:
- print(f"[WARNING] Failed as an exception (1): {e}")
+ print(f"[ERROR] Failed as an exception (1): {e}")
try:
for deletekey in deletekeys:
@@ -2476,9 +2551,17 @@ class AppBase:
except:
self.logger.info("Error in initial replacement of escaped dollar!")
- # Basic fix in case variant isn't set
+ paramname = ""
try:
- self.logger.info(f"[DEBUG] Parameter variant: {parameter['variant']} of length {len(parameter['value'])}")
+ paramname = parameter["name"]
+ except:
+ pass
+
+ # Basic fix in case variant isn't set
+ # Variant is ALWAYS STATIC_VALUE from mid 2021~
+ try:
+ #self.logger.info(f"[DEBUG] Parameter '{paramname}' of length {len(parameter['value'])}")
+ parameter["variant"] = parameter["variant"]
except:
parameter["variant"] = "STATIC_VALUE"
@@ -2651,19 +2734,6 @@ class AppBase:
return True
return False
- #if tmp == "[]":
- # tmp = []
-
- #if type(tmp) == list and len(tmp) == 0 and not flip:
- # new_list.append(item)
- #elif type(tmp) == list and len(tmp) > 0 and flip:
- # new_list.append(item)
- #elif type(tmp) == str and not tmp and not flip:
- # new_list.append(item)
- #elif type(tmp) == str and tmp and flip:
- # new_list.append(item)
- #else:
- # failed_list.append(item)
elif check.lower() == "contains_any_of":
newvalue = [destinationvalue.lower()]
@@ -2729,6 +2799,7 @@ class AppBase:
return True, ""
except Exception as error:
self.logger.info(f"[WARNING] Failed checking startnode: {error}")
+ return True, ""
available_checks = [
"=",
@@ -2783,9 +2854,6 @@ class AppBase:
correct_branches += 1
continue
- # FIXME: Check if the previous node has a result or not
-
- #self.logger.info("[DEBUG] Relevant conditions: %s" % branch["conditions"])
successful_conditions = []
failed_conditions = []
successful_conditions = 0
@@ -2798,27 +2866,20 @@ class AppBase:
check, sourcevalue, is_loop = parse_params(action, fullexecution, condition["source"], self)
if check:
continue
- return False, {"success": False, "reason": "Failed condition: %s %s %s because %s" % (sourcevalue, condition["condition"]["value"], destinationvalue, check)}
- #sourcevalue = sourcevalue.encode("utf-8")
sourcevalue = parse_wrapper_start(sourcevalue, self)
destinationvalue = condition["destination"]["value"]
check, destinationvalue, is_loop = parse_params(action, fullexecution, condition["destination"], self)
if check:
continue
- return False, {"success": False, "reason": "Failed condition: %s %s %s because %s" % (sourcevalue, condition["condition"]["value"], destinationvalue, check)}
- #destinationvalue = destinationvalue.encode("utf-8")
destinationvalue = parse_wrapper_start(destinationvalue, self)
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"]))
continue
- #self.logger.info(destinationvalue)
- # NEGATE
-
# Configuration = negated because of WorkflowAppActionParam..
validation = run_validation(sourcevalue, condition["condition"]["value"], destinationvalue)
try:
@@ -2830,12 +2891,6 @@ class AppBase:
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)}
-
- #self.logger.info("CONDITIONS VS SUCCESS: %d vs %d" % (total_conditions, successful_conditions))
-
if total_conditions == successful_conditions:
correct_branches += 1
@@ -2845,15 +2900,9 @@ class AppBase:
if matching_branches > 0 and correct_branches > 0:
return True, ""
- # FIXME: Check if previous branches are at all finished
-
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, ""
-
#
#
@@ -2881,7 +2930,7 @@ class AppBase:
# Checks whether conditions are met, otherwise set
branchcheck, tmpresult = check_branch_conditions(action, fullexecution, self)
if isinstance(tmpresult, object) or isinstance(tmpresult, list) or isinstance(tmpresult, dict):
- self.logger.info("[DEBUG] Fixing branch return as object -> string")
+ #self.logger.info("[DEBUG] Fixing branch return as object -> string")
try:
#tmpresult = tmpresult.replace("'", "\"")
tmpresult = json.dumps(tmpresult)
@@ -2904,7 +2953,8 @@ class AppBase:
if " " in actionname:
actionname.replace(" ", "_", -1)
- #print(action)
+ #print("ACTION: ", action)
+ #print("exec: ", self.full_execution)
#if action.generated:
# actionname = actionname.lower()
@@ -2916,7 +2966,8 @@ class AppBase:
self.action_result["status"] = "FAILURE"
self.action_result["result"] = json.dumps({
"success": False,
- "reason": f"Function {actionname} doesn't exist.",
+ "reason": f"Function {actionname} doesn't exist, or the App is out of date.",
+ "details": "If this persists, please restart delete the Docker image locally, restart your Orborus instance and then try again to force-download the latest version. Contact support@shuffler.io with this data if the issue persists.",
})
elif callable(func):
try:
@@ -2931,12 +2982,14 @@ class AppBase:
# What variables are necessary here tho hmm
params = {}
- try:
- for item in action["authentication"]:
- #self.logger.info("AUTH: ", key, value)
- params[item["key"]] = item["value"]
- except KeyError:
- self.logger.info("[DEBUG] No authentication specified!")
+ # This replacement should happen in backend as part of params
+ # error log is useless
+ #try:
+ # for item in action["authentication"]:
+ # self.logger.info("AUTH PARAM: ", key, value)
+ # #params[item["key"]] = item["value"]
+ #except KeyError as e:
+ # self.logger.info(f"[WARNING] No authentication specified! Is this correct? err: {e}")
# Fixes OpenAPI body parameters for later.
newparams = []
@@ -3007,13 +3060,12 @@ class AppBase:
pass
- self.logger.info(f"""HANDLING {action["parameters"][counter]["value"]}""")
+ self.logger.info(f"""HANDLING BODY: {action["parameters"][counter]["value"]}""")
action["parameters"][counter]["value"] = recurse_cleanup_script(action["parameters"][counter]["value"])
#self.logger.info(action["parameters"])
# This seems redundant now
- self.logger.info("[DEBUG] Pre parameters")
for parameter in newparams:
action["parameters"].append(parameter)
@@ -3035,7 +3087,6 @@ class AppBase:
# Multi_parameter has the data for each. variable
minlength = 0
- self.logger.info("[DEBUG] Pre-loading parameters")
multi_parameters = json.loads(json.dumps(params))
multiexecution = False
multi_execution_lists = []
@@ -3293,9 +3344,6 @@ class AppBase:
# 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("'"):
@@ -3428,8 +3476,73 @@ class AppBase:
break
try:
- newres = func(**params)
+ #try:
+ # Individual functions shouldn't take longer than this
+ # This is an attempt to make timeouts occur less, incentivizing users to make use efficient API's
+ # PS: Not implemented for lists - only single actions as of May 2023
+ timeout = 30
+
+ # Check if current app is Shuffle Tools, then set to 55 due to certain actions being slow (ioc parser..)
+ #uu In general, this should be disabled for onprem
+ if self.action["app_name"].lower() == "shuffle tools":
+ timeout = 55
+
+ timeout = 30
+
+ try:
+ executor = concurrent.futures.ThreadPoolExecutor()
+ future = executor.submit(func, **params)
+ newres = future.result(timeout)
+
+ if not future.done():
+ # The future is still running, so we need to cancel it
+ future.cancel()
+ newres = json.dumps({
+ "success": False,
+ "reason": "Timeout error within %d seconds. This happens if we can't reach or use the API you're trying to use within the time limit." % timeout,
+ "exception": str(e),
+ })
+
+ else:
+ # The future is done, so we can just get the result from newres :)
+ #newres = future.result()
+ #print("Future is done!")
+ pass
+
+ except concurrent.futures.TimeoutError as e:
+ newres = json.dumps({
+ "success": False,
+ "reason": "Timeout error within %d seconds (2). This happens if we can't reach or use the API you're trying to use within the time limit" % timeout
+ })
+
break
+
+
+
+ #thread = threading.Thread(target=func, args=(**params,))
+ #thread.start()
+
+ #thread.join(timeout)
+
+ #if thread.is_alive():
+ # # The thread is still running, so we need to stop it
+ # # You can handle this as needed, such as raising an exception
+ # timeout_handler()
+
+
+ #with Timeout(timeout):
+ # newres = func(**params)
+ # break
+ #except Timeout.Timeout as e:
+ # self.logger.info(f"[DEBUG] Timeout error: {e}")
+ # newres = json.dumps({
+ # "success": False,
+ # "reason": "Timeout error within %d seconds. This typically happens if we can't reach the API you're trying to reach." % timeout,
+ # "exception": str(e),
+ # })
+
+ # break
+
except TypeError as e:
newres = ""
self.logger.info(f"[DEBUG] Got exec type error: {e}")
@@ -3442,7 +3555,7 @@ class AppBase:
errorstring = f"{e}"
if "the JSON object must be" in errorstring:
- self.logger.info("[ERROR] Something is wrong with the input for this function. Are lists and JSON data handled parsed properly (0)?")
+ self.logger.info("[ERROR] Something is wrong with the input for this function. Are lists and JSON data handled parsed properly (0)? the JSON object must be in...")
try:
e = json.loads(f"{e}")
except:
@@ -3461,7 +3574,7 @@ class AppBase:
try:
del params[field]
- self.logger.info("[WARNING] Removed field invalid field %s" % field)
+ self.logger.info("[WARNING] Removed invalid field %s (2)" % field)
except KeyError:
break
else:
@@ -3472,7 +3585,7 @@ class AppBase:
})
break
except Exception as e:
- self.logger.info("[ERROR] Something is wrong with the input for this function. Are lists and JSON data handled parsed properly (1)?")
+ self.logger.info(f"[ERROR] Something is wrong with the input for this function. Are lists and JSON data handled parsed properly (1)? err: {e}")
try:
e = json.loads(f"{e}")
@@ -3499,7 +3612,8 @@ class AppBase:
newres = asyncio.run(parse_value(newres))
else:
- self.logger.info("[DEBUG] Not in coroutine (1)")
+ #self.logger.info("[DEBUG] Not in coroutine (1)")
+ pass
except Exception as e:
self.logger.warning("[ERROR] Failed to parse coroutine value for old app: {e}")
@@ -3553,7 +3667,7 @@ class AppBase:
result += "Failed autocasting. Can't handle %s type from function. Must be string" % type(newres)
self.logger.info("Can't handle type %s value from function" % (type(newres)))
- self.logger.info("[INFO] POST NEWRES RESULT!")#, result)
+ #self.logger.info("[INFO] POST NEWRES RESULT!")#, result)
else:
#self.logger.info("[INFO] APP_SDK DONE: Starting MULTI execution (length: %d) with values %s" % (minlength, multi_parameters))
# 1. Use number of executions based on the arrays being similar
@@ -3712,7 +3826,6 @@ class AppBase:
def execute():
if request.method == "POST":
#print(request.get_json(force=True))
- #print("DATA: ", request.data)
requestdata = {}
try:
requestdata = json.loads(request.data)
@@ -3725,7 +3838,6 @@ class AppBase:
#logger.info(f"[DEBUG] Datatype: {type(requestdata)}: {requestdata}")
# Remaking class for each request
- #print(f"APP: {app}")
app = cls(redis=None, logger=logger, console_logger=logger)
extra_info = ""
@@ -3754,7 +3866,7 @@ class AppBase:
# BASE URL (backend)
try:
app.url = requestdata["url"]
- logger.info(f"BACKEND URL: {app.url}")
+ logger.info(f"BACKEND URL (url): {app.url}")
except Exception as e:
logger.info(f"[ERROR] Failed parsing url (backend): {e}")
extra_info += f"\n{e}"
@@ -3762,7 +3874,7 @@ class AppBase:
# URL (worker)
try:
app.base_url = requestdata["base_url"]
- logger.info(f"WORKER URL: {app.base_url}")
+ logger.info(f"WORKER URL (base url): {app.base_url}")
except Exception as e:
logger.info(f"[ERROR] Failed parsing base url (worker): {e}")
extra_info += f"\n{e}"
@@ -3840,11 +3952,7 @@ class AppBase:
else:
self.logger.info("ACTION TYPE (unhandled): %s" % type(action))
- #await app.execute_action(app.action)
app.execute_action(app.action)
- #app.run(host="0.0.0.0", port=33334)
-
if __name__ == "__main__":
AppBase.run()
- #asyncio.run(AppBase.run(), debug=True)
diff --git a/backend/app_sdk/build.sh b/backend/app_sdk/build.sh
old mode 100644
new mode 100755
index 1ff45c75..dd540b94
--- a/backend/app_sdk/build.sh
+++ b/backend/app_sdk/build.sh
@@ -2,7 +2,7 @@
### DEFAULT
NAME=shuffle-app_sdk
-VERSION=1.1.0
+VERSION=1.2.0
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 -t shuffle/shuffle:app_sdk -t shuffle/$NAME:$VERSION -t docker.pkg.github.com/shuffle/shuffle/$NAME:$VERSION -t ghcr.io/shuffle/$NAME:$VERSION -t ghcr.io/shuffle/$NAME:nightly
diff --git a/backend/app_sdk/requirements.txt b/backend/app_sdk/requirements.txt
index bacb3bc8..803bbbe6 100644
--- a/backend/app_sdk/requirements.txt
+++ b/backend/app_sdk/requirements.txt
@@ -1,5 +1,5 @@
urllib3==1.26.5
-requests==2.25.1
+requests==2.31.0
MarkupSafe==2.0.1
liquidpy==0.7.6
flask[async]==2.0.2
diff --git a/backend/build.sh b/backend/build.sh
old mode 100644
new mode 100755
index bb1f4a28..17c372c7
--- a/backend/build.sh
+++ b/backend/build.sh
@@ -1,10 +1,10 @@
#!/bin/sh
docker stop shuffle-backend
docker rm shuffle-backend
-docker rmi frikky/shuffle:backend
+docker rmi ghcr.io/shuffle/shuffle-backend:nightly
-docker build . -t frikky/shuffle:backend
-docker push frikky/shuffle:backend
+docker build . -t ghcr.io/shuffle/shuffle-backend:nightly
+docker push ghcr.io/shuffle/shuffle-backend:nightly
echo "Starting server"
#docker run -it \
diff --git a/backend/database/opensearch/docker-compose.yml b/backend/database/opensearch/docker-compose.yml
old mode 100644
new mode 100755
diff --git a/backend/go-app/docker.go b/backend/go-app/docker.go
old mode 100644
new mode 100755
index f9c27eef..a6324faf
--- a/backend/go-app/docker.go
+++ b/backend/go-app/docker.go
@@ -241,16 +241,15 @@ func buildImageMemory(fs billy.Filesystem, tags []string, dockerfileFolder strin
BuildArgs: map[string]*string{},
Labels: labels,
}
-
// NetworkMode: "host",
httpProxy := os.Getenv("HTTP_PROXY")
if len(httpProxy) > 0 {
- buildOptions.BuildArgs["http_proxy"] = &httpProxy
+ buildOptions.BuildArgs["HTTP_PROXY"] = &httpProxy
}
httpsProxy := os.Getenv("HTTPS_PROXY")
if len(httpProxy) > 0 {
- buildOptions.BuildArgs["https_proxy"] = &httpsProxy
+ buildOptions.BuildArgs["HTTPS_PROXY"] = &httpsProxy
}
// Build the actual image
@@ -352,7 +351,7 @@ func buildImage(tags []string, dockerfileFolder string) error {
httpProxy := os.Getenv("HTTP_PROXY")
if len(httpProxy) > 0 {
- buildOptions.BuildArgs["http_proxy"] = &httpProxy
+ buildOptions.BuildArgs["HTTP_PROXY"] = &httpProxy
}
httpsProxy := os.Getenv("HTTPS_PROXY")
if len(httpProxy) > 0 {
@@ -386,170 +385,6 @@ func buildImage(tags []string, dockerfileFolder string) error {
return nil
}
-// FIXME - very specific for webhooks. Make it easier?
-func stopWebhook(image string, identifier string) error {
- ctx := context.Background()
-
- containername := fmt.Sprintf("%s-%s", image, identifier)
-
- cli, err := client.NewEnvClient()
- if err != nil {
- log.Println("Unable to create docker client")
- return err
- }
-
- // containers, err := cli.ContainerList(ctx, types.ContainerListOptions{
- // All: true,
- // })
-
- if err := cli.ContainerStop(ctx, containername, nil); err != nil {
- log.Printf("Unable to stop container %s - running removal anyway, just in case: %s", containername, err)
- }
-
- removeOptions := types.ContainerRemoveOptions{
- RemoveVolumes: true,
- Force: true,
- }
-
- if err := cli.ContainerRemove(ctx, containername, removeOptions); err != nil {
- log.Printf("Unable to remove container: %s", err)
- }
-
- return nil
-}
-
-// Starts a new webhook
-func handleStopHookDocker(resp http.ResponseWriter, request *http.Request) {
- cors := shuffle.HandleCors(resp, request)
- if cors {
- return
- }
-
- location := strings.Split(request.URL.String(), "/")
-
- var fileId string
- if location[1] == "api" {
- if len(location) <= 4 {
- resp.WriteHeader(401)
- resp.Write([]byte(`{"success": false}`))
- return
- }
-
- fileId = location[4]
- }
-
- if len(fileId) != 32 {
- resp.WriteHeader(401)
- resp.Write([]byte(`{"success": false, "message": "ID not valid"}`))
- return
- }
-
- ctx := context.Background()
- hook, err := shuffle.GetHook(ctx, fileId)
- if err != nil {
- log.Printf("Failed getting hook %s (stop docker): %s", fileId, err)
- resp.WriteHeader(401)
- resp.Write([]byte(`{"success": false}`))
- return
- }
-
- log.Printf("Status: %s", hook.Status)
- log.Printf("Running: %t", hook.Running)
- if !hook.Running {
- message := fmt.Sprintf("Error: %s isn't running", hook.Id)
- log.Println(message)
- resp.WriteHeader(401)
- resp.Write([]byte(fmt.Sprintf(`{"success": false, "message": "%s"}`, message)))
- return
- }
-
- hook.Status = "stopped"
- hook.Running = false
- hook.Actions = []shuffle.HookAction{}
- err = shuffle.SetHook(ctx, *hook)
- if err != nil {
- log.Printf("Failed setting hook: %s", err)
- resp.WriteHeader(401)
- resp.Write([]byte(`{"success": false}`))
- return
- }
-
- image := "webhook"
-
- // This is here to force stop and remove the old webhook
- err = stopWebhook(image, fileId)
- if err != nil {
- log.Printf("Container stop issue for %s-%s: %s", image, fileId, err)
- }
-
- resp.WriteHeader(200)
- resp.Write([]byte(`{"success": true, "message": "Stopped webhook"}`))
-}
-
-// THis is an example
-// Can also be used as base data?
-var webhook = `{
- "id": "d6ef8912e8bd37776e654cbc14c2629c",
- "info": {
- "url": "http://localhost:5001",
- "name": "TheHive",
- "description": "Webhook for TheHive"
- },
- "transforms": {},
- "actions": {},
- "type": "webhook",
- "running": false,
- "status": "stopped"
-}`
-
-// Starts a new webhook
-func handleDeleteHookDocker(resp http.ResponseWriter, request *http.Request) {
- ctx := context.Background()
- cors := shuffle.HandleCors(resp, request)
- if cors {
- return
- }
-
- location := strings.Split(request.URL.String(), "/")
-
- var fileId string
- if location[1] == "api" {
- if len(location) <= 4 {
- resp.WriteHeader(401)
- resp.Write([]byte(`{"success": false}`))
- return
- }
-
- fileId = location[4]
- }
-
- if len(fileId) != 32 {
- resp.WriteHeader(401)
- resp.Write([]byte(`{"success": false, "message": "ID not valid"}`))
- return
- }
-
- err := shuffle.DeleteKey(ctx, "hooks", fileId)
- if err != nil {
- resp.WriteHeader(401)
- resp.Write([]byte(`{"success": false, "message": "Can't delete"}`))
- return
- }
-
- image := "webhook"
-
- // This is here to force stop and remove the old webhook
- err = stopWebhook(image, fileId)
- if err != nil {
- log.Printf("Container stop issue for %s-%s: %s", image, fileId, err)
- resp.Write([]byte(`{"success": false, "message": "Couldn't stop webhook"}`))
- return
- }
-
- resp.WriteHeader(200)
- resp.Write([]byte(`{"success": true, "message": "Deleted webhook"}`))
-}
-
// Checks if an image exists
func imageCheckBuilder(images []string) error {
//log.Printf("[FIXME] ImageNames to check: %#v", images)
@@ -595,32 +430,7 @@ func imageCheckBuilder(images []string) error {
return nil
}
-func hookTest() {
- var hook shuffle.Hook
- err := json.Unmarshal([]byte(webhook), &hook)
- log.Println(webhook)
- if err != nil {
- log.Printf("Failed hook unmarshaling: %s", err)
- return
- }
-
- ctx := context.Background()
- err = shuffle.SetHook(ctx, hook)
- if err != nil {
- log.Printf("Failed setting hook: %s", err)
- }
-
- returnHook, err := shuffle.GetHook(ctx, hook.Id)
- if err != nil {
- log.Printf("Failed getting hook %s (test): %s", hook.Id, err)
- }
-
- if len(returnHook.Id) > 0 {
- log.Printf("Success! - %s", returnHook.Id)
- }
-}
-
-//https://stackoverflow.com/questions/23935141/how-to-copy-docker-images-from-one-host-to-another-without-using-a-repository
+// 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 := shuffle.HandleCors(resp, request)
if cors {
@@ -643,14 +453,9 @@ func getDockerImage(resp http.ResponseWriter, request *http.Request) {
return
}
- type requestCheck struct {
- Name string `datastore:"name" json:"name" yaml:"name"`
- }
-
// This has to be done in a weird way because Datastore doesn't
// support map[string]interface and similar (openapi3.Swagger)
- var version requestCheck
-
+ var version shuffle.DockerRequestCheck
err = json.Unmarshal(body, &version)
if err != nil {
resp.WriteHeader(422)
@@ -684,8 +489,11 @@ func getDockerImage(resp http.ResponseWriter, request *http.Request) {
alternativeName = strings.Join(alternativeNameSplit[1:3], "/")
}
+ log.Printf("[INFO] Trying to download image: %s. Alt: %s", version.Name, alternativeName)
+
for _, image := range images {
for _, tag := range image.RepoTags {
+ //log.Printf("[DEBUG] Tag: %s", tag)
if strings.ToLower(tag) == strings.ToLower(version.Name) {
img = image
tagFound = tag
@@ -699,11 +507,34 @@ func getDockerImage(resp http.ResponseWriter, request *http.Request) {
}
}
+ pullOptions := types.ImagePullOptions{}
+ if len(img.ID) == 0 {
+ _, err := dockercli.ImagePull(context.Background(), version.Name, pullOptions)
+ if err == nil {
+ tagFound = version.Name
+ img.ID = version.Name
+ img2.ID = version.Name
+
+ dockercli.ImageTag(ctx, version.Name, alternativeName)
+ }
+ }
+
+ if len(img2.ID) == 0 {
+ _, err := dockercli.ImagePull(context.Background(), alternativeName, pullOptions)
+ if err == nil {
+ tagFound = alternativeName
+ img.ID = alternativeName
+ img2.ID = alternativeName
+
+ dockercli.ImageTag(ctx, alternativeName, version.Name)
+ }
+ }
+
// REBUILDS THE APP
if len(img.ID) == 0 {
if len(img2.ID) == 0 {
workflowapps, err := shuffle.GetAllWorkflowApps(ctx, 0, 0)
- //log.Printf("[INFO] Getting workflowapps for a rebuild. Got %d with err %#v", len(workflowapps), err)
+ log.Printf("[INFO] Getting workflowapps for a rebuild. Got %d with err %#v", len(workflowapps), err)
if err == nil {
imageName := ""
imageVersion := ""
@@ -728,7 +559,7 @@ func getDockerImage(resp http.ResponseWriter, request *http.Request) {
foundApp := shuffle.WorkflowApp{}
imageName = strings.ToLower(imageName)
imageVersion = strings.ToLower(imageVersion)
- log.Printf("[DEBUG] Looking for appname %s with version %s", imageName, imageVersion)
+ log.Printf("[DEBUG] Docker Looking for appname %s with version %s", imageName, imageVersion)
for _, app := range workflowapps {
if strings.ToLower(strings.Replace(app.Name, " ", "_", -1)) == imageName && app.AppVersion == imageVersion {
@@ -848,7 +679,7 @@ func handleRemoteDownloadApp(resp http.ResponseWriter, ctx context.Context, user
app := tmpapp{}
err := json.Unmarshal(respBody, &app)
if err != nil || app.Success == false || len(app.OpenAPI) == 0 {
- log.Printf("[ERROR] Failed app unmarshal during auto-download. Success%#v. Applength: %d: %s", app.Success, len(app.OpenAPI), err)
+ log.Printf("[ERROR] Failed app unmarshal during auto-download. Success: %#v. Applength: %d: %s", app.Success, len(app.OpenAPI), err)
resp.WriteHeader(401)
resp.Write([]byte(`{"success": false, "reason": "App doesn't exist"}`))
return
@@ -966,37 +797,29 @@ func activateWorkflowAppDocker(resp http.ResponseWriter, request *http.Request)
}
}
- if app.Sharing || app.Public {
- org, err := shuffle.GetOrg(ctx, user.ActiveOrg.Id)
- if err == nil {
- added := false
- if !shuffle.ArrayContains(org.ActiveApps, app.ID) {
- org.ActiveApps = append(org.ActiveApps, app.ID)
- added = true
- }
-
- if added {
- err = shuffle.SetOrg(ctx, *org, org.Id)
- if err != nil {
- log.Printf("[WARNING] Failed setting org when autoadding apps on save: %s", err)
- } else {
- log.Printf("[INFO] Added public app %s (%s) to org %s (%s)", app.Name, app.ID, user.ActiveOrg.Name, user.ActiveOrg.Id)
- cacheKey := fmt.Sprintf("apps_%s", user.Id)
- shuffle.DeleteCache(ctx, cacheKey)
- }
- }
- }
- } else {
- log.Printf("[WARNING] User is trying to activate %s which is NOT public", app.Name)
- resp.WriteHeader(401)
- resp.Write([]byte(`{"success": false}`))
+ // Just making sure it's being built properly
+ if app == nil {
+ log.Printf("[WARNING] App is nil. This shouldn't happen. Starting remote download(3)")
+ handleRemoteDownloadApp(resp, ctx, user, fileId)
return
}
- log.Printf("[DEBUG] App %s (%s) activated for org %s by user %s", app.Name, app.ID, user.ActiveOrg.Id, user.Username)
+ // Check the app.. hmm
+ openApiApp, err := shuffle.GetOpenApiDatastore(ctx, app.ID)
+ if err != nil {
+ log.Printf("[WARNING] Error getting app %s (openapi config): %s", app.ID, err)
+ resp.WriteHeader(401)
+ resp.Write([]byte(`{"success": false, "reason": "Couldn't find app OpenAPI"}`))
+ return
+ }
- // If onprem, it should autobuild the container(s) from here
+ log.Printf("[INFO] User %s (%s) is activating %s. Public: %t, Shared: %t", user.Username, user.Id, app.Name, app.Public, app.Sharing)
+ buildSwaggerApp(resp, []byte(openApiApp.Body), user, true)
- resp.WriteHeader(200)
- resp.Write([]byte(`{"success": true}`))
+ //app.Active = true
+ //app.Generated = true
+ //app, err := shuffle.SetApp(ctx, app)
+
+ //resp.WriteHeader(200)
+ //resp.Write([]byte(`{"success": true}`))
}
diff --git a/backend/go-app/go.mod b/backend/go-app/go.mod
old mode 100644
new mode 100755
index 822b91e0..b43db79b
--- a/backend/go-app/go.mod
+++ b/backend/go-app/go.mod
@@ -1,100 +1,105 @@
-module main
+module shuffle-shared
+
+replace github.com/shuffle/shuffle-shared => ../../../../git/shuffle-shared
go 1.19
-//replace github.com/shuffle/shuffle-shared => ../../../shuffle-shared
-
require (
- cloud.google.com/go/datastore v1.10.0
- cloud.google.com/go/pubsub v1.28.0
- cloud.google.com/go/storage v1.28.1
+ cloud.google.com/go/datastore v1.11.0
+ cloud.google.com/go/pubsub v1.31.0
+ cloud.google.com/go/storage v1.30.1
github.com/basgys/goxml2json v1.1.0
github.com/carlescere/scheduler v0.0.0-20170109141437-ee74d2f83d82
- github.com/docker/docker v20.10.21+incompatible
+ github.com/docker/docker v24.0.2+incompatible
github.com/frikky/kin-openapi v0.42.0
- github.com/fsouza/go-dockerclient v1.9.0
+ github.com/fsouza/go-dockerclient v1.9.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.5.0
+ github.com/go-git/go-billy/v5 v5.4.1
+ github.com/go-git/go-git/v5 v5.7.0
github.com/gorilla/mux v1.8.0
github.com/h2non/filetype v1.1.3
github.com/satori/go.uuid v1.2.0
- github.com/shuffle/shuffle-shared v0.3.35
- golang.org/x/crypto v0.3.0
- google.golang.org/api v0.103.0
+ github.com/shuffle/shuffle-shared v0.4.19
+ golang.org/x/crypto v0.9.0
+ google.golang.org/api v0.125.0
google.golang.org/appengine v1.6.7
- google.golang.org/grpc v1.51.0
+ google.golang.org/grpc v1.55.0
gopkg.in/src-d/go-git.v4 v4.13.1
gopkg.in/yaml.v3 v3.0.1
)
require (
- cloud.google.com/go v0.105.0 // indirect
- cloud.google.com/go/compute v1.13.0 // indirect
- cloud.google.com/go/compute/metadata v0.2.1 // indirect
- cloud.google.com/go/iam v0.7.0 // indirect
+ cloud.google.com/go v0.110.2 // indirect
+ cloud.google.com/go/compute v1.19.3 // indirect
+ cloud.google.com/go/compute/metadata v0.2.3 // indirect
+ cloud.google.com/go/iam v1.0.1 // indirect
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
github.com/Masterminds/semver v1.5.0 // indirect
github.com/Microsoft/go-winio v0.6.0 // indirect
- github.com/Microsoft/hcsshim v0.9.3 // indirect
- github.com/ProtonMail/go-crypto v0.0.0-20221026131551-cf6655e29de4 // indirect
- github.com/acomagu/bufpipe v1.0.3 // indirect
+ github.com/ProtonMail/go-crypto v0.0.0-20230518184743-7afd39499903 // indirect
+ github.com/acomagu/bufpipe v1.0.4 // indirect
github.com/adrg/strutil v0.2.3 // indirect
github.com/algolia/algoliasearch-client-go/v3 v3.18.1 // indirect
+ github.com/bitly/go-simplejson v0.5.0 // indirect
github.com/bradfitz/gomemcache v0.0.0-20221031212613-62deef7fc822 // indirect
github.com/bradfitz/slice v0.0.0-20180809154707-2b758aa73013 // indirect
- github.com/cloudflare/circl v1.1.0 // indirect
- github.com/containerd/cgroups v1.0.3 // indirect
- github.com/containerd/containerd v1.6.6 // indirect
- github.com/docker/distribution v2.7.1+incompatible // indirect
+ github.com/cloudflare/circl v1.3.3 // indirect
+ github.com/containerd/containerd v1.6.18 // indirect
+ github.com/docker/distribution v2.8.2+incompatible // indirect
github.com/docker/go-connections v0.4.0 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/emirpasic/gods v1.18.1 // indirect
- github.com/frikky/go-elasticsearch/v8 v8.13.1 // indirect
- github.com/go-git/gcfg v1.5.0 // indirect
+ github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-openapi/jsonpointer v0.19.5 // indirect
github.com/go-openapi/swag v0.19.5 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
- github.com/golang/protobuf v1.5.2 // indirect
+ github.com/golang/protobuf v1.5.3 // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/google/go-github/v28 v28.1.1 // indirect
github.com/google/go-querystring v1.0.0 // indirect
+ github.com/google/s2a-go v0.1.4 // indirect
github.com/google/uuid v1.3.0 // indirect
- github.com/googleapis/enterprise-certificate-proxy v0.2.0 // indirect
- github.com/googleapis/gax-go/v2 v2.7.0 // indirect
- github.com/imdario/mergo v0.3.13 // indirect
+ github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect
+ github.com/googleapis/gax-go/v2 v2.10.0 // indirect
+ github.com/imdario/mergo v0.3.15 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/kevinburke/ssh_config v1.2.0 // indirect
- github.com/mailru/easyjson v0.7.0 // indirect
- github.com/moby/sys/mount v0.3.3 // indirect
- github.com/moby/sys/mountinfo v0.6.2 // indirect
+ github.com/klauspost/compress v1.11.13 // indirect
+ github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e // indirect
+ github.com/moby/patternmatcher v0.5.0 // indirect
+ github.com/moby/sys/sequential v0.5.0 // indirect
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect
github.com/morikuni/aec v1.0.0 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 // indirect
- github.com/opencontainers/runc v1.1.2 // indirect
+ github.com/opencontainers/runc v1.1.5 // indirect
+ github.com/opensearch-project/opensearch-go v1.1.0 // indirect
+ github.com/opensearch-project/opensearch-go/v2 v2.3.0 // indirect
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
- github.com/pjbgf/sha1cd v0.2.0 // indirect
+ github.com/pjbgf/sha1cd v0.3.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/sergi/go-diff v1.1.0 // indirect
github.com/sirupsen/logrus v1.8.1 // indirect
- github.com/skeema/knownhosts v1.1.0 // indirect
+ github.com/skeema/knownhosts v1.1.1 // indirect
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect
github.com/src-d/gcfg v1.4.0 // indirect
- github.com/xanzy/ssh-agent v0.3.2 // indirect
+ github.com/xanzy/ssh-agent v0.3.3 // indirect
go.opencensus.io v0.24.0 // indirect
go4.org v0.0.0-20201209231011-d4a079459e60 // indirect
- golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
- golang.org/x/net v0.2.0 // indirect
- golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783 // indirect
- golang.org/x/sync v0.1.0 // indirect
- golang.org/x/sys v0.2.0 // indirect
- golang.org/x/text v0.4.0 // indirect
- golang.org/x/tools v0.1.12 // indirect
+ golang.org/x/mod v0.8.0 // indirect
+ golang.org/x/net v0.10.0 // indirect
+ golang.org/x/oauth2 v0.8.0 // indirect
+ golang.org/x/sync v0.2.0 // indirect
+ golang.org/x/sys v0.8.0 // indirect
+ golang.org/x/text v0.9.0 // indirect
+ golang.org/x/tools v0.6.0 // indirect
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
- google.golang.org/genproto v0.0.0-20221202195650-67e5cbc046fd // indirect
- google.golang.org/protobuf v1.28.1 // indirect
+ google.golang.org/genproto v0.0.0-20230530153820-e85fd2cbaebc // indirect
+ google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc // indirect
+ google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc // indirect
+ google.golang.org/protobuf v1.30.0 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
+
)
diff --git a/backend/go-app/main.go b/backend/go-app/main.go
old mode 100644
new mode 100755
index 4c9b21b5..ee4622d9
--- a/backend/go-app/main.go
+++ b/backend/go-app/main.go
@@ -13,7 +13,7 @@ import (
//"crypto/tls"
//"crypto/x509"
- "encoding/base64"
+ //"encoding/base64"
"encoding/hex"
"encoding/json"
"errors"
@@ -21,7 +21,6 @@ import (
"io"
"io/ioutil"
"log"
- "math/rand"
"net/http"
"net/url"
"os"
@@ -38,9 +37,6 @@ import (
"cloud.google.com/go/storage"
"google.golang.org/appengine/mail"
- //"github.com/elastic/go-elasticsearch/v7"
- //"github.com/elastic/go-elasticsearch/v8/esapi"
-
"github.com/frikky/kin-openapi/openapi2"
"github.com/frikky/kin-openapi/openapi2conv"
"github.com/frikky/kin-openapi/openapi3"
@@ -82,18 +78,14 @@ import (
var gceProject = "shuffle"
var bucketName = "shuffler.appspot.com"
var baseAppPath = "/home/frikky/git/shaffuru/tmp/apps"
+
var baseDockerName = "frikky/shuffle"
var registryName = "registry.hub.docker.com"
var runningEnvironment = "onprem"
var syncUrl = "https://shuffler.io"
-
-//var syncUrl = "http://localhost:5002"
var syncSubUrl = "https://shuffler.io"
-//var syncUrl = "http://localhost:5002"
-//var syncSubUrl = "https://050196912a9d.ngrok.io"
-
var dbclient *datastore.Client
type Userapi struct {
@@ -701,9 +693,9 @@ func createNewUser(username, password, role, apikey string, org shuffle.OrgMini)
for tutorialIndex, tutorial := range neworg.Tutorials {
if tutorial.Name == "Invite teammates" {
neworg.Tutorials[tutorialIndex].Description = fmt.Sprintf("%d users are in your org. Org name and Image change next.", len(neworg.Users))
- if len(neworg.Users) > 0 {
+ if len(neworg.Users) > 1 {
neworg.Tutorials[tutorialIndex].Done = true
- neworg.Tutorials[tutorialIndex].Link = "/admin"
+ neworg.Tutorials[tutorialIndex].Link = "/admin?tab=users"
}
break
@@ -796,7 +788,7 @@ func handleRegister(resp http.ResponseWriter, request *http.Request) {
CloudSync: false,
}
- err = shuffle.SetOrg(ctx, newOrg, orgId)
+ err = shuffle.SetOrg(ctx, newOrg, newOrg.Id)
if err != nil {
log.Printf("[WARNING] Failed setting init organization: %s", err)
} else {
@@ -948,48 +940,104 @@ func handleInfo(resp http.ResponseWriter, request *http.Request) {
})
// Updating user info if there's something wrong
- if (len(userInfo.ActiveOrg.Name) == 0 || len(userInfo.ActiveOrg.Id) == 0) && len(userInfo.Orgs) > 0 {
- _, err := shuffle.GetOrg(ctx, userInfo.Orgs[0])
- if err != nil {
+ if len(userInfo.ActiveOrg.Name) == 0 || len(userInfo.ActiveOrg.Id) == 0 {
+ if len(userInfo.Orgs) == 0 || (len(userInfo.Orgs) > 0 && userInfo.Orgs[0] == "") {
orgs, err := shuffle.GetAllOrgs(ctx)
- if err == nil {
- newStringOrgs := []string{}
- newOrgs := []shuffle.Org{}
+ log.Printf("[INFO] Fixing organization for user %s (%s). Found orgs: %d", userInfo.Username, userInfo.Id, len(orgs))
+ if err == nil && len(orgs) > 0 {
for _, org := range orgs {
- if strings.ToLower(org.Name) == strings.ToLower(userInfo.Orgs[0]) {
- newOrgs = append(newOrgs, org)
- newStringOrgs = append(newStringOrgs, org.Id)
+ if len(org.Id) == 0 {
+ continue
}
- }
- if len(newOrgs) > 0 {
+ // Prolly some way here to jump into another org
+ // when you have access to the DB
userInfo.ActiveOrg = shuffle.OrgMini{
- Id: newOrgs[0].Id,
- Name: newOrgs[0].Name,
- }
-
- userInfo.Orgs = newStringOrgs
-
- err = shuffle.SetUser(ctx, &userInfo, true)
- if err != nil {
- log.Printf("Error patching User for activeOrg: %s", err)
- } else {
- log.Printf("Updated the users' org")
+ Name: org.Name,
+ Id: org.Id,
+ Role: "admin",
}
+ userInfo.Orgs = []string{org.Id}
+ break
}
- } else {
- log.Printf("Failed getting orgs for user. Major issue.: %s", err)
}
- } else {
- // 1. Check if the org exists by ID
- // 2. if it does, overwrite user
- userInfo.ActiveOrg = shuffle.OrgMini{
- Id: userInfo.Orgs[0],
+ // Make a new one in case we couldn't find one
+ if len(userInfo.ActiveOrg.Id) == 0 {
+ orgSetupName := "default"
+ orgId := uuid.NewV4().String()
+ newOrg := shuffle.Org{
+ Name: orgSetupName,
+ Id: orgId,
+ Org: orgSetupName,
+ Users: []shuffle.User{},
+ Roles: []string{"admin", "user"},
+ CloudSync: false,
+ }
+
+ err = shuffle.SetOrg(ctx, newOrg, newOrg.Id)
+ if err == nil {
+ userInfo.ActiveOrg = shuffle.OrgMini{
+ Name: newOrg.Name,
+ Id: newOrg.Id,
+ Role: "admin",
+ }
+ userInfo.Orgs = []string{newOrg.Id}
+ } else {
+ log.Printf("[WARNING] Failed to set new org: %s", err)
+ }
}
+
+ // Set user
err = shuffle.SetUser(ctx, &userInfo, true)
if err != nil {
- log.Printf("[INFO] Error patching User for activeOrg: %s", err)
+ log.Printf("[WARNING] Failed fixing org info for user %s (%s)", userInfo.Username, userInfo.Id)
+ } else {
+ log.Printf("[INFO] Set organization for %s (%s) to be %s (%s)", userInfo.Username, userInfo.Id, userInfo.ActiveOrg.Name, userInfo.ActiveOrg.Id)
+ }
+ } else if len(userInfo.Orgs) > 0 && userInfo.Orgs[0] != "" {
+ _, err := shuffle.GetOrg(ctx, userInfo.Orgs[0])
+ if err != nil {
+ orgs, err := shuffle.GetAllOrgs(ctx)
+ if err == nil {
+ newStringOrgs := []string{}
+ newOrgs := []shuffle.Org{}
+ for _, org := range orgs {
+ if strings.ToLower(org.Name) == strings.ToLower(userInfo.Orgs[0]) {
+ newOrgs = append(newOrgs, org)
+ newStringOrgs = append(newStringOrgs, org.Id)
+ }
+ }
+
+ if len(newOrgs) > 0 {
+ userInfo.ActiveOrg = shuffle.OrgMini{
+ Id: newOrgs[0].Id,
+ Name: newOrgs[0].Name,
+ }
+
+ userInfo.Orgs = newStringOrgs
+
+ err = shuffle.SetUser(ctx, &userInfo, true)
+ if err != nil {
+ log.Printf("Error patching User for activeOrg: %s", err)
+ } else {
+ log.Printf("Updated the users' org")
+ }
+ }
+ } else {
+ log.Printf("Failed getting orgs for user. Major issue.: %s", err)
+ }
+
+ } else {
+ // 1. Check if the org exists by ID
+ // 2. if it does, overwrite user
+ userInfo.ActiveOrg = shuffle.OrgMini{
+ Id: userInfo.Orgs[0],
+ }
+ err = shuffle.SetUser(ctx, &userInfo, true)
+ if err != nil {
+ log.Printf("[INFO] Error patching User for activeOrg: %s", err)
+ }
}
}
}
@@ -1052,15 +1100,18 @@ func handleInfo(resp http.ResponseWriter, request *http.Request) {
chatDisabled = true
}
+ userOrgs = shuffle.SortOrgList(userOrgs)
orgPriorities := org.Priorities
- if len(org.Priorities) < 5 {
- log.Printf("[WARNING] Should find and add priorities as length is less than 5 for org %s", userInfo.ActiveOrg.Id)
+ if len(org.Priorities) < 10 {
+ log.Printf("[WARNING] Should find and add priorities as length is less than 10 for org %s", userInfo.ActiveOrg.Id)
newPriorities, err := shuffle.GetPriorities(ctx, userInfo, org)
if err != nil {
log.Printf("[WARNING] Failed getting new priorities for org %s: %s", org.Id, err)
//orgPriorities = []shuffle.Priority{}
} else {
orgPriorities = newPriorities
+
+ // A way to manage them over time
}
}
@@ -1225,15 +1276,6 @@ 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 := shuffle.HandleCors(resp, request)
if cors {
@@ -1278,51 +1320,7 @@ func checkAdminLogin(resp http.ResponseWriter, request *http.Request) {
// Should run calculations
if len(org.SSOConfig.OpenIdAuthorization) > 0 {
- 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")))
- }
-
- if len(os.Getenv("SSO_REDIRECT_URL")) > 0 {
- redirectUrl = url.QueryEscape(fmt.Sprintf("%s/api/v1/login_openid", os.Getenv("SSO_REDIRECT_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)
-
- if len(org.SSOConfig.OpenIdClientSecret) > 0 {
-
- //baseSSOUrl += fmt.Sprintf("?client_id=%s&response_type=code&scope=openid&redirect_uri=%s&state=%s&client_secret=%s", org.SSOConfig.OpenIdClientId, redirectUrl, state, org.SSOConfig.OpenIdClientSecret)
- state := base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("org=%s&redirect=%s&challenge=%s", org.Id, redirectUrl, org.SSOConfig.OpenIdClientSecret)))
- log.Printf("URL: %s", redirectUrl)
-
- baseSSOUrl += fmt.Sprintf("?client_id=%s&response_type=id_token&scope=openid&redirect_uri=%s&state=%s&response_mode=form_post&nonce=%s", org.SSOConfig.OpenIdClientId, redirectUrl, state, state)
- //baseSSOUrl += fmt.Sprintf("&client_secret=%s", org.SSOConfig.OpenIdClientSecret)
- log.Printf("[DEBUG] Found OpenID url (client secret). Extra redirect check: %s - %s", request.URL.String(), baseSSOUrl)
- } else {
- log.Printf("[DEBUG] Found OpenID url (PKCE!!). Extra redirect check: %s", request.URL.String())
- 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)
- }
+ baseSSOUrl = shuffle.GetOpenIdUrl(request, *org)
break
}
@@ -1339,156 +1337,6 @@ func checkAdminLogin(resp http.ResponseWriter, request *http.Request) {
resp.Write([]byte(fmt.Sprintf(`{"success": true, "reason": "redirect", "sso_url": "%s"}`, baseSSOUrl)))
}
-func handleLogin(resp http.ResponseWriter, request *http.Request) {
- cors := shuffle.HandleCors(resp, request)
- if cors {
- return
- }
-
- // Gets a struct of Username, password
- data, err := shuffle.ParseLoginParameters(resp, request)
- if err != nil {
- resp.WriteHeader(401)
- resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err)))
- return
- }
-
- log.Printf("[INFO] Handling login of %s", data.Username)
-
- err = checkUsername(data.Username)
- if err != nil {
- resp.WriteHeader(401)
- resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err)))
- return
- }
-
- ctx := context.Background()
- log.Printf("[INFO] Login Username: %s", data.Username)
- users, err := shuffle.FindUser(ctx, strings.ToLower(strings.TrimSpace(data.Username)))
- if err != nil && len(users) == 0 {
- log.Printf("[WARNING] Failed getting user %s: %s", data.Username, err)
- resp.WriteHeader(401)
- resp.Write([]byte(`{"success": false, "reason": "Username and/or password is incorrect"}`))
- return
- }
-
- if len(users) != 1 {
- log.Printf(`Found multiple or no users with the same username: %s: %d`, data.Username, len(users))
- resp.WriteHeader(401)
- resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Error: %d users with username %s"}`, len(users), data.Username)))
- return
- }
-
- Userdata := users[0]
-
- err = bcrypt.CompareHashAndPassword([]byte(Userdata.Password), []byte(data.Password))
- if err != nil {
- log.Printf("Password for %s is incorrect: %s", data.Username, err)
- resp.WriteHeader(401)
- resp.Write([]byte(`{"success": false, "reason": "Username and/or password is incorrect"}`))
- return
- }
-
- if !Userdata.Active {
- log.Printf("%s is not active, but tried to login. Error: %v", data.Username, err)
- resp.WriteHeader(401)
- resp.Write([]byte(`{"success": false, "reason": "This user is deactivated"}`))
- return
- }
-
- tutorialsFinished := []shuffle.Tutorial{}
- for _, tutorial := range Userdata.PersonalInfo.Tutorials {
- tutorialsFinished = append(tutorialsFinished, shuffle.Tutorial{
- Name: tutorial,
- })
- }
- returnValue := shuffle.HandleInfo{
- Success: true,
- Tutorials: tutorialsFinished,
- }
-
- loginData := `{"success": true}`
- newData, err := json.Marshal(returnValue)
- if err == nil {
- loginData = string(newData)
- }
-
- if len(Userdata.Session) != 0 {
- log.Println("[INFO] User session already exists - resetting it")
- expiration := time.Now().Add(3600 * time.Second)
-
- http.SetCookie(resp, &http.Cookie{
- Name: "session_token",
- Value: Userdata.Session,
- Expires: expiration,
- })
-
- returnValue.Cookies = append(returnValue.Cookies, shuffle.SessionCookie{
- Key: "session_token",
- Value: Userdata.Session,
- Expiration: expiration.Unix(),
- })
-
- loginData = fmt.Sprintf(`{"success": true, "cookies": [{"key": "session_token", "value": "%s", "expiration": %d}]}`, Userdata.Session, expiration.Unix())
- newData, err := json.Marshal(returnValue)
- if err == nil {
- loginData = string(newData)
- }
- //log.Printf("SESSION LENGTH MORE THAN 0 IN LOGIN: %s", Userdata.Session)
-
- err = shuffle.SetSession(ctx, Userdata, Userdata.Session)
- if err != nil {
- log.Printf("Error adding session to database: %s", err)
- }
-
- resp.WriteHeader(200)
- resp.Write([]byte(loginData))
- return
- } else {
- log.Printf("[INFO] User session is empty - create one!")
-
- sessionToken := uuid.NewV4().String()
- expiration := time.Now().Add(3600 * time.Second)
- http.SetCookie(resp, &http.Cookie{
- Name: "session_token",
- Value: sessionToken,
- Expires: expiration,
- })
-
- // ADD TO DATABASE
- err = shuffle.SetSession(ctx, Userdata, sessionToken)
- if err != nil {
- log.Printf("Error adding session to database: %s", err)
- }
-
- Userdata.Session = sessionToken
- err = shuffle.SetUser(ctx, &Userdata, true)
- if err != nil {
- log.Printf("Failed updating user when setting session: %s", err)
- resp.WriteHeader(500)
- resp.Write([]byte(`{"success": false}`))
- return
- }
-
- returnValue.Cookies = append(returnValue.Cookies, shuffle.SessionCookie{
- Key: "session_token",
- Value: sessionToken,
- Expiration: expiration.Unix(),
- })
-
- loginData = fmt.Sprintf(`{"success": true, "cookies": [{"key": "session_token", "value": "%s", "expiration": %d}]}`, sessionToken, expiration.Unix())
- newData, err := json.Marshal(returnValue)
- if err == nil {
- loginData = string(newData)
- }
- }
-
- log.Printf("[INFO] %s SUCCESSFULLY LOGGED IN with session %s", data.Username, Userdata.Session)
-
- resp.WriteHeader(200)
- resp.Write([]byte(loginData))
-}
-
func fixOrgUser(ctx context.Context, org *shuffle.Org) *shuffle.Org {
//found := false
//for _, id := range user.Orgs {
@@ -1590,7 +1438,7 @@ func fixUserOrg(ctx context.Context, user *shuffle.User) *shuffle.User {
org.Users = append(org.Users, *user)
}
- err = shuffle.SetOrg(ctx, *org, orgId)
+ err = shuffle.SetOrg(ctx, *org, org.Id)
if err != nil {
log.Printf("Failed setting org %s", orgId)
}
@@ -2211,6 +2059,15 @@ func handleWebhookCallback(resp http.ResponseWriter, request *http.Request) {
}
}
+ // Find user agent header
+ userAgent := request.Header.Get("User-Agent")
+ if strings.Contains(strings.ToLower(userAgent), "microsoftpreview") || strings.Contains(strings.ToLower(userAgent), "googlebot") {
+ log.Printf("[AUDIT] Blocking googlebot and microsoftbot for webhooks. UA: '%s'", userAgent)
+ resp.WriteHeader(400)
+ resp.Write([]byte(`{"success": false, "reason": "Google/Microsoft preview bots not allowed. Please change the useragent."}`))
+ return
+ }
+
// ID: webhook_
if len(hookId) != 44 {
log.Printf("[INFO] Couldn't handle hookId. Too short in webhook: %d", len(hookId))
@@ -2296,7 +2153,7 @@ func handleWebhookCallback(resp http.ResponseWriter, request *http.Request) {
if err == nil {
for _, branch := range workflow.Branches {
if branch.SourceID == hook.Id {
- log.Printf("[INFO] Found ID %s for hook", hook.Id)
+ log.Printf("[DEBUG] Found ID %s for hook", hook.Id)
if branch.DestinationID != hook.Start {
newBody.Start = branch.DestinationID
break
@@ -2308,57 +2165,76 @@ func handleWebhookCallback(resp http.ResponseWriter, request *http.Request) {
b, err := json.Marshal(newBody)
if err != nil {
- log.Printf("Failed newBody marshaling: %s", err)
- resp.WriteHeader(401)
+ log.Printf("[ERROR] Failed newBody marshaling for webhook: %s", err)
+ resp.WriteHeader(500)
resp.Write([]byte(`{"success": false}`))
return
}
+ // Should wrap the response input Body as well?
for _, item := range hook.Workflows {
- //log.Printf("Running webhook for workflow %s with startnode %s", item, hook.Start)
+ log.Printf("[INFO] Running webhook for workflow %s with startnode %s", item, hook.Start)
+
+ // This ID is empty to force it to get the webhook within the execution
workflow := shuffle.Workflow{
ID: "",
}
- //parsedBody := string(body)
- //parsedBody = strings.Replace(parsedBody, "\"", "\\\"", -1)
- //if len(parsedBody) > 0 {
- // if string(parsedBody[0]) == `"` && string(parsedBody[len(parsedBody)-1]) == "\"" {
- // parsedBody = parsedBody[1 : len(parsedBody)-1]
- // }
- //}
-
- //bodyWrapper := fmt.Sprintf(`{"start": "%s", "execution_source": "webhook", "execution_argument": "%s"}`, hook.Start, string(parsedBody))
- //if len(hook.Start) == 0 {
- // log.Printf("No start node for hook %s - running with workflow default.", hook.Id)
- // bodyWrapper = string(parsedBody)
- //}
+ if len(hook.Start) == 0 {
+ log.Printf("[WARNING] No start node for hook %s - running with workflow default.", hook.Id)
+ //bodyWrapper = string(parsedBody)
+ }
newRequest := &http.Request{
URL: &url.URL{},
Method: "POST",
Body: ioutil.NopCloser(bytes.NewReader(b)),
}
- //start, startok := request.URL.Query()["start"]
// OrgId: activeOrgs[0].Id,
workflowExecution, executionResp, err := handleExecution(item, workflow, newRequest, hook.OrgId)
- if err == nil {
- /*
- err = increaseStatisticsField(ctx, "total_webhooks_ran", workflowExecution.Workflow.ID, 1, workflowExecution.ExecutionOrg)
- if err != nil {
- log.Printf("Failed to increase total apps loaded stats: %s", err)
- }
- */
+ if err == nil {
+ if hook.Version == "v2" {
+ timeout := 15
+ //if hook.VersionTimeout != 0 {
+ // timeout = hook.VersionTimeout
+ //}
+
+ log.Printf("[DEBUG] Waiting for Webhook response from %s for max %d seconds! Checking every 1 second. Hook ID: %s", workflowExecution.ExecutionId, timeout, hook.Id)
+ // Try every second for 15 seconds
+ for i := 0; i < timeout; i++ {
+ time.Sleep(1 * time.Second)
+
+ newExec, err := shuffle.GetWorkflowExecution(ctx, workflowExecution.ExecutionId)
+ if err != nil {
+ log.Printf("[ERROR] Failed to get workflow execution: %s", err)
+ break
+ }
+
+ if newExec.Status != "EXECUTING" {
+ log.Printf("[INFO] Got response from webhook v2 of length '%d' <- %s", len(newExec.Result), newExec.ExecutionId)
+ resp.WriteHeader(200)
+ resp.Write([]byte(newExec.Result))
+ return
+ }
+ }
+ }
+
+ // Fallback
resp.WriteHeader(200)
- resp.Write([]byte(fmt.Sprintf(`{"success": true, "execution_id": "%s"}`, workflowExecution.ExecutionId)))
+ if len(hook.CustomResponse) > 0 {
+ resp.Write([]byte(hook.CustomResponse))
+ } else {
+ resp.Write([]byte(fmt.Sprintf(`{"success": true, "execution_id": "%s"}`, workflowExecution.ExecutionId)))
+ }
return
}
resp.WriteHeader(500)
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, executionResp)))
}
+
}
func executeCloudAction(action shuffle.CloudSyncJob, apikey string) error {
@@ -2408,6 +2284,8 @@ func executeCloudAction(action shuffle.CloudSyncJob, apikey string) error {
return errors.New(fmt.Sprintf("Cloud error from Shuffler: %s", responseData.Reason))
}
+ log.Printf("[INFO] Cloud action executed successfully for '%s'", action.Action)
+
return nil
}
@@ -3623,8 +3501,9 @@ func handleCloudExecutionOnprem(workflowId, startNode, executionSource, executio
func handleCloudJob(job shuffle.CloudSyncJob) error {
// May need authentication in all of these..?
-
log.Printf("[INFO] Handle job with type %s and action %s", job.Type, job.Action)
+ shuffle.IncrementCache(ctx, job.OrgId, "org_sync_actions")
+
if job.Type == "outlook" {
if job.Action == "execute" {
// FIXME: Get the email
@@ -3688,7 +3567,7 @@ func handleCloudJob(job shuffle.CloudSyncJob) error {
} else if job.Type == "schedule" {
if job.Action == "execute" {
- log.Printf("Should handle schedule for workflow %s with start node %s and data %s", job.PrimaryItemId, job.SecondaryItem, job.ThirdItem)
+ log.Printf("[INFO] Should handle schedule for workflow %s with start node %s and data %s", job.PrimaryItemId, job.SecondaryItem, job.ThirdItem)
err := handleCloudExecutionOnprem(job.PrimaryItemId, job.SecondaryItem, "schedule", job.ThirdItem)
if err != nil {
log.Printf("[INFO] Failed executing workflow from cloud schedule: %s", err)
@@ -3698,7 +3577,7 @@ func handleCloudJob(job shuffle.CloudSyncJob) error {
}
} else if job.Type == "email_trigger" {
if job.Action == "execute" {
- log.Printf("Should handle email for workflow %s with start node %s and data %s", job.PrimaryItemId, job.SecondaryItem, job.ThirdItem)
+ log.Printf("[INFO] Should handle email for workflow %s with start node %s and data %s", job.PrimaryItemId, job.SecondaryItem, job.ThirdItem)
err := handleCloudExecutionOnprem(job.PrimaryItemId, job.SecondaryItem, "email_trigger", job.ThirdItem)
if err != nil {
log.Printf("Failed executing workflow from email trigger: %s", err)
@@ -3709,7 +3588,7 @@ func handleCloudJob(job shuffle.CloudSyncJob) error {
} else if job.Type == "user_input" {
if job.Action == "continue" {
- log.Printf("Should handle user_input CONTINUE for workflow %s with start node %s and execution ID %s", job.PrimaryItemId, job.SecondaryItem, job.ThirdItem)
+ log.Printf("[INFO] Should handle user_input CONTINUE for workflow %s with start node %s and execution ID %s", job.PrimaryItemId, job.SecondaryItem, job.ThirdItem)
// FIXME: Handle authorization
ctx := context.Background()
workflowExecution, err := shuffle.GetWorkflowExecution(ctx, job.ThirdItem)
@@ -3807,47 +3686,50 @@ func remoteOrgJobController(org shuffle.Org, body []byte) error {
if !responseData.Success {
log.Printf("[WARNING] Should stop org job controller because no success?")
- if strings.Contains(responseData.Reason, "Bad apikey") || strings.Contains(responseData.Reason, "Error getting the organization") || strings.Contains(responseData.Reason, "Organization isn't syncing") {
+ if strings.Contains(strings.ToLower(responseData.Reason), "bad apikey") || strings.Contains(responseData.Reason, "Error getting the organization") || strings.Contains(responseData.Reason, "Organization isn't syncing") {
log.Printf("[WARNING] Remote error; Bad apikey or org error. Stopping sync for org: %s", responseData.Reason)
if value, exists := scheduledOrgs[org.Id]; exists {
// Looks like this does the trick? Hurr
- log.Printf("[WARNING] STOPPING ORG SCHEDULE for: %s", org.Id)
-
+ log.Printf("[INFO] STOPPING ORG SCHEDULE for: %s", org.Id)
value.Lock()
- org, err := shuffle.GetOrg(ctx, org.Id)
- if err != nil {
- log.Printf("[WARNING] Failed finding org %s: %s", org.Id, err)
- return err
- }
-
- org.SyncConfig.Interval = 0
- org.SyncConfig.Apikey = ""
- org.CloudSync = false
-
- // Just in case
- org, err = handleStopCloudSync(syncUrl, *org)
-
- startDate := time.Now().Unix()
- org.SyncFeatures.Webhook = shuffle.SyncData{Active: false, Type: "trigger", Name: "Webhook", StartDate: startDate}
- org.SyncFeatures.UserInput = shuffle.SyncData{Active: false, Type: "trigger", Name: "User Input", StartDate: startDate}
- org.SyncFeatures.EmailTrigger = shuffle.SyncData{Active: false, Type: "action", Name: "Email Trigger", StartDate: startDate}
- org.SyncFeatures.Schedules = shuffle.SyncData{Active: false, Type: "trigger", Name: "Schedule", StartDate: startDate, Limit: 0}
- org.SyncFeatures.SendMail = shuffle.SyncData{Active: false, Type: "action", Name: "Send Email", StartDate: startDate, Limit: 0}
- org.SyncFeatures.SendSms = shuffle.SyncData{Active: false, Type: "action", Name: "Send SMS", StartDate: startDate, Limit: 0}
- org.CloudSyncActive = false
-
- err = shuffle.SetOrg(ctx, *org, org.Id)
- if err != nil {
- log.Printf("[WARNING] Failed setting organization when stopping sync: %s", err)
- } else {
- log.Printf("[INFO] Successfully STOPPED org cloud sync for %s (%s)", org.Name, org.Id)
- }
-
- return errors.New("Stopped schedule for org locally because of bad apikey.")
} else {
- return errors.New(fmt.Sprintf("Failed finding the schedule for org %s (%s)", org.Name, org.Id))
+ log.Printf("[INFO] Failed finding the schedule for org %s (%s)", org.Name, org.Id)
}
+
+ org, err := shuffle.GetOrg(ctx, org.Id)
+ if err != nil {
+ log.Printf("[WARNING] Failed finding org %s: %s", org.Id, err)
+ return err
+ }
+
+ // Just in case
+ org, err = handleStopCloudSync(syncUrl, *org)
+ if err != nil {
+ log.Printf("[ERROR] Failed stopping cloud sync remotely: %s", err)
+ }
+
+ org.SyncConfig.Interval = 0
+ org.CloudSync = false
+ org.SyncConfig.Apikey = ""
+
+ startDate := time.Now().Unix()
+ org.SyncFeatures.Webhook = shuffle.SyncData{Active: false, Type: "trigger", Name: "Webhook", StartDate: startDate}
+ org.SyncFeatures.UserInput = shuffle.SyncData{Active: false, Type: "trigger", Name: "User Input", StartDate: startDate}
+ org.SyncFeatures.EmailTrigger = shuffle.SyncData{Active: false, Type: "action", Name: "Email Trigger", StartDate: startDate}
+ org.SyncFeatures.Schedules = shuffle.SyncData{Active: false, Type: "trigger", Name: "Schedule", StartDate: startDate, Limit: 0}
+ org.SyncFeatures.SendMail = shuffle.SyncData{Active: false, Type: "action", Name: "Send Email", StartDate: startDate, Limit: 0}
+ org.SyncFeatures.SendSms = shuffle.SyncData{Active: false, Type: "action", Name: "Send SMS", StartDate: startDate, Limit: 0}
+ org.CloudSyncActive = false
+
+ err = shuffle.SetOrg(ctx, *org, org.Id)
+ if err != nil {
+ log.Printf("[WARNING] Failed setting organization when stopping sync: %s", err)
+ } else {
+ log.Printf("[INFO] Successfully STOPPED org cloud sync for %s (%s)", org.Name, org.Id)
+ }
+
+ return nil
}
return errors.New("[ERROR] Remote job handler issues.")
@@ -3916,7 +3798,7 @@ func runInitCloudSetup() {
}
func runInitEs(ctx context.Context) {
- log.Printf("[DEBUG] Starting INIT setup (ES)")
+ log.Printf("[DEBUG] Starting INIT setup for Elasticsearch/Opensearch")
httpProxy := os.Getenv("HTTP_PROXY")
if len(httpProxy) > 0 {
@@ -3933,7 +3815,7 @@ func runInitEs(ctx context.Context) {
log.Printf("[DEBUG] Setting default environment for org to %s", defaultEnv)
}
- log.Printf("[DEBUG] Getting organizations")
+ log.Printf("[DEBUG] Getting organizations for Elasticsearch/Opensearch")
activeOrgs, err := shuffle.GetAllOrgs(ctx)
setUsers := false
@@ -4023,6 +3905,11 @@ func runInitEs(ctx context.Context) {
}
}
+ if strings.Contains(os.Getenv("SHUFFLE_OPENSEARCH_URL"), "https") {
+ log.Printf("[INFO] Waiting during init to make sure the opensearch instance is up and running with security features properly")
+ time.Sleep(30 * time.Second)
+ }
+
_ = setUsers
schedules, err := shuffle.GetAllSchedules(ctx, "ALL")
if err != nil {
@@ -4098,7 +3985,7 @@ func runInitEs(ctx context.Context) {
CloudSync: false,
}
- err = shuffle.SetOrg(ctx, newOrg, orgId)
+ err = shuffle.SetOrg(ctx, newOrg, newOrg.Id)
setUsers := false
if err != nil {
log.Printf("[WARNING] Failed setting organization when creating original user: %s", err)
@@ -4150,8 +4037,13 @@ func runInitEs(ctx context.Context) {
}
for _, org := range activeOrgs {
+ if len(org.Id) == 0 {
+ log.Printf("[DEBUG] No ID found for org with name '%s'. Why was it made?", org.Name)
+ continue
+ }
+
if !org.CloudSync {
- log.Printf("[INFO] Skipping org syncCheck for %s because sync isn't set (1).", org.Id)
+ log.Printf("[INFO] Skipping org syncCheck for '%s' because sync isn't set (1).", org.Id)
continue
}
@@ -4301,7 +4193,10 @@ func runInitEs(ctx context.Context) {
url := os.Getenv("SHUFFLE_APP_DOWNLOAD_LOCATION")
if len(url) == 0 {
- url = "https://github.com/frikky/shuffle-apps"
+ log.Printf("[INFO] Skipping download of apps since no URL is set. Default would be https://github.com/shuffle/shuffle-apps")
+ url = "https://github.com/shuffle/shuffle-apps"
+ //url = ""
+ //return
}
username := os.Getenv("SHUFFLE_DOWNLOAD_AUTH_USERNAME")
@@ -4323,10 +4218,9 @@ func runInitEs(ctx context.Context) {
cloneOptions.ReferenceName = plumbing.ReferenceName(branch)
}
- log.Printf("[DEBUG] Getting apps from %s", url)
+ log.Printf("[DEBUG] Getting apps from url '%s'", url)
r, err := git.Clone(storer, fs, cloneOptions)
-
if err != nil {
log.Printf("[WARNING] Failed loading repo into memory (init): %s", err)
}
@@ -4338,7 +4232,6 @@ func runInitEs(ctx context.Context) {
_ = r
//iterateAppGithubFolders(fs, dir, "", "testing")
- // FIXME: Get all the apps?
_, _, err = IterateAppGithubFolders(ctx, fs, dir, "", "", forceUpdate)
if err != nil {
log.Printf("[WARNING] Error from app load in init: %s", err)
@@ -4353,7 +4246,7 @@ func runInitEs(ctx context.Context) {
}
log.Printf("[INFO] Downloading OpenAPI data for search - EXTRA APPS")
- apis := "https://github.com/frikky/security-openapis"
+ apis := "https://github.com/shuffle/security-openapis"
// THis gets memory problems hahah
//apis := "https://github.com/APIs-guru/openapi-directory"
@@ -4388,7 +4281,7 @@ func runInit(ctx context.Context) {
//}
//log.Printf("[DEBUG] Finalized init statistics update")
- log.Printf("[DEBUG] Starting INIT setup")
+ log.Printf("[DEBUG] Starting INIT setup (NOT Opensearch/Elasticsearch!)")
httpProxy := os.Getenv("HTTP_PROXY")
if len(httpProxy) > 0 {
log.Printf("Running with HTTP proxy %s (env: HTTP_PROXY)", httpProxy)
@@ -4460,11 +4353,11 @@ func runInit(ctx context.Context) {
CloudSync: false,
}
- err = shuffle.SetOrg(ctx, newOrg, orgId)
+ err = shuffle.SetOrg(ctx, newOrg, newOrg.Id)
if err != nil {
- log.Printf("Failed setting organization: %s", err)
+ log.Printf("[WARNING] Failed setting organization: %s", err)
} else {
- log.Printf("Successfully created the default org!")
+ log.Printf("[WARNING] Successfully created the default org!")
setUsers = true
}
} else {
@@ -4961,7 +4854,7 @@ func runInit(ctx context.Context) {
url := os.Getenv("SHUFFLE_APP_DOWNLOAD_LOCATION")
if len(url) == 0 {
- url = "https://github.com/frikky/shuffle-apps"
+ url = "https://github.com/shuffle/shuffle-apps"
}
username := os.Getenv("SHUFFLE_DOWNLOAD_AUTH_USERNAME")
@@ -4982,7 +4875,7 @@ func runInit(ctx context.Context) {
cloneOptions.ReferenceName = plumbing.ReferenceName(branch)
}
- log.Printf("[DEBUG] Getting apps from %s", url)
+ log.Printf("[DEBUG] Getting apps from URL '%s'", url)
r, err := git.Clone(storer, fs, cloneOptions)
@@ -5012,7 +4905,7 @@ func runInit(ctx context.Context) {
}
log.Printf("[INFO] Downloading OpenAPI data for search - EXTRA APPS")
- apis := "https://github.com/frikky/security-openapis"
+ apis := "https://github.com/shuffle/security-openapis"
// FIXME: This part gets memory problems. Fix in the future to load these apps too.
//apis := "https://github.com/APIs-guru/openapi-directory"
@@ -5139,7 +5032,7 @@ func handleStopCloudSync(syncUrl string, org shuffle.Org) (*shuffle.Org, error)
if err != nil {
return &org, err
}
- log.Printf("Remote disable ret: %s", string(respBody))
+ log.Printf("[INFO] Remote disable ret: %s", string(respBody))
responseData := retStruct{}
err = json.Unmarshal(respBody, &responseData)
@@ -6018,7 +5911,7 @@ func initHandlers() {
// General - duplicates and old.
r.HandleFunc("/api/v1/getusers", shuffle.HandleGetUsers).Methods("GET", "OPTIONS")
- r.HandleFunc("/api/v1/login", handleLogin).Methods("POST", "OPTIONS")
+ r.HandleFunc("/api/v1/login", shuffle.HandleLogin).Methods("POST", "OPTIONS")
r.HandleFunc("/api/v1/logout", shuffle.HandleLogout).Methods("POST", "OPTIONS")
r.HandleFunc("/api/v1/register", handleRegister).Methods("POST", "OPTIONS")
r.HandleFunc("/api/v1/checkusers", checkAdminLogin).Methods("GET", "OPTIONS")
@@ -6041,11 +5934,14 @@ func initHandlers() {
r.HandleFunc("/api/v1/streams/results", handleGetStreamResults).Methods("POST", "OPTIONS")
// Used by orborus
- r.HandleFunc("/api/v1/workflows/queue", handleGetWorkflowqueue).Methods("GET")
+ r.HandleFunc("/api/v1/workflows/queue", handleGetWorkflowqueue).Methods("GET", "POST")
r.HandleFunc("/api/v1/workflows/queue/confirm", handleGetWorkflowqueueConfirm).Methods("POST")
// App specific
// From here down isnt checked for org specific
+ r.HandleFunc("/api/v1/apps/{key}/execute", executeSingleAction).Methods("POST", "OPTIONS")
+ r.HandleFunc("/api/v1/apps/categories", shuffle.GetActiveCategories).Methods("GET", "OPTIONS")
+ r.HandleFunc("/api/v1/apps/categories/run", shuffle.RunCategoryAction).Methods("POST", "OPTIONS")
r.HandleFunc("/api/v1/apps/upload", handleAppZipUpload).Methods("POST", "OPTIONS")
r.HandleFunc("/api/v1/apps/{appId}/activate", activateWorkflowAppDocker).Methods("GET", "OPTIONS")
r.HandleFunc("/api/v1/apps/frameworkConfiguration", shuffle.GetFrameworkConfiguration).Methods("GET", "OPTIONS")
@@ -6097,6 +5993,12 @@ func initHandlers() {
r.HandleFunc("/api/v1/workflows/{key}", deleteWorkflow).Methods("DELETE", "OPTIONS")
r.HandleFunc("/api/v1/workflows/{key}", shuffle.SaveWorkflow).Methods("PUT", "OPTIONS")
r.HandleFunc("/api/v1/workflows/{key}", shuffle.GetSpecificWorkflow).Methods("GET", "OPTIONS")
+ r.HandleFunc("/api/v1/workflows/recommend", shuffle.HandleActionRecommendation).Methods("POST", "OPTIONS")
+
+ // New for recommendations in Shuffle
+ r.HandleFunc("/api/v1/recommendations/get_actions", shuffle.HandleActionRecommendation).Methods("POST", "OPTIONS")
+ r.HandleFunc("/api/v1/recommendations/modify", shuffle.HandleRecommendationAction).Methods("POST", "OPTIONS")
+ r.HandleFunc("/api/v1/workflows/{key}/revisions", shuffle.GetWorkflowRevisions).Methods("GET", "OPTIONS")
// Triggers
r.HandleFunc("/api/v1/hooks/new", shuffle.HandleNewHook).Methods("POST", "OPTIONS")
@@ -6149,10 +6051,12 @@ func initHandlers() {
r.HandleFunc("/api/v1/environments/{key}/rerun", shuffle.HandleRerunExecutions).Methods("GET", "POST", "OPTIONS")
r.HandleFunc("/api/v1/orgs/{orgId}/validate_app_values", shuffle.HandleKeyValueCheck).Methods("POST", "OPTIONS")
+ r.HandleFunc("/api/v1/orgs/{orgId}/list_cache", shuffle.HandleListCacheKeys).Methods("GET", "OPTIONS")
r.HandleFunc("/api/v1/orgs/{orgId}/get_cache", shuffle.HandleGetCacheKey).Methods("POST", "OPTIONS")
- r.HandleFunc("/api/v1/orgs/{orgId}/set_cache", shuffle.HandleSetCacheKey).Methods("POST", "OPTIONS")
+ r.HandleFunc("/api/v1/orgs/{orgId}/set_cache", shuffle.HandleSetCacheKey).Methods("POST", "PUT", "OPTIONS")
+ r.HandleFunc("/api/v1/orgs/{orgId}/cache/{cache_key}", shuffle.HandleDeleteCacheKey).Methods("DELETE", "OPTIONS")
r.HandleFunc("/api/v1/orgs/{orgId}/stats", shuffle.HandleGetStatistics).Methods("GET", "OPTIONS")
- r.HandleFunc("/api/v1/apps/{key}/execute", executeSingleAction).Methods("POST", "OPTIONS")
+ r.HandleFunc("/api/v1/orgs/{orgId}/revisions", shuffle.GetWorkflowRevisions).Methods("GET", "OPTIONS")
// Docker orborus specific - downloads an image
r.HandleFunc("/api/v1/get_docker_image", getDockerImage).Methods("POST", "OPTIONS")
@@ -6182,6 +6086,8 @@ func initHandlers() {
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")
+ r.HandleFunc("/api/v1/conversation", shuffle.RunActionAI).Methods("POST", "OPTIONS")
+
//r.HandleFunc("/api/v1/users/notifications/{notificationId}/markasread", shuffle.HandleMarkAsRead).Methods("GET", "OPTIONS")
r.HandleFunc("/api/v1/dashboards/{key}/widgets", shuffle.HandleNewWidget).Methods("POST", "OPTIONS")
r.HandleFunc("/api/v1/dashboards/{key}/widgets/{widget_id}", shuffle.HandleGetWidget).Methods("GET", "OPTIONS")
diff --git a/backend/go-app/main_test.go b/backend/go-app/main_test.go
old mode 100644
new mode 100755
index e9d358b6..4e39ea0e
--- a/backend/go-app/main_test.go
+++ b/backend/go-app/main_test.go
@@ -24,6 +24,7 @@ type endpoint struct {
handler http.HandlerFunc
path string
method string
+ body []byte
}
func init() {
@@ -44,7 +45,7 @@ func TestAuthenticationRequired(t *testing.T) {
{handler: shuffle.HandleNewOutlookRegister, path: "/functions/outlook/register", method: "GET"},
{handler: shuffle.HandleGetOutlookFolders, path: "/functions/outlook/getFolders", method: "GET"},
{handler: shuffle.HandleApiGeneration, path: "/api/v1/users/generateapikey", method: "GET"},
- {handler: handleLogin, path: "/api/v1/users/login", method: "POST"}, // prob not this one
+ {handler: shuffle.HandleLogin, path: "/api/v1/users/login", method: "POST"}, // prob not this one
// handleRegister generates nil pointer exception. Not necessary for this anyway.
//{handler: handleRegister, path: "/api/v1/users/register", method: "POST"},
{handler: shuffle.HandleGetUsers, path: "/api/v1/users/getusers", method: "GET"},
@@ -108,8 +109,8 @@ func TestAuthenticationRequired(t *testing.T) {
{handler: verifySwagger, path: "/api/v1/verify_swagger", method: "POST"},
{handler: verifySwagger, path: "/api/v1/verify_openapi", method: "POST"},
- {handler: echoOpenapiData, path: "/api/v1/get_openapi_uri", method: "POST"},
- {handler: echoOpenapiData, path: "/api/v1/validate_openapi", method: "POST"},
+ {handler: shuffle.EchoOpenapiData, path: "/api/v1/get_openapi_uri", method: "POST"},
+ {handler: shuffle.EchoOpenapiData, path: "/api/v1/validate_openapi", method: "POST"},
{handler: shuffle.ValidateSwagger, path: "/api/v1/validate_openapi", method: "POST"},
{handler: getOpenapi, path: "/api/v1/get_openapi", method: "GET"},
@@ -117,7 +118,7 @@ func TestAuthenticationRequired(t *testing.T) {
{handler: handleCloudSetup, path: "/api/v1/cloud/setup", method: "POST"},
{handler: shuffle.HandleGetOrgs, path: "/api/v1/orgs", method: "POST"},
- {handler: shuffle.HandleGetFileContent, path: "/api/v1/files/{fileId}/content", method: "POST"},
+ {handler: shuffle.HandleGetFileContent, path: "/api/v1/files/{fileId}/content", method: "POST", body: []byte("hi")},
}
var err error
@@ -197,10 +198,11 @@ func TestAuthenticationNotRequired(t *testing.T) {
// requirements might change after the refactor.
func TestCors(t *testing.T) {
handlers := []endpoint{
+ {handler: handleLogin, path: "/api/v1/users/login", method: "POST"}, // prob not this one
+
{handler: shuffle.HandleNewOutlookRegister, path: "/functions/outlook/register", method: "GET"},
{handler: shuffle.HandleGetOutlookFolders, path: "/functions/outlook/getFolders", method: "GET"},
{handler: shuffle.HandleApiGeneration, path: "/api/v1/users/generateapikey", method: "GET"},
- {handler: handleLogin, path: "/api/v1/users/login", method: "POST"}, // prob not this one
// handleRegister generates nil pointer exception
{handler: handleRegister, path: "/api/v1/users/register", method: "POST"},
{handler: shuffle.HandleGetUsers, path: "/api/v1/users/getusers", method: "GET"},
diff --git a/backend/go-app/walkoff.go b/backend/go-app/walkoff.go
old mode 100644
new mode 100755
index 1a8cba08..75c979dd
--- a/backend/go-app/walkoff.go
+++ b/backend/go-app/walkoff.go
@@ -11,6 +11,7 @@ import (
"io"
"io/ioutil"
"log"
+ "math/rand"
"net/http"
"net/url"
"os"
@@ -104,7 +105,7 @@ func createSchedule(ctx context.Context, scheduleId, workflowId, name, startNode
}
}
- log.Printf("Starting frequency: %d", newfrequency)
+ log.Printf("[INFO] Starting frequency for execution: %d", newfrequency)
jobret, err := newscheduler.Every(newfrequency).Seconds().NotImmediately().Run(job)
if err != nil {
log.Printf("Failed to schedule workflow: %s", err)
@@ -164,7 +165,7 @@ func handleGetWorkflowqueueConfirm(resp http.ResponseWriter, request *http.Reque
executionRequests, err := shuffle.GetWorkflowQueue(ctx, id, 100)
if err != nil {
log.Printf("[WARNING] (1) Failed reading body for workflowqueue: %s", err)
- resp.WriteHeader(401)
+ resp.WriteHeader(500)
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Entity parsing error - confirm"}`)))
return
}
@@ -178,8 +179,8 @@ func handleGetWorkflowqueueConfirm(resp http.ResponseWriter, request *http.Reque
body, err := ioutil.ReadAll(request.Body)
if err != nil {
- log.Println("Failed reading body for stream result queue")
- resp.WriteHeader(401)
+ log.Println("[WARNING] Failed reading body for stream result queue")
+ resp.WriteHeader(500)
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err)))
return
}
@@ -189,16 +190,16 @@ func handleGetWorkflowqueueConfirm(resp http.ResponseWriter, request *http.Reque
var removeExecutionRequests shuffle.ExecutionRequestWrapper
err = json.Unmarshal(body, &removeExecutionRequests)
if err != nil {
- log.Printf("Failed executionrequest in queue unmarshaling: %s", err)
- resp.WriteHeader(401)
+ log.Printf("[WARNING] Failed executionrequest in queue unmarshaling: %s", err)
+ resp.WriteHeader(400)
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err)))
return
}
if len(removeExecutionRequests.Data) == 0 {
- log.Printf("No requests to fix remove from DB")
- resp.WriteHeader(401)
- resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Some removal error"}`)))
+ log.Printf("[WARNING] No requests to fix remove from DB")
+ resp.WriteHeader(200)
+ resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Queue removal error"}`)))
return
}
@@ -251,16 +252,42 @@ func handleGetWorkflowqueue(resp http.ResponseWriter, request *http.Request) {
return
}
- id := request.Header.Get("Org-Id")
- if len(id) == 0 {
- log.Printf("[INFO] No org-id header set")
+ // This is really the environment's name - NOT org-id
+ orgId := request.Header.Get("Org-Id")
+ if len(orgId) == 0 {
+ log.Printf("[AUDIT] No org-id header set")
resp.WriteHeader(401)
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Specify the org-id header."}`)))
return
}
- ctx := context.Background()
- env, err := shuffle.GetEnvironment(ctx, id, "")
+ environment := request.Header.Get("org")
+ if len(environment) == 0 {
+ //log.Printf("[AUDIT] No 'org' header set (get workflow queue). ")
+ /*
+ resp.WriteHeader(403)
+ resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Specify the org header. This can be done by setting the 'ORG' environment variable for Orborus to your Org ID in Shuffle"}`)))
+ return
+ */
+ }
+
+ orborusLabel := request.Header.Get("x-orborus-label")
+
+ // This section is cloud custom for now
+ auth := request.Header.Get("Authorization")
+ if len(auth) == 0 {
+ //log.Printf("[AUDIT] No Authorization header set. Env: %s, org: %s", orgId, environment)
+ /*
+ resp.WriteHeader(401)
+ resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Specify the auth header (only applicable for cloud for now)."}`)))
+ return
+ */
+ }
+
+ //log.Printf("[AUDIT] Get workflow queue for org %s, env %s, orborus label %s", orgId, environment, orborusLabel)
+
+ ctx := shuffle.GetContext(request)
+ env, err := shuffle.GetEnvironment(ctx, orgId, "")
timeNow := time.Now().Unix()
if err == nil && len(env.Id) > 0 && len(env.Name) > 0 {
if time.Now().Unix() > env.Edited+60 {
@@ -273,7 +300,155 @@ func handleGetWorkflowqueue(resp http.ResponseWriter, request *http.Request) {
}
}
- executionRequests, err := shuffle.GetWorkflowQueue(ctx, id, 100)
+ //log.Printf("Found env: %#v", env)
+ if len(env.OrgId) > 0 {
+ environment = env.OrgId
+ }
+
+ if request.Method == "POST" {
+ if rand.Intn(1) == 0 {
+ // Parse out body
+ body, err := ioutil.ReadAll(request.Body)
+ if err == nil {
+
+ // Parse out CPU, memory and disk.
+
+ var envData shuffle.OrborusStats
+ err = json.Unmarshal(body, &envData)
+ if err == nil && !envData.Swarm && !envData.Kubernetes && (envData.CPU > 0 || envData.Memory > 0 || envData.Disk > 0) {
+
+ // Set the input in memory
+ envData.OrgId = orgId
+ envData.Environment = environment
+ envData.OrborusLabel = orborusLabel
+ envData.Timestamp = time.Now().Unix()
+
+ if envData.CPU > 0 && envData.MaxCPU > 0 {
+ envData.CPUPercent = float64(envData.CPU) / float64(envData.MaxCPU)
+ }
+
+ if envData.Memory > 0 && envData.MaxMemory > 0 {
+ envData.MemoryPercent = float64(envData.Memory) / float64(envData.MaxMemory)
+ }
+
+ // Check if CPU percent constantly has stayed above X% for the last Y requests
+ percentageCheck := 90
+ concurrentChecks := 2
+
+ //if int(envData.CPUPercent) > percentageCheck {
+ // Get cached data
+ percentages := []float64{}
+ cacheKey := fmt.Sprintf("%s_%s_percent", orgId, strings.ToLower(environment))
+
+ // Marshal float list into []byte
+ cacheData := []byte{}
+ cache, err := shuffle.GetCache(ctx, cacheKey)
+ if err == nil {
+ // Unmarshal into percentages
+ cacheData := []byte(cache.([]uint8))
+ err = json.Unmarshal(cacheData, &percentages)
+ if err != nil {
+ log.Printf("[INFO] error in cache unmarshal for percentages: %s", err)
+ }
+
+ if len(percentages) > concurrentChecks {
+ percentages = percentages[:concurrentChecks]
+ }
+
+ percentages = append(percentages, envData.CPUPercent)
+ if len(percentages) > concurrentChecks {
+ //log.Printf("[INFO] Checking percentages: %v", percentages)
+
+ // percentageCheck := 1
+ sendAlert := true
+ for _, p := range percentages {
+ if int(p) < percentageCheck {
+ //log.Printf("[AUDIT] CPU percent is below %d: %d", percentageCheck, int(p))
+ sendAlert = false
+ break
+ }
+ }
+
+ if sendAlert {
+ log.Printf("[INFO] CPU percent has been above %d percent for the last 5 requests. Sending alert. Env: %s, org: %s", percentageCheck, environment, orgId)
+
+ // Set notification + alert for organization
+ err = shuffle.CreateOrgNotification(
+ ctx,
+ fmt.Sprintf("CPU percent has been above %d percent", percentageCheck),
+ fmt.Sprintf("A environment %s has been using more than %d\\% CPU for the last 5 requests.", environment, percentageCheck),
+ fmt.Sprintf("/admin?tab=environments"),
+ environment,
+ true,
+ )
+
+ if err != nil {
+ log.Printf("[ERROR] error creating notification: %s", err)
+ }
+
+ org, err := shuffle.GetOrg(ctx, environment)
+ if err == nil {
+ foundRecommendation := false
+ for _, recommendation := range org.Priorities {
+ if strings.Contains(recommendation.Name, "CPU") {
+ foundRecommendation = true
+ break
+ }
+ }
+
+ if !foundRecommendation {
+ // Add to start of org.Priorities
+ org, _ = shuffle.AddPriority(*org, shuffle.Priority{
+ Name: fmt.Sprintf("High CPU in environment %s", orgId),
+ Description: fmt.Sprintf("The environment %s has been using more than %d percent CPU. This indicates you may need to look at scaling.", orgId, percentageCheck),
+ Type: "scale",
+ Active: true,
+ URL: fmt.Sprintf("/admin?tab=environments"),
+ Severity: 1,
+ }, false)
+
+ //Make last item the first item
+ org.Priorities = append([]shuffle.Priority{org.Priorities[len(org.Priorities)-1]}, org.Priorities[:len(org.Priorities)-1]...)
+ err = shuffle.SetOrg(ctx, *org, org.Id)
+ if err != nil {
+ log.Printf("[ERROR] Problem setting org: %s", err)
+ }
+ }
+ }
+ }
+
+ if len(percentages) > 1 {
+ percentages = percentages[1:]
+ }
+ }
+
+ // Marshal float list into []byte
+ } else {
+ //log.Printf("[ERROR] Failed getting cache: %s", err)
+ percentages = append(percentages, envData.CPUPercent)
+ }
+
+ if len(percentages) > 0 {
+ //log.Printf("[DEBUG] Setting cache for %s: %#v", cacheKey, percentages)
+ cacheData, err = json.Marshal(percentages)
+ if err != nil {
+ log.Printf("[INFO] error in cache marshal: %s", err)
+ }
+
+ // Add the new data
+ go shuffle.SetCache(ctx, cacheKey, cacheData, 5)
+ }
+ }
+
+ //log.Printf("CPU percent: %f", envData.CPUPercent)
+ //log.Printf("Memory percent: %f", envData.MemoryPercent*100)
+
+ go shuffle.SetenvStats(ctx, envData)
+ }
+ }
+ }
+
+ executionRequests, err := shuffle.GetWorkflowQueue(ctx, orgId, 100)
if err != nil {
// Skipping as this comes up over and over
//log.Printf("(2) Failed reading body for workflowqueue: %s", err)
@@ -290,21 +465,21 @@ func handleGetWorkflowqueue(resp http.ResponseWriter, request *http.Request) {
// Try again :)
if len(env.Id) == 0 && len(env.Name) == 0 {
- orgId := ""
+ foundId := ""
for _, requestData := range executionRequests.Data {
execution, err := shuffle.GetWorkflowExecution(ctx, requestData.ExecutionId)
if err == nil {
if len(execution.ExecutionOrg) > 0 {
- orgId = execution.ExecutionOrg
+ foundId = execution.ExecutionOrg
break
}
}
}
if len(orgId) > 0 {
- env, err := shuffle.GetEnvironment(ctx, id, orgId)
+ env, err := shuffle.GetEnvironment(ctx, orgId, foundId)
if err != nil {
- log.Printf("[WARNING] No env found matching %s - continuing without updating orborus anyway: %s", id, err)
+ log.Printf("[WARNING] No env found matching %s - continuing without updating orborus anyway: %s", orgId, err)
//resp.WriteHeader(401)
//resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "No env found matching %s"}`, id)))
//return
@@ -361,15 +536,15 @@ func handleGetStreamResults(resp http.ResponseWriter, request *http.Request) {
err = json.Unmarshal(body, &actionResult)
if err != nil {
log.Printf("[WARNING] Failed ActionResult unmarshaling (stream result): %s", err)
- resp.WriteHeader(401)
- resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err)))
- return
+ //resp.WriteHeader(401)
+ //resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err)))
+ //return
}
ctx := context.Background()
workflowExecution, err := shuffle.GetWorkflowExecution(ctx, actionResult.ExecutionId)
if err != nil {
- log.Printf("[WARNING] Failed getting execution (streamresult) %s: %s", actionResult.ExecutionId, err)
+ log.Printf("[WARNING][%s] Failed getting execution (streamresult): %s", actionResult.ExecutionId, err)
resp.WriteHeader(401)
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Bad authorization key or execution_id might not exist."}`)))
return
@@ -386,7 +561,7 @@ func handleGetStreamResults(resp http.ResponseWriter, request *http.Request) {
}
if len(workflowExecution.ExecutionOrg) > 0 && user.ActiveOrg.Id == workflowExecution.ExecutionOrg && user.Role == "admin" {
- log.Printf("[DEBUG] Correct org for execution!")
+ log.Printf("[DEBUG] User %s is in correct org. Allowing org continuation for execution!", user.Username)
} else {
log.Printf("[WARNING] Bad authorization key when getting stream results %s.", actionResult.ExecutionId)
resp.WriteHeader(401)
@@ -461,23 +636,26 @@ func handleWorkflowQueue(resp http.ResponseWriter, request *http.Request) {
}
//log.Printf("Actionresult unmarshal: %s", string(body))
- log.Printf("[DEBUG] Got workflow result from %s of length %d.", request.RemoteAddr, len(body))
- err = shuffle.ValidateNewWorkerExecution(body)
+ log.Printf("[DEBUG] Got workflow result from %s of length %d", request.RemoteAddr, len(body))
+ ctx := context.Background()
+ err = shuffle.ValidateNewWorkerExecution(ctx, body)
if err == nil {
resp.WriteHeader(200)
resp.Write([]byte(fmt.Sprintf(`{"success": true, "reason": "success"}`)))
return
} else {
- log.Printf("[DEBUG] Handling other execution variant (subflow): %s", err)
+ log.Printf("[DEBUG] Handling other execution variant (subflow?): %s", err)
}
+ log.Printf("[DEBUG] Got workflow result from %s of length %d.", request.RemoteAddr, len(body))
+
var actionResult shuffle.ActionResult
err = json.Unmarshal(body, &actionResult)
if err != nil {
log.Printf("[WARNING] Failed ActionResult unmarshaling (queue): %s", err)
- resp.WriteHeader(401)
- resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err)))
- return
+ //resp.WriteHeader(401)
+ //resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err)))
+ //return
}
//log.Printf("Received action: %#v", actionResult)
@@ -488,7 +666,6 @@ func handleWorkflowQueue(resp http.ResponseWriter, request *http.Request) {
// 4. Push to db
// IF FAIL: Set executionstatus: abort or cancel
- ctx := context.Background()
workflowExecution, err := shuffle.GetWorkflowExecution(ctx, actionResult.ExecutionId)
if err != nil {
log.Printf("[ERROR] Failed getting execution (workflowqueue) %s: %s", actionResult.ExecutionId, err)
@@ -523,66 +700,71 @@ func handleWorkflowQueue(resp http.ResponseWriter, request *http.Request) {
}
}
- if actionResult.Status == "WAITING" && actionResult.Action.AppName == "User Input" {
- log.Printf("[INFO] SHOULD WAIT A BIT AND RUN CLOUD STUFF WITH USER INPUT! WAITING!")
+ /*
+ // Removed as UserInput is now handled as an app
+ if actionResult.Status == "WAITING" && actionResult.Action.AppName == "User Input" {
+ log.Printf("[INFO] SHOULD WAIT A BIT AND RUN USER INPUT! WAITING!")
- var trigger shuffle.Trigger
- err = json.Unmarshal([]byte(actionResult.Result), &trigger)
- if err != nil {
- log.Printf("[WARNING] Failed unmarshaling actionresult for user input: %s", err)
- resp.WriteHeader(401)
- resp.Write([]byte(`{"success": false}`))
- return
- }
+ var trigger shuffle.Trigger
+ err = json.Unmarshal([]byte(actionResult.Result), &trigger)
+ if err != nil {
+ log.Printf("[WARNING] Failed unmarshaling actionresult for user input: %s", err)
+ resp.WriteHeader(401)
+ resp.Write([]byte(`{"success": false}`))
+ return
+ }
- orgId := workflowExecution.ExecutionOrg
- if len(workflowExecution.OrgId) == 0 && len(workflowExecution.Workflow.OrgId) > 0 {
- orgId = workflowExecution.Workflow.OrgId
- }
+ orgId := workflowExecution.ExecutionOrg
+ if len(workflowExecution.OrgId) == 0 && len(workflowExecution.Workflow.OrgId) > 0 {
+ orgId = workflowExecution.Workflow.OrgId
+ }
- err := handleUserInput(trigger, orgId, workflowExecution.Workflow.ID, workflowExecution.ExecutionId)
- if err != nil {
- log.Printf("[WARNING] Failed userinput handler: %s", err)
+ err := handleUserInput(trigger, orgId, workflowExecution.Workflow.ID, workflowExecution.ExecutionId)
+ if err != nil {
+ log.Printf("[WARNING] Failed userinput handler: %s", err)
- actionResult.Result = fmt.Sprintf(`{"success": False, "reason": "%s"}`, err)
+ actionResult.Result = fmt.Sprintf(`{"success": false, "reason": "%s"}`, err)
- workflowExecution.Results = append(workflowExecution.Results, actionResult)
- workflowExecution.Status = "ABORTED"
- err = shuffle.SetWorkflowExecution(ctx, *workflowExecution, true)
- if err != nil {
- log.Printf("[WARNING] Failed to set execution during wait: %s", err)
- } else {
- log.Printf("[INFO] Successfully set the execution %s to waiting.", workflowExecution.ExecutionId)
+ workflowExecution.Results = append(workflowExecution.Results, actionResult)
+ workflowExecution.Status = "ABORTED"
+ err = shuffle.SetWorkflowExecution(ctx, *workflowExecution, true)
+ if err != nil {
+ log.Printf("[WARNING] Failed to set execution during wait: %s", err)
+ } else {
+ log.Printf("[INFO] Successfully set the execution %s to waiting.", workflowExecution.ExecutionId)
+ }
+
+ resp.WriteHeader(401)
+ resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Error: %s"}`, err)))
+ return
+ } else {
+ log.Printf("[INFO] Successful userinput handler")
+ resp.WriteHeader(200)
+ resp.Write([]byte(fmt.Sprintf(`{"success": true, "reason": "CLOUD IS DONE"}`)))
+
+ actionResult.Result = `{"success": True, "reason": "Waiting for user feedback based on configuration"}`
+
+ workflowExecution.Results = append(workflowExecution.Results, actionResult)
+ workflowExecution.Status = actionResult.Status
+ err = shuffle.SetWorkflowExecution(ctx, *workflowExecution, true)
+ if err != nil {
+ log.Printf("[WARNING] Failed setting userinput: %s", err)
+ } else {
+ log.Printf("[DEBUG] Successfully set the execution to waiting.")
+ }
+ }
+
+ return
}
-
- resp.WriteHeader(401)
- resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Error: %s"}`, err)))
- return
- } else {
- log.Printf("[INFO] Successful userinput handler")
- resp.WriteHeader(200)
- resp.Write([]byte(fmt.Sprintf(`{"success": true, "reason": "CLOUD IS DONE"}`)))
-
- actionResult.Result = `{"success": True, "reason": "Waiting for user feedback based on configuration"}`
-
- workflowExecution.Results = append(workflowExecution.Results, actionResult)
- workflowExecution.Status = actionResult.Status
- err = shuffle.SetWorkflowExecution(ctx, *workflowExecution, true)
- if err != nil {
- log.Printf("[WARNING] Failed setting userinput: %s", err)
- } else {
- log.Printf("[DEBUG] Successfully set the execution to waiting.")
- }
- }
-
- return
- }
+ */
runWorkflowExecutionTransaction(ctx, 0, workflowExecution.ExecutionId, actionResult, resp)
}
// Will make sure transactions are always ran for an execution. This is recursive if it fails. Allowed to fail up to 5 times
func runWorkflowExecutionTransaction(ctx context.Context, attempts int64, workflowExecutionId string, actionResult shuffle.ActionResult, resp http.ResponseWriter) {
+ log.Printf("[DEBUG] Running workflow execution transaction for %s", workflowExecutionId)
+
// Should start a tx for the execution here
workflowExecution, err := shuffle.GetWorkflowExecution(ctx, workflowExecutionId)
if err != nil {
@@ -593,7 +775,6 @@ func runWorkflowExecutionTransaction(ctx context.Context, attempts int64, workfl
}
//log.Printf("BASE LENGTH: %d", len(workflowExecution.Results))
-
workflowExecution, dbSave, err := shuffle.ParsedExecutionResult(ctx, *workflowExecution, actionResult, false, 0)
if err != nil {
b, suberr := json.Marshal(actionResult)
@@ -638,7 +819,7 @@ func JSONCheck(str string) bool {
func handleExecutionStatistics(execution shuffle.WorkflowExecution) {
// FIXME: CLEAN UP THE JSON THAT'S SAVED.
- // https://github.com/frikky/Shuffle/issues/172
+ // https://github.com/shuffle/Shuffle/issues/172
appResults := []shuffle.AppExecutionExample{}
for _, result := range execution.Results {
resultCheck := JSONCheck(result.Result)
@@ -872,14 +1053,16 @@ func handleExecution(id string, workflow shuffle.Workflow, request *http.Request
workflow = *tmpworkflow
}
- if len(workflow.ExecutingOrg.Id) == 0 {
- if len(orgId) > 0 {
- workflow.ExecutingOrg.Id = orgId
- } else {
- log.Printf("[INFO] Stopped execution because there is no executing org for workflow %s", workflow.ID)
- return shuffle.WorkflowExecution{}, fmt.Sprintf("Workflow has no executing org defined"), errors.New("Workflow has no executing org defined")
+ /*
+ if len(workflow.ExecutingOrg.Id) == 0 {
+ if len(orgId) > 0 {
+ workflow.ExecutingOrg.Id = orgId
+ } else {
+ log.Printf("[INFO] Stopped execution because there is no executing org for workflow %s", workflow.ID)
+ return shuffle.WorkflowExecution{}, fmt.Sprintf("Workflow has no executing org defined"), errors.New("Workflow has no executing org defined")
+ }
}
- }
+ */
if len(workflow.Actions) == 0 {
workflow.Actions = []shuffle.Action{}
@@ -923,13 +1106,692 @@ func handleExecution(id string, workflow shuffle.Workflow, request *http.Request
workflowExecution, execInfo, _, err := shuffle.PrepareWorkflowExecution(ctx, workflow, request, 10)
if err != nil {
- log.Printf("[WARNING] Failed in prepareExecution for execution Id %s: %s", workflowExecution.ExecutionId, err)
- return workflowExecution, fmt.Sprintf("Failed preparration: %s", err), err
+ if strings.Contains(fmt.Sprintf("%s", err), "User Input") {
+ // Special for user input callbacks
+ return workflowExecution, fmt.Sprintf("%s", err), nil
+ } else {
+ log.Printf("[WARNING] Failed in prepareExecution: %s", err)
+ return shuffle.WorkflowExecution{}, fmt.Sprintf("Failed starting workflow: %s", err), err
+ }
}
err = imageCheckBuilder(execInfo.ImageNames)
if err != nil {
log.Printf("[ERROR] Failed building the required images from %#v: %s", execInfo.ImageNames, err)
+ return shuffle.WorkflowExecution{}, "Failed unmarshal during execution", err
+ }
+
+ makeNew := true
+ start, startok := request.URL.Query()["start"]
+ if request.Method == "POST" {
+ body, err := ioutil.ReadAll(request.Body)
+ if err != nil {
+ log.Printf("[ERROR] Failed request POST read: %s", err)
+ return shuffle.WorkflowExecution{}, "Failed getting body", err
+ }
+
+ // This one doesn't really matter.
+ log.Printf("[INFO] Running POST execution with body of length %d for workflow %s", len(string(body)), workflowExecution.Workflow.ID)
+
+ if len(body) >= 4 {
+ if body[0] == 34 && body[len(body)-1] == 34 {
+ body = body[1 : len(body)-1]
+ }
+ if body[0] == 34 && body[len(body)-1] == 34 {
+ body = body[1 : len(body)-1]
+ }
+ }
+
+ sourceAuth, sourceAuthOk := request.URL.Query()["source_auth"]
+ if sourceAuthOk {
+ //log.Printf("\n\n\nSETTING SOURCE WORKFLOW AUTH TO %s!!!\n\n\n", sourceAuth[0])
+ workflowExecution.ExecutionSourceAuth = sourceAuth[0]
+ } else {
+ //log.Printf("Did NOT get source workflow")
+ }
+
+ sourceNode, sourceNodeOk := request.URL.Query()["source_node"]
+ if sourceNodeOk {
+ //log.Printf("\n\n\nSETTING SOURCE WORKFLOW NODE TO %s!!!\n\n\n", sourceNode[0])
+ workflowExecution.ExecutionSourceNode = sourceNode[0]
+ } else {
+ //log.Printf("Did NOT get source workflow")
+ }
+
+ //workflowExecution.ExecutionSource = "default"
+ sourceWorkflow, sourceWorkflowOk := request.URL.Query()["source_workflow"]
+ if sourceWorkflowOk {
+ //log.Printf("Got source workflow %s", sourceWorkflow)
+ workflowExecution.ExecutionSource = sourceWorkflow[0]
+ } else {
+ //log.Printf("Did NOT get source workflow")
+ }
+
+ sourceExecution, sourceExecutionOk := request.URL.Query()["source_execution"]
+ if sourceExecutionOk {
+ //log.Printf("[INFO] Got source execution%s", sourceExecution)
+ workflowExecution.ExecutionParent = sourceExecution[0]
+ } else {
+ //log.Printf("Did NOT get source execution")
+ }
+
+ if len(string(body)) < 50 {
+ //log.Println(body)
+ // String in string
+ //log.Println(body)
+
+ //if string(body)[0] == "\"" && string(body)[string(body)
+ log.Printf("[DEBUG] Body: %s", string(body))
+ }
+
+ var execution shuffle.ExecutionRequest
+ err = json.Unmarshal(body, &execution)
+ if err != nil {
+ log.Printf("[WARNING] Failed execution POST unmarshaling - continuing anyway: %s", err)
+ //return shuffle.WorkflowExecution{}, "", err
+ }
+
+ if execution.Start == "" && len(body) > 0 {
+ execution.ExecutionArgument = string(body)
+ }
+
+ // FIXME - this should have "execution_argument" from executeWorkflow frontend
+ //log.Printf("EXEC: %#v", execution)
+ if len(execution.ExecutionArgument) > 0 {
+ workflowExecution.ExecutionArgument = execution.ExecutionArgument
+ }
+
+ if len(execution.ExecutionSource) > 0 {
+ workflowExecution.ExecutionSource = execution.ExecutionSource
+ }
+
+ //log.Printf("Execution data: %#v", execution)
+ if len(execution.Start) == 36 && len(workflow.Actions) > 0 {
+ log.Printf("[INFO] Should start execution on node %s", execution.Start)
+ workflowExecution.Start = execution.Start
+
+ found := false
+ for _, action := range workflow.Actions {
+ if action.ID == execution.Start {
+ found = true
+ break
+ }
+ }
+
+ if !found {
+ log.Printf("[ERROR] Action %s was NOT found! Exiting execution.", execution.Start)
+ return shuffle.WorkflowExecution{}, fmt.Sprintf("Startnode %s was not found in actions", workflow.Start), errors.New(fmt.Sprintf("Startnode %s was not found in actions", workflow.Start))
+ }
+ } else if len(execution.Start) > 0 {
+ //log.Printf("[INFO] !")
+ //log.Printf("[ERROR] START ACTION %s IS WRONG ID LENGTH %d!", execution.Start, len(execution.Start))
+ //return shuffle.WorkflowExecution{}, fmt.Sprintf("Startnode %s was not found in actions", execution.Start), errors.New(fmt.Sprintf("Startnode %s was not found in actions", execution.Start))
+ }
+
+ if len(execution.ExecutionId) == 36 {
+ workflowExecution.ExecutionId = execution.ExecutionId
+ } else {
+ sessionToken := uuid.NewV4()
+ workflowExecution.ExecutionId = sessionToken.String()
+ }
+ } else {
+ // Check for parameters of start and ExecutionId
+ // This is mostly used for user input trigger
+
+ answer, answerok := request.URL.Query()["answer"]
+ referenceId, referenceok := request.URL.Query()["reference_execution"]
+ if answerok && referenceok {
+ // If answer is false, reference execution with result
+ log.Printf("[INFO] Answer is OK AND reference is OK!")
+ if answer[0] == "false" {
+ log.Printf("Should update reference and return, no need for further execution!")
+
+ // Get the reference execution
+ oldExecution, err := shuffle.GetWorkflowExecution(ctx, referenceId[0])
+ if err != nil {
+ log.Printf("Failed getting execution (execution) %s: %s", referenceId[0], err)
+ return shuffle.WorkflowExecution{}, fmt.Sprintf("Failed getting execution ID %s because it doesn't exist.", referenceId[0]), err
+ }
+
+ if oldExecution.Workflow.ID != id {
+ log.Println("Wrong workflowid!")
+ return shuffle.WorkflowExecution{}, fmt.Sprintf("Bad ID %s", referenceId), errors.New("Bad ID")
+ }
+
+ newResults := []shuffle.ActionResult{}
+ //log.Printf("%#v", oldExecution.Results)
+ for _, result := range oldExecution.Results {
+ log.Printf("%s - %s", result.Action.ID, start[0])
+ if result.Action.ID == start[0] {
+ note, noteok := request.URL.Query()["note"]
+ if noteok {
+ result.Result = fmt.Sprintf("User note: %s", note[0])
+ } else {
+ result.Result = fmt.Sprintf("User clicked %s", answer[0])
+ }
+
+ // Stopping the whole thing
+ result.CompletedAt = int64(time.Now().Unix())
+ result.Status = "ABORTED"
+ oldExecution.Status = result.Status
+ oldExecution.Result = result.Result
+ oldExecution.LastNode = result.Action.ID
+ }
+
+ newResults = append(newResults, result)
+ }
+
+ oldExecution.Results = newResults
+ err = shuffle.SetWorkflowExecution(ctx, *oldExecution, true)
+ if err != nil {
+ log.Printf("Error saving workflow execution actionresult setting: %s", err)
+ return shuffle.WorkflowExecution{}, fmt.Sprintf("Failed setting workflowexecution actionresult in execution: %s", err), err
+ }
+
+ return shuffle.WorkflowExecution{}, "", nil
+ }
+ }
+
+ if referenceok {
+ log.Printf("Handling an old execution continuation!")
+ // Will use the old name, but still continue with NEW ID
+ oldExecution, err := shuffle.GetWorkflowExecution(ctx, referenceId[0])
+ if err != nil {
+ log.Printf("Failed getting execution (execution) %s: %s", referenceId[0], err)
+ return shuffle.WorkflowExecution{}, fmt.Sprintf("Failed getting execution ID %s because it doesn't exist.", referenceId[0]), err
+ }
+
+ workflowExecution = *oldExecution
+ }
+
+ if len(workflowExecution.ExecutionId) == 0 {
+ sessionToken := uuid.NewV4()
+ workflowExecution.ExecutionId = sessionToken.String()
+ } else {
+ log.Printf("Using the same executionId as before: %s", workflowExecution.ExecutionId)
+ makeNew = false
+ }
+
+ // Don't override workflow defaults
+ }
+
+ if startok {
+ //log.Printf("\n\n[INFO] Setting start to %s based on query!\n\n", start[0])
+ //workflowExecution.Workflow.Start = start[0]
+ workflowExecution.Start = start[0]
+ }
+
+ // FIXME - regex uuid, and check if already exists?
+ if len(workflowExecution.ExecutionId) != 36 {
+ log.Printf("Invalid uuid: %s", workflowExecution.ExecutionId)
+ return shuffle.WorkflowExecution{}, "Invalid uuid", err
+ }
+
+ // FIXME - find owner of workflow
+ // FIXME - get the actual workflow itself and build the request
+ // MAYBE: Don't send the workflow within the pubsub, as this requires more data to be sent
+ // Check if a worker already exists for company, else run one with:
+ // locations, project IDs and subscription names
+
+ // When app is executed:
+ // Should update with status execution (somewhere), which will trigger the next node
+ // IF action.type == internal, we need the internal watcher to be running and executing
+ // This essentially means the WORKER has to be the responsible party for new actions in the INTERNAL landscape
+ // Results are ALWAYS posted back to cloud@execution_id?
+ if makeNew {
+ workflowExecution.Type = "workflow"
+ //workflowExecution.Stream = "tmp"
+ //workflowExecution.WorkflowQueue = "tmp"
+ //workflowExecution.SubscriptionNameNodestream = "testcompany-nodestream"
+ //workflowExecution.Locations = []string{"europe-west2"}
+ workflowExecution.ProjectId = gceProject
+ workflowExecution.WorkflowId = workflow.ID
+ workflowExecution.StartedAt = int64(time.Now().Unix())
+ workflowExecution.CompletedAt = 0
+ workflowExecution.Authorization = uuid.NewV4().String()
+
+ // Status for the entire workflow.
+ workflowExecution.Status = "EXECUTING"
+ }
+
+ if len(workflowExecution.ExecutionSource) == 0 {
+ log.Printf("[INFO] No execution source (trigger) specified. Setting to default")
+ workflowExecution.ExecutionSource = "default"
+ } else {
+ log.Printf("[INFO] Execution source is %s for execution ID %s in workflow %s", workflowExecution.ExecutionSource, workflowExecution.ExecutionId, workflowExecution.Workflow.ID)
+ }
+
+ workflowExecution.ExecutionVariables = workflow.ExecutionVariables
+ if len(workflowExecution.Start) == 0 && len(workflowExecution.Workflow.Start) > 0 {
+ workflowExecution.Start = workflowExecution.Workflow.Start
+ }
+
+ startnodeFound := false
+ newStartnode := ""
+ for _, item := range workflowExecution.Workflow.Actions {
+ if item.ID == workflowExecution.Start {
+ startnodeFound = true
+ }
+
+ if item.IsStartNode {
+ newStartnode = item.ID
+ }
+ }
+
+ if !startnodeFound {
+ log.Printf("[INFO] Couldn't find startnode %s. Remapping to %#v", workflowExecution.Start, newStartnode)
+
+ if len(newStartnode) > 0 {
+ workflowExecution.Start = newStartnode
+ } else {
+ return shuffle.WorkflowExecution{}, fmt.Sprintf("Startnode couldn't be found"), errors.New("Startnode isn't defined in this workflow..")
+ }
+ }
+
+ childNodes := shuffle.FindChildNodes(workflowExecution, workflowExecution.Start, []string{}, []string{})
+
+ //topic := "workflows"
+ startFound := false
+ // FIXME - remove this?
+ newActions := []shuffle.Action{}
+ defaultResults := []shuffle.ActionResult{}
+
+ allAuths := []shuffle.AppAuthenticationStorage{}
+ for _, action := range workflowExecution.Workflow.Actions {
+ //action.LargeImage = ""
+ if action.ID == workflowExecution.Start {
+ startFound = true
+ }
+ //log.Println(action.Environment)
+
+ if action.Environment == "" {
+ return shuffle.WorkflowExecution{}, fmt.Sprintf("Environment is not defined for %s", action.Name), errors.New("Environment not defined!")
+ }
+
+ // FIXME: Authentication parameters
+ if len(action.AuthenticationId) > 0 {
+ if len(allAuths) == 0 {
+ allAuths, err = shuffle.GetAllWorkflowAppAuth(ctx, workflow.ExecutingOrg.Id)
+ if err != nil {
+ log.Printf("Api authentication failed in get all app auth: %s", err)
+ return shuffle.WorkflowExecution{}, fmt.Sprintf("Api authentication failed in get all app auth: %s", err), err
+ }
+ }
+
+ curAuth := shuffle.AppAuthenticationStorage{Id: ""}
+ for _, auth := range allAuths {
+ if auth.Id == action.AuthenticationId {
+ curAuth = auth
+ break
+ }
+ }
+
+ if len(curAuth.Id) == 0 {
+ return shuffle.WorkflowExecution{}, fmt.Sprintf("Auth ID %s doesn't exist", action.AuthenticationId), errors.New(fmt.Sprintf("Auth ID %s doesn't exist", action.AuthenticationId))
+ }
+
+ if curAuth.Encrypted {
+ setField := true
+ newFields := []shuffle.AuthenticationStore{}
+ for _, field := range curAuth.Fields {
+ parsedKey := fmt.Sprintf("%s_%d_%s_%s", curAuth.OrgId, curAuth.Created, curAuth.Label, field.Key)
+ newValue, err := shuffle.HandleKeyDecryption([]byte(field.Value), parsedKey)
+ if err != nil {
+ log.Printf("[WARNING] Failed decryption for %s: %s", field.Key, err)
+ setField = false
+ break
+ }
+
+ field.Value = string(newValue)
+ newFields = append(newFields, field)
+ }
+
+ if setField {
+ curAuth.Fields = newFields
+ }
+ } else {
+ log.Printf("[INFO] AUTH IS NOT ENCRYPTED - attempting encrypting!")
+ err = shuffle.SetWorkflowAppAuthDatastore(ctx, curAuth, curAuth.Id)
+ if err != nil {
+ log.Printf("[WARNING] Failed running encryption during execution: %s", err)
+ }
+ }
+
+ newParams := []shuffle.WorkflowAppActionParameter{}
+ if strings.ToLower(curAuth.Type) == "oauth2" {
+ log.Printf("[DEBUG] Should replace auth parameters (Oauth2)")
+
+ for _, param := range curAuth.Fields {
+ if param.Key == "expiration" {
+ continue
+ }
+
+ newParams = append(newParams, shuffle.WorkflowAppActionParameter{
+ Name: param.Key,
+ Value: param.Value,
+ })
+ }
+
+ for _, param := range action.Parameters {
+ //log.Printf("Param: %#v", param)
+ if param.Configuration {
+ continue
+ }
+
+ newParams = append(newParams, param)
+ }
+ } else {
+ // Rebuild params with the right data. This is to prevent issues on the frontend
+ for _, param := range action.Parameters {
+
+ for _, authparam := range curAuth.Fields {
+ if param.Name == authparam.Key {
+ param.Value = authparam.Value
+ //log.Printf("Name: %s - value: %s", param.Name, param.Value)
+ //log.Printf("Name: %s - value: %s\n", param.Name, param.Value)
+ break
+ }
+ }
+
+ newParams = append(newParams, param)
+ }
+ }
+
+ action.Parameters = newParams
+ }
+
+ action.LargeImage = ""
+ if len(action.Label) == 0 {
+ action.Label = action.ID
+ }
+ //log.Printf("LABEL: %s", action.Label)
+ newActions = append(newActions, action)
+
+ // If the node is NOT found, it's supposed to be set to SKIPPED,
+ // as it's not a childnode of the startnode
+ // This is a configuration item for the workflow itself.
+ if len(workflowExecution.Results) > 0 {
+ defaultResults = []shuffle.ActionResult{}
+ for _, result := range workflowExecution.Results {
+ if result.Status == "WAITING" {
+ result.Status = "FINISHED"
+ result.Result = "Continuing"
+ }
+
+ defaultResults = append(defaultResults, result)
+ }
+ } else if len(workflowExecution.Results) == 0 && !workflowExecution.Workflow.Configuration.StartFromTop {
+ found := false
+ for _, nodeId := range childNodes {
+ if nodeId == action.ID {
+ //log.Printf("Found %s", action.ID)
+ found = true
+ }
+ }
+
+ if !found {
+ if action.ID == workflowExecution.Start {
+ continue
+ }
+
+ //log.Printf("[WARNING] Set %s to SKIPPED as it's NOT a childnode of the startnode.", action.ID)
+ curaction := shuffle.Action{
+ AppName: action.AppName,
+ AppVersion: action.AppVersion,
+ Label: action.Label,
+ Name: action.Name,
+ ID: action.ID,
+ }
+ //action
+ //curaction.Parameters = []
+ defaultResults = append(defaultResults, shuffle.ActionResult{
+ Action: curaction,
+ ExecutionId: workflowExecution.ExecutionId,
+ Authorization: workflowExecution.Authorization,
+ Result: "Skipped because it's not under the startnode",
+ StartedAt: 0,
+ CompletedAt: 0,
+ Status: "SKIPPED",
+ })
+ }
+ }
+ }
+
+ removeTriggers := []string{}
+ for triggerIndex, trigger := range workflowExecution.Workflow.Triggers {
+ //log.Printf("[INFO] ID: %s vs %s", trigger.ID, workflowExecution.Start)
+ if trigger.ID == workflowExecution.Start {
+ if trigger.AppName == "User Input" {
+ startFound = true
+ break
+ }
+ }
+
+ if trigger.AppName == "User Input" || trigger.AppName == "Shuffle Workflow" {
+ found := false
+ for _, node := range childNodes {
+ if node == trigger.ID {
+ found = true
+ break
+ }
+ }
+
+ if !found {
+ //log.Printf("SHOULD SET TRIGGER %s TO BE SKIPPED", trigger.ID)
+
+ curaction := shuffle.Action{
+ AppName: "shuffle-subflow",
+ AppVersion: trigger.AppVersion,
+ Label: trigger.Label,
+ Name: trigger.Name,
+ ID: trigger.ID,
+ }
+
+ defaultResults = append(defaultResults, shuffle.ActionResult{
+ Action: curaction,
+ ExecutionId: workflowExecution.ExecutionId,
+ Authorization: workflowExecution.Authorization,
+ Result: "Skipped because it's not under the startnode",
+ StartedAt: 0,
+ CompletedAt: 0,
+ Status: "SKIPPED",
+ })
+ } else {
+ // Replaces trigger with the subflow
+ //if trigger.AppName == "Shuffle Workflow" {
+ // replaceActions := false
+ // workflowAction := ""
+ // for _, param := range trigger.Parameters {
+ // if param.Name == "argument" && !strings.Contains(param.Value, ".#") {
+ // replaceActions = true
+ // }
+
+ // if param.Name == "startnode" {
+ // workflowAction = param.Value
+ // }
+ // }
+
+ // if replaceActions {
+ // replacementNodes, newBranches, lastnode := shuffle.GetReplacementNodes(ctx, workflowExecution, trigger, trigger.Label)
+ // log.Printf("REPLACEMENTS: %d, %d", len(replacementNodes), len(newBranches))
+ // if len(replacementNodes) > 0 {
+ // for _, action := range replacementNodes {
+ // found := false
+
+ // for subActionIndex, subaction := range newActions {
+ // if subaction.ID == action.ID {
+ // found = true
+ // //newActions[subActionIndex].Name = action.Name
+ // newActions[subActionIndex].Label = action.Label
+ // break
+ // }
+ // }
+
+ // if !found {
+ // action.SubAction = true
+ // newActions = append(newActions, action)
+ // }
+
+ // // Check if it's already set to have a value
+ // for resultIndex, result := range defaultResults {
+ // if result.Action.ID == action.ID {
+ // defaultResults = append(defaultResults[:resultIndex], defaultResults[resultIndex+1:]...)
+ // break
+ // }
+ // }
+ // }
+
+ // for _, branch := range newBranches {
+ // workflowExecution.Workflow.Branches = append(workflowExecution.Workflow.Branches, branch)
+ // }
+
+ // // Append branches:
+ // // parent -> new inner node (FIRST one)
+ // for branchIndex, branch := range workflowExecution.Workflow.Branches {
+ // if branch.DestinationID == trigger.ID {
+ // log.Printf("REPLACE DESTINATION WITH %s!!", workflowAction)
+ // workflowExecution.Workflow.Branches[branchIndex].DestinationID = workflowAction
+ // }
+
+ // if branch.SourceID == trigger.ID {
+ // log.Printf("REPLACE SOURCE WITH LASTNODE %s!!", lastnode)
+ // workflowExecution.Workflow.Branches[branchIndex].SourceID = lastnode
+ // }
+ // }
+
+ // // Remove the trigger
+ // removeTriggers = append(removeTriggers, workflowExecution.Workflow.Triggers[triggerIndex].ID)
+ // }
+
+ // log.Printf("NEW ACTION LENGTH %d, RESULT: %d, Triggers: %d, BRANCHES: %d", len(newActions), len(defaultResults), len(workflowExecution.Workflow.Triggers), len(workflowExecution.Workflow.Branches))
+ // }
+ //}
+ _ = triggerIndex
+ }
+ }
+ }
+
+ //newTriggers := []shuffle.Trigger{}
+ //for _, trigger := range workflowExecution.Workflow.Triggers {
+ // found := false
+ // for _, triggerId := range removeTriggers {
+ // if trigger.ID == triggerId {
+ // found = true
+ // break
+ // }
+ // }
+
+ // if found {
+ // log.Printf("[WARNING] Removed trigger %s during execution", trigger.ID)
+ // continue
+ // }
+
+ // newTriggers = append(newTriggers, trigger)
+ //}
+ //workflowExecution.Workflow.Triggers = newTriggers
+ _ = removeTriggers
+
+ if !startFound {
+ if len(workflowExecution.Start) == 0 && len(workflowExecution.Workflow.Start) > 0 {
+ workflowExecution.Start = workflow.Start
+ } else if len(workflowExecution.Workflow.Actions) > 0 {
+ workflowExecution.Start = workflowExecution.Workflow.Actions[0].ID
+ } else {
+ log.Printf("[ERROR] Startnode %s doesn't exist!!", workflowExecution.Start)
+ return shuffle.WorkflowExecution{}, fmt.Sprintf("Workflow action %s doesn't exist in workflow", workflowExecution.Start), errors.New(fmt.Sprintf(`Workflow start node "%s" doesn't exist. Exiting!`, workflowExecution.Start))
+ }
+ }
+
+ //log.Printf("EXECUTION START: %s", workflowExecution.Start)
+
+ // Verification for execution environments
+ workflowExecution.Results = defaultResults
+ workflowExecution.Workflow.Actions = newActions
+ onpremExecution := true
+ _ = onpremExecution
+ environments := []string{}
+
+ if len(workflowExecution.ExecutionOrg) == 0 && len(workflow.ExecutingOrg.Id) > 0 {
+ workflowExecution.ExecutionOrg = workflow.ExecutingOrg.Id
+ }
+
+ var allEnvs []shuffle.Environment
+ if len(workflowExecution.ExecutionOrg) > 0 {
+ //log.Printf("[INFO] Executing ORG: %s", workflowExecution.ExecutionOrg)
+
+ allEnvironments, err := shuffle.GetEnvironments(ctx, workflowExecution.ExecutionOrg)
+ if err != nil {
+ log.Printf("[WARNING] Failed finding environments: %s", err)
+ return shuffle.WorkflowExecution{}, fmt.Sprintf("Workflow environments not found for this org"), errors.New(fmt.Sprintf("Workflow environments not found for this org"))
+ }
+
+ for _, curenv := range allEnvironments {
+ if curenv.Archived {
+ continue
+ }
+
+ allEnvs = append(allEnvs, curenv)
+ }
+ } else {
+ log.Printf("[ERROR] No org identified for execution of %s. Returning", workflowExecution.Workflow.ID)
+ return shuffle.WorkflowExecution{}, "No org identified for execution", errors.New("No org identified for execution")
+ }
+
+ if len(allEnvs) == 0 {
+ log.Printf("[ERROR] No active environments found for org: %s", workflowExecution.ExecutionOrg)
+ return shuffle.WorkflowExecution{}, "No active environments found", errors.New(fmt.Sprintf("No active env found for org %s", workflowExecution.ExecutionOrg))
+ }
+
+ // Check if the actions are children of the startnode?
+ imageNames := []string{}
+ cloudExec := false
+ _ = cloudExec
+ for _, action := range workflowExecution.Workflow.Actions {
+ // Verify if the action environment exists and append
+ found := false
+ for _, env := range allEnvs {
+ if env.Name == action.Environment {
+ found = true
+
+ if env.Type == "cloud" {
+ cloudExec = true
+ } else if env.Type == "onprem" {
+ onpremExecution = true
+ } else {
+ log.Printf("[ERROR] No handler for environment type %s", env.Type)
+ return shuffle.WorkflowExecution{}, "No active environments found", errors.New(fmt.Sprintf("No handler for environment type %s", env.Type))
+ }
+ break
+ }
+ }
+
+ if !found {
+ log.Printf("[ERROR] Couldn't find environment %s. Maybe it's inactive?", action.Environment)
+ return shuffle.WorkflowExecution{}, "Couldn't find the environment", errors.New(fmt.Sprintf("Couldn't find env %s in org %s", action.Environment, workflowExecution.ExecutionOrg))
+ }
+
+ found = false
+ for _, env := range environments {
+ if env == action.Environment {
+
+ found = true
+ break
+ }
+ }
+
+ // Check if the app exists?
+ newName := action.AppName
+ newName = strings.ReplaceAll(newName, " ", "-")
+ imageNames = append(imageNames, fmt.Sprintf("%s:%s_%s", baseDockerName, newName, action.AppVersion))
+
+ if !found {
+ environments = append(environments, action.Environment)
+ }
+ }
+
+ err = imageCheckBuilder(imageNames)
+ if err != nil {
+ log.Printf("[ERROR] Failed building the required images from %#v: %s", imageNames, err)
return shuffle.WorkflowExecution{}, "Failed building missing Docker images", err
}
@@ -1116,7 +1978,8 @@ func executeWorkflow(resp http.ResponseWriter, request *http.Request) {
return
}
- //memcacheName := fmt.Sprintf("%s_%s", user.Username, fileId)
+ log.Printf("[INFO] Inside execute workflow for ID %s", fileId)
+
ctx := context.Background()
workflow, err := shuffle.GetWorkflow(ctx, fileId)
if err != nil && workflow.ID == "" {
@@ -1133,6 +1996,7 @@ func executeWorkflow(resp http.ResponseWriter, request *http.Request) {
// 1. Parent workflow contains this workflow ID in the source trigger?
// 2. Parent workflow's owner is same org?
// 3. Parent execution auth is correct
+ log.Printf("[INFO] Inside execute workflow access validation!")
executionAuthValid, newOrgId = shuffle.RunExecuteAccessValidation(request, workflow)
if !executionAuthValid {
@@ -1161,12 +2025,18 @@ func executeWorkflow(resp http.ResponseWriter, request *http.Request) {
}
}
- log.Printf("[INFO] Starting execution of %s!", fileId)
+ log.Printf("[AUDIT] Starting execution of workflow '%s' by user %s (%s)!", fileId, user.Username, user.Id)
user.ActiveOrg.Users = []shuffle.UserMini{}
workflow.ExecutingOrg = user.ActiveOrg
workflowExecution, executionResp, err := handleExecution(fileId, *workflow, request, user.ActiveOrg.Id)
if err == nil {
+ if strings.Contains(executionResp, "User Input:") {
+ resp.WriteHeader(400)
+ resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, executionResp)))
+ return
+ }
+
resp.WriteHeader(200)
resp.Write([]byte(fmt.Sprintf(`{"success": true, "execution_id": "%s", "authorization": "%s"}`, workflowExecution.ExecutionId, workflowExecution.Authorization)))
return
@@ -1627,15 +2497,15 @@ func scheduleWorkflow(resp http.ResponseWriter, request *http.Request) {
return
}
- log.Printf("Action: %#v", action)
+ //log.Printf("Starting Cloud schedule Action: %#v", action)
err = executeCloudAction(action, org.SyncConfig.Apikey)
if err != nil {
- log.Printf("Failed cloud action START schedule: %s", err)
+ log.Printf("[WARNING] Failed cloud action START schedule: %s", err)
resp.WriteHeader(401)
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err)))
return
} else {
- log.Printf("Successfully set up cloud action schedule")
+ log.Printf("[INFO] Successfully set up cloud action schedule")
resp.WriteHeader(200)
resp.Write([]byte(fmt.Sprintf(`{"success": true, "reason": "Done"}`)))
return
@@ -1923,7 +2793,7 @@ func loadGithubWorkflows(url, username, password, userId, branch, orgId string)
storer := memory.NewStorage()
r, err := git.Clone(storer, fs, cloneOptions)
if err != nil {
- log.Printf("Failed loading repo %s into memory (github workflows): %s", url, err)
+ log.Printf("[INFO] Failed loading repo %s into memory (github workflows): %s", url, err)
return err
}
@@ -2194,7 +3064,7 @@ func iterateOpenApiGithub(fs billy.Filesystem, dir []os.FileInfo, extra string,
if !found {
err = shuffle.SetWorkflowAppDatastore(ctx, api, api.ID)
if err != nil {
- log.Printf("[WARNING] Failed setting workflowapp in loop: %s", err)
+ log.Printf("[WARNING] Failed setting workflowapp %s (%s) in loop: %s", api.Name, api.ID, err)
continue
} else {
appCounter += 1
@@ -2423,7 +3293,7 @@ func setNewWorkflowApp(resp http.ResponseWriter, request *http.Request) {
// Might require reflection into the python code to append the fields as well
for index, action := range workflowapp.Actions {
if action.AuthNotRequired {
- log.Printf("Skipping auth setup: %s", action.Name)
+ log.Printf("[WARNING] Skipping auth setup for: %s", action.Name)
continue
}
@@ -2471,7 +3341,7 @@ func setNewWorkflowApp(resp http.ResponseWriter, request *http.Request) {
err = shuffle.SetWorkflowAppDatastore(ctx, workflowapp, workflowapp.ID)
if err != nil {
- log.Printf("Failed setting workflowapp: %s", err)
+ log.Printf("[WARNING] Failed setting workflowapp: %s", err)
resp.WriteHeader(401)
resp.Write([]byte(`{"success": false}`))
return
@@ -2514,7 +3384,7 @@ func handleUserInput(trigger shuffle.Trigger, organizationId string, workflowId
if len(triggerType) == 0 {
log.Printf("[WARNING] No type specified for user input node")
- return errors.New("No type specified for user input node")
+ //return errors.New("No type specified for user input node")
}
// FIXME: This is not the right time to send them, BUT it's well served for testing. Save -> send email / sms
@@ -2597,7 +3467,7 @@ func executeSingleAction(resp http.ResponseWriter, request *http.Request) {
if user.Role == "org-reader" {
log.Printf("[WARNING] Org-reader doesn't have access to execute single action: %s (%s)", user.Username, user.Id)
- resp.WriteHeader(401)
+ resp.WriteHeader(403)
resp.Write([]byte(`{"success": false, "reason": "Read only user"}`))
return
}
@@ -2664,10 +3534,14 @@ func executeSingleAction(resp http.ResponseWriter, request *http.Request) {
time.Sleep(2 * time.Second)
log.Printf("[INFO] Starting validation of execution %s", workflowExecution.ExecutionId)
- returnBytes := shuffle.HandleRetValidation(ctx, workflowExecution)
+ returnBody := shuffle.HandleRetValidation(ctx, workflowExecution, 1)
+ returnBytes, err := json.Marshal(returnBody)
+ if err != nil {
+ log.Printf("[ERROR] Failed to marshal retStruct in single execution: %s", err)
+ }
resp.WriteHeader(200)
- resp.Write(returnBytes)
+ resp.Write([]byte(returnBytes))
}
// Onlyname is used to
@@ -2696,6 +3570,11 @@ func IterateAppGithubFolders(ctx context.Context, fs billy.Filesystem, dir []os.
// Folder?
switch mode := file.Mode(); {
case mode.IsDir():
+ // Specific folder for skipping
+ if file.Name() == "unsupported" {
+ continue
+ }
+
tmpExtra := fmt.Sprintf("%s%s/", extra, file.Name())
dir, err := fs.ReadDir(tmpExtra)
if err != nil {
@@ -2719,7 +3598,8 @@ func IterateAppGithubFolders(ctx context.Context, fs billy.Filesystem, dir []os.
//buildFirst, buildLast, err := IterateAppGithubFolders(fs, dir, tmpExtra, "", forceUpdate)
if !forceUpdate {
- return buildLaterFirst, buildLaterList, err
+ continue
+ //return buildLaterFirst, buildLaterList, err
}
}
@@ -2737,7 +3617,7 @@ func IterateAppGithubFolders(ctx context.Context, fs billy.Filesystem, dir []os.
fullPath = fmt.Sprintf("%s%s", extra, "api.yml")
fileReader, err = fs.Open(fullPath)
if err != nil {
- log.Printf("Failed finding api.yaml/yml: %s", err)
+ log.Printf("[INFO] Failed finding api.yaml/yml for file %s: %s", filename, err)
continue
}
}
@@ -2793,7 +3673,8 @@ func IterateAppGithubFolders(ctx context.Context, fs billy.Filesystem, dir []os.
err = gyaml.Unmarshal(appfileData, &workflowapp)
if err != nil {
log.Printf("[WARNING] Failed building workflowapp %s: %s", extra, err)
- return buildLaterFirst, buildLaterList, errors.New(fmt.Sprintf("Failed building %s: %s", extra, err))
+ continue
+ //return buildLaterFirst, buildLaterList, errors.New(fmt.Sprintf("Failed building %s: %s", extra, err))
//continue
}
@@ -2841,7 +3722,7 @@ func IterateAppGithubFolders(ctx context.Context, fs billy.Filesystem, dir []os.
}
}
- workflowapp.ReferenceInfo.GithubUrl = fmt.Sprintf("https://github.com/frikky/shuffle-apps/tree/master/%s/%s", strings.ToLower(newName), workflowapp.AppVersion)
+ workflowapp.ReferenceInfo.GithubUrl = fmt.Sprintf("https://github.com/shuffle/shuffle-apps/tree/master/%s/%s", strings.ToLower(newName), workflowapp.AppVersion)
tags := []string{
fmt.Sprintf("%s:%s_%s", baseDockerName, strings.ToLower(newName), workflowapp.AppVersion),
@@ -3008,6 +3889,32 @@ func IterateAppGithubFolders(ctx context.Context, fs billy.Filesystem, dir []os.
cacheKey = fmt.Sprintf("workflowapps-sorted-1000")
shuffle.DeleteCache(ctx, cacheKey)
+ newSortedList := []shuffle.BuildLaterStruct{}
+ initApps := []string{
+ "tools",
+ "http",
+ "email",
+ }
+ for _, buildLater := range buildLaterFirst {
+ found := false
+ for _, appname := range initApps {
+ for _, tag := range buildLater.Tags {
+ if strings.Contains(strings.ToLower(tag), appname) {
+ newSortedList = append(newSortedList, buildLater)
+ found = true
+ break
+ }
+ }
+
+ if found {
+ break
+ }
+ }
+ }
+
+ // Prepend newSortedList to buildLaterFirst
+ buildLaterFirst = append(newSortedList, buildLaterFirst...)
+
if len(extra) == 0 {
log.Printf("[INFO] Starting build of %d containers (FIRST)", len(buildLaterFirst))
for _, item := range buildLaterFirst {
@@ -3103,8 +4010,8 @@ func LoadSpecificApps(resp http.ResponseWriter, request *http.Request) {
var tmpBody tmpStruct
err = json.Unmarshal(body, &tmpBody)
if err != nil {
- log.Printf("Error with unmarshal tmpBody: %s", err)
- resp.WriteHeader(401)
+ log.Printf("[WARNING] Error with unmarshal app git clone: %s", err)
+ resp.WriteHeader(500)
resp.Write([]byte(`{"success": false}`))
return
}
@@ -3131,20 +4038,20 @@ func LoadSpecificApps(resp http.ResponseWriter, request *http.Request) {
storer := memory.NewStorage()
r, err := git.Clone(storer, fs, cloneOptions)
if err != nil {
- log.Printf("Failed loading repo %s into memory (github workflows 2): %s", tmpBody.URL, err)
- resp.WriteHeader(401)
+ log.Printf("[WARNING] Failed loading repo %s into memory (github apps 2): %s", tmpBody.URL, err)
+ resp.WriteHeader(500)
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err)))
return
}
dir, err := fs.ReadDir("/")
if err != nil {
- log.Printf("FAiled reading folder: %s", err)
+ log.Printf("[WARNING] FAiled reading folder: %s", err)
}
_ = r
if tmpBody.ForceUpdate {
- log.Printf("[AUDIT] Running with force update from user %s (%s) for %s!", user.Username, user.Id, tmpBody.URL)
+ log.Printf("[AUDIT] Running app get with force update from user %s (%s) for %s!", user.Username, user.Id, tmpBody.URL)
} else {
log.Printf("[AUDIT] Updating apps with updates for user %s (%s) for %s (no force)", user.Username, user.Id, tmpBody.URL)
}
diff --git a/backend/tests/cache.sh b/backend/tests/cache.sh
old mode 100644
new mode 100755
diff --git a/backend/tests/cleanup.sh b/backend/tests/cleanup.sh
old mode 100644
new mode 100755
diff --git a/backend/tests/dockerpull.sh b/backend/tests/dockerpull.sh
old mode 100644
new mode 100755
diff --git a/backend/tests/execute.sh b/backend/tests/execute.sh
old mode 100644
new mode 100755
diff --git a/backend/tests/file_download.sh b/backend/tests/file_download.sh
old mode 100644
new mode 100755
diff --git a/backend/tests/files.sh b/backend/tests/files.sh
index c92a0ebc..5a699822 100755
--- a/backend/tests/files.sh
+++ b/backend/tests/files.sh
@@ -16,7 +16,7 @@
#r.HandleFunc("/api/v1/files/{fileId}", handleDeleteFile).Methods("DELETE", "OPTIONS")
-#curl http://localhost:5001/api/v1/files/create -H "Authorization: Bearer 09627dcb-7e2a-4843-819b-417d268ff840" -d '{"filename": "rule2.yar", "org_id": "11f67b76-6051-4425-b0d6-be23daac6d12", "workflow_id": "global", "namespace": "yara"}'
-curl http://localhost:5002/api/v1/files/file_366ee8d2-1af6-4270-8639-213af30b4a29/upload -H "Authorization: Bearer 09627dcb-7e2a-4843-819b-417d268ff840" -F 'shuffle_file=@upload.sh'
+curl http://localhost:5001/api/v1/files/create -H "Authorization: Bearer 317f5066-395c-414d-aa3d-479cf27f47dd" -d '{"filename": "rule2.yar", "org_id": "292c7e25-40ad-4f05-904f-77d3c7b735e6", "workflow_id": "global", "namespace": "yara"}'
+curl http://localhost:5001/api/v1/files/file_eb89e315-eb66-4d76-9df7-530fb003fc84/upload -H "Authorization: Bearer 317f5066-395c-414d-aa3d-479cf27f47dd" -F 'shuffle_file=@upload.sh'
#curl http://localhost:5001/api/v1/files/namespaces/yara -H "Authorization: Bearer c5b4c827-65ec-47f4-9e8a-234cdba38959" --output rules.zip
diff --git a/backend/tests/forparser.py b/backend/tests/forparser.py
old mode 100644
new mode 100755
diff --git a/backend/tests/hooks.sh b/backend/tests/hooks.sh
old mode 100644
new mode 100755
diff --git a/backend/tests/hotload.sh b/backend/tests/hotload.sh
old mode 100644
new mode 100755
diff --git a/backend/tests/list_files.sh b/backend/tests/list_files.sh
old mode 100644
new mode 100755
diff --git a/backend/tests/migrate_db.sh b/backend/tests/migrate_db.sh
old mode 100644
new mode 100755
diff --git a/backend/tests/run_function.py b/backend/tests/run_function.py
old mode 100644
new mode 100755
diff --git a/backend/tests/scheduleapps.sh b/backend/tests/scheduleapps.sh
old mode 100644
new mode 100755
diff --git a/backend/tests/schedules.sh b/backend/tests/schedules.sh
old mode 100644
new mode 100755
diff --git a/backend/tests/sendmail.sh b/backend/tests/sendmail.sh
old mode 100644
new mode 100755
diff --git a/backend/tests/testWorkflows.sh b/backend/tests/testWorkflows.sh
old mode 100644
new mode 100755
diff --git a/backend/tests/test_wrappers.py b/backend/tests/test_wrappers.py
old mode 100644
new mode 100755
diff --git a/backend/tests/triggers.sh b/backend/tests/triggers.sh
old mode 100644
new mode 100755
diff --git a/backend/tests/users.sh b/backend/tests/users.sh
old mode 100644
new mode 100755
diff --git a/backend/tests/validate_app_values.sh b/backend/tests/validate_app_values.sh
old mode 100644
new mode 100755
diff --git a/backend/tests/websocket.sh b/backend/tests/websocket.sh
old mode 100644
new mode 100755
diff --git a/backend/tests/workflowdata.json b/backend/tests/workflowdata.json
old mode 100644
new mode 100755
diff --git a/backend/tests/workflowresults.sh b/backend/tests/workflowresults.sh
old mode 100644
new mode 100755
diff --git a/backend/tests/workflows.sh b/backend/tests/workflows.sh
old mode 100644
new mode 100755
diff --git a/docker-compose.yml b/docker-compose.yml
old mode 100644
new mode 100755
index 4451238d..9e66eb53
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -43,7 +43,6 @@ services:
- /var/run/docker.sock:/var/run/docker.sock
environment:
#- DOCKER_HOST=tcp://docker-socket-proxy:2375
- - SHUFFLE_WORKER_VERSION=latest
- ENVIRONMENT_NAME=${ENVIRONMENT_NAME}
- BASE_URL=http://${OUTER_HOSTNAME}:5001
- DOCKER_API_VERSION=1.40
@@ -58,12 +57,12 @@ services:
security_opt:
- seccomp:unconfined
opensearch:
- image: opensearchproject/opensearch:2.4.0
+ image: opensearchproject/opensearch:2.5.0
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
+ - "OPENSEARCH_JAVA_OPTS=-Xms2048m -Xmx2048m" # minimum and maximum Java heap size, recommend setting both to 50% of system RAM
- cluster.initial_master_nodes=shuffle-opensearch
- cluster.routing.allocation.disk.threshold_enabled=false
- cluster.name=shuffle-cluster
@@ -114,4 +113,10 @@ services:
networks:
shuffle:
driver: bridge
- #driver: overlay
+
+ # uncomment to set MTU for swarm mode.
+ # MTU should be whatever is your host's preferred MTU is.
+ # Refer to this doc to figure out what your host's MTU is:
+ # https://shuffler.io/docs/troubleshooting#TLS_timeout_error/Timeout_Errors/EOF_Errors
+ # driver_opts:
+ # com.docker.network.driver.mtu: 1460
diff --git a/frontend/Dockerfile b/frontend/Dockerfile
old mode 100644
new mode 100755
diff --git a/frontend/README.md b/frontend/README.md
old mode 100644
new mode 100755
diff --git a/frontend/build.sh b/frontend/build.sh
old mode 100644
new mode 100755
diff --git a/frontend/certs/certreq.csr b/frontend/certs/certreq.csr
old mode 100644
new mode 100755
diff --git a/frontend/certs/fullchain.pem b/frontend/certs/fullchain.pem
old mode 100644
new mode 100755
diff --git a/frontend/certs/privkey.pem b/frontend/certs/privkey.pem
old mode 100644
new mode 100755
diff --git a/frontend/confd/conf.d/nginx.conf.toml b/frontend/confd/conf.d/nginx.conf.toml
old mode 100644
new mode 100755
diff --git a/frontend/confd/templates/nginx.conf b/frontend/confd/templates/nginx.conf
old mode 100644
new mode 100755
diff --git a/frontend/package.json b/frontend/package.json
old mode 100644
new mode 100755
index d23724c9..5082a3b8
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -1,13 +1,14 @@
{
"name": "shuffler",
"homepage": "https://shuffler.io",
- "version": "1.1.0",
+ "version": "1.2.0",
"private": true,
"dependencies": {
- "@babel/core": "^7.15.8",
+ "@codemirror/commands": "^6.2.2",
"@emotion/is-prop-valid": "^1.1.1",
"@emotion/react": "^11.7.0",
"@emotion/styled": "^11.6.0",
+ "@lezer/highlight": "^1.1.3",
"@material-ui/core": "^4.5.2",
"@material-ui/icons": "^4.5.1",
"@material-ui/lab": "^4.0.0-alpha.58",
@@ -17,10 +18,10 @@
"@mui/icons-material": "^5.2.1",
"@mui/material": "^5.2.3",
"@mui/x-data-grid": "^5.17.11",
+ "@uiw/codemirror-themes": "^4.19.9",
"@uiw/react-codemirror": "^3.2.1",
"@use-it/interval": "^1.0.0",
"algoliasearch": "^4.13.1",
- "babel-eslint": "^10.1.0",
"class-transformer": "^0.4.0",
"create-react-app": "^4.0.3",
"cytoscape": "^3.15.1",
@@ -58,7 +59,7 @@
"react-draggable": "^3.3.2",
"react-driftjs": "^1.2.2",
"react-dropzone": "^10.1.10",
- "react-ga": "^2.7.0",
+ "react-ga4": "^2.0.0",
"react-iframe": "^1.8.0",
"react-instantsearch-dom": "^6.28.0",
"react-json-pretty": "^2.2.0",
@@ -76,14 +77,12 @@
"shellwords": "^0.1.1",
"simplebar": "^4.2.3",
"styled-components": "^4.4.0",
- "webpack": "^4.44.2",
- "websocket": "^1.0.30",
"yaml": "^1.7.2",
"yamljs": "^0.3.0",
"zone.js": "~0.11.4"
},
"scripts": {
- "start": "react-scripts start",
+ "start": "HTTPS=false&&PORT=3000 react-scripts --openssl-legacy-provider start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject",
@@ -105,7 +104,11 @@
"not op_mini all"
],
"devDependencies": {
+ "@babel/core": "^7.15.8",
+ "@babel/plugin-proposal-private-property-in-object": "^7.21.11",
+ "babel-eslint": "^10.1.0",
"prettier": "2.4.1",
- "promise-window": "^1.2.1"
+ "promise-window": "^1.2.1",
+ "webpack": "^4.44.2"
}
}
diff --git a/frontend/public/favicon.ico b/frontend/public/favicon.ico
old mode 100644
new mode 100755
diff --git a/frontend/public/images/Shuffle_logo.png b/frontend/public/images/Shuffle_logo.png
old mode 100644
new mode 100755
diff --git a/frontend/public/images/experienced.png b/frontend/public/images/experienced.png
new file mode 100644
index 00000000..8e5f3e5a
Binary files /dev/null and b/frontend/public/images/experienced.png differ
diff --git a/frontend/public/images/logos/orange_logo.svg b/frontend/public/images/logos/orange_logo.svg
new file mode 100644
index 00000000..1024dd6a
--- /dev/null
+++ b/frontend/public/images/logos/orange_logo.svg
@@ -0,0 +1,5 @@
+
diff --git a/frontend/public/images/welcome-to-shuffle.png b/frontend/public/images/welcome-to-shuffle.png
new file mode 100644
index 00000000..cb976e31
Binary files /dev/null and b/frontend/public/images/welcome-to-shuffle.png differ
diff --git a/frontend/public/index.html b/frontend/public/index.html
old mode 100644
new mode 100755
diff --git a/frontend/public/manifest.json b/frontend/public/manifest.json
old mode 100644
new mode 100755
diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx
old mode 100644
new mode 100755
index 49d3566c..1ab5b924
--- a/frontend/src/App.jsx
+++ b/frontend/src/App.jsx
@@ -1,7 +1,6 @@
import React, { useState, useEffect } from "react";
-//import { Route, Routes } from "react-router";
-import { Route, Routes, BrowserRouter } from "react-router-dom";
+import { Link, Route, Routes, BrowserRouter, useNavigate } from "react-router-dom";
import { CookiesProvider } from "react-cookie";
import { removeCookies, useCookies } from "react-cookie";
@@ -10,7 +9,7 @@ import GettingStarted from "./views/GettingStarted";
import EditWebhook from "./views/EditWebhook";
import AngularWorkflow from "./views/AngularWorkflow";
-import Header from "./components/Header";
+import Header from "./components/Header.jsx";
import theme from "./theme";
import Apps from "./views/Apps";
import AppCreator from "./views/AppCreator";
@@ -25,6 +24,7 @@ import Introduction from "./views/Introduction";
import SetAuthentication from "./views/SetAuthentication";
import SetAuthenticationSSO from "./views/SetAuthenticationSSO";
import Search from "./views/Search.jsx";
+import RunWorkflow from "./views/RunWorkflow.jsx";
import LandingPageNew from "./views/LandingpageNew";
import LoginPage from "./views/LoginPage";
@@ -335,7 +335,9 @@ const App = (message, props) => {
userdata={userdata}
{...props}
/>
+ {/*
+ */}
{
globalUrl={globalUrl}
setCookie={setCookie}
cookies={cookies}
+ checkLogin={checkLogin}
{...props}
/>
}
@@ -469,17 +472,18 @@ const App = (message, props) => {
/>
}
/>
+ } />
+
}
/>
{
path="/workflows"
element={
{
/>
}
/>
+ } />
+ } />
`),
+ "SIEM": encodeURI(`data:image/svg+xml;utf-8,`),
+
+ "CASES": encodeURI(`data:image/svg+xml;utf-8,`),
+
"EDR & AV": encodeURI(`data:image/svg+xml;utf-8,`),
+
+ "INTEL": encodeURI(`data:image/svg+xml;utf-8,`),
+
+ "COMMS": encodeURI(`data:image/svg+xml;utf-8,`),
+
+ "NETWORK": encodeURI(`data:image/svg+xml;utf-8,`),
+
+ "INTEL": encodeURI(`data:image/svg+xml;utf-8,`),
+
+ "ASSETS": encodeURI(`data:image/svg+xml;utf-8,`),
+
+ "IAM": encodeURI(`data:image/svg+xml;utf-8,`),
}
export const usecases = {
@@ -99,58 +116,7 @@ export const usecases = {
"human": true,
},
]},
- "Ransomware": {
- "manual": [],
- "automated": [
- {
- "source": "BOTTOM_LEFT",
- "target": "EDR & AV",
- "description": "EDR & AV alert",
- "human": false,
- },
- {
- "source": "EDR & AV",
- "target": "SHUFFLE",
- "description": "",
- "human": false,
- },
- {
- "source": "SHUFFLE",
- "target": "EDR & AV",
- "human": false,
- "description": "isolate",
- },
- {
- "source": "SHUFFLE",
- "target": "IAM",
- "human": false,
- "description": "Block access",
- },
- {
- "source": "SHUFFLE",
- "target": "COMMS",
- "description": "Notify oncall and affected user",
- "human": false,
- },
- {
- "source": "SHUFFLE",
- "target": "CASES",
- "description": "Create enriched alert",
- "human": false,
- },
- {
- "source": "SHUFFLE",
- "target": "CASES",
- "human": false,
- },
- {
- "source": "CASES",
- "target": "EDR & AV",
- "description": "Validate alert",
- "human": true,
- },
- ]
- },
+
"Exploits": {
"manual": [],
"automated": [
@@ -203,46 +169,6 @@ export const usecases = {
},
]
},
- "AWS S3 honeypots": {
- "manual": [],
- "automated": [
- {
- "source": "TOP_LEFT",
- "target": "SIEM",
- "description": "S3 logs",
- "human": false,
- },
-
- {
- "source": "SIEM",
- "target": "SHUFFLE",
- "human": false,
- },
- {
- "source": "SHUFFLE",
- "target": "INTEL",
- "description": "Add sighting",
- "human": false,
- },
- {
- "source": "INTEL",
- "target": "SHUFFLE",
- "human": false,
- },
- {
- "source": "SHUFFLE",
- "target": "CASES",
- "description": "Create case",
- "human": false,
- },
- {
- "source": "SHUFFLE",
- "target": "NETWORK",
- "description": "Block IP",
- "human": false,
- },
- ]
- },
"SIEM alerts": {
"manual": [],
"automated": [
@@ -518,6 +444,58 @@ export const usecases = {
},
]
},
+ "Ransomware": {
+ "manual": [],
+ "automated": [
+ {
+ "source": "BOTTOM_LEFT",
+ "target": "EDR & AV",
+ "description": "EDR & AV alert",
+ "human": false,
+ },
+ {
+ "source": "EDR & AV",
+ "target": "SHUFFLE",
+ "description": "",
+ "human": false,
+ },
+ {
+ "source": "SHUFFLE",
+ "target": "EDR & AV",
+ "human": false,
+ "description": "isolate",
+ },
+ {
+ "source": "SHUFFLE",
+ "target": "IAM",
+ "human": false,
+ "description": "Block access",
+ },
+ {
+ "source": "SHUFFLE",
+ "target": "COMMS",
+ "description": "Notify oncall and affected user",
+ "human": false,
+ },
+ {
+ "source": "SHUFFLE",
+ "target": "CASES",
+ "description": "Create enriched alert",
+ "human": false,
+ },
+ {
+ "source": "SHUFFLE",
+ "target": "CASES",
+ "human": false,
+ },
+ {
+ "source": "CASES",
+ "target": "EDR & AV",
+ "description": "Validate alert",
+ "human": true,
+ },
+ ]
+ },
"Draw": {
}
}
@@ -541,13 +519,83 @@ const AppFramework = (props) => {
const [usecaseType, setUsecaseType] = React.useState(0)
const [selectedUsecase, setSelectedUsecase] = React.useState(selectedOption !== undefined ? selectedOption : "Phishing")
+ const [injectedApps, setInjectedApps] = React.useState([])
+
const scale = size === undefined ? 1 : size > 5 ? 3 : size
const alert = useAlert()
+
+ const handleLoadNextSuggestion = (frameworkData) => {
+ console.log("Should check for next apps to load from App suggestion model")
+ //fetch(globalUrl + "/api/v1/workflows/usecases", {
+ //credentials: "include",
+ //cors: "no-cors",
+
+ const max_suggestions = 5
+ const max_per_category = 2
+ const priorities = {
+ "edr": 1,
+ "siem": 2,
+ "cases": 3,
+ "intel": 4,
+ "comms": 5,
+ }
+
+ // Based on apps recommended from repo https://github.com/Shuffle/app-recommender
+ //fetch("http://localhost:8080/app_recommendations", {
+ fetch("https://europe-west2-shuffler.cloudfunctions.net/app_recommendations", {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ Accept: "application/json",
+ },
+ body: JSON.stringify(frameworkData),
+ })
+ .then((response) => {
+ //if (response.status !== 200) {
+ // console.log("Status not 200 for framework!");
+ //}
+
+ return response.json();
+ })
+ .then((responseJson) => {
+ // Loop the response dict
+ var suggestions = []
+ var suggestion_cnt = 0
+ var category_count = {}
+ for (const [key, value] of Object.entries(responseJson)) {
+ if (suggestion_cnt >= max_suggestions) {
+ break
+ }
+
+ if (category_count[key] === undefined) {
+ category_count[key] = 1
+ } else {
+ category_count[key] += 1
+ }
+
+ if (category_count[key] > max_per_category) {
+ continue
+ }
+
+ //console.log("Found: " + key + " with value of len: " + value.length)
+
+ //suggestion_cnt += value.slice(0,1).length
+ suggestions.push(value.slice(0,1))
+ }
+
+ setInjectedApps(suggestions)
+ })
+ .catch((error) => {
+ console.log("Recommendation error: ", error);
+ })
+ }
+
const showRecommendations = (changed, frameworkData) => {
console.log("Inside recommendation loader")
setChangedApp(changed)
+ handleLoadNextSuggestion(frameworkData)
// Alternative changed
// This is for secondary values like email = comms
@@ -822,6 +870,12 @@ const AppFramework = (props) => {
})
}
+ useEffect(() => {
+ if (!window.location.pathname.includes("usecases")) {
+ handleLoadNextSuggestion(frameworkData)
+ }
+ }, [])
+
useEffect(() => {
console.log("New selected app: ", newSelectedApp, discoveryData)
if (newSelectedApp.objectID === undefined || newSelectedApp.objectID === undefined || newSelectedApp.objectID.length === 0) {
@@ -894,9 +948,8 @@ const AppFramework = (props) => {
}, [newSelectedApp])
- const isCloud =
- window.location.host === "localhost:3002" ||
- window.location.host === "shuffler.io";
+ const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io";
+
const imgSize = 50;
var parsedFrameworkData = frameworkData === undefined ? {} : frameworkData
@@ -1206,7 +1259,10 @@ const AppFramework = (props) => {
}
//setDiscoveryData({})
- setDiscoveryWrapper({})
+ if (setDiscoveryWrapper !== undefined) {
+ setDiscoveryWrapper({})
+ }
+
setSelectionOpen(false)
setDefaultSearch("")
setPaperTitle("")
@@ -1278,7 +1334,10 @@ const AppFramework = (props) => {
const baselocationY = 50*scale
const shiftmodifier = 3*scale
//const svgSize = `${40*scale}px`
- const svgSize = `${40}px`
+ //const svgSize = `${40}px`
+
+ const foundMiddleImage = userdata !== undefined && userdata !== null && userdata.active_org !== undefined && userdata.active_org.image !== undefined && userdata.active_org.image !== null && userdata.active_org.image !== "" ? userdata.active_org.image : '/images/Shuffle_logo.png'
+
const fontSize = `${12*scale}px`
const defaultSize = `${85*scale}px`
@@ -1303,9 +1362,8 @@ const AppFramework = (props) => {
margin_y: parsedFrameworkData.Cases.large_image === undefined ? `${19*scale}px` : `0px`,
width: parsedFrameworkData.Cases.large_image === undefined ? iconSize : defaultSize,
height: parsedFrameworkData.Cases.large_image === undefined ? iconSize : defaultSize,
- large_image: parsedFrameworkData.Cases.large_image === undefined ? encodeURI(`data:image/svg+xml;utf-8,`) : parsedFrameworkData.Cases.large_image,
+ large_image: parsedFrameworkData.Cases.large_image === undefined ? parsedDatatypeImages["CASES"] : parsedFrameworkData.Cases.large_image,
+
label: securityFramework[0].text.toUpperCase(),
id: securityFramework[0].text.toUpperCase(),
animate: true,
@@ -1332,9 +1390,8 @@ const AppFramework = (props) => {
margin_y: parsedFrameworkData.IAM.large_image === undefined ? `${19*scale}px` : `0px`,
width: parsedFrameworkData.IAM.large_image === undefined ? iconSize : defaultSize,
height: parsedFrameworkData.IAM.large_image === undefined ? iconSize : defaultSize,
- large_image: parsedFrameworkData.IAM.large_image === undefined ? encodeURI(`data:image/svg+xml;utf-8,`) : parsedFrameworkData.IAM.large_image,
+ large_image: parsedFrameworkData.IAM.large_image === undefined ? parsedDatatypeImages["IAM"] : parsedFrameworkData.IAM.large_image,
+
label: securityFramework[3].text.toUpperCase(),
id: securityFramework[3].text.toUpperCase(),
animate: false,
@@ -1361,9 +1418,8 @@ const AppFramework = (props) => {
margin_y: parsedFrameworkData.Assets.large_image === undefined ? `${19*scale}px` : `0px`,
width: parsedFrameworkData.Assets.large_image === undefined ? iconSize : defaultSize,
height: parsedFrameworkData.Assets.large_image === undefined ? iconSize : defaultSize,
- large_image: parsedFrameworkData.Assets.large_image === undefined ? encodeURI(`data:image/svg+xml;utf-8,`) : parsedFrameworkData.Assets.large_image,
+ large_image: parsedFrameworkData.Assets.large_image === undefined ? parsedDatatypeImages["ASSETS"] : parsedFrameworkData.Assets.large_image,
+
label: securityFramework[2].text.toUpperCase(),
id: securityFramework[2].text.toUpperCase(),
animate: false,
@@ -1390,9 +1446,8 @@ const AppFramework = (props) => {
margin_y: parsedFrameworkData.Intel.large_image === undefined ? `${19*scale}px` : `0px`,
width: parsedFrameworkData.Intel.large_image === undefined ? iconSize : defaultSize,
height: parsedFrameworkData.Intel.large_image === undefined ? iconSize : defaultSize,
- large_image: parsedFrameworkData.Intel.large_image === undefined ? encodeURI(`data:image/svg+xml;utf-8,`): parsedFrameworkData.Intel.large_image,
+ large_image: parsedFrameworkData.Intel.large_image === undefined ? parsedDatatypeImages["INTEL"] : parsedFrameworkData.Intel.large_image,
+
label: securityFramework[4].text.toUpperCase(),
id: securityFramework[4].text.toUpperCase(),
animate: false,
@@ -1419,9 +1474,8 @@ const AppFramework = (props) => {
margin_y: parsedFrameworkData.Comms.large_image === undefined ? `${19*scale}px` : `0px`,
width: parsedFrameworkData.Comms.large_image === undefined ? iconSize : defaultSize,
height: parsedFrameworkData.Comms.large_image === undefined ? iconSize : defaultSize,
- large_image: parsedFrameworkData.Comms.large_image === undefined ? encodeURI(`data:image/svg+xml;utf-8,`) : parsedFrameworkData.Comms.large_image,
+ large_image: parsedFrameworkData.Comms.large_image === undefined ? parsedDatatypeImages["COMMS"] : parsedFrameworkData.Comms.large_image,
+
label: securityFramework[5].text.toUpperCase(),
id: securityFramework[5].text.toUpperCase(),
animate: false,
@@ -1448,9 +1502,8 @@ const AppFramework = (props) => {
margin_y: parsedFrameworkData["EDR & AV"].large_image === undefined ? `${19*scale}px` : `0px`,
width: parsedFrameworkData["EDR & AV"].large_image === undefined ? iconSize : defaultSize,
height: parsedFrameworkData["EDR & AV"].large_image === undefined ? iconSize : defaultSize,
- large_image: parsedFrameworkData["EDR & AV"].large_image === undefined ? encodeURI(`data:image/svg+xml;utf-8,`) : parsedFrameworkData["EDR & AV"].large_image,
+ large_image: parsedFrameworkData["EDR & AV"].large_image === undefined ? parsedDatatypeImages["EDR & AV"] : parsedFrameworkData["EDR & AV"].large_image,
+
label: securityFramework[7].text.toUpperCase(),
id: securityFramework[7].text.toUpperCase(),
animate: false,
@@ -1477,9 +1530,7 @@ const AppFramework = (props) => {
margin_y: parsedFrameworkData.Network.large_image === undefined ? `${19*scale}px` : `0px`,
width: parsedFrameworkData.Network.large_image === undefined ? iconSize : defaultSize,
height: parsedFrameworkData.Network.large_image === undefined ? iconSize : defaultSize,
- large_image: parsedFrameworkData.Network.large_image === undefined ? encodeURI(`data:image/svg+xml;utf-8,`) : parsedFrameworkData.Network.large_image,
+ large_image: parsedFrameworkData.Network.large_image === undefined ? parsedDatatypeImages["NETWORK"] : parsedFrameworkData.Network.large_image,
label: securityFramework[6].text.toUpperCase(),
id: securityFramework[6].text.toUpperCase(),
animate: false,
@@ -1506,9 +1557,7 @@ const AppFramework = (props) => {
margin_y: parsedFrameworkData.SIEM.large_image === undefined ? `${19*scale}px` : `0px`,
width: parsedFrameworkData.SIEM.large_image === undefined ? iconSize : defaultSize,
height: parsedFrameworkData.SIEM.large_image === undefined ? iconSize : defaultSize,
- large_image: parsedFrameworkData.SIEM.large_image === undefined ? encodeURI(`data:image/svg+xml;utf-8,`) : parsedFrameworkData.SIEM.large_image,
+ large_image: parsedFrameworkData.SIEM.large_image === undefined ? parsedDatatypeImages["SIEM"] : parsedFrameworkData.SIEM.large_image,
label: securityFramework[1].text.toUpperCase(),
id: securityFramework[1].text.toUpperCase(),
animate: false,
@@ -1532,6 +1581,7 @@ const AppFramework = (props) => {
isValid: true,
errors: [],
middle_node: true,
+ large_image: foundMiddleImage,
},
renderedPosition: {
x: baselocationX,
@@ -1643,8 +1693,8 @@ const AppFramework = (props) => {
const UsecaseHandler = (props) => {
const { data, index, diff } = props
- const leftImage = data.left_image !== undefined ? parsedDatatypeImages[data.left_image] : undefined
- const rightImage = data.right_image !== undefined ? parsedDatatypeImages[data.right_image] : undefined
+ const leftImage = data.left_image !== undefined ? parsedDatatypeImages[data.left_image.toUpperCase()] : undefined
+ const rightImage = data.right_image !== undefined ? parsedDatatypeImages[data.right_image.toUpperCase()] : undefined
if (leftImage === undefined) {
console.log("LEFT MISSING: ", leftImage)
@@ -1660,7 +1710,6 @@ const AppFramework = (props) => {
const parsedLeftImage =
const parsedLeftText =