Add the initial mediamark app
Single Go binary serving the API and an embedded keyboard-first UI for promoting fafflix titles into the cheeztv kids tree via hardlinks. - internal/library: hardlink sync, idempotent re-runs, drift reporting, strict single-path-element name validation as the traversal guard - internal/arr: minimal sonarr/radarr v3 client with a 60s list cache and a key-brokered poster proxy - internal/auth: server-side Authentik group enforcement on every route - internal/server: library JSON API, art proxy, health probes, SPA - ui: two-tile landing page, fuzzy-filtered title list, detail panel - Makefile, Dockerfile, .woodpecker pipelines, pre-commit config
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestPermit(t *testing.T) {
|
||||
m := New("X-Forwarded-Groups", []string{"akP-mediamark-user", "akR-admins"})
|
||||
cases := []struct {
|
||||
name string
|
||||
values []string
|
||||
want bool
|
||||
}{
|
||||
{"no header", nil, false},
|
||||
{"empty header", []string{""}, false},
|
||||
{"exact match", []string{"akP-mediamark-user"}, true},
|
||||
{"comma list containing match", []string{"akP-other,akP-mediamark-user,akP-x"}, true},
|
||||
{"space separated", []string{"akP-other akR-admins"}, true},
|
||||
{"padded", []string{" akP-mediamark-user "}, true},
|
||||
{"repeated header lines", []string{"akP-nope", "akR-admins"}, true},
|
||||
{"unrelated groups only", []string{"akP-arrstack-kids,akP-nope"}, false},
|
||||
{"prefix lookalike", []string{"akP-mediamark-users"}, false},
|
||||
{"substring lookalike", []string{"xakP-mediamark-user"}, false},
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
r := httptest.NewRequest(http.MethodGet, "/api/library/movies", nil)
|
||||
for _, v := range c.values {
|
||||
r.Header.Add("X-Forwarded-Groups", v)
|
||||
}
|
||||
if got := m.Permit(r); got != c.want {
|
||||
t.Fatalf("Permit = %v, want %v", got, c.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEmptyAllowListDeniesEverything(t *testing.T) {
|
||||
m := New("X-Forwarded-Groups", nil)
|
||||
r := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
r.Header.Set("X-Forwarded-Groups", "anything")
|
||||
if m.Permit(r) {
|
||||
t.Fatal("empty allow-list permitted a request")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWrapBlocksAndPasses(t *testing.T) {
|
||||
m := New("X-Grp", []string{"good"})
|
||||
called := false
|
||||
h := m.Wrap(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
called = true
|
||||
w.WriteHeader(http.StatusTeapot)
|
||||
}))
|
||||
|
||||
rec := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/library/movies/x/mark", nil)
|
||||
req.Header.Set("X-Grp", "bad,worse")
|
||||
h.ServeHTTP(rec, req)
|
||||
if rec.Code != http.StatusForbidden {
|
||||
t.Fatalf("status = %d, want 403", rec.Code)
|
||||
}
|
||||
if called {
|
||||
t.Fatal("handler ran despite a forbidden request")
|
||||
}
|
||||
// The rejection must not echo the submitted groups back.
|
||||
if strings.Contains(rec.Body.String(), "worse") {
|
||||
t.Fatalf("403 body echoed submitted groups: %q", rec.Body.String())
|
||||
}
|
||||
|
||||
rec = httptest.NewRecorder()
|
||||
req = httptest.NewRequest(http.MethodPost, "/api/library/movies/x/mark", nil)
|
||||
req.Header.Set("X-Grp", "good")
|
||||
h.ServeHTTP(rec, req)
|
||||
if rec.Code != http.StatusTeapot || !called {
|
||||
t.Fatalf("allowed request not passed through: status %d called %v", rec.Code, called)
|
||||
}
|
||||
}
|
||||
|
||||
// The configured header is the only one trusted; a client-forged alternative
|
||||
// must not grant access.
|
||||
func TestOnlyConfiguredHeaderIsRead(t *testing.T) {
|
||||
m := New("X-Auth-Request-Groups", []string{"good"})
|
||||
r := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
r.Header.Set("X-Forwarded-Groups", "good")
|
||||
if m.Permit(r) {
|
||||
t.Fatal("a non-configured header granted access")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user