4f3f07d4dd
- Vendor shuffle-shared v1.2.51 as backend/go-app/shuffle-shared - Add replace directive in go.mod to use the local moduled copy - In HandleCheckLicense, force org.Licensed=true and set every SyncFeatures limit to 1e9, skipping all license-key logic - Update Dockerfile to ADD the local shuffle-shared before go build - Verified: backend image builds successfully via docker
228 lines
6.7 KiB
Go
228 lines
6.7 KiB
Go
package shuffle
|
|
|
|
import (
|
|
"fmt"
|
|
"io/ioutil"
|
|
"log"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
func HandleStreamWorkflowUpdate(resp http.ResponseWriter, request *http.Request) {
|
|
cors := HandleCors(resp, request)
|
|
if cors {
|
|
return
|
|
}
|
|
|
|
//// Removed check here as it may be a public workflow
|
|
user, err := HandleApiAuthentication(resp, request)
|
|
if err != nil {
|
|
log.Printf("[AUDIT] Api authentication failed in getting specific workflow (stream update): %s. Continuing because it may be public.", err)
|
|
}
|
|
|
|
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 strings.Contains(fileId, "?") {
|
|
fileId = strings.Split(fileId, "?")[0]
|
|
}
|
|
|
|
if len(fileId) != 36 {
|
|
resp.WriteHeader(401)
|
|
resp.Write([]byte(`{"success": false, "reason": "Workflow ID when getting workflow is not valid"}`))
|
|
return
|
|
}
|
|
|
|
ctx := GetContext(request)
|
|
workflow, err := GetWorkflow(ctx, fileId)
|
|
if err != nil {
|
|
log.Printf("[WARNING] Workflow %s doesn't exist.", fileId)
|
|
resp.WriteHeader(401)
|
|
resp.Write([]byte(`{"success": false, "reason": "Failed finding workflow."}`))
|
|
return
|
|
}
|
|
|
|
if user.Id != workflow.Owner || len(user.Id) == 0 {
|
|
if workflow.OrgId == user.ActiveOrg.Id && user.Role != "org-reader" {
|
|
log.Printf("[AUDIT] User %s is accessing workflow %s as admin (SET workflow stream)", user.Username, workflow.ID)
|
|
|
|
//} else if workflow.Public {
|
|
//log.Printf("[AUDIT] Letting user %s access workflow %s for streaming because it's public (SET workflow stream)", user.Username, workflow.ID)
|
|
|
|
} else if project.Environment == "cloud" && user.Verified == true && user.SupportAccess == true && user.Role == "admin" {
|
|
log.Printf("[AUDIT] Letting verified support admin %s access workflow %s", user.Username, workflow.ID)
|
|
|
|
} else {
|
|
log.Printf("[AUDIT] Wrong user (%s) for workflow %s (SET workflow stream)", user.Username, workflow.ID)
|
|
resp.WriteHeader(401)
|
|
resp.Write([]byte(`{"success": false}`))
|
|
return
|
|
}
|
|
}
|
|
|
|
body, err := ioutil.ReadAll(request.Body)
|
|
if err != nil {
|
|
log.Printf("[WARNING] Error with body read in workflow stream: %s", err)
|
|
resp.WriteHeader(401)
|
|
resp.Write([]byte(`{"success": false}`))
|
|
return
|
|
}
|
|
|
|
/*
|
|
streamKey := fmt.Sprintf("%s_stream_users", workflow.ID)
|
|
cache, err = GetCache(ctx, streamKey, user.Id, 30)
|
|
if err != nil {
|
|
log.Printf("[WARNING] Failed setting cache for apikey: %s", err)
|
|
} else {
|
|
// We are here to get the users in the stream
|
|
cacheData := []byte(cache.([]uint8))
|
|
}
|
|
*/
|
|
|
|
// FIXME: Should append to the stream and keep some items in memory
|
|
// Not just purely overwrite it
|
|
sessionKey := fmt.Sprintf("%s_stream", workflow.ID)
|
|
err = SetCache(ctx, sessionKey, body, 30)
|
|
if err != nil {
|
|
log.Printf("[WARNING] Failed setting cache for apikey: %s", err)
|
|
}
|
|
|
|
resp.WriteHeader(200)
|
|
resp.Write([]byte(`{"success": true}`))
|
|
}
|
|
|
|
func HandleStreamWorkflow(resp http.ResponseWriter, request *http.Request) {
|
|
cors := HandleCors(resp, request)
|
|
if cors {
|
|
return
|
|
}
|
|
|
|
//// Removed check here as it may be a public workflow
|
|
user, err := HandleApiAuthentication(resp, request)
|
|
if err != nil {
|
|
log.Printf("[AUDIT] Api authentication failed in getting specific workflow (stream): %s. Continuing because it may be public.", err)
|
|
}
|
|
|
|
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 strings.Contains(fileId, "?") {
|
|
fileId = strings.Split(fileId, "?")[0]
|
|
}
|
|
|
|
if len(fileId) != 36 {
|
|
resp.WriteHeader(401)
|
|
resp.Write([]byte(`{"success": false, "reason": "Workflow ID when getting workflow is not valid"}`))
|
|
return
|
|
}
|
|
|
|
//ctx := GetContext(request)
|
|
ctx := GetContext(request)
|
|
workflow, err := GetWorkflow(ctx, fileId)
|
|
if err != nil {
|
|
log.Printf("[WARNING] Workflow %s doesn't exist.", fileId)
|
|
resp.WriteHeader(401)
|
|
resp.Write([]byte(`{"success": false, "reason": "Failed finding workflow."}`))
|
|
return
|
|
}
|
|
|
|
if user.Id != workflow.Owner || len(user.Id) == 0 {
|
|
|
|
if workflow.OrgId == user.ActiveOrg.Id && (user.Role == "admin" || user.Role == "org-reader") {
|
|
log.Printf("[AUDIT] User %s is accessing workflow %s as admin (stream edit workflow)", user.Username, workflow.ID)
|
|
|
|
} else if workflow.Public {
|
|
log.Printf("[AUDIT] Letting user %s access workflow %s for streaming because it's public (get workflow stream)", user.Username, workflow.ID)
|
|
|
|
} else if project.Environment == "cloud" && user.Verified == true && user.Active == true && user.SupportAccess == true && strings.HasSuffix(user.Username, "@shuffler.io") {
|
|
log.Printf("[AUDIT] Letting verified support admin %s access workflow %s", user.Username, workflow.ID)
|
|
} else {
|
|
log.Printf("[AUDIT] Wrong user (%s) for workflow %s (get workflow stream)", user.Username, workflow.ID)
|
|
resp.WriteHeader(401)
|
|
resp.Write([]byte(`{"success": false}`))
|
|
return
|
|
}
|
|
}
|
|
|
|
// FIXME: If public, it should ONLY allow you to set certain actions
|
|
|
|
resp.Header().Set("Connection", "Keep-Alive")
|
|
resp.Header().Set("X-Content-Type-Options", "nosniff")
|
|
|
|
conn, ok := resp.(http.Flusher)
|
|
if !ok {
|
|
log.Printf("[ERROR] Flusher error: %t", ok)
|
|
http.Error(resp, "Streaming supported on AppEngine", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
resp.Header().Set("Content-Type", "text/event-stream")
|
|
resp.WriteHeader(http.StatusOK)
|
|
|
|
sessionKey := fmt.Sprintf("%s_stream", workflow.ID)
|
|
previousCache := []byte{}
|
|
for {
|
|
cache, err := GetCache(ctx, sessionKey)
|
|
if err == nil {
|
|
|
|
cacheData := []byte(cache.([]uint8))
|
|
if string(previousCache) == string(cacheData) {
|
|
//log.Printf("[DEBUG] Still same cache for %s", user.Id)
|
|
} else {
|
|
|
|
// A way to only check for data from other people
|
|
if (len(user.Id) > 0 && !strings.Contains(string(cacheData), user.Id)) || len(user.Id) == 0 {
|
|
//log.Printf("[DEBUG] NEW cache for %s (1) - sending: %s.", user.Id, cacheData)
|
|
|
|
//fw.Write(cacheData)
|
|
//w.Write(cacheData)
|
|
|
|
_, err := fmt.Fprintf(resp, "%s", string(cacheData))
|
|
if err != nil {
|
|
log.Printf("[ERROR] Failed in writing stream to user '%s' (%s): %s", user.Username, user.Id, err)
|
|
|
|
if strings.Contains(err.Error(), "broken pipe") {
|
|
break
|
|
}
|
|
} else {
|
|
previousCache = cacheData
|
|
conn.Flush()
|
|
}
|
|
|
|
} else {
|
|
//log.Printf("[ERROR] NEW cache for %s (2) - NOT sending: %s.", user.Id, cacheData)
|
|
|
|
previousCache = cacheData
|
|
}
|
|
|
|
}
|
|
} else {
|
|
//log.Printf("[DEBUG] Failed getting cache for %s: %s", user.Id, err)
|
|
}
|
|
|
|
// FIXME: This is a hack to make sure we don't fully utilize the thread
|
|
time.Sleep(100 * time.Millisecond)
|
|
}
|
|
}
|