Files
Yggdrasil/internal/acl/acl_test.go
T
Klagarge fc1b45058c feat: shiroa book, ACL parser, and OIDC-gated Go server
- docs/: shiroa book with three example categories (thermo, security,
  homelab); pages declare access via '#let access-groups = (...)'
- cmd/parser: scans docs/ and generates acl.json (public by default,
  skips book.typ/template.typ/partials)
- internal/acl: loads acl.json, normalizes URLs to page keys so a page's
  renderer data (*.multi.sir.in) inherits its protection; fail closed on
  malformed input
- internal/oidc: authorization-code flow client, verified ID tokens,
  groups claim from Authentik
- internal/middleware: in-memory sessions, login/logout/callback handlers,
  CSRF states, access gate + security headers
- cmd/server + embed.go: single binary embedding dist/ and acl.json;
  /searchindex.json blocked; graceful shutdown
- Justfile (build/run/parse/check/clean/preview-docs), Gitea Actions
  workflow (shiroa v0.4.0-rc2), rootless Dockerfile, README
- legacy docs moved to legacy_docs/; .idea untracked
2026-10-10 18:42:40 +02:00

68 lines
2.1 KiB
Go

package acl
import "testing"
func TestPageKey(t *testing.T) {
cases := []struct{ in, want string }{
{"/", "/index"},
{"/index.html", "/index"},
{"/thermo/fluids.html", "/thermo/fluids"},
// The renderer fetches content from theme variants; they must map
// to the same page or the ACL would be bypassable.
{"/thermo/fluids.light.multi.sir.in", "/thermo/fluids"},
{"/thermo/fluids.ayu.multi.sir.in", "/thermo/fluids"},
// Directory requests resolve to the directory index page.
{"/thermo/", "/thermo/index"},
{"/thermo", "/thermo"},
// Path traversal is neutralized before matching.
{"/thermo/../security/ssh.html", "/security/ssh"},
}
for _, c := range cases {
if got, err := PageKey(c.in); err != nil {
t.Errorf("PageKey(%q) error: %v", c.in, err)
} else if got != c.want {
t.Errorf("PageKey(%q) = %q, want %q", c.in, got, c.want)
}
}
if _, err := PageKey("relative.html"); err == nil {
t.Error("PageKey must reject relative paths")
}
}
func TestLoadAndCheck(t *testing.T) {
// Keys in acl.json are final page URLs; Load normalizes them.
data := []byte(`{
"/thermo/fluids.html": ["family", "admin"],
"/index.html": ["admin"],
"/public.html": ["family"]
}`)
a, err := Load(data)
if err != nil {
t.Fatalf("Load: %v", err)
}
if got, _ := a.GroupsFor("/thermo/fluids"); len(got) != 2 {
t.Errorf("expected 2 groups for /thermo/fluids, got %v", got)
}
if _, protected := a.GroupsFor("/thermo/fluids"); !protected {
t.Error("/thermo/fluids should be protected")
}
if _, protected := a.GroupsFor("/thermo/heat-pumps"); protected {
t.Error("/thermo/heat-pumps should be public")
}
if !Allows([]string{"family"}, []string{"family", "admin"}) {
t.Error("family should be allowed")
}
if Allows([]string{"guest"}, []string{"family", "admin"}) {
t.Error("guest should be denied")
}
if Allows(nil, []string{"admin"}) {
t.Error("no groups should never be allowed on a protected page")
}
}
func TestLoadMalformed(t *testing.T) {
if _, err := Load([]byte("not json")); err == nil {
t.Fatal("malformed acl.json must be an error (fail closed)")
}
}