New file |
| | |
| | | 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 |
| | | |
| | | // File handles automatic file reloading on changes. |
| | | type File struct { |
| | | Path string |
| | | Info os.FileInfo |
| | | /* must be set in inherited types during initialization */ |
| | | Reload func() |
| | | mu sync.Mutex |
| | | } |
| | | |
| | | // ReloadIfNeeded checks file Stat and calls Reload() if any changes |
| | | // were detected. File mutex is Locked for the duration of Reload() |
| | | // call. |
| | | // |
| | | // This function will panic() if Stat fails. |
| | | 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() |
| | | } |
| | | } |
| | | |
| | | // HtdigestFile is a File holding htdigest authentication data. |
| | | type HtdigestFile struct { |
| | | // File is used for automatic reloading of the authentication data. |
| | | File |
| | | // Users is a map of realms to users to HA1 digests. |
| | | Users map[string]map[string]string |
| | | mu sync.RWMutex |
| | | } |
| | | |
| | | func reloadHTDigest(hf *HtdigestFile) { |
| | | r, err := os.Open(hf.Path) |
| | | if err != nil { |
| | | panic(err) |
| | | } |
| | | reader := csv.NewReader(r) |
| | | reader.Comma = ':' |
| | | reader.Comment = '#' |
| | | reader.TrimLeadingSpace = true |
| | | |
| | | records, err := 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] |
| | | } |
| | | } |
| | | |
| | | // HtdigestFileProvider is a SecretProvider implementation based on |
| | | // htdigest-formated files. It will automatically reload htdigest file |
| | | // on changes. It panics on syntax errors in htdigest files. |
| | | func HtdigestFileProvider(filename string) SecretProvider { |
| | | hf := &HtdigestFile{File: File{Path: filename}} |
| | | hf.Reload = func() { reloadHTDigest(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 |
| | | } |
| | | } |
| | | |
| | | // HtpasswdFile is a File holding basic authentication data. |
| | | type HtpasswdFile struct { |
| | | // File is used for automatic reloading of the authentication data. |
| | | File |
| | | // Users is a map of users to their secrets (salted encrypted |
| | | // passwords). |
| | | Users map[string]string |
| | | mu sync.RWMutex |
| | | } |
| | | |
| | | func reloadHTPasswd(h *HtpasswdFile) { |
| | | r, err := os.Open(h.Path) |
| | | if err != nil { |
| | | panic(err) |
| | | } |
| | | reader := csv.NewReader(r) |
| | | reader.Comma = ':' |
| | | reader.Comment = '#' |
| | | reader.TrimLeadingSpace = true |
| | | |
| | | records, err := 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] |
| | | } |
| | | } |
| | | |
| | | // HtpasswdFileProvider is a SecretProvider implementation based on |
| | | // htpasswd-formated files. It will automatically reload htpasswd file |
| | | // on changes. It panics 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() { reloadHTPasswd(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 |
| | | } |
| | | } |