| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247 |
- package ldap
- import (
- "encoding/json"
- "net/http"
- "net/http/httptest"
- "net/url"
- "os"
- "strings"
- "testing"
- "imuslab.com/arozos/mod/auth/ldap/ldapreader"
- db "imuslab.com/arozos/mod/database"
- )
- // ── helpers ──────────────────────────────────────────────────────────────────
- func newTestDB(t *testing.T) (*db.Database, func()) {
- t.Helper()
- dir, err := os.MkdirTemp("", "arozos-ldap-test-*")
- if err != nil {
- t.Fatalf("MkdirTemp: %v", err)
- }
- database, err := db.NewDatabase(dir+"/test.db", false)
- if err != nil {
- os.RemoveAll(dir)
- t.Fatalf("NewDatabase: %v", err)
- }
- return database, func() { os.RemoveAll(dir) }
- }
- // minimalLdapHandler returns a handler suitable for testing the config-only
- // endpoints (ReadConfig / WriteConfig). Fields not required by those handlers
- // are left nil.
- func minimalLdapHandler(coredb *db.Database) *ldapHandler {
- if err := coredb.NewTable("ldap"); err != nil {
- _ = err // table may already exist
- }
- return &ldapHandler{
- coredb: coredb,
- ldapreader: ldapreader.NewLDAPReader("", "", "", ""),
- }
- }
- func postForm(t *testing.T, h http.HandlerFunc, values url.Values) *httptest.ResponseRecorder {
- t.Helper()
- req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(values.Encode()))
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- w := httptest.NewRecorder()
- h(w, req)
- return w
- }
- func getReq(t *testing.T, h http.HandlerFunc) *httptest.ResponseRecorder {
- t.Helper()
- req := httptest.NewRequest(http.MethodGet, "/", nil)
- w := httptest.NewRecorder()
- h(w, req)
- return w
- }
- // ── ReadConfig ────────────────────────────────────────────────────────────────
- func TestLdapReadConfig_DefaultsToDisabled(t *testing.T) {
- coredb, cleanup := newTestDB(t)
- defer cleanup()
- h := minimalLdapHandler(coredb)
- w := getReq(t, h.ReadConfig)
- if w.Code != http.StatusOK {
- t.Fatalf("ReadConfig returned %d, want 200", w.Code)
- }
- var cfg Config
- if err := json.Unmarshal(w.Body.Bytes(), &cfg); err != nil {
- t.Fatalf("response is not valid JSON: %v\nbody: %s", err, w.Body.String())
- }
- if cfg.Enabled {
- t.Error("ReadConfig: expected Enabled=false for fresh DB, got true")
- }
- }
- func TestLdapReadConfig_ReturnsAllFields(t *testing.T) {
- coredb, cleanup := newTestDB(t)
- defer cleanup()
- h := minimalLdapHandler(coredb)
- // Pre-seed values.
- coredb.Write("ldap", "FQDN", "ldap.example.com")
- coredb.Write("ldap", "BaseDN", "cn=users,dc=example,dc=com")
- coredb.Write("ldap", "BindUsername", "admin")
- w := getReq(t, h.ReadConfig)
- var cfg Config
- if err := json.Unmarshal(w.Body.Bytes(), &cfg); err != nil {
- t.Fatalf("ReadConfig JSON parse: %v", err)
- }
- if cfg.FQDN != "ldap.example.com" {
- t.Errorf("FQDN: got %q, want %q", cfg.FQDN, "ldap.example.com")
- }
- if cfg.BaseDN != "cn=users,dc=example,dc=com" {
- t.Errorf("BaseDN: got %q, want %q", cfg.BaseDN, "cn=users,dc=example,dc=com")
- }
- if cfg.BindUsername != "admin" {
- t.Errorf("BindUsername: got %q, want %q", cfg.BindUsername, "admin")
- }
- }
- // ── WriteConfig ───────────────────────────────────────────────────────────────
- func TestLdapWriteConfig_MissingEnabledField(t *testing.T) {
- coredb, cleanup := newTestDB(t)
- defer cleanup()
- h := minimalLdapHandler(coredb)
- w := postForm(t, h.WriteConfig, url.Values{"fqdn": {"ldap.example.com"}})
- body := w.Body.String()
- if !strings.Contains(body, "error") {
- t.Errorf("WriteConfig without 'enabled': expected error, got %q", body)
- }
- }
- func TestLdapWriteConfig_DisabledAllowsEmptyFields(t *testing.T) {
- coredb, cleanup := newTestDB(t)
- defer cleanup()
- h := minimalLdapHandler(coredb)
- // When enabled=false, other required fields are optional.
- values := url.Values{"enabled": {"false"}}
- w := postForm(t, h.WriteConfig, values)
- body := w.Body.String()
- if strings.Contains(body, `"error"`) {
- t.Errorf("WriteConfig disabled: unexpected error: %q", body)
- }
- }
- func TestLdapWriteConfig_EnabledRequiresFields(t *testing.T) {
- coredb, cleanup := newTestDB(t)
- defer cleanup()
- h := minimalLdapHandler(coredb)
- // enabled=true but missing bind_username, bind_password, fqdn, base_dn.
- values := url.Values{"enabled": {"true"}}
- w := postForm(t, h.WriteConfig, values)
- body := w.Body.String()
- if !strings.Contains(body, "error") {
- t.Errorf("WriteConfig enabled without required fields: expected error, got %q", body)
- }
- }
- func TestLdapWriteConfig_RoundTrip(t *testing.T) {
- coredb, cleanup := newTestDB(t)
- defer cleanup()
- h := minimalLdapHandler(coredb)
- writeVals := url.Values{
- "enabled": {"false"},
- "bind_username": {"cn=admin,dc=example,dc=com"},
- "bind_password": {"s3cr3t"},
- "fqdn": {"ldap.example.com"},
- "base_dn": {"cn=users,dc=example,dc=com"},
- }
- wWrite := postForm(t, h.WriteConfig, writeVals)
- if strings.Contains(wWrite.Body.String(), `"error"`) {
- t.Fatalf("WriteConfig returned error: %s", wWrite.Body.String())
- }
- wRead := getReq(t, h.ReadConfig)
- var cfg Config
- if err := json.Unmarshal(wRead.Body.Bytes(), &cfg); err != nil {
- t.Fatalf("ReadConfig JSON parse: %v", err)
- }
- checks := []struct{ field, got, want string }{
- {"BindUsername", cfg.BindUsername, "cn=admin,dc=example,dc=com"},
- {"BindPassword", cfg.BindPassword, "s3cr3t"},
- {"FQDN", cfg.FQDN, "ldap.example.com"},
- {"BaseDN", cfg.BaseDN, "cn=users,dc=example,dc=com"},
- }
- for _, c := range checks {
- if c.got != c.want {
- t.Errorf("%s: got %q, want %q", c.field, c.got, c.want)
- }
- }
- }
- func TestLdapWriteConfig_UpdateOverwritesPreviousValues(t *testing.T) {
- coredb, cleanup := newTestDB(t)
- defer cleanup()
- h := minimalLdapHandler(coredb)
- // First write.
- postForm(t, h.WriteConfig, url.Values{
- "enabled": {"false"},
- "bind_username": {"admin"},
- "bind_password": {"pass1"},
- "fqdn": {"ldap.old.com"},
- "base_dn": {"dc=old,dc=com"},
- })
- // Second write with new values.
- postForm(t, h.WriteConfig, url.Values{
- "enabled": {"false"},
- "bind_username": {"newadmin"},
- "bind_password": {"pass2"},
- "fqdn": {"ldap.new.com"},
- "base_dn": {"dc=new,dc=com"},
- })
- wRead := getReq(t, h.ReadConfig)
- var cfg Config
- json.Unmarshal(wRead.Body.Bytes(), &cfg) //nolint:errcheck
- if cfg.FQDN != "ldap.new.com" {
- t.Errorf("FQDN after update: got %q, want %q", cfg.FQDN, "ldap.new.com")
- }
- if cfg.BindUsername != "newadmin" {
- t.Errorf("BindUsername after update: got %q, want %q", cfg.BindUsername, "newadmin")
- }
- }
- // ── Config struct ─────────────────────────────────────────────────────────────
- func TestLdapConfig_JSONTags(t *testing.T) {
- cfg := Config{
- Enabled: true,
- BindUsername: "user",
- BindPassword: "pass",
- FQDN: "ldap.example.com",
- BaseDN: "dc=example,dc=com",
- }
- data, err := json.Marshal(cfg)
- if err != nil {
- t.Fatalf("Marshal Config: %v", err)
- }
- body := string(data)
- for _, want := range []string{`"enabled"`, `"bind_username"`, `"bind_password"`, `"fqdn"`, `"base_dn"`} {
- if !strings.Contains(body, want) {
- t.Errorf("Config JSON missing field %s; body: %s", want, body)
- }
- }
- }
|