Files
felhom-controller/controller/internal/infra/filebrowser_sso_test.go
T

82 lines
4.5 KiB
Go

package infra
import (
"strings"
"testing"
"gitea.dooplex.hu/admin/felhom-controller/internal/settings"
)
const testSSOHeader = "X-Felhom-User-0123456789abcdef0123456789abcdef"
// With the header sign-in FileBrowser trusts THIS box's secret header name (never the old public X-Felhom-User), keeps
// password sign-in on (admin: the fallback and the operator's way), and sends a header user's logout to the dashboard.
// Without it the config is the pre-R-1100 one.
//
// COMPANION RED-PROOF (observed 2026-10-19): render renderFileBrowserAuth's header as "X-Felhom-User" → this fails with
// `the auth block lacks "header: \"X-Felhom-User-0123…\""`. Restored.
func TestFileBrowserConfig_R1100_TrustsOnlyTheBoxSecret(t *testing.T) {
sso := FileBrowserSSO{Header: testSSOHeader, LogoutURL: "https://felhom.example.hu/"}
c := RenderFileBrowserConfigKept([]settings.StoragePath{{Path: "/mnt/felhom-drives/hdd_1", Label: "HDD"}}, true, "", sso)
block := c[strings.Index(c, "\nauth:\n"):]
block = block[:strings.Index(block, "\nuserDefaults:")]
for _, want := range []string{" password:\n enabled: true", " proxy:\n enabled: true", `header: "` + testSSOHeader + `"`, `logoutRedirectUrl: "https://felhom.example.hu/"`} {
if !strings.Contains(block, want) {
t.Fatalf("the auth block lacks %q:\n%s", want, block)
}
}
if strings.Count(c, "header:") != 1 || strings.Contains(c, `header: "X-Felhom-User"`) {
t.Fatal("FileBrowser trusts a header name other than the box's secret")
}
plain := RenderFileBrowserConfigKept([]settings.StoragePath{{Path: "/mnt/felhom-drives/hdd_1", Label: "HDD"}}, true, "", FileBrowserSSO{})
if strings.Contains(plain, "auth:") || strings.Contains(plain, "X-Felhom-User") {
t.Fatal("without the header sign-in the config must carry no auth block")
}
}
// The household's header user is created by FileBrowser from userDefaults, so with the header sign-in the defaults are
// the household's file rights (not FileBrowser's admin or API); without it, the old download-only defaults.
//
// COMPANION RED-PROOF (observed 2026-10-19): return the download-only block for both cases → this fails with "the
// header user could not change files: modify false". Restored.
func TestFileBrowserConfig_R1100_HeaderUserGetsTheHouseholdsRights(t *testing.T) {
with := RenderFileBrowserConfigKept(nil, false, "", FileBrowserSSO{Header: testSSOHeader, LogoutURL: "https://felhom.example.hu/"})
perms := with[strings.Index(with, " permissions:\n"):strings.Index(with, "frontend:")]
for _, k := range []string{"modify", "share", "delete", "create", "download", "realtime"} {
if !strings.Contains(perms, " "+k+": true\n") {
t.Fatalf("the header user could not change files: %s false\n%s", k, perms)
}
}
for _, k := range []string{"admin", "api"} {
if !strings.Contains(perms, " "+k+": false\n") {
t.Fatalf("the header user must not get %s", k)
}
}
without := RenderFileBrowserConfigKept(nil, false, "", FileBrowserSSO{})
if !strings.Contains(without, " modify: false\n") || !strings.Contains(without, " download: true\n") {
t.Fatal("without the header sign-in the old download-only defaults must stay")
}
}
// The route asks the controller before FileBrowser and names the header in authResponseHeaders — which is what makes
// traefik remove a client's copy of it (measured, audits/proxyauth-spike-2026-10-18). Without the header sign-in: the
// old route, no middleware.
//
// COMPANION RED-PROOF (observed 2026-10-19): drop the router's `middlewares:` line in RenderFileBrowserRoute → this fails
// with "the files router does not ask the controller first". Restored.
func TestFileBrowserRoute_R1100_AsksTheControllerAndStripsTheHeader(t *testing.T) {
r := RenderFileBrowserRoute("example.hu", testSSOHeader)
router := r[strings.Index(r, " filebrowser:\n rule:"):strings.Index(r, " services:")]
if !strings.Contains(router, " middlewares:\n - "+FilesSSOMiddleware+"\n") {
t.Fatalf("the files router does not ask the controller first:\n%s", router)
}
for _, want := range []string{" " + FilesSSOMiddleware + ":\n forwardAuth:\n", `address: "` + FilesAuthURL + `"`, "authResponseHeaders:\n - \"" + testSSOHeader + "\""} {
if !strings.Contains(r, want) {
t.Fatalf("the route lacks %q:\n%s", want, r)
}
}
if plain := RenderFileBrowserRoute("example.hu", ""); strings.Contains(plain, "middlewares") || strings.Contains(plain, "X-Felhom-User") {
t.Fatalf("without the header sign-in the route must be the old one:\n%s", plain)
}
}