package certmatch import ( "testing" "time" "git.ravensburg.dev/cabele/opnsense-portal/internal/opnsense" ) func TestPatternMatching(t *testing.T) { m, err := NewMatcher("{username}", "") if err != nil { t.Fatal(err) } if !m.Matches("mmueller", "mmueller") { t.Error("identischer CN muss matchen") } if !m.Matches("MMueller", "mmueller") { t.Error("Vergleich muss Groß-/Kleinschreibung ignorieren") } if m.Matches("jdoe", "mmueller") { t.Error("fremder CN darf nicht matchen") } if m.Matches("mmueller2", "mmueller") { t.Error("Präfix-Treffer darf nicht als Match gelten") } if m.Matches("", "mmueller") || m.Matches("mmueller", "") { t.Error("leere Werte dürfen nie matchen") } } func TestPatternWithSuffix(t *testing.T) { m, err := NewMatcher("{username}@firma.de", "") if err != nil { t.Fatal(err) } if !m.Matches("mmueller@firma.de", "mmueller") { t.Error("Template mit Suffix muss matchen") } if m.Matches("mmueller", "mmueller") { t.Error("CN ohne Suffix darf bei diesem Template nicht matchen") } } func TestRegexMatching(t *testing.T) { // Regex mit {username}-Platzhalter: wird vor dem Kompilieren ersetzt und // dabei quotiert, damit Sonderzeichen im Namen nicht zur Injection werden. m, err := NewMatcher("", `^(vpn-)?{username}(-\d+)?$`) if err != nil { t.Fatal(err) } for _, cn := range []string{"mmueller", "vpn-mmueller", "mmueller-2"} { if !m.Matches(cn, "mmueller") { t.Errorf("CN %q muss matchen", cn) } } for _, cn := range []string{"jdoe", "mmuellerX", "vpn-jdoe"} { if m.Matches(cn, "mmueller") { t.Errorf("CN %q darf nicht matchen", cn) } } } func TestRegexQuotesUsername(t *testing.T) { m, err := NewMatcher("", `^{username}$`) if err != nil { t.Fatal(err) } // Ein Benutzername mit Regex-Metazeichen darf nicht als Muster wirken. if m.Matches("mmueller", "m.*") { t.Error("Benutzername muss vor dem Einsetzen quotiert werden") } if !m.Matches("m.*", "m.*") { t.Error("literaler Vergleich muss weiterhin funktionieren") } } func TestNewMatcherValidation(t *testing.T) { if _, err := NewMatcher("", ""); err == nil { t.Error("weder Pattern noch Regex muss abgelehnt werden") } if _, err := NewMatcher("{username}", "^x$"); err == nil { t.Error("beides gleichzeitig muss abgelehnt werden") } if _, err := NewMatcher("", "([unbalanced"); err == nil { t.Error("unkompilierbare Regex muss abgelehnt werden") } if _, err := NewMatcher("kein-platzhalter", ""); err == nil { t.Error("Pattern ohne {username} muss abgelehnt werden") } } func TestFilterExcludesRevokedAndExpired(t *testing.T) { now := time.Date(2026, 8, 14, 12, 0, 0, 0, time.UTC) m, _ := NewMatcher("{username}", "") prov := opnsense.Provider{VPNID: "1", Name: "VPN Homeoffice"} accounts := []opnsense.Account{ {RefID: "ok", CommonName: "mmueller", ValidTo: now.AddDate(1, 0, 0)}, {RefID: "revoked", CommonName: "mmueller", ValidTo: now.AddDate(1, 0, 0), Revoked: true}, {RefID: "expired", CommonName: "mmueller", ValidTo: now.AddDate(0, 0, -1)}, {RefID: "fremd", CommonName: "jdoe", ValidTo: now.AddDate(1, 0, 0)}, } got := m.Filter("mmueller", prov, accounts, now) if len(got) != 1 { t.Fatalf("got %d Treffer, want 1: %+v", len(got), got) } if got[0].Account.RefID != "ok" { t.Errorf("falscher Treffer: %+v", got[0]) } if got[0].Provider.Name != "VPN Homeoffice" { t.Errorf("Provider muss durchgereicht werden: %+v", got[0].Provider) } } func TestTokenRoundTrip(t *testing.T) { e := Entry{ Provider: opnsense.Provider{VPNID: "1"}, Account: opnsense.Account{RefID: "abc123"}, } vpnID, refID, ok := ParseToken(e.Token()) if !ok || vpnID != "1" || refID != "abc123" { t.Fatalf("ParseToken(%q) = %q,%q,%v", e.Token(), vpnID, refID, ok) } for _, bad := range []string{"", "nurEins", "a:b:c", ":x", "x:"} { if _, _, ok := ParseToken(bad); ok { t.Errorf("ParseToken(%q) darf nicht gelingen", bad) } } } func TestDescribeForAudit(t *testing.T) { m, _ := NewMatcher("{username}@firma.de", "") if got := m.Describe("mmueller"); got != "mmueller@firma.de" { t.Errorf("Describe = %q", got) } rx, _ := NewMatcher("", `^{username}$`) if got := rx.Describe("mmueller"); got != `regex:^mmueller$` { t.Errorf("Describe = %q", got) } }