package idp
import (
"bytes"
"context"
"io"
"net/http"
"net/url"
"strings"
"testing"
"github.com/ory/fosite"
"github.com/stretchr/testify/require"
)
func TestRedirectPrefixes(t *testing.T) {
const prefix = "https://oauth-redirect.googleusercontent.com/r/,http://127.0.0.1/"
for _, tc := range []struct {
uri string
ok bool
}{
{"https://oauth-redirect.googleusercontent.com/r/client", true},
{"http://127.0.0.1/callback", true},
{"http://127.0.0.1:1234/callback", false},
{"https://oauth-redirect.googleusercontent.com.evil/r/client", false},
{"https://oauth-redirect.googleusercontent.com/rubbish", false},
{"https://oauth-redirect.googleusercontent.com/r/../evil", false},
{"https://oauth-redirect.googleusercontent.com/r/%2e%2e/evil", false},
{"https://oauth-redirect.googleusercontent.com/r/client?x=1", false},
{"https://oauth-redirect.googleusercontent.com/r/client#x", false},
{"https://evil@oauth-redirect.googleusercontent.com/r/client", false},
{"http://localhost/callback", false},
{"http://example.com/callback", false},
} {
t.Run(tc.uri, func(t *testing.T) { require.Equal(t, tc.ok, validRedirectURIs([]string{tc.uri}, prefix)) })
}
for _, config := range []string{"garbage", "https://good/r/,", "http://example.com/", "https://good/r/?x=1"} {
require.False(t, validRedirectURIs([]string{"https://good/r/client"}, config))
}
require.False(t, validRedirectURIs(make([]string, 9), ""))
require.False(t, validRedirectURIs([]string{strings.Repeat("x", 2049)}, ""))
}
func TestDCRRejectsRestrictedRedirects(t *testing.T) {
for _, config := range []string{"", "broken", "https://good/r/"} {
t.Run(config, func(t *testing.T) {
t.Setenv("MCP_REDIRECT_PREFIXES", config)
server, _, _ := setupTestServer(t)
resp, err := http.Post(server.URL+RegistrationEndpoint, "application/json", bytes.NewBufferString(`{"redirect_uris":["https://evil/callback"],"token_endpoint_auth_method":"none"}`))
require.NoError(t, err)
defer resp.Body.Close()
require.Equal(t, http.StatusBadRequest, resp.StatusCode)
})
}
}
func TestConsentEscapesClientAndRedirect(t *testing.T) {
server, repo, _ := setupTestServer(t)
const id = "<script>client</script>"
const redirect = "https://client/callback?value=<script>"
require.NoError(t, repo.RegisterClient(context.Background(), &fosite.DefaultClient{ID: id, RedirectURIs: []string{redirect}, GrantTypes: []string{"authorization_code"}, ResponseTypes: []string{"code"}, Public: true}))
client := &http.Client{Jar: mustCookieJar(t), CheckRedirect: func(r *http.Request, via []*http.Request) error { return http.ErrUseLastResponse }}
q := url.Values{"client_id": {id}, "redirect_uri": {redirect}, "response_type": {"code"}, "state": {"test-state"}}
resp, err := client.Get(server.URL + AuthorizationEndpoint + "?" + q.Encode())
require.NoError(t, err)
resp.Body.Close()
form, err := client.Get(server.URL + resp.Header.Get("Location"))
require.NoError(t, err)
defer form.Body.Close()
require.Equal(t, 200, form.StatusCode)
body, err := io.ReadAll(form.Body)
require.NoError(t, err)
require.Contains(t, string(body), "<script>client</script>")
require.Contains(t, string(body), "https://client/callback?value=<script>")
require.NotContains(t, string(body), "<script>")
}