Added unit tests for auth.go: AuthUserByToken

Signed-off-by: Russ Magee <rmagee@gmail.com>
This commit is contained in:
Russ Magee 2020-02-17 23:14:37 -08:00
parent d18396f535
commit 733fc46d86
3 changed files with 93 additions and 15 deletions

34
auth.go
View file

@ -30,6 +30,9 @@ import (
// Verify a password against system standard shadow file // Verify a password against system standard shadow file
// Note auxilliary fields for expiry policy are *not* inspected. // Note auxilliary fields for expiry policy are *not* inspected.
func VerifyPass(reader func(string) ([]byte, error), user, password string) (bool, error) { func VerifyPass(reader func(string) ([]byte, error), user, password string) (bool, error) {
if reader == nil {
reader = ioutil.ReadFile // dependency injection hides that this is required
}
passlib.UseDefaults(passlib.Defaults20180601) passlib.UseDefaults(passlib.Defaults20180601)
pwFileData, e := reader("/etc/shadow") pwFileData, e := reader("/etc/shadow")
if e != nil { if e != nil {
@ -70,8 +73,11 @@ func VerifyPass(reader func(string) ([]byte, error), user, password string) (boo
// This checks /etc/xs.passwd for auth info, and system /etc/passwd // This checks /etc/xs.passwd for auth info, and system /etc/passwd
// to cross-check the user actually exists. // to cross-check the user actually exists.
// nolint: gocyclo // nolint: gocyclo
func AuthUserByPasswd(username string, auth string, fname string) (valid bool, allowedCmds string) { func AuthUserByPasswd(reader func(string) ([]byte, error), userlookup func(string) (*user.User, error), username string, auth string, fname string) (valid bool, allowedCmds string) {
b, e := ioutil.ReadFile(fname) // nolint: gosec if reader == nil {
reader = ioutil.ReadFile // dependency injection hides that this is required
}
b, e := reader(fname) // nolint: gosec
if e != nil { if e != nil {
valid = false valid = false
log.Printf("ERROR: Cannot read %s!\n", fname) log.Printf("ERROR: Cannot read %s!\n", fname)
@ -115,7 +121,8 @@ func AuthUserByPasswd(username string, auth string, fname string) (valid bool, a
r = nil r = nil
runtime.GC() runtime.GC()
if !userExistsOnSystem(username) { _, userErr := userlookup(username)
if userErr != nil {
valid = false valid = false
} }
return return
@ -123,24 +130,26 @@ func AuthUserByPasswd(username string, auth string, fname string) (valid bool, a
// ------------- End xs-local passwd auth routine(s) ----------- // ------------- End xs-local passwd auth routine(s) -----------
func userExistsOnSystem(who string) bool {
_, userErr := user.Lookup(who)
return userErr == nil
}
// AuthUserByToken checks user login information against an auth token. // AuthUserByToken checks user login information against an auth token.
// Auth tokens are stored in each user's $HOME/.xs_id and are requested // Auth tokens are stored in each user's $HOME/.xs_id and are requested
// via the -g option. // via the -g option.
// The function also check system /etc/passwd to cross-check the user // The function also check system /etc/passwd to cross-check the user
// actually exists. // actually exists.
func AuthUserByToken(username string, connhostname string, auth string) (valid bool) { func AuthUserByToken(reader func(string) ([]byte, error), userlookup func(string) (*user.User, error), username string, connhostname string, auth string) (valid bool) {
if reader == nil {
reader = ioutil.ReadFile // dependency injection hides that this is required
}
if userlookup == nil {
userlookup = user.Lookup // again for dependency injection as dep is now hidden
}
auth = strings.TrimSpace(auth) auth = strings.TrimSpace(auth)
u, ue := user.Lookup(username) u, ue := userlookup(username)
if ue != nil { if ue != nil {
return false return false
} }
b, e := ioutil.ReadFile(fmt.Sprintf("%s/.xs_id", u.HomeDir)) b, e := reader(fmt.Sprintf("%s/.xs_id", u.HomeDir))
if e != nil { if e != nil {
log.Printf("INFO: Cannot read %s/.xs_id\n", u.HomeDir) log.Printf("INFO: Cannot read %s/.xs_id\n", u.HomeDir)
return false return false
@ -167,7 +176,8 @@ func AuthUserByToken(username string, connhostname string, auth string) (valid b
break break
} }
} }
if !userExistsOnSystem(username) { _, userErr := userlookup(username)
if userErr != nil {
valid = false valid = false
} }
return return

View file

@ -2,6 +2,9 @@ package xs
import ( import (
"errors" "errors"
"fmt"
"os/user"
"strings"
"testing" "testing"
) )
@ -16,16 +19,40 @@ var (
joebloggs:$6$F.0IXOrb0w0VJHG1$3O4PYyng7F3hlh42mbroEdQZvslybY5etPPiLMQJ1xosjABY.Q4xqAfyIfe03Du61ZjGQIt3nL0j12P9k1fsK/:18310:0:99999:7::: joebloggs:$6$F.0IXOrb0w0VJHG1$3O4PYyng7F3hlh42mbroEdQZvslybY5etPPiLMQJ1xosjABY.Q4xqAfyIfe03Du61ZjGQIt3nL0j12P9k1fsK/:18310:0:99999:7:::
disableduser:!:18310::::::` disableduser:!:18310::::::`
dummyAuthTokenFile = "hostA:abcdefg\nhostB:wxyz\n"
testGoodUsers = []userVerifs{ testGoodUsers = []userVerifs{
{"johndoe", "testpass", true}, {"johndoe", "testpass", true},
{"joebloggs", "testpass2", true}, {"joebloggs", "testpass2", true},
{"johndoe", "badpass", false}, {"johndoe", "badpass", false},
} }
userlookup_arg_u string
readfile_arg_f string
) )
func _mock_user_Lookup(username string) (*user.User, error) {
username = userlookup_arg_u
if username == "baduser" {
return &user.User{}, errors.New("bad user")
}
urec := &user.User{Uid: "1000", Gid: "1000", Username: username, Name: "Full Name", HomeDir: "/home/user"}
fmt.Printf(" [mock user rec:%v]\n", urec)
return urec, nil
}
func _mock_ioutil_ReadFile(f string) ([]byte, error) { func _mock_ioutil_ReadFile(f string) ([]byte, error) {
f = readfile_arg_f
if f == "/etc/shadow" {
fmt.Println(" [mocking ReadFile(\"/etc/shadow\")]")
return []byte(dummyShadowA), nil return []byte(dummyShadowA), nil
} }
if strings.Contains(f, "/.xs_id") {
fmt.Println(" [mocking ReadFile(\".xs_id\")]")
return []byte(dummyAuthTokenFile), nil
}
return []byte{}, errors.New("no readfile_arg_f supplied")
}
func _mock_ioutil_ReadFileEmpty(f string) ([]byte, error) { func _mock_ioutil_ReadFileEmpty(f string) ([]byte, error) {
return []byte{}, nil return []byte{}, nil
@ -36,6 +63,7 @@ func _mock_ioutil_ReadFileHasError(f string) ([]byte, error) {
} }
func TestVerifyPass(t *testing.T) { func TestVerifyPass(t *testing.T) {
readfile_arg_f = "/etc/shadow"
for idx, rec := range testGoodUsers { for idx, rec := range testGoodUsers {
stat, e := VerifyPass(_mock_ioutil_ReadFile, rec.user, rec.passwd) stat, e := VerifyPass(_mock_ioutil_ReadFile, rec.user, rec.passwd)
if rec.good && (!stat || e != nil) { if rec.good && (!stat || e != nil) {
@ -64,3 +92,43 @@ func TestVerifyPassFailsOnDisabledEntry(t *testing.T) {
t.Fatal("failed to fail on disabled user entry") t.Fatal("failed to fail on disabled user entry")
} }
} }
////
func TestAuthUserByTokenFailsOnMissingEntryForHost(t *testing.T) {
stat := AuthUserByToken(_mock_ioutil_ReadFile, _mock_user_Lookup, "johndoe", "hostZ", "abcdefg")
if stat {
t.Fatal("failed to fail on missing/mismatched host entry")
}
}
func TestAuthUserByTokenFailsOnMissingEntryForUser(t *testing.T) {
stat := AuthUserByToken(_mock_ioutil_ReadFile, _mock_user_Lookup, "unkuser", "hostA", "abcdefg")
if stat {
t.Fatal("failed to fail on wrong user")
}
}
func TestAuthUserByTokenFailsOnUserLookupFailure(t *testing.T) {
userlookup_arg_u = "baduser"
stat := AuthUserByToken(_mock_ioutil_ReadFile, _mock_user_Lookup, "johndoe", "hostA", "abcdefg")
if stat {
t.Fatal("failed to fail with bad return from user.Lookup()")
}
}
func TestAuthUserByTokenFailsOnMismatchedTokenForUser(t *testing.T) {
stat := AuthUserByToken(_mock_ioutil_ReadFile, _mock_user_Lookup, "johndoe", "hostA", "badtoken")
if stat {
t.Fatal("failed to fail with valid user, bad token")
}
}
func TestAuthUserByTokenSucceedsWithMatchedUserAndToken(t *testing.T) {
userlookup_arg_u = "johndoe"
readfile_arg_f = "/.xs_id"
stat := AuthUserByToken(_mock_ioutil_ReadFile, _mock_user_Lookup, userlookup_arg_u, "hostA", "hostA:abcdefg")
if !stat {
t.Fatal("failed with valid user and token")
}
}

View file

@ -708,14 +708,14 @@ func main() {
var valid bool var valid bool
var allowedCmds string // Currently unused var allowedCmds string // Currently unused
if xs.AuthUserByToken(string(rec.Who()), string(rec.ConnHost()), string(rec.AuthCookie(true))) { if xs.AuthUserByToken(ioutil.ReadFile, user.Lookup, string(rec.Who()), string(rec.ConnHost()), string(rec.AuthCookie(true))) {
valid = true valid = true
} else { } else {
if useSystemPasswd { if useSystemPasswd {
//var passErr error //var passErr error
valid, _ /*passErr*/ = xs.VerifyPass(ioutil.ReadFile, string(rec.Who()), string(rec.AuthCookie(true))) valid, _ /*passErr*/ = xs.VerifyPass(ioutil.ReadFile, string(rec.Who()), string(rec.AuthCookie(true)))
} else { } else {
valid, allowedCmds = xs.AuthUserByPasswd(string(rec.Who()), string(rec.AuthCookie(true)), "/etc/xs.passwd") valid, allowedCmds = xs.AuthUserByPasswd(ioutil.ReadFile, user.Lookup, string(rec.Who()), string(rec.AuthCookie(true)), "/etc/xs.passwd")
} }
} }