package auth import ( "encoding/csv" "os" "sync" ) /* SecretProvider is used by authenticators. Takes user name and realm as an argument, returns secret required for authentication (HA1 for digest authentication, properly encrypted password for basic). Returning an empty string means failing the authentication. */ type SecretProvider func(user, realm string) string /* Common functions for file auto-reloading */ type File struct { Path string Info os.FileInfo /* must be set in inherited types during initialization */ Reload func() mu sync.Mutex } func (f *File) ReloadIfNeeded() { info, err := os.Stat(f.Path) if err != nil { panic(err) } f.mu.Lock() defer f.mu.Unlock() if f.Info == nil || f.Info.ModTime() != info.ModTime() { f.Info = info f.Reload() } } /* Structure used for htdigest file authentication. Users map realms to maps of users to their HA1 digests. */ type HtdigestFile struct { File Users map[string]map[string]string mu sync.RWMutex } func reload_htdigest(hf *HtdigestFile) { r, err := os.Open(hf.Path) if err != nil { panic(err) } csv_reader := csv.NewReader(r) csv_reader.Comma = ':' csv_reader.Comment = '#' csv_reader.TrimLeadingSpace = true records, err := csv_reader.ReadAll() if err != nil { panic(err) } hf.mu.Lock() defer hf.mu.Unlock() hf.Users = make(map[string]map[string]string) for _, record := range records { _, exists := hf.Users[record[1]] if !exists { hf.Users[record[1]] = make(map[string]string) } hf.Users[record[1]][record[0]] = record[2] } } /* SecretProvider implementation based on htdigest-formated files. Will reload htdigest file on changes. Will panic on syntax errors in htdigest files. */ func HtdigestFileProvider(filename string) SecretProvider { hf := &HtdigestFile{File: File{Path: filename}} hf.Reload = func() { reload_htdigest(hf) } return func(user, realm string) string { hf.ReloadIfNeeded() hf.mu.RLock() defer hf.mu.RUnlock() _, exists := hf.Users[realm] if !exists { return "" } digest, exists := hf.Users[realm][user] if !exists { return "" } return digest } } /* Structure used for htdigest file authentication. Users map users to their salted encrypted password */ type HtpasswdFile struct { File Users map[string]string mu sync.RWMutex } func reload_htpasswd(h *HtpasswdFile) { r, err := os.Open(h.Path) if err != nil { panic(err) } csv_reader := csv.NewReader(r) csv_reader.Comma = ':' csv_reader.Comment = '#' csv_reader.TrimLeadingSpace = true records, err := csv_reader.ReadAll() if err != nil { panic(err) } h.mu.Lock() defer h.mu.Unlock() h.Users = make(map[string]string) for _, record := range records { h.Users[record[0]] = record[1] } } /* SecretProvider implementation based on htpasswd-formated files. Will reload htpasswd file on changes. Will panic on syntax errors in htpasswd files. Realm argument of the SecretProvider is ignored. */ func HtpasswdFileProvider(filename string) SecretProvider { h := &HtpasswdFile{File: File{Path: filename}} h.Reload = func() { reload_htpasswd(h) } return func(user, realm string) string { h.ReloadIfNeeded() h.mu.RLock() password, exists := h.Users[user] h.mu.RUnlock() if !exists { return "" } return password } }