Skip to content

Commit 47c5762

Browse files
Diaskerchansuke
authored andcommitted
fix: prevent rootpath duplication in OIDC redirect URLs, fixes argoproj#21857 argoproj#20790 argoproj#12195 (argoproj#22254)
Signed-off-by: Diasker <[email protected]>
1 parent 1fbc2d7 commit 47c5762

File tree

2 files changed

+149
-4
lines changed

2 files changed

+149
-4
lines changed

server/rootpath_test.go

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
package server
2+
3+
import (
4+
"net/http"
5+
"net/http/httptest"
6+
"testing"
7+
8+
"github.com/stretchr/testify/assert"
9+
)
10+
11+
// TestWithRootPathEmptyRootPath tests that withRootPath returns the original handler when RootPath is empty
12+
func TestWithRootPathEmptyRootPath(t *testing.T) {
13+
// Create a simple handler
14+
originalHandler := http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
15+
w.WriteHeader(http.StatusOK)
16+
})
17+
18+
// Create a server with empty RootPath
19+
server := &ArgoCDServer{
20+
ArgoCDServerOpts: ArgoCDServerOpts{
21+
RootPath: "",
22+
},
23+
}
24+
25+
// Call withRootPath
26+
handler := withRootPath(originalHandler, server)
27+
28+
// Verify that the returned handler is the original handler
29+
// Since we can't directly compare function references, we'll use a type assertion
30+
_, isServeMux := handler.(*http.ServeMux)
31+
assert.False(t, isServeMux, "When RootPath is empty, withRootPath should return the original handler, not a ServeMux")
32+
}
33+
34+
// TestWithRootPathNonEmptyRootPath tests that withRootPath returns a ServeMux when RootPath is not empty
35+
func TestWithRootPathNonEmptyRootPath(t *testing.T) {
36+
// Create a simple handler
37+
originalHandler := http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
38+
w.WriteHeader(http.StatusOK)
39+
})
40+
41+
// Create a server with non-empty RootPath
42+
server := &ArgoCDServer{
43+
ArgoCDServerOpts: ArgoCDServerOpts{
44+
RootPath: "/argocd",
45+
},
46+
}
47+
48+
// Call withRootPath
49+
handler := withRootPath(originalHandler, server)
50+
51+
// Verify that the returned handler is a ServeMux
52+
_, isServeMux := handler.(*http.ServeMux)
53+
assert.True(t, isServeMux, "When RootPath is not empty, withRootPath should return a ServeMux")
54+
}
55+
56+
// TestNewRedirectServerEmptyRootPath tests that newRedirectServer correctly handles empty rootPath
57+
func TestNewRedirectServerEmptyRootPath(t *testing.T) {
58+
// Call newRedirectServer with empty rootPath
59+
server := newRedirectServer(8080, "")
60+
61+
// Verify the server address
62+
assert.Equal(t, "localhost:8080", server.Addr, "When rootPath is empty, server address should be 'localhost:8080'")
63+
64+
// Test the redirect handler
65+
req := httptest.NewRequest(http.MethodGet, "/applications", nil)
66+
req.Host = "example.com:8080"
67+
w := httptest.NewRecorder()
68+
69+
server.Handler.ServeHTTP(w, req)
70+
71+
// Verify the redirect URL
72+
assert.Equal(t, http.StatusTemporaryRedirect, w.Code, "Should return a 307 Temporary Redirect status code")
73+
redirectURL := w.Header().Get("Location")
74+
expectedURL := "https://example.com:8080/applications"
75+
assert.Equal(t, expectedURL, redirectURL, "Redirect URL should not include rootPath when rootPath is empty")
76+
}
77+
78+
// TestNewRedirectServerNonEmptyRootPath tests that newRedirectServer correctly handles non-empty rootPath
79+
func TestNewRedirectServerNonEmptyRootPath(t *testing.T) {
80+
// Call newRedirectServer with non-empty rootPath
81+
server := newRedirectServer(8080, "/argocd")
82+
83+
// Verify the server address
84+
assert.Equal(t, "localhost:8080/argocd", server.Addr, "When rootPath is '/argocd', server address should be 'localhost:8080/argocd'")
85+
86+
// Test the redirect handler
87+
req := httptest.NewRequest(http.MethodGet, "/applications", nil)
88+
req.Host = "example.com:8080"
89+
w := httptest.NewRecorder()
90+
91+
server.Handler.ServeHTTP(w, req)
92+
93+
// Verify the redirect URL
94+
assert.Equal(t, http.StatusTemporaryRedirect, w.Code, "Should return a 307 Temporary Redirect status code")
95+
redirectURL := w.Header().Get("Location")
96+
expectedURL := "https://example.com:8080/argocd/applications"
97+
assert.Equal(t, expectedURL, redirectURL, "Redirect URL should include rootPath when rootPath is not empty")
98+
}
99+
100+
// TestNewRedirectServerRootPathDuplication tests that newRedirectServer does not duplicate rootPath in the redirect URL
101+
func TestNewRedirectServerRootPathDuplication(t *testing.T) {
102+
// Call newRedirectServer with non-empty rootPath
103+
server := newRedirectServer(8080, "/argocd")
104+
105+
// Test the redirect handler with a request path that already includes rootPath
106+
req := httptest.NewRequest(http.MethodGet, "/argocd/applications", nil)
107+
req.Host = "example.com:8080"
108+
w := httptest.NewRecorder()
109+
110+
server.Handler.ServeHTTP(w, req)
111+
112+
// Verify the redirect URL
113+
assert.Equal(t, http.StatusTemporaryRedirect, w.Code, "Should return a 307 Temporary Redirect status code")
114+
redirectURL := w.Header().Get("Location")
115+
116+
// The URL should not have duplicated rootPath
117+
duplicatedURL := "https://example.com:8080/argocd/argocd/applications"
118+
assert.NotEqual(t, duplicatedURL, redirectURL, "Redirect URL should not have duplicated rootPath")
119+
120+
// The correct URL should be
121+
correctURL := "https://example.com:8080/argocd/applications"
122+
assert.Equal(t, correctURL, redirectURL, "Redirect URL should be correct without duplicated rootPath")
123+
}

server/server.go

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1125,8 +1125,13 @@ func (server *ArgoCDServer) setTokenCookie(token string, w http.ResponseWriter)
11251125
}
11261126

11271127
func withRootPath(handler http.Handler, a *ArgoCDServer) http.Handler {
1128+
// If RootPath is empty, directly return the original handler
1129+
if a.RootPath == "" {
1130+
return handler
1131+
}
1132+
11281133
// get rid of slashes
1129-
root := strings.TrimRight(strings.TrimLeft(a.RootPath, "/"), "/")
1134+
root := strings.Trim(a.RootPath, "/")
11301135

11311136
mux := http.NewServeMux()
11321137
mux.Handle("/"+root+"/", http.StripPrefix("/"+root, handler))
@@ -1343,15 +1348,32 @@ func (server *ArgoCDServer) registerDexHandlers(mux *http.ServeMux) {
13431348

13441349
// newRedirectServer returns an HTTP server which does a 307 redirect to the HTTPS server
13451350
func newRedirectServer(port int, rootPath string) *http.Server {
1346-
addr := fmt.Sprintf("localhost:%d/%s", port, strings.TrimRight(strings.TrimLeft(rootPath, "/"), "/"))
1351+
var addr string
1352+
if rootPath == "" {
1353+
addr = fmt.Sprintf("localhost:%d", port)
1354+
} else {
1355+
addr = fmt.Sprintf("localhost:%d/%s", port, strings.Trim(rootPath, "/"))
1356+
}
1357+
13471358
return &http.Server{
13481359
Addr: addr,
13491360
Handler: http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
13501361
target := "https://" + req.Host
1362+
13511363
if rootPath != "" {
1352-
target += "/" + strings.TrimRight(strings.TrimLeft(rootPath, "/"), "/")
1364+
root := strings.Trim(rootPath, "/")
1365+
prefix := "/" + root
1366+
1367+
// If the request path already starts with rootPath, no need to add rootPath again
1368+
if strings.HasPrefix(req.URL.Path, prefix) {
1369+
target += req.URL.Path
1370+
} else {
1371+
target += prefix + req.URL.Path
1372+
}
1373+
} else {
1374+
target += req.URL.Path
13531375
}
1354-
target += req.URL.Path
1376+
13551377
if len(req.URL.RawQuery) > 0 {
13561378
target += "?" + req.URL.RawQuery
13571379
}

0 commit comments

Comments
 (0)