-
Notifications
You must be signed in to change notification settings - Fork 67
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
dd28057
commit 21273de
Showing
8 changed files
with
272 additions
and
17 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
package main | ||
|
||
// Copyright (c) Microsoft Corporation. | ||
// Licensed under the Apache License 2.0. | ||
|
||
import ( | ||
"errors" | ||
"net/http" | ||
"regexp" | ||
"strings" | ||
|
||
"github.com/go-logr/logr" | ||
|
||
"github.com/Azure/ARO-HCP/internal/api" | ||
"github.com/Azure/ARO-HCP/internal/api/arm" | ||
) | ||
|
||
// Referenced in https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/resource-name-rules#microsoftresources | ||
var rxResourceGroupName = regexp.MustCompile(`^[-a-z0-9_().]{0,89}[-a-z0-9_()]$`) | ||
var rxResourceName = regexp.MustCompile(`^[a-zA-Z0-9-]{3,24}$`) | ||
|
||
func MiddlewareValidateStatic(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) { | ||
|
||
subId := r.PathValue(PathSegmentSubscriptionID) | ||
resourceGroupName := r.PathValue(PathSegmentResourceGroupName) | ||
resourceProviderNamespace := r.PathValue(PathSegmentResourceProviderNamespace) | ||
resourceType := r.PathValue(PathSegmentResourceType) | ||
operationId := r.PathValue(PathSegmentOperationID) | ||
resourceName := r.PathValue(PathSegmentResourceName) | ||
|
||
if r.URL.Path != strings.ToLower(r.URL.Path) { | ||
if log, ok := r.Context().Value(LoggerFromContext).(logr.Logger); ok { | ||
log.Error(errors.New("LowerCase error"), "path was not lower case") | ||
} | ||
arm.WriteInternalServerError(w) | ||
return | ||
} | ||
|
||
if subId != "" { | ||
valid := api.IsValid(subId) | ||
if !valid { | ||
arm.WriteError(w, http.StatusBadRequest, arm.CloudErrorCodeInvalidSubscriptionID, "", "The provided subscription identifier '%s' is malformed or invalid.", subId) | ||
return | ||
} | ||
} | ||
|
||
if resourceGroupName != "" { | ||
if !rxResourceGroupName.MatchString(resourceGroupName) { | ||
arm.WriteError(w, http.StatusBadRequest, arm.CloudErrorCodeResourceGroupNotFound, "", "Resource group '%s' is invalid.", resourceGroupName) | ||
return | ||
} | ||
} | ||
|
||
if resourceProviderNamespace != "" { | ||
if resourceProviderNamespace != strings.ToLower(api.ProviderNamespace) { | ||
arm.WriteError(w, http.StatusBadRequest, arm.CloudErrorCodeInvalidResourceNamespace, "", "The resource namespace '%s' is invalid.", resourceProviderNamespace) | ||
return | ||
} | ||
} | ||
|
||
if resourceName != "" { | ||
if !rxResourceName.MatchString(resourceName) { | ||
arm.WriteError(w, http.StatusBadRequest, arm.CloudErrorCodeResourceNotFound, "", "The Resource '%s/%s/%s' under resource group '%s' is invalid.", resourceProviderNamespace, resourceType, resourceName, resourceGroupName) | ||
return | ||
} | ||
} | ||
|
||
if operationId != "" { | ||
valid := api.IsValid(operationId) | ||
if !valid { | ||
arm.WriteError(w, http.StatusBadRequest, arm.CloudErrorCodeInvalidOperationID, "", "The provided operation identifier '%s' is malformed or invalid.", operationId) | ||
return | ||
} | ||
} | ||
|
||
next(w, r) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
package main | ||
|
||
import ( | ||
"encoding/json" | ||
"io" | ||
"net/http" | ||
"net/http/httptest" | ||
"strings" | ||
"testing" | ||
|
||
"github.com/Azure/ARO-HCP/internal/api/arm" | ||
) | ||
|
||
type CloudErrorContainer struct { | ||
Error arm.CloudErrorBody `json:"error"` | ||
} | ||
|
||
func TestMiddlewareValidateStatic(t *testing.T) { | ||
// This will act as the next handler if middleware validation passes | ||
nextHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
w.WriteHeader(http.StatusOK) // indicate success | ||
}) | ||
|
||
tests := []struct { | ||
name string | ||
path string | ||
subscriptionID string | ||
resourceGroupName string | ||
resourceProviderNamespace string | ||
resourceType string | ||
resourceName string | ||
operationsId string | ||
expectedStatusCode int | ||
expectedBody string | ||
}{ | ||
{ | ||
name: "Valid request", | ||
path: "/subscriptions/42d9eac4-d29a-4d6e-9e26-3439758b1491", | ||
subscriptionID: "42d9eac4-d29a-4d6e-9e26-3439758b1491", | ||
expectedStatusCode: http.StatusOK, | ||
}, | ||
{ | ||
name: "Invalid subscription ID", | ||
path: "/subscriptions/invalid!sub!id", | ||
subscriptionID: "invalid!sub!id", | ||
expectedStatusCode: http.StatusBadRequest, | ||
expectedBody: "The provided subscription identifier 'invalid!sub!id' is malformed or invalid.", | ||
}, | ||
{ | ||
name: "Invalid resource group name", | ||
path: "/resourcegroups/resourcegroup!", | ||
resourceGroupName: "resourcegroup!", | ||
expectedStatusCode: http.StatusBadRequest, | ||
expectedBody: "Resource group 'resourcegroup!' is invalid.", | ||
}, | ||
{ | ||
name: "Invalid resource provider namespace", | ||
path: "/providers/invalid", | ||
resourceProviderNamespace: "invalid", | ||
expectedStatusCode: http.StatusBadRequest, | ||
expectedBody: "The resource namespace 'invalid' is invalid.", | ||
}, | ||
{ | ||
name: "Invalid resource name", | ||
path: "/resourcegroup/providers/microsoft.redhatopenshift/hcpopenshiftcluster/$", | ||
resourceGroupName: "resourcegroup", | ||
resourceProviderNamespace: "microsoft.redhatopenshift", | ||
resourceType: "hcpopenshiftcluster", | ||
resourceName: "$", | ||
expectedStatusCode: http.StatusBadRequest, | ||
expectedBody: "The Resource 'microsoft.redhatopenshift/hcpopenshiftcluster/$' under resource group 'resourcegroup' is invalid.", | ||
}, | ||
{ | ||
name: "Invalid operation id", | ||
path: "/operations/abc", | ||
operationsId: "abc", | ||
expectedStatusCode: http.StatusBadRequest, | ||
expectedBody: "The provided operation identifier 'abc' is malformed or invalid.", | ||
}, | ||
} | ||
|
||
for _, tc := range tests { | ||
t.Run(tc.name, func(t *testing.T) { | ||
req := httptest.NewRequest("GET", "http://example.com"+tc.path, nil) | ||
|
||
// Use httptest.ResponseRecorder to record the response | ||
w := httptest.NewRecorder() | ||
|
||
req.SetPathValue(PathSegmentSubscriptionID, tc.subscriptionID) | ||
req.SetPathValue(PathSegmentResourceGroupName, tc.resourceGroupName) | ||
req.SetPathValue(PathSegmentResourceProviderNamespace, tc.resourceProviderNamespace) | ||
req.SetPathValue(PathSegmentResourceType, tc.resourceType) | ||
req.SetPathValue(PathSegmentResourceName, tc.resourceName) | ||
req.SetPathValue(PathSegmentOperationID, tc.operationsId) | ||
|
||
// Execute the middleware | ||
MiddlewareValidateStatic(w, req, nextHandler) | ||
|
||
// Check the response status code | ||
if status := w.Code; status != tc.expectedStatusCode { | ||
t.Errorf("handler returned wrong status code: got %v want %v", | ||
status, tc.expectedStatusCode) | ||
} | ||
|
||
if tc.expectedStatusCode != http.StatusOK { | ||
|
||
var resp CloudErrorContainer | ||
body, err := io.ReadAll(http.MaxBytesReader(w, w.Result().Body, 4*megabyte)) | ||
if err != nil { | ||
t.Fatalf("failed to read response body: %v", err) | ||
} | ||
err = json.Unmarshal(body, &resp) | ||
if err != nil { | ||
t.Fatalf("failed to unmarshal response body: %v", err) | ||
} | ||
|
||
// Check if the error message contains the expected text | ||
if !strings.Contains(resp.Error.Message, tc.expectedBody) { | ||
t.Errorf("handler returned unexpected body: got %v want %v", | ||
resp.Error.Message, tc.expectedBody) | ||
} | ||
} | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters