mirror of
https://github.com/wahyd4/loginsrv.git
synced 2026-08-09 04:46:29 +10:00
@@ -12,12 +12,17 @@ import (
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Auth is the htpassword authenticater
|
||||
type Auth struct {
|
||||
filename string
|
||||
userHash map[string]string
|
||||
// Used in func reloadIfChanged to reload htpasswd file if it changed
|
||||
modTime time.Time
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
// NewAuth creates an htpassword authenticater
|
||||
@@ -33,11 +38,20 @@ func (a *Auth) parse(filename string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fileInfo, err := os.Stat(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
a.modTime = fileInfo.ModTime()
|
||||
|
||||
cr := csv.NewReader(r)
|
||||
cr.Comma = ':'
|
||||
cr.Comment = '#'
|
||||
cr.TrimLeadingSpace = true
|
||||
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
a.userHash = map[string]string{}
|
||||
for {
|
||||
record, err := cr.Read()
|
||||
@@ -57,6 +71,9 @@ func (a *Auth) parse(filename string) error {
|
||||
|
||||
// Authenticate the user
|
||||
func (a *Auth) Authenticate(username, password string) (bool, error) {
|
||||
reloadIfChanged(a)
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
if hash, exist := a.userHash[username]; exist {
|
||||
h := []byte(hash)
|
||||
p := []byte(password)
|
||||
@@ -75,6 +92,22 @@ func (a *Auth) Authenticate(username, password string) (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// Reload htpasswd file if it changed during current run
|
||||
func reloadIfChanged(a *Auth) {
|
||||
fileInfo, err := os.Stat(a.filename)
|
||||
if err != nil {
|
||||
//On error, retain current file
|
||||
return
|
||||
}
|
||||
|
||||
currentmodTime := fileInfo.ModTime()
|
||||
|
||||
if currentmodTime != a.modTime {
|
||||
a.modTime = currentmodTime
|
||||
a.parse(a.filename)
|
||||
}
|
||||
}
|
||||
|
||||
func compareSha(hashedPassword, password []byte) bool {
|
||||
d := sha1.New()
|
||||
d.Write(password)
|
||||
|
||||
+35
-6
@@ -4,6 +4,7 @@ import (
|
||||
. "github.com/stretchr/testify/assert"
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// password for all of them is 'secret'
|
||||
@@ -18,7 +19,7 @@ bob-foo:{fooo}sdcsdcsdc/BfQ=
|
||||
|
||||
`
|
||||
|
||||
func TestClient_Hashes(t *testing.T) {
|
||||
func TestAuth_Hashes(t *testing.T) {
|
||||
auth, err := NewAuth(writeTmpfile(testfile))
|
||||
NoError(t, err)
|
||||
|
||||
@@ -36,7 +37,35 @@ func TestClient_Hashes(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestClient_UnknownUser(t *testing.T) {
|
||||
func TestAuth_ReloadFile(t *testing.T) {
|
||||
filename := writeTmpfile(`bob:$apr1$IDZSCL/o$N68zaFDDRivjour94OVeB.`)
|
||||
auth, err := NewAuth(filename)
|
||||
NoError(t, err)
|
||||
|
||||
authenticated, err := auth.Authenticate("bob", "secret")
|
||||
NoError(t, err)
|
||||
True(t, authenticated)
|
||||
|
||||
authenticated, err = auth.Authenticate("alice", "secret")
|
||||
NoError(t, err)
|
||||
False(t, authenticated)
|
||||
|
||||
// The refresh is time based, so we have to wait a second, here
|
||||
time.Sleep(time.Second)
|
||||
|
||||
err = ioutil.WriteFile(filename, []byte(`alice:$apr1$IDZSCL/o$N68zaFDDRivjour94OVeB.`), 06644)
|
||||
NoError(t, err)
|
||||
|
||||
authenticated, err = auth.Authenticate("bob", "secret")
|
||||
NoError(t, err)
|
||||
False(t, authenticated)
|
||||
|
||||
authenticated, err = auth.Authenticate("alice", "secret")
|
||||
NoError(t, err)
|
||||
True(t, authenticated)
|
||||
}
|
||||
|
||||
func TestAuth_UnknownUser(t *testing.T) {
|
||||
auth, err := NewAuth(writeTmpfile(testfile))
|
||||
NoError(t, err)
|
||||
|
||||
@@ -45,12 +74,12 @@ func TestClient_UnknownUser(t *testing.T) {
|
||||
False(t, authenticated)
|
||||
}
|
||||
|
||||
func TestClient_ErrorOnMissingFile(t *testing.T) {
|
||||
func TestAuth_ErrorOnMissingFile(t *testing.T) {
|
||||
_, err := NewAuth("/tmp/foo/bar/nothing")
|
||||
Error(t, err)
|
||||
}
|
||||
|
||||
func TestClient_ErrorOnInvalidFileContents(t *testing.T) {
|
||||
func TestAuth_ErrorOnInvalidFileContents(t *testing.T) {
|
||||
_, err := NewAuth(writeTmpfile("foo bar bazz"))
|
||||
Error(t, err)
|
||||
|
||||
@@ -58,7 +87,7 @@ func TestClient_ErrorOnInvalidFileContents(t *testing.T) {
|
||||
Error(t, err)
|
||||
}
|
||||
|
||||
func TestClient_BadMD5Format(t *testing.T) {
|
||||
func TestAuth_BadMD5Format(t *testing.T) {
|
||||
// missing $ separator in md5 hash
|
||||
a, err := NewAuth(writeTmpfile("foo:$apr1$IDZSCL/oN68zaFDDRivjour94OVeB."))
|
||||
NoError(t, err)
|
||||
@@ -68,7 +97,7 @@ func TestClient_BadMD5Format(t *testing.T) {
|
||||
False(t, authenticated)
|
||||
}
|
||||
|
||||
func TestClient_Hashes_UnknownAlgoError(t *testing.T) {
|
||||
func TestAuth_Hashes_UnknownAlgoError(t *testing.T) {
|
||||
auth, err := NewAuth(writeTmpfile(testfile))
|
||||
NoError(t, err)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user