From a132940a898acea52d97a4d2e36d5d61c9f23a07 Mon Sep 17 00:00:00 2001 From: CodeShell <122738806+CodeShellDev@users.noreply.github.com> Date: Sat, 25 Oct 2025 21:31:21 +0200 Subject: [PATCH 01/12] add Field Policy --- internals/proxy/middlewares/endpoints.go | 6 ++ internals/proxy/middlewares/policy.go | 128 +++++++++++++++++++++++ internals/proxy/proxy.go | 1 + utils/config/structure/structure.go | 3 +- 4 files changed, 137 insertions(+), 1 deletion(-) create mode 100644 internals/proxy/middlewares/policy.go diff --git a/internals/proxy/middlewares/endpoints.go b/internals/proxy/middlewares/endpoints.go index 649ebf4b..02df94bb 100644 --- a/internals/proxy/middlewares/endpoints.go +++ b/internals/proxy/middlewares/endpoints.go @@ -53,6 +53,12 @@ func getEndpoints(endpoints []string) ([]string, []string) { } func isBlocked(endpoint string, endpoints []string) bool { + if endpoints == nil { + return false + } else if len(endpoints) <= 0 { + return false + } + allowed, blocked := getEndpoints(endpoints) isExplicitlyBlocked := slices.ContainsFunc(blocked, func(try string) bool { diff --git a/internals/proxy/middlewares/policy.go b/internals/proxy/middlewares/policy.go new file mode 100644 index 00000000..8c629fa6 --- /dev/null +++ b/internals/proxy/middlewares/policy.go @@ -0,0 +1,128 @@ +package middlewares + +import ( + "net/http" + "slices" + "strings" + + log "github.com/codeshelldev/secured-signal-api/utils/logger" + request "github.com/codeshelldev/secured-signal-api/utils/request" +) + +var Policy Middleware = Middleware{ + Name: "Policy", + Use: policyHandler, +} + +func policyHandler(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + settings := getSettingsByReq(req) + + policies := settings.ACCESS.FIELD_POLOCIES + + if policies == nil { + policies = getSettings("*").ACCESS.FIELD_POLOCIES + } + + body, err := request.GetReqBody(w, req) + + if err != nil { + log.Error("Could not get Request Body: ", err.Error()) + } + + if body.Empty { + body.Data = map[string]any{} + } + + headerData := request.GetReqHeaders(req) + + shouldBlock, field := doBlock(body.Data, headerData, policies) + + if shouldBlock { + log.Warn("User tried to use blocked field: ", field) + http.Error(w, "Forbidden", http.StatusForbidden) + return + } + + next.ServeHTTP(w, req) + }) +} + +func getPolicies(policies []string) ([]string, []string) { + blockedFields := []string{} + allowedFields := []string{} + + for _, field := range policies { + field, block := strings.CutPrefix(field, "!") + + if block { + blockedFields = append(blockedFields, field) + } else { + allowedFields = append(allowedFields, field) + } + } + + return allowedFields, blockedFields +} + +func doBlock(body map[string]any, headers map[string]any, policies []string) (bool, string) { + if policies == nil { + return false, "" + } else if len(policies) <= 0 { + return false, "" + } + + allowed, blocked := getPolicies(policies) + + var blockField string + + isExplicitlyBlocked := slices.ContainsFunc(blocked, func(try string) bool { + isHeader := strings.HasPrefix(try, "#") + isBody := strings.HasPrefix(try, "@") + + if body[try] != nil && isBody { + blockField = try + return true + } + + if headers[try] != nil && isHeader { + blockField = try + return true + } + + return false + }) + + isExplictlyAllowed := slices.ContainsFunc(allowed, func(try string) bool { + isHeader := strings.HasPrefix(try, "#") + isBody := strings.HasPrefix(try, "@") + + if body[try] != nil && isBody { + return true + } + + if headers[try] != nil && isHeader { + return true + } + + return false + }) + + // Block all except explicitly Allowed + if len(blocked) == 0 && len(allowed) != 0 { + return !isExplictlyAllowed, blockField + } + + // Allow all except explicitly Blocked + if len(allowed) == 0 && len(blocked) != 0 { + return isExplicitlyBlocked, blockField + } + + // Excplicitly Blocked except excplictly Allowed + if len(blocked) != 0 && len(allowed) != 0 { + return isExplicitlyBlocked && !isExplictlyAllowed, blockField + } + + // Block all + return true, "" +} diff --git a/internals/proxy/proxy.go b/internals/proxy/proxy.go index acf9c3f7..2884533e 100644 --- a/internals/proxy/proxy.go +++ b/internals/proxy/proxy.go @@ -35,6 +35,7 @@ func (proxy Proxy) Init() http.Handler { Use(m.Server). Use(m.Auth). Use(m.Endpoints). + Use(m.Policy). Use(m.Template). Use(m.Mapping). Use(m.Message). diff --git a/utils/config/structure/structure.go b/utils/config/structure/structure.go index 2a768199..3ff1ec7a 100644 --- a/utils/config/structure/structure.go +++ b/utils/config/structure/structure.go @@ -20,7 +20,7 @@ type SETTINGS struct { type MESSAGE_SETTINGS struct { VARIABLES map[string]any `koanf:"variables"` - FIELD_MAPPINGS map[string][]FieldMapping `koanf:"fieldMappings"` + FIELD_MAPPINGS map[string][]FieldMapping `koanf:"fieldmappings"` TEMPLATE string `koanf:"template"` } @@ -31,4 +31,5 @@ type FieldMapping struct { type ACCESS_SETTINGS struct { ENDPOINTS []string `koanf:"endpoints"` + FIELD_POLOCIES []string `koanf:"fieldpolicies"` } \ No newline at end of file From 34cc71ab785af89ab9d069a9f177963e4faf4c0c Mon Sep 17 00:00:00 2001 From: CodeShell <122738806+CodeShellDev@users.noreply.github.com> Date: Sat, 25 Oct 2025 21:41:39 +0200 Subject: [PATCH 02/12] fix policy struct --- internals/proxy/middlewares/policy.go | 13 ++++++------- utils/config/structure/structure.go | 7 ++++++- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/internals/proxy/middlewares/policy.go b/internals/proxy/middlewares/policy.go index 8c629fa6..5ca6f17e 100644 --- a/internals/proxy/middlewares/policy.go +++ b/internals/proxy/middlewares/policy.go @@ -5,6 +5,7 @@ import ( "slices" "strings" + "github.com/codeshelldev/secured-signal-api/utils/config/structure" log "github.com/codeshelldev/secured-signal-api/utils/logger" request "github.com/codeshelldev/secured-signal-api/utils/request" ) @@ -48,16 +49,14 @@ func policyHandler(next http.Handler) http.Handler { }) } -func getPolicies(policies []string) ([]string, []string) { +func getPolicies(policies map[string]structure.FieldPolicy) ([]string, []string) { blockedFields := []string{} allowedFields := []string{} - for _, field := range policies { - field, block := strings.CutPrefix(field, "!") - - if block { + for field, policy := range policies { + if policy.Action == "block" { blockedFields = append(blockedFields, field) - } else { + } else if policy.Action == "allow" { allowedFields = append(allowedFields, field) } } @@ -65,7 +64,7 @@ func getPolicies(policies []string) ([]string, []string) { return allowedFields, blockedFields } -func doBlock(body map[string]any, headers map[string]any, policies []string) (bool, string) { +func doBlock(body map[string]any, headers map[string]any, policies map[string]structure.FieldPolicy) (bool, string) { if policies == nil { return false, "" } else if len(policies) <= 0 { diff --git a/utils/config/structure/structure.go b/utils/config/structure/structure.go index 3ff1ec7a..363d46c1 100644 --- a/utils/config/structure/structure.go +++ b/utils/config/structure/structure.go @@ -31,5 +31,10 @@ type FieldMapping struct { type ACCESS_SETTINGS struct { ENDPOINTS []string `koanf:"endpoints"` - FIELD_POLOCIES []string `koanf:"fieldpolicies"` + FIELD_POLOCIES map[string]FieldPolicy `koanf:"fieldpolicies"` +} + +type FieldPolicy struct { + Field string `koanf:"field"` + Action string `koanf:"action"` } \ No newline at end of file From 9c905001ac754ae66428ba65a39e28286fb711b1 Mon Sep 17 00:00:00 2001 From: CodeShell <122738806+CodeShellDev@users.noreply.github.com> Date: Sat, 25 Oct 2025 21:56:14 +0200 Subject: [PATCH 03/12] fix body stream not renewed --- internals/proxy/middlewares/policy.go | 5 +++-- utils/request/request.go | 3 +++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/internals/proxy/middlewares/policy.go b/internals/proxy/middlewares/policy.go index 5ca6f17e..be675126 100644 --- a/internals/proxy/middlewares/policy.go +++ b/internals/proxy/middlewares/policy.go @@ -54,9 +54,10 @@ func getPolicies(policies map[string]structure.FieldPolicy) ([]string, []string) allowedFields := []string{} for field, policy := range policies { - if policy.Action == "block" { + switch policy.Action { + case "block": blockedFields = append(blockedFields, field) - } else if policy.Action == "allow" { + case "allow": allowedFields = append(allowedFields, field) } } diff --git a/utils/request/request.go b/utils/request/request.go index 1c8fbd47..485e5f46 100644 --- a/utils/request/request.go +++ b/utils/request/request.go @@ -1,6 +1,7 @@ package req import ( + "bytes" "encoding/json" "errors" "io" @@ -88,6 +89,8 @@ func GetBody(req *http.Request) ([]byte, error) { if err != nil { req.Body.Close() + req.Body = io.NopCloser(bytes.NewReader(bodyBytes)) + return nil, err } defer req.Body.Close() From 99c91521311aad0f58f6e42ff36de41fa20ad347 Mon Sep 17 00:00:00 2001 From: CodeShell <122738806+CodeShellDev@users.noreply.github.com> Date: Sat, 25 Oct 2025 22:04:42 +0200 Subject: [PATCH 04/12] fix body close --- utils/request/request.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/utils/request/request.go b/utils/request/request.go index 485e5f46..ae5e8157 100644 --- a/utils/request/request.go +++ b/utils/request/request.go @@ -86,14 +86,13 @@ func GetFormData(body []byte) (map[string]any, error) { func GetBody(req *http.Request) ([]byte, error) { bodyBytes, err := io.ReadAll(req.Body) - if err != nil { - req.Body.Close() + req.Body.Close() - req.Body = io.NopCloser(bytes.NewReader(bodyBytes)) + req.Body = io.NopCloser(bytes.NewReader(bodyBytes)) + if err != nil { return nil, err } - defer req.Body.Close() return bodyBytes, nil } From ce6045b8dd5817f7fe2a30fdce7165cdb10b8d78 Mon Sep 17 00:00:00 2001 From: CodeShell <122738806+CodeShellDev@users.noreply.github.com> Date: Sat, 25 Oct 2025 22:11:43 +0200 Subject: [PATCH 05/12] use policy field without prefix for check --- internals/proxy/middlewares/policy.go | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/internals/proxy/middlewares/policy.go b/internals/proxy/middlewares/policy.go index be675126..3dbc6296 100644 --- a/internals/proxy/middlewares/policy.go +++ b/internals/proxy/middlewares/policy.go @@ -80,12 +80,14 @@ func doBlock(body map[string]any, headers map[string]any, policies map[string]st isHeader := strings.HasPrefix(try, "#") isBody := strings.HasPrefix(try, "@") - if body[try] != nil && isBody { + tryWithoutPrefix := try[:1] + + if body[tryWithoutPrefix] != nil && isBody { blockField = try return true } - if headers[try] != nil && isHeader { + if headers[tryWithoutPrefix] != nil && isHeader { blockField = try return true } @@ -97,11 +99,16 @@ func doBlock(body map[string]any, headers map[string]any, policies map[string]st isHeader := strings.HasPrefix(try, "#") isBody := strings.HasPrefix(try, "@") - if body[try] != nil && isBody { + tryWithoutPrefix := try[:1] + + // TODO: check for value before returning: + // body[tryWithoutPrefix] != try.Value + + if body[tryWithoutPrefix] != nil && isBody { return true } - if headers[try] != nil && isHeader { + if headers[tryWithoutPrefix] != nil && isHeader { return true } From 892bbddf7d07ab5bcb22f51232457dc2f196fe2a Mon Sep 17 00:00:00 2001 From: CodeShell <122738806+CodeShellDev@users.noreply.github.com> Date: Mon, 27 Oct 2025 19:05:14 +0100 Subject: [PATCH 06/12] implemented map iteration logic for blocking --- internals/proxy/middlewares/policy.go | 75 +++++++++++---------------- 1 file changed, 31 insertions(+), 44 deletions(-) diff --git a/internals/proxy/middlewares/policy.go b/internals/proxy/middlewares/policy.go index 3dbc6296..0c4dc65b 100644 --- a/internals/proxy/middlewares/policy.go +++ b/internals/proxy/middlewares/policy.go @@ -2,7 +2,6 @@ package middlewares import ( "net/http" - "slices" "strings" "github.com/codeshelldev/secured-signal-api/utils/config/structure" @@ -49,22 +48,31 @@ func policyHandler(next http.Handler) http.Handler { }) } -func getPolicies(policies map[string]structure.FieldPolicy) ([]string, []string) { - blockedFields := []string{} - allowedFields := []string{} +func getPolicies(policies map[string]structure.FieldPolicy) (map[string]structure.FieldPolicy, map[string]structure.FieldPolicy) { + blockedFields := map[string]structure.FieldPolicy{} + allowedFields := map[string]structure.FieldPolicy{} for field, policy := range policies { switch policy.Action { case "block": - blockedFields = append(blockedFields, field) + blockedFields[field] = policy case "allow": - allowedFields = append(allowedFields, field) + allowedFields[field] = policy } } return allowedFields, blockedFields } +func hasField(field string, body map[string]any, headers map[string]any) bool { + isHeader := strings.HasPrefix(field, "#") + isBody := strings.HasPrefix(field, "@") + + fieldWithoutPrefix := field[:1] + + return (body[fieldWithoutPrefix] != nil && isBody) || (headers[fieldWithoutPrefix] != nil && isHeader) +} + func doBlock(body map[string]any, headers map[string]any, policies map[string]structure.FieldPolicy) (bool, string) { if policies == nil { return false, "" @@ -74,60 +82,39 @@ func doBlock(body map[string]any, headers map[string]any, policies map[string]st allowed, blocked := getPolicies(policies) - var blockField string - - isExplicitlyBlocked := slices.ContainsFunc(blocked, func(try string) bool { - isHeader := strings.HasPrefix(try, "#") - isBody := strings.HasPrefix(try, "@") - - tryWithoutPrefix := try[:1] - - if body[tryWithoutPrefix] != nil && isBody { - blockField = try - return true - } - - if headers[tryWithoutPrefix] != nil && isHeader { - blockField = try - return true - } - - return false - }) - - isExplictlyAllowed := slices.ContainsFunc(allowed, func(try string) bool { - isHeader := strings.HasPrefix(try, "#") - isBody := strings.HasPrefix(try, "@") - - tryWithoutPrefix := try[:1] + var cause string - // TODO: check for value before returning: - // body[tryWithoutPrefix] != try.Value + var isExplictlyAllowed, isExplicitlyBlocked bool - if body[tryWithoutPrefix] != nil && isBody { - return true + for field := range allowed { + if hasField(field, body, headers) { + isExplictlyAllowed = true + cause = field + break } + } - if headers[tryWithoutPrefix] != nil && isHeader { - return true + for field := range blocked { + if hasField(field, body, headers) { + isExplicitlyBlocked = true + cause = field + break } - - return false - }) + } // Block all except explicitly Allowed if len(blocked) == 0 && len(allowed) != 0 { - return !isExplictlyAllowed, blockField + return !isExplictlyAllowed, cause } // Allow all except explicitly Blocked if len(allowed) == 0 && len(blocked) != 0 { - return isExplicitlyBlocked, blockField + return isExplicitlyBlocked, cause } // Excplicitly Blocked except excplictly Allowed if len(blocked) != 0 && len(allowed) != 0 { - return isExplicitlyBlocked && !isExplictlyAllowed, blockField + return isExplicitlyBlocked && !isExplictlyAllowed, cause } // Block all From 81484b696ea8b79893049ec464a68e8d6926abb7 Mon Sep 17 00:00:00 2001 From: CodeShell <122738806+CodeShellDev@users.noreply.github.com> Date: Mon, 27 Oct 2025 19:17:08 +0100 Subject: [PATCH 07/12] debug fields --- internals/proxy/middlewares/policy.go | 36 ++++++++++++++++++++++----- 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/internals/proxy/middlewares/policy.go b/internals/proxy/middlewares/policy.go index 0c4dc65b..fdedc8ea 100644 --- a/internals/proxy/middlewares/policy.go +++ b/internals/proxy/middlewares/policy.go @@ -1,10 +1,12 @@ package middlewares import ( + "errors" "net/http" "strings" "github.com/codeshelldev/secured-signal-api/utils/config/structure" + "github.com/codeshelldev/secured-signal-api/utils/jsonutils" log "github.com/codeshelldev/secured-signal-api/utils/logger" request "github.com/codeshelldev/secured-signal-api/utils/request" ) @@ -64,13 +66,25 @@ func getPolicies(policies map[string]structure.FieldPolicy) (map[string]structur return allowedFields, blockedFields } -func hasField(field string, body map[string]any, headers map[string]any) bool { +func getField(field string, body map[string]any, headers map[string]any) (any, error) { isHeader := strings.HasPrefix(field, "#") isBody := strings.HasPrefix(field, "@") fieldWithoutPrefix := field[:1] - return (body[fieldWithoutPrefix] != nil && isBody) || (headers[fieldWithoutPrefix] != nil && isHeader) + var value any + + if body[fieldWithoutPrefix] != nil && isBody { + value = body[fieldWithoutPrefix] + } else if headers[fieldWithoutPrefix] != nil && isHeader { + value = headers[fieldWithoutPrefix] + } + + if value != nil { + return value, nil + } + + return value, errors.New("field not found") } func doBlock(body map[string]any, headers map[string]any, policies map[string]structure.FieldPolicy) (bool, string) { @@ -86,16 +100,26 @@ func doBlock(body map[string]any, headers map[string]any, policies map[string]st var isExplictlyAllowed, isExplicitlyBlocked bool - for field := range allowed { - if hasField(field, body, headers) { + for field, policy := range allowed { + value, err := getField(field, body, headers) + + log.Dev("Checking ", field, "...") + log.Dev("Got Value of ", jsonutils.ToJson(value)) + + if value == policy.Value && err == nil { isExplictlyAllowed = true cause = field break } } - for field := range blocked { - if hasField(field, body, headers) { + for field, policy := range blocked { + value, err := getField(field, body, headers) + + log.Dev("Checking ", field, "...") + log.Dev("Got Value of ", jsonutils.ToJson(value)) + + if value == policy.Value && err == nil { isExplicitlyBlocked = true cause = field break From 8c441c0b5a01621a3df5120533199b90bbedec17 Mon Sep 17 00:00:00 2001 From: CodeShell <122738806+CodeShellDev@users.noreply.github.com> Date: Mon, 27 Oct 2025 19:17:17 +0100 Subject: [PATCH 08/12] fix config structure --- utils/config/structure/structure.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/config/structure/structure.go b/utils/config/structure/structure.go index 363d46c1..1e1cdd7f 100644 --- a/utils/config/structure/structure.go +++ b/utils/config/structure/structure.go @@ -35,6 +35,6 @@ type ACCESS_SETTINGS struct { } type FieldPolicy struct { - Field string `koanf:"field"` + Value string `koanf:"value"` Action string `koanf:"action"` } \ No newline at end of file From a9d23a1787250878d19fecf34e48f381c39d0c12 Mon Sep 17 00:00:00 2001 From: CodeShell <122738806+CodeShellDev@users.noreply.github.com> Date: Mon, 27 Oct 2025 19:17:54 +0100 Subject: [PATCH 09/12] change to correct type --- utils/config/structure/structure.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/config/structure/structure.go b/utils/config/structure/structure.go index 1e1cdd7f..0fd2e428 100644 --- a/utils/config/structure/structure.go +++ b/utils/config/structure/structure.go @@ -35,6 +35,6 @@ type ACCESS_SETTINGS struct { } type FieldPolicy struct { - Value string `koanf:"value"` + Value any `koanf:"value"` Action string `koanf:"action"` } \ No newline at end of file From c46d6edfc858e3e57c7a8ffed9e8549b1f6c510b Mon Sep 17 00:00:00 2001 From: CodeShell <122738806+CodeShellDev@users.noreply.github.com> Date: Mon, 27 Oct 2025 19:26:49 +0100 Subject: [PATCH 10/12] fix string slice --- internals/proxy/middlewares/policy.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internals/proxy/middlewares/policy.go b/internals/proxy/middlewares/policy.go index fdedc8ea..b252c251 100644 --- a/internals/proxy/middlewares/policy.go +++ b/internals/proxy/middlewares/policy.go @@ -70,7 +70,7 @@ func getField(field string, body map[string]any, headers map[string]any) (any, e isHeader := strings.HasPrefix(field, "#") isBody := strings.HasPrefix(field, "@") - fieldWithoutPrefix := field[:1] + fieldWithoutPrefix := field[1:] var value any From 246bb6af01f13643931a7e0c8480c1972a18f479 Mon Sep 17 00:00:00 2001 From: CodeShell <122738806+CodeShellDev@users.noreply.github.com> Date: Mon, 27 Oct 2025 19:29:15 +0100 Subject: [PATCH 11/12] fix policy middleware order --- internals/proxy/proxy.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internals/proxy/proxy.go b/internals/proxy/proxy.go index 2884533e..c74bf028 100644 --- a/internals/proxy/proxy.go +++ b/internals/proxy/proxy.go @@ -35,9 +35,9 @@ func (proxy Proxy) Init() http.Handler { Use(m.Server). Use(m.Auth). Use(m.Endpoints). - Use(m.Policy). Use(m.Template). Use(m.Mapping). + Use(m.Policy). Use(m.Message). Then(proxy.Use()) From 470ef3e4aa3d45b00bbbd3455a1a3a6ee851aa07 Mon Sep 17 00:00:00 2001 From: CodeShell <122738806+CodeShellDev@users.noreply.github.com> Date: Mon, 27 Oct 2025 19:37:02 +0100 Subject: [PATCH 12/12] test for possible key-lowering in fieldPolicies --- utils/config/config.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/utils/config/config.go b/utils/config/config.go index e0779624..5422647e 100644 --- a/utils/config/config.go +++ b/utils/config/config.go @@ -130,6 +130,8 @@ func normalizeKeys(config *koanf.Koanf) { for _, key := range config.Keys() { lower := strings.ToLower(key) + log.Dev("Lowering key: ", key) + data[lower] = config.Get(key) }