Skip to content

Commit 4ba31f7

Browse files
authored
FFM-11542 Add validation to Targets on auth path (#321)
* FFM-11542 Add validation to Targets on auth path **What** - Adds the same validation for Targets that Saas uses on targets in the /client/auth path **Why** - Without this the Proxy will accept invalid targets from SDKs when they authenticate and then log out an error when it trys to forward them on and Saas rejects them **Testing** - Added tests
1 parent 6d83182 commit 4ba31f7

File tree

3 files changed

+199
-1
lines changed

3 files changed

+199
-1
lines changed

transport/encode_decode.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"fmt"
77
"io"
88
"net/http"
9+
"regexp"
910

1011
"github.com/harness/ff-proxy/v2/domain"
1112
proxyservice "github.com/harness/ff-proxy/v2/proxy-service"
@@ -102,6 +103,15 @@ func decodeAuthRequest(c echo.Context) (interface{}, error) {
102103
if req.Target.Name == "" {
103104
req.Target.Name = req.Target.Identifier
104105
}
106+
107+
if req.Target.Identifier != "" && !isIdentifierValid(req.Target.Identifier) {
108+
return nil, fmt.Errorf("%w: target identifier is invalid", errBadRequest)
109+
}
110+
111+
if req.Target.Name != "" && !isNameValid(req.Target.Name) {
112+
return nil, fmt.Errorf("%w: target name is invalid", errBadRequest)
113+
}
114+
105115
return req, nil
106116
}
107117

@@ -246,3 +256,26 @@ func decodeMetricsRequest(c echo.Context) (interface{}, error) {
246256

247257
return req, nil
248258
}
259+
260+
var (
261+
identifierRegex = regexp.MustCompile("^[A-Za-z0-9.@_-]*$")
262+
nameRegex = regexp.MustCompile(`^[\p{L}\d .@_-]*$`)
263+
)
264+
265+
// IsIdentifierValid determine is an identifier confirms to the required format
266+
// returns true if the identifier is valid, otherwise this will return false
267+
func isIdentifierValid(identifier string) bool {
268+
if identifier == "" {
269+
return false
270+
}
271+
return identifierRegex.MatchString(identifier)
272+
}
273+
274+
// IsNameValid determine if the name confirms to the required format
275+
// returns true if the name is valid, otherwise will return false
276+
func isNameValid(name string) bool {
277+
if name == "" {
278+
return false
279+
}
280+
return nameRegex.MatchString(name)
281+
}

transport/encode_decode_test.go

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
package transport
2+
3+
import "testing"
4+
5+
func Test_isIdentifierValid(t *testing.T) {
6+
type args struct {
7+
identifier string
8+
}
9+
tests := []struct {
10+
name string
11+
args args
12+
want bool
13+
}{
14+
{
15+
"Alphanumeric is valid",
16+
args{
17+
identifier: "TargetName123",
18+
},
19+
true,
20+
},
21+
{
22+
"Spaces are invalid",
23+
args{
24+
identifier: "target name",
25+
},
26+
false,
27+
},
28+
{
29+
"Special characters are invalid",
30+
args{
31+
identifier: "target$({}><?/",
32+
},
33+
false,
34+
},
35+
{
36+
"Emails are valid",
37+
args{
38+
identifier: "[email protected]",
39+
},
40+
true,
41+
},
42+
{
43+
"Underscore is valid",
44+
args{
45+
identifier: "__global__cf_target",
46+
},
47+
true,
48+
},
49+
{
50+
"Dash is valid",
51+
args{
52+
identifier: "test-user",
53+
},
54+
true,
55+
},
56+
{
57+
"Single character is valid",
58+
args{
59+
identifier: "t",
60+
},
61+
true,
62+
},
63+
{
64+
"Empty string is invalid",
65+
args{
66+
identifier: "",
67+
},
68+
false,
69+
},
70+
}
71+
72+
for _, tt := range tests {
73+
t.Run(tt.name, func(t *testing.T) {
74+
if got := isIdentifierValid(tt.args.identifier); got != tt.want {
75+
t.Errorf("IsIdentifierValid() = %v, want %v", got, tt.want)
76+
}
77+
})
78+
}
79+
}
80+
81+
func Test_isNameValid(t *testing.T) {
82+
type args struct {
83+
name string
84+
}
85+
tests := []struct {
86+
name string
87+
args args
88+
want bool
89+
}{
90+
{
91+
"Alphanumeric is valid",
92+
args{
93+
name: "TargetName123",
94+
},
95+
true,
96+
},
97+
{
98+
"Spaces are valid",
99+
args{
100+
name: "Global Target",
101+
},
102+
true,
103+
},
104+
{
105+
"Special characters are invalid",
106+
args{
107+
name: "target$({}><?/",
108+
},
109+
false,
110+
},
111+
{
112+
"Emails are valid",
113+
args{
114+
115+
},
116+
true,
117+
},
118+
{
119+
"Underscore is valid",
120+
args{
121+
name: "__global__cf_target",
122+
},
123+
true,
124+
},
125+
{
126+
"Dash is valid",
127+
args{
128+
name: "test-user",
129+
},
130+
true,
131+
},
132+
{
133+
"Empty string is invalid",
134+
args{
135+
name: "",
136+
},
137+
false,
138+
},
139+
{
140+
"Unicode characters are valid",
141+
args{
142+
name: "ńoooo",
143+
},
144+
true,
145+
},
146+
}
147+
148+
for _, tt := range tests {
149+
t.Run(tt.name, func(t *testing.T) {
150+
if got := isNameValid(tt.args.name); got != tt.want {
151+
t.Errorf("IsIdentifierValid() = %v, want %v", got, tt.want)
152+
}
153+
})
154+
}
155+
}

transport/http_server_test.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
"github.com/alicebob/miniredis/v2"
1717
"github.com/harness-community/sse/v3"
1818
sdkstream "github.com/harness/ff-golang-server-sdk/stream"
19+
clientgen "github.com/harness/ff-proxy/v2/gen/client"
1920
"github.com/labstack/echo/v4"
2021
"github.com/prometheus/client_golang/prometheus"
2122
"github.com/redis/go-redis/v9"
@@ -24,7 +25,6 @@ import (
2425
"github.com/harness/ff-proxy/v2/cache"
2526
"github.com/harness/ff-proxy/v2/config/local"
2627
"github.com/harness/ff-proxy/v2/domain"
27-
clientgen "github.com/harness/ff-proxy/v2/gen/client"
2828
"github.com/harness/ff-proxy/v2/hash"
2929
"github.com/harness/ff-proxy/v2/log"
3030
"github.com/harness/ff-proxy/v2/middleware"
@@ -1041,6 +1041,16 @@ func TestHTTPServer_PostAuthentication(t *testing.T) {
10411041
expectedCacheTargets: targets,
10421042
expectedClientServiceTargets: []domain.Target{},
10431043
},
1044+
"Given I make an auth request with an invalid Target Identifier": {
1045+
method: http.MethodPost,
1046+
body: []byte(fmt.Sprintf(`{"apiKey": "%s", "target": {"identifier": "hello world"}}`, apiKey1)),
1047+
expectedStatusCode: http.StatusBadRequest,
1048+
},
1049+
"Given I make an auth request with an invalid Target Name": {
1050+
method: http.MethodPost,
1051+
body: []byte(fmt.Sprintf(`{"apiKey": "%s", "target": {"identifier": "helloworld", "name": "Hello/World"}}`, apiKey1)),
1052+
expectedStatusCode: http.StatusBadRequest,
1053+
},
10441054
}
10451055

10461056
mr, err := miniredis.Run()

0 commit comments

Comments
 (0)