Skip to content

Commit 9ac71ad

Browse files
committed
feat(backend): Reject invalid requests.
Signed-off-by: Yael <fishel.yael@gmail.com>
1 parent 90c21df commit 9ac71ad

6 files changed

+348
-1
lines changed

workspaces/backend/api/errors.go

-1
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,6 @@ func (a *App) LogError(r *http.Request, err error) {
4646
a.logger.Error(err.Error(), "method", method, "uri", uri)
4747
}
4848

49-
//nolint:unused
5049
func (a *App) badRequestResponse(w http.ResponseWriter, r *http.Request, err error) {
5150
httpError := &HTTPError{
5251
StatusCode: http.StatusBadRequest,

workspaces/backend/api/validation.go

+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*
2+
Copyright 2024.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package api
18+
19+
import (
20+
"fmt"
21+
"unicode/utf8"
22+
)
23+
24+
// ValidateKubernetesResourceName validates one or more Kubernetes resource names.
25+
// It ensures each name meets the following criteria:
26+
// 1. The name must not contain non-ASCII characters.
27+
// 2. The name must not exceed 255 characters in length.
28+
func ValidateKubernetesResourceName(params ...string) error {
29+
for _, param := range params {
30+
if err := NonASCIIValidator(param); err != nil {
31+
return err
32+
}
33+
if err := LengthValidator(param); err != nil {
34+
return err
35+
}
36+
}
37+
return nil
38+
}
39+
40+
// NonASCIIValidator checks if a given string contains only ASCII characters.
41+
func NonASCIIValidator(param string) error {
42+
if utf8.ValidString(param) && len(param) == len([]rune(param)) {
43+
return nil
44+
}
45+
return fmt.Errorf("Invalid value: '%s' contains non-ASCII characters.", param)
46+
}
47+
48+
// LengthValidator ensures a given string does not exceed 255 characters.
49+
func LengthValidator(param string) error {
50+
if len(param) > 255 {
51+
return fmt.Errorf("Invalid value: '%s' exceeds the allowed limit of 255 characters.", param)
52+
}
53+
54+
return nil
55+
}

workspaces/backend/api/workspacekinds_handler.go

+4
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ func (a *App) GetWorkspaceKindHandler(w http.ResponseWriter, r *http.Request, ps
3535

3636
if name == "" {
3737
a.serverErrorResponse(w, r, fmt.Errorf("workspace kind name is missing"))
38+
}
39+
40+
if err := ValidateKubernetesResourceName(name); err != nil {
41+
a.badRequestResponse(w, r, err)
3842
return
3943
}
4044

workspaces/backend/api/workspacekinds_handler_test.go

+106
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"encoding/json"
2121
"fmt"
2222
"io"
23+
"math/rand"
2324
"net/http"
2425
"net/http/httptest"
2526
"strings"
@@ -265,4 +266,109 @@ var _ = Describe("WorkspaceKinds Handler", func() {
265266
Expect(rs.StatusCode).To(Equal(http.StatusNotFound), "Expected HTTP status 404 Not Found")
266267
})
267268
})
269+
270+
Context("with unsupported request parameters", Ordered, func() {
271+
272+
var (
273+
a App
274+
validAsciiName string
275+
invalidAsciiName string
276+
validMaxLengthName string
277+
invalidLengthName string
278+
)
279+
280+
// generateASCII generates a random ASCII string of the specified length.
281+
generateASCII := func(length int) string {
282+
const asciiChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
283+
284+
var sb strings.Builder
285+
for i := 0; i < length; i++ {
286+
sb.WriteByte(asciiChars[rand.Intn(len(asciiChars))])
287+
}
288+
return sb.String()
289+
}
290+
291+
BeforeAll(func() {
292+
validAsciiName = "test"
293+
invalidAsciiName = validAsciiName + string(rune(rand.Intn(0x10FFFF-128)+128))
294+
validMaxLengthName = generateASCII(255)
295+
invalidLengthName = generateASCII(256)
296+
297+
repos := repositories.NewRepositories(k8sClient)
298+
a = App{
299+
Config: config.EnvConfig{
300+
Port: 4000,
301+
},
302+
repositories: repos,
303+
}
304+
})
305+
306+
It("should return 400 status code for a non-ascii workspace", func() {
307+
By("creating the HTTP request")
308+
path := strings.Replace(WorkspacesByNamespacePath, ":"+WorkspaceNamePathParam, invalidAsciiName, 1)
309+
req, err := http.NewRequest(http.MethodGet, path, http.NoBody)
310+
Expect(err).NotTo(HaveOccurred(), "Failed to create HTTP request")
311+
312+
By("executing GetWorkspaceKindHandler")
313+
ps := httprouter.Params{
314+
httprouter.Param{
315+
Key: WorkspaceNamePathParam,
316+
Value: invalidAsciiName,
317+
},
318+
}
319+
rr := httptest.NewRecorder()
320+
a.GetWorkspaceKindHandler(rr, req, ps)
321+
rs := rr.Result()
322+
defer rs.Body.Close()
323+
324+
By("verifying the HTTP response status code")
325+
Expect(rs.StatusCode).To(Equal(http.StatusBadRequest), "Expected HTTP status 400 Bad Request")
326+
})
327+
328+
It("should return 400 status code for a workspace longer than 255", func() {
329+
By("creating the HTTP request")
330+
path := strings.Replace(WorkspacesByNamespacePath, ":"+WorkspaceNamePathParam, invalidLengthName, 1)
331+
req, err := http.NewRequest(http.MethodGet, path, http.NoBody)
332+
Expect(err).NotTo(HaveOccurred(), "Failed to create HTTP request")
333+
334+
By("executing GetWorkspaceKindHandler")
335+
ps := httprouter.Params{
336+
httprouter.Param{
337+
Key: WorkspaceNamePathParam,
338+
Value: invalidLengthName,
339+
},
340+
}
341+
rr := httptest.NewRecorder()
342+
a.GetWorkspaceKindHandler(rr, req, ps)
343+
rs := rr.Result()
344+
defer rs.Body.Close()
345+
346+
By("verifying the HTTP response status code")
347+
Expect(rs.StatusCode).To(Equal(http.StatusBadRequest), "Expected HTTP status 400 Bad Request")
348+
349+
})
350+
351+
It("should return 200 status code for a workspace with a length of 255 characters", func() {
352+
By("creating the HTTP request")
353+
fmt.Println("Here Should except 255 length params")
354+
path := strings.Replace(WorkspacesByNamespacePath, ":"+WorkspaceNamePathParam, validMaxLengthName, 1)
355+
req, err := http.NewRequest(http.MethodGet, path, http.NoBody)
356+
Expect(err).NotTo(HaveOccurred(), "Failed to create HTTP request")
357+
358+
By("executing GetWorkspaceKindHandler")
359+
ps := httprouter.Params{
360+
httprouter.Param{
361+
Key: WorkspaceNamePathParam,
362+
Value: validMaxLengthName,
363+
},
364+
}
365+
rr := httptest.NewRecorder()
366+
a.GetWorkspaceKindHandler(rr, req, ps)
367+
rs := rr.Result()
368+
defer rs.Body.Close()
369+
370+
By("verifying the HTTP response status code")
371+
Expect(rs.StatusCode).To(Equal(http.StatusNotFound), "Expected HTTP status 404 Not Found")
372+
})
373+
})
268374
})

workspaces/backend/api/workspaces_handler.go

+18
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@ func (a *App) GetWorkspaceHandler(w http.ResponseWriter, r *http.Request, ps htt
4343
}
4444
if workspaceName == "" {
4545
a.serverErrorResponse(w, r, fmt.Errorf("workspaceName is nil"))
46+
}
47+
48+
if err := ValidateKubernetesResourceName(namespace, workspaceName); err != nil {
49+
a.badRequestResponse(w, r, err)
4650
return
4751
}
4852

@@ -70,6 +74,11 @@ func (a *App) GetWorkspaceHandler(w http.ResponseWriter, r *http.Request, ps htt
7074
func (a *App) GetWorkspacesHandler(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
7175
namespace := ps.ByName(NamespacePathParam)
7276

77+
if err := ValidateKubernetesResourceName(namespace); err != nil {
78+
a.badRequestResponse(w, r, err)
79+
return
80+
}
81+
7382
var workspaces []models.WorkspaceModel
7483
var err error
7584
if namespace == "" {
@@ -97,6 +106,10 @@ func (a *App) CreateWorkspaceHandler(w http.ResponseWriter, r *http.Request, ps
97106

98107
if namespace == "" {
99108
a.serverErrorResponse(w, r, fmt.Errorf("namespace is missing"))
109+
}
110+
111+
if err := ValidateKubernetesResourceName(namespace); err != nil {
112+
a.badRequestResponse(w, r, err)
100113
return
101114
}
102115

@@ -106,6 +119,11 @@ func (a *App) CreateWorkspaceHandler(w http.ResponseWriter, r *http.Request, ps
106119
return
107120
}
108121

122+
if err := ValidateKubernetesResourceName(workspaceModel.Name, workspaceModel.Namespace); err != nil {
123+
a.badRequestResponse(w, r, err)
124+
return
125+
}
126+
109127
workspaceModel.Namespace = namespace
110128

111129
createdWorkspace, err := a.repositories.Workspace.CreateWorkspace(r.Context(), workspaceModel)

0 commit comments

Comments
 (0)