Files
fleet/server/sso/callback_url_test.go
Juan Fernandez 194f0cfb8f Fix SSO callback URLs doubling the subpath under a URL prefix
Fixes #46641

When Fleet runs under a subpath, server_url already includes that
subpath, so appending url_prefix again produced a doubled ACS callback
path (e.g. https://host/subpath/subpath/api/v1/fleet/sso/callback),
breaking SAML authentication for both login and MDM end user
authentication.

Drop url_prefix from the callback URL construction so the path is
appended directly to server_url, which is the full external base URL.
Fixes the same flaw in all five ACS-construction sites: login SSO
initiate and callback, and MDM SSO initiate plus both callback branches.
2026-06-26 10:40:55 -04:00

81 lines
2.5 KiB
Go

package sso
import (
"net/url"
"testing"
"github.com/stretchr/testify/require"
)
func TestCallbackURL(t *testing.T) {
const callbackPath = "/api/v1/fleet/sso/callback"
testCases := []struct {
name string
baseURL string
urlPrefix string
want string
}{
{
name: "no prefix configured",
baseURL: "https://fleet.example.com",
urlPrefix: "",
want: "https://fleet.example.com/api/v1/fleet/sso/callback",
},
{
name: "root prefix is treated as no prefix",
baseURL: "https://fleet.example.com",
urlPrefix: "/",
want: "https://fleet.example.com/api/v1/fleet/sso/callback",
},
{
name: "prefix set and base url already includes it",
baseURL: "https://fleet.example.com/apps/fleet",
urlPrefix: "/apps/fleet",
want: "https://fleet.example.com/apps/fleet/api/v1/fleet/sso/callback",
},
{
name: "prefix set and base url omits it",
baseURL: "https://fleet.example.com",
urlPrefix: "/apps/fleet",
want: "https://fleet.example.com/apps/fleet/api/v1/fleet/sso/callback",
},
{
name: "base url includes prefix with trailing slash",
baseURL: "https://fleet.example.com/apps/fleet/",
urlPrefix: "/apps/fleet",
want: "https://fleet.example.com/apps/fleet/api/v1/fleet/sso/callback",
},
{
name: "prefix configured with trailing slash",
baseURL: "https://fleet.example.com/apps/fleet",
urlPrefix: "/apps/fleet/",
want: "https://fleet.example.com/apps/fleet/api/v1/fleet/sso/callback",
},
{
name: "proxy mounts fleet under an additional outer segment",
baseURL: "https://fleet.example.com/gateway/apps/fleet",
urlPrefix: "/apps/fleet",
want: "https://fleet.example.com/gateway/apps/fleet/api/v1/fleet/sso/callback",
},
{
name: "outer segment that is not a full path segment still gets the prefix",
baseURL: "https://fleet.example.com/myapps/fleet",
urlPrefix: "/apps/fleet",
want: "https://fleet.example.com/myapps/fleet/apps/fleet/api/v1/fleet/sso/callback",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
base, err := url.Parse(tc.baseURL)
require.NoError(t, err)
got := CallbackURL(base, tc.urlPrefix, callbackPath)
require.Equal(t, tc.want, got.String())
// The base URL must not be mutated, so callers can still use it for
// other purposes (e.g. the expected SAML audience).
require.Equal(t, tc.baseURL, base.String())
})
}
}