Files
shuffle-cracked/backend/go-app/shuffle-shared/rls.go
T
Marat Kharitonov 4f3f07d4dd Crack: bypass license check - force all limits unlimited
- 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
2026-08-12 03:14:24 +03:00

415 lines
10 KiB
Go

package shuffle
import (
"bytes"
"encoding/json"
"fmt"
"sort"
"strings"
"math"
)
const MaxDepth = 10
// ----------------------
// Public API
// ----------------------
func EvalPolicyJSON(policy, oldJSON, newJSON string) (string, bool, string) {
var oldDoc, newDoc map[string]any
if err := json.Unmarshal([]byte(oldJSON), &oldDoc); err != nil {
// Try a quick string replacement just in case
// This is primarily because of python dicts
if strings.HasPrefix(oldJSON, "{'") {
fixed := strings.ReplaceAll(strings.ReplaceAll(oldJSON, "{'", "{\""), "'}", "\"}")
fixed = strings.ReplaceAll(fixed, "':", "\":")
fixed = strings.ReplaceAll(fixed, ",'", ",\"")
fixed = strings.ReplaceAll(fixed, ": True", ": true")
fixed = strings.ReplaceAll(fixed, ": False", ": false")
if err2 := json.Unmarshal([]byte(fixed), &oldDoc); err2 == nil {
return fixed, false, "invalid old JSON (single quotes)"
}
} else {
return oldJSON, false, "invalid old JSON"
}
}
if err := json.Unmarshal([]byte(newJSON), &newDoc); err != nil {
return oldJSON, false, "invalid new JSON"
}
rules := parsePolicy(policy)
merged, ok, reason := evalPolicyRules(rules, oldDoc, newDoc)
if !ok {
oldBytes, _ := marshalOrdered(oldDoc)
return string(oldBytes), false, reason
}
resultBytes, _ := marshalOrdered(merged)
return string(resultBytes), true, ""
}
// ----------------------
// Core Logic
// ----------------------
func evalPolicyRules(rules []Rule, oldDoc, newDoc map[string]any) (map[string]any, bool, string) {
// Default: Overwrite candidate
candidate := deepCopyMap(newDoc)
ruleMatched := false
// Phase 1: Determine Candidate
for _, r := range rules {
if r.Action == ActionOverwrite {
if r.Condition == "same_shape" && compareShape(oldDoc, newDoc) {
candidate = deepCopyMap(newDoc)
ruleMatched = true
break
}
} else if r.Action == ActionMerge {
// Handle "merge" (implicit true) OR "merge if always"
if r.Condition == "true" || r.Condition == "always" {
candidate = mergeJSON(oldDoc, newDoc)
ruleMatched = true
break
}
if strings.HasPrefix(r.Condition, "allowed_fields[") {
fields := parseAllowedFields(r.Condition)
candidate = mergeAllowedFields(oldDoc, newDoc, fields)
ruleMatched = true
break
}
}
}
// If explicit rules existed but didn't match, fail.
hasPositiveRules := false
for _, r := range rules {
if r.Action == ActionMerge || r.Action == ActionOverwrite {
hasPositiveRules = true
break
}
}
if hasPositiveRules && !ruleMatched {
return deepCopyMap(oldDoc), false, "no matching allow rule"
}
// Phase 2: Deny Guardrails
for _, r := range rules {
if r.Action == ActionDeny {
if r.Condition == "has_deleted_field" {
if path := findDeletedField(oldDoc, candidate, ""); path != "" {
return deepCopyMap(oldDoc), false, fmt.Sprintf("deny: field deletion detected at '%s'", path)
}
}
}
}
return candidate, true, ""
}
// ----------------------
// Smart Merge Logic
// ----------------------
func mergeAllowedFields(oldDoc, newDoc map[string]any, allowed []string) map[string]any {
result := deepCopyMap(oldDoc)
for _, k := range allowed {
if newVal, ok := newDoc[k]; ok {
if oldVal, exists := result[k]; exists {
if oldMap, ok1 := oldVal.(map[string]any); ok1 {
if newMap, ok2 := newVal.(map[string]any); ok2 {
result[k] = mergeJSON(oldMap, newMap)
continue
}
}
}
result[k] = deepCopy(newVal)
}
}
return result
}
func mergeJSON(target, source map[string]any) map[string]any {
result := deepCopyMap(target)
for k, vNew := range source {
vOld, exists := result[k]
if !exists {
result[k] = deepCopy(vNew)
continue
}
oldMap, oldIsMap := vOld.(map[string]any)
newMap, newIsMap := vNew.(map[string]any)
oldSlice, oldIsSlice := vOld.([]any)
newSlice, newIsSlice := vNew.([]any)
if oldIsMap && newIsMap {
result[k] = mergeJSON(oldMap, newMap)
} else if oldIsSlice && newIsSlice {
// KEYED LIST LOGIC
if isKeyedList(oldSlice) || isKeyedList(newSlice) {
result[k] = mergeKeyedList(oldSlice, newSlice)
} else {
// Primitive List -> Overwrite
result[k] = deepCopy(vNew)
}
} else {
result[k] = deepCopy(vNew)
}
}
return result
}
func isKeyedList(s []any) bool {
if len(s) == 0 { return false }
_, ok := getID(s[0])
return ok
}
// getID robustly handles float/int/string IDs
func getID(v any) (any, bool) {
if m, ok := v.(map[string]any); ok {
// Priority 1: "id"
if val, found := m["id"]; found {
return normalizeID(val), true
}
// Priority 2: "uid"
if val, found := m["uid"]; found {
return normalizeID(val), true
}
}
return nil, false
}
// normalizeID ensures that 1.0 (float) and 1 (int) are treated as the same key
func normalizeID(v any) any {
switch n := v.(type) {
case float64:
// If it's a whole number, return it as int to ensure map matching works
if n == math.Trunc(n) {
return int(n)
}
return n
case int:
return int(n)
default:
return v // strings, etc.
}
}
func mergeKeyedList(oldList, newList []any) []any {
// 1. Start with a COPY of the Old List (Preserve History)
result := make([]any, len(oldList))
// Lookup Map: ID -> Index in Result
lookup := make(map[any]int)
for i, item := range oldList {
result[i] = deepCopy(item)
if id, ok := getID(item); ok {
lookup[id] = i
}
}
// 2. Merge in the New Items
for _, newItem := range newList {
newID, ok := getID(newItem)
if ok {
if idx, found := lookup[newID]; found {
// UPDATE: Merge newItem into the existing result item
oldItemMap, _ := result[idx].(map[string]any)
newItemMap, _ := newItem.(map[string]any)
result[idx] = mergeJSON(oldItemMap, newItemMap)
continue
}
}
// APPEND: It's new (or has no ID), so add it
result = append(result, deepCopy(newItem))
// If it has an ID, add to lookup (handles duplicates in new list)
if ok {
lookup[newID] = len(result) - 1
}
}
return result
}
// ----------------------
// Check Logic (Deletion)
// ----------------------
func findDeletedField(oldVal, newVal any, currentPath string) string {
switch o := oldVal.(type) {
case map[string]any:
n, ok := newVal.(map[string]any)
if !ok { return currentPath }
for k, vOld := range o {
vNew, exists := n[k]
nextPath := k
if currentPath != "" { nextPath = currentPath + "." + k }
if !exists { return nextPath }
if path := findDeletedField(vOld, vNew, nextPath); path != "" { return path }
}
case []any:
n, ok := newVal.([]any)
if !ok { return currentPath }
// KEYED MATCHING
if len(o) > 0 {
if _, hasID := getID(o[0]); hasID {
newItemsByID := make(map[any]any)
for _, item := range n {
if id, ok := getID(item); ok {
newItemsByID[id] = item
}
}
for _, oldItem := range o {
id, _ := getID(oldItem)
newItem, found := newItemsByID[id]
nextPath := fmt.Sprintf("%s[id=%v]", currentPath, id)
if !found { return nextPath } // ID missing
if path := findDeletedField(oldItem, newItem, nextPath); path != "" {
return path
}
}
return ""
}
}
// POSITIONAL MATCHING
if len(n) < len(o) {
if currentPath == "" { return "[]" }
return fmt.Sprintf("%s[%d]", currentPath, len(n))
}
for i, vOld := range o {
if i >= len(n) { return fmt.Sprintf("%s[%d]", currentPath, i) }
vNew := n[i]
nextPath := fmt.Sprintf("[%d]", i)
if currentPath != "" { nextPath = fmt.Sprintf("%s[%d]", currentPath, i) }
if path := findDeletedField(vOld, vNew, nextPath); path != "" { return path }
}
}
return ""
}
func compareShape(a, b map[string]any) bool {
if len(a) != len(b) { return false }
for k, vA := range a {
vB, ok := b[k]
if !ok { return false }
mapA, aIsMap := vA.(map[string]any)
mapB, bIsMap := vB.(map[string]any)
if aIsMap && bIsMap {
if !compareShape(mapA, mapB) { return false }
} else if aIsMap != bIsMap {
return false
}
}
return true
}
// ----------------------
// Parser / Utils
// ----------------------
type NewAction string
const (
ActionMerge NewAction = "merge"
ActionOverwrite NewAction = "overwrite"
ActionDeny NewAction = "deny"
)
type Rule struct {
Action NewAction
Condition string
}
func parsePolicy(policy string) []Rule {
var rules []Rule
parts := strings.Split(policy, ";")
for _, p := range parts {
p = strings.TrimSpace(p)
if p == "" { continue }
fields := strings.Fields(p)
if len(fields) == 1 {
rules = append(rules, Rule{Action: NewAction(strings.ToLower(fields[0])), Condition: "true"})
continue
}
if len(fields) < 3 || fields[1] != "if" { continue }
rules = append(rules, Rule{Action: NewAction(strings.ToLower(fields[0])), Condition: strings.Join(fields[2:], " ")})
}
return rules
}
func parseAllowedFields(cond string) []string {
start := strings.Index(cond, "[")
end := strings.LastIndex(cond, "]")
if start == -1 || end == -1 { return nil }
inner := cond[start+1 : end]
if strings.TrimSpace(inner) == "" { return nil }
raw := strings.Split(inner, ",")
clean := make([]string, 0, len(raw))
for _, s := range raw {
clean = append(clean, strings.Trim(strings.TrimSpace(s), "\"'"))
}
return clean
}
func deepCopy(v any) any {
switch val := v.(type) {
case map[string]any: return deepCopyMap(val)
case []any:
out := make([]any, len(val))
for i, item := range val { out[i] = deepCopy(item) }
return out
default: return val
}
}
func deepCopyMap(m map[string]any) map[string]any {
if m == nil { return nil }
out := make(map[string]any, len(m))
for k, v := range m { out[k] = deepCopy(v) }
return out
}
func marshalOrdered(v any) ([]byte, error) {
switch val := v.(type) {
case map[string]any:
keys := make([]string, 0, len(val))
for k := range val { keys = append(keys, k) }
sort.Strings(keys)
var buf bytes.Buffer
buf.WriteString("{")
for i, k := range keys {
if i > 0 { buf.WriteString(",") }
b, _ := json.Marshal(k)
buf.Write(b)
buf.WriteString(":")
valBytes, _ := marshalOrdered(val[k])
buf.Write(valBytes)
}
buf.WriteString("}")
return buf.Bytes(), nil
case []any:
var buf bytes.Buffer
buf.WriteString("[")
for i, item := range val {
if i > 0 { buf.WriteString(",") }
valBytes, _ := marshalOrdered(item)
buf.Write(valBytes)
}
buf.WriteString("]")
return buf.Bytes(), nil
default: return json.Marshal(v)
}
}