Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NBHF4R9EAejDJUMdwr6C68
141 lines
4.2 KiB
Go
141 lines
4.2 KiB
Go
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)
|
|
}
|
|
}
|