Skip to content

Commit ddebb20

Browse files
authored
feat: field policy (#94)
1 parent 3e0e347 commit ddebb20

File tree

6 files changed

+167
-4
lines changed

6 files changed

+167
-4
lines changed

internals/proxy/middlewares/endpoints.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,12 @@ func getEndpoints(endpoints []string) ([]string, []string) {
5353
}
5454

5555
func isBlocked(endpoint string, endpoints []string) bool {
56+
if endpoints == nil {
57+
return false
58+
} else if len(endpoints) <= 0 {
59+
return false
60+
}
61+
5662
allowed, blocked := getEndpoints(endpoints)
5763

5864
isExplicitlyBlocked := slices.ContainsFunc(blocked, func(try string) bool {
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
package middlewares
2+
3+
import (
4+
"errors"
5+
"net/http"
6+
"strings"
7+
8+
"github.com/codeshelldev/secured-signal-api/utils/config/structure"
9+
"github.com/codeshelldev/secured-signal-api/utils/jsonutils"
10+
log "github.com/codeshelldev/secured-signal-api/utils/logger"
11+
request "github.com/codeshelldev/secured-signal-api/utils/request"
12+
)
13+
14+
var Policy Middleware = Middleware{
15+
Name: "Policy",
16+
Use: policyHandler,
17+
}
18+
19+
func policyHandler(next http.Handler) http.Handler {
20+
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
21+
settings := getSettingsByReq(req)
22+
23+
policies := settings.ACCESS.FIELD_POLOCIES
24+
25+
if policies == nil {
26+
policies = getSettings("*").ACCESS.FIELD_POLOCIES
27+
}
28+
29+
body, err := request.GetReqBody(w, req)
30+
31+
if err != nil {
32+
log.Error("Could not get Request Body: ", err.Error())
33+
}
34+
35+
if body.Empty {
36+
body.Data = map[string]any{}
37+
}
38+
39+
headerData := request.GetReqHeaders(req)
40+
41+
shouldBlock, field := doBlock(body.Data, headerData, policies)
42+
43+
if shouldBlock {
44+
log.Warn("User tried to use blocked field: ", field)
45+
http.Error(w, "Forbidden", http.StatusForbidden)
46+
return
47+
}
48+
49+
next.ServeHTTP(w, req)
50+
})
51+
}
52+
53+
func getPolicies(policies map[string]structure.FieldPolicy) (map[string]structure.FieldPolicy, map[string]structure.FieldPolicy) {
54+
blockedFields := map[string]structure.FieldPolicy{}
55+
allowedFields := map[string]structure.FieldPolicy{}
56+
57+
for field, policy := range policies {
58+
switch policy.Action {
59+
case "block":
60+
blockedFields[field] = policy
61+
case "allow":
62+
allowedFields[field] = policy
63+
}
64+
}
65+
66+
return allowedFields, blockedFields
67+
}
68+
69+
func getField(field string, body map[string]any, headers map[string]any) (any, error) {
70+
isHeader := strings.HasPrefix(field, "#")
71+
isBody := strings.HasPrefix(field, "@")
72+
73+
fieldWithoutPrefix := field[1:]
74+
75+
var value any
76+
77+
if body[fieldWithoutPrefix] != nil && isBody {
78+
value = body[fieldWithoutPrefix]
79+
} else if headers[fieldWithoutPrefix] != nil && isHeader {
80+
value = headers[fieldWithoutPrefix]
81+
}
82+
83+
if value != nil {
84+
return value, nil
85+
}
86+
87+
return value, errors.New("field not found")
88+
}
89+
90+
func doBlock(body map[string]any, headers map[string]any, policies map[string]structure.FieldPolicy) (bool, string) {
91+
if policies == nil {
92+
return false, ""
93+
} else if len(policies) <= 0 {
94+
return false, ""
95+
}
96+
97+
allowed, blocked := getPolicies(policies)
98+
99+
var cause string
100+
101+
var isExplictlyAllowed, isExplicitlyBlocked bool
102+
103+
for field, policy := range allowed {
104+
value, err := getField(field, body, headers)
105+
106+
log.Dev("Checking ", field, "...")
107+
log.Dev("Got Value of ", jsonutils.ToJson(value))
108+
109+
if value == policy.Value && err == nil {
110+
isExplictlyAllowed = true
111+
cause = field
112+
break
113+
}
114+
}
115+
116+
for field, policy := range blocked {
117+
value, err := getField(field, body, headers)
118+
119+
log.Dev("Checking ", field, "...")
120+
log.Dev("Got Value of ", jsonutils.ToJson(value))
121+
122+
if value == policy.Value && err == nil {
123+
isExplicitlyBlocked = true
124+
cause = field
125+
break
126+
}
127+
}
128+
129+
// Block all except explicitly Allowed
130+
if len(blocked) == 0 && len(allowed) != 0 {
131+
return !isExplictlyAllowed, cause
132+
}
133+
134+
// Allow all except explicitly Blocked
135+
if len(allowed) == 0 && len(blocked) != 0 {
136+
return isExplicitlyBlocked, cause
137+
}
138+
139+
// Excplicitly Blocked except excplictly Allowed
140+
if len(blocked) != 0 && len(allowed) != 0 {
141+
return isExplicitlyBlocked && !isExplictlyAllowed, cause
142+
}
143+
144+
// Block all
145+
return true, ""
146+
}

internals/proxy/proxy.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ func (proxy Proxy) Init() http.Handler {
3737
Use(m.Endpoints).
3838
Use(m.Template).
3939
Use(m.Mapping).
40+
Use(m.Policy).
4041
Use(m.Message).
4142
Then(proxy.Use())
4243

utils/config/config.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,8 @@ func normalizeKeys(config *koanf.Koanf) {
130130
for _, key := range config.Keys() {
131131
lower := strings.ToLower(key)
132132

133+
log.Dev("Lowering key: ", key)
134+
133135
data[lower] = config.Get(key)
134136
}
135137

utils/config/structure/structure.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ type SETTINGS struct {
2020

2121
type MESSAGE_SETTINGS struct {
2222
VARIABLES map[string]any `koanf:"variables"`
23-
FIELD_MAPPINGS map[string][]FieldMapping `koanf:"fieldMappings"`
23+
FIELD_MAPPINGS map[string][]FieldMapping `koanf:"fieldmappings"`
2424
TEMPLATE string `koanf:"template"`
2525
}
2626

@@ -31,4 +31,10 @@ type FieldMapping struct {
3131

3232
type ACCESS_SETTINGS struct {
3333
ENDPOINTS []string `koanf:"endpoints"`
34+
FIELD_POLOCIES map[string]FieldPolicy `koanf:"fieldpolicies"`
35+
}
36+
37+
type FieldPolicy struct {
38+
Value any `koanf:"value"`
39+
Action string `koanf:"action"`
3440
}

utils/request/request.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package req
22

33
import (
4+
"bytes"
45
"encoding/json"
56
"errors"
67
"io"
@@ -85,12 +86,13 @@ func GetFormData(body []byte) (map[string]any, error) {
8586
func GetBody(req *http.Request) ([]byte, error) {
8687
bodyBytes, err := io.ReadAll(req.Body)
8788

88-
if err != nil {
89-
req.Body.Close()
89+
req.Body.Close()
90+
91+
req.Body = io.NopCloser(bytes.NewReader(bodyBytes))
9092

93+
if err != nil {
9194
return nil, err
9295
}
93-
defer req.Body.Close()
9496

9597
return bodyBytes, nil
9698
}

0 commit comments

Comments
 (0)