@@ -39,8 +39,8 @@ func TestPerformDCR_PublicClient(t *testing.T) {
3939 Scopes : []string {"read" , "write" },
4040 }
4141
42- // Perform DCR
43- creds , err := PerformDCR (context .Background (), discovery , "test-server" )
42+ // Perform DCR (empty redirectURI uses default)
43+ creds , err := PerformDCR (context .Background (), discovery , "test-server" , "" )
4444 // Verify no error
4545 if err != nil {
4646 t .Fatalf ("DCR failed: %v" , err )
@@ -82,8 +82,8 @@ func TestPerformDCR_NoRegistrationEndpoint(t *testing.T) {
8282 RegistrationEndpoint : "" , // Empty - DCR not supported
8383 }
8484
85- // Attempt DCR
86- creds , err := PerformDCR (context .Background (), discovery , "test-server" )
85+ // Attempt DCR (empty redirectURI uses default)
86+ creds , err := PerformDCR (context .Background (), discovery , "test-server" , "" )
8787
8888 // Verify error occurred
8989 if err == nil {
@@ -93,3 +93,86 @@ func TestPerformDCR_NoRegistrationEndpoint(t *testing.T) {
9393 t .Error ("Expected nil credentials on error" )
9494 }
9595}
96+
97+ // TestIsValidRedirectURI verifies redirect URI validation logic
98+ func TestIsValidRedirectURI (t * testing.T ) {
99+ tests := []struct {
100+ name string
101+ redirectURI string
102+ expectError bool
103+ description string
104+ }{
105+ {
106+ name : "empty string" ,
107+ redirectURI : "" ,
108+ expectError : false ,
109+ description : "Empty string should be allowed (uses default)" ,
110+ },
111+ {
112+ name : "localhost http" ,
113+ redirectURI : "http://localhost:5000/callback" ,
114+ expectError : false ,
115+ description : "Localhost with HTTP should be allowed" ,
116+ },
117+ {
118+ name : "localhost https" ,
119+ redirectURI : "https://localhost:5000/callback" ,
120+ expectError : false ,
121+ description : "Localhost with HTTPS should be allowed" ,
122+ },
123+ {
124+ name : "127.0.0.1" ,
125+ redirectURI : "http://127.0.0.1:8080/callback" ,
126+ expectError : false ,
127+ description : "127.0.0.1 should be allowed" ,
128+ },
129+ {
130+ name : "IPv6 localhost" ,
131+ redirectURI : "http://[::1]:8080/callback" ,
132+ expectError : false ,
133+ description : "IPv6 localhost should be allowed" ,
134+ },
135+ {
136+ name : "mcp.docker.com production" ,
137+ redirectURI : "https://mcp.docker.com/oauth/callback" ,
138+ expectError : false ,
139+ description : "Production mcp.docker.com should be allowed" ,
140+ },
141+ {
142+ name : "evil domain" ,
143+ redirectURI : "https://evil.com/callback" ,
144+ expectError : true ,
145+ description : "Arbitrary domains should be blocked" ,
146+ },
147+ {
148+ name : "attacker ngrok" ,
149+ redirectURI : "https://attacker.ngrok.io/callback" ,
150+ expectError : true ,
151+ description : "Attacker-controlled domains should be blocked" ,
152+ },
153+ {
154+ name : "subdomain of docker.com" ,
155+ redirectURI : "https://evil.docker.com/callback" ,
156+ expectError : true ,
157+ description : "Only mcp.docker.com should be allowed, not subdomains" ,
158+ },
159+ {
160+ name : "invalid URL" ,
161+ redirectURI : "not-a-valid-url" ,
162+ expectError : true ,
163+ description : "Invalid URL format should be rejected" ,
164+ },
165+ }
166+
167+ for _ , tt := range tests {
168+ t .Run (tt .name , func (t * testing.T ) {
169+ err := isValidRedirectURI (tt .redirectURI )
170+ if tt .expectError && err == nil {
171+ t .Errorf ("Expected error for %q (%s)" , tt .redirectURI , tt .description )
172+ }
173+ if ! tt .expectError && err != nil {
174+ t .Errorf ("Unexpected error for %q: %v (%s)" , tt .redirectURI , err , tt .description )
175+ }
176+ })
177+ }
178+ }
0 commit comments