diff --git a/docs/docs/CHANGELOG.md b/docs/docs/CHANGELOG.md index 4bd5ea6..e6a659f 100644 --- a/docs/docs/CHANGELOG.md +++ b/docs/docs/CHANGELOG.md @@ -40,6 +40,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Bump AI-robots.txt to version 1.39 - Add a default block rule for Huawei Cloud. - Add a default block rule for Alibaba Cloud. +- Add X-Request-URI support so that Subrequest Authentication has path support. ### Security-relevant changes diff --git a/lib/policy/checker.go b/lib/policy/checker.go index 5753e14..4f5ad55 100644 --- a/lib/policy/checker.go +++ b/lib/policy/checker.go @@ -102,6 +102,13 @@ func NewPathChecker(rexStr string) (checker.Impl, error) { } func (pc *PathChecker) Check(r *http.Request) (bool, error) { + originalUrl := r.Header.Get("X-Original-URI") + if originalUrl != "" { + if pc.regexp.MatchString(originalUrl) { + return true, nil + } + } + if pc.regexp.MatchString(r.URL.Path) { return true, nil } diff --git a/lib/policy/checker_test.go b/lib/policy/checker_test.go index 6109bab..6cc3e0d 100644 --- a/lib/policy/checker_test.go +++ b/lib/policy/checker_test.go @@ -198,3 +198,96 @@ func TestHeaderExistsChecker(t *testing.T) { }) } } + +func TestPathChecker_XOriginalURI(t *testing.T) { + tests := []struct { + name string + regex string + xOriginalURI string + urlPath string + headerKey string + expectedMatch bool + expectError bool + }{ + { + name: "X-Original-URI matches regex (with trailing space - current typo)", + regex: "^/api/.*", + xOriginalURI: "/api/users", + urlPath: "/different/path", + headerKey: "X-Original-URI", + expectedMatch: true, + expectError: false, + }, + { + name: "X-Original-URI doesn't match, falls back to URL.Path", + regex: "^/admin/.*", + xOriginalURI: "/api/users", + urlPath: "/admin/dashboard", + headerKey: "X-Original-URI", + expectedMatch: true, + expectError: false, + }, + { + name: "Neither X-Original-URI nor URL.Path match", + regex: "^/admin/.*", + xOriginalURI: "/api/users", + urlPath: "/public/info", + headerKey: "X-Original-URI ", + expectedMatch: false, + expectError: false, + }, + { + name: "Empty X-Original-URI, URL.Path matches", + regex: "^/static/.*", + xOriginalURI: "", + urlPath: "/static/css/style.css", + headerKey: "X-Original-URI", + expectedMatch: true, + expectError: false, + }, + { + name: "Complex regex matching X-Original-URI", + regex: `^/api/v[0-9]+/(users|posts)/[0-9]+$`, + xOriginalURI: "/api/v1/users/123", + urlPath: "/different", + headerKey: "X-Original-URI", + expectedMatch: true, + expectError: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Create the PathChecker + pc, err := NewPathChecker(tt.regex) + if err != nil { + if !tt.expectError { + t.Fatalf("NewPathChecker() unexpected error: %v", err) + } + return + } + + if tt.expectError { + t.Fatal("NewPathChecker() expected error but got none") + } + + req, err := http.NewRequest("GET", "http://example.com"+tt.urlPath, nil) + if err != nil { + t.Fatalf("Failed to create request: %v", err) + } + + if tt.xOriginalURI != "" { + req.Header.Set(tt.headerKey, tt.xOriginalURI) + } + + match, err := pc.Check(req) + if err != nil { + t.Fatalf("Check() unexpected error: %v", err) + } + + if match != tt.expectedMatch { + t.Errorf("Check() = %v, want %v", match, tt.expectedMatch) + } + }) + } +}