mosquitto-go-auth/backends/files_test.go

362 lines
10 KiB
Go

package backends
import (
"fmt"
"os"
"path/filepath"
"syscall"
"testing"
"time"
"github.com/iegomez/mosquitto-go-auth/hashing"
log "github.com/sirupsen/logrus"
. "github.com/smartystreets/goconvey/convey"
)
func TestFiles(t *testing.T) {
//Initialize Files with mock password and acl files.
authOpts := make(map[string]string)
Convey("Given empty opts NewFiles should fail", t, func() {
files, err := NewFiles(authOpts, log.DebugLevel, hashing.NewHasher(authOpts, "files"))
So(err, ShouldBeError)
files.Halt()
})
pwPath, _ := filepath.Abs("../test-files/passwords")
aclPath, _ := filepath.Abs("../test-files/acls")
authOpts["password_path"] = pwPath
authOpts["acl_path"] = aclPath
clientID := "test_client"
Convey("Given valid params NewFiles should return a new files backend instance", t, func() {
files, err := NewFiles(authOpts, log.DebugLevel, hashing.NewHasher(authOpts, "files"))
So(err, ShouldBeNil)
/*
ACL file looks like this:
topic test/general
topic deny test/general_denied
user test1
topic write test/topic/1
topic read test/topic/2
user test2
topic read test/topic/+
user test3
topic read test/#
topic deny test/denied
user test with space
topic test/space
topic read test/multiple spaces in/topic
topic read test/lots of spaces in/topic and borders
user not_present
topic read test/not_present
pattern read test/%u
pattern read test/%c
*/
// passwords are the same as users,
// except for user4 that's not present in passwords and should be skipped when reading acls
user1 := "test1"
user2 := "test2"
user3 := "test3"
user4 := "not_present"
elton := "test with space" // You know, because he's a rocket man. Ok, I'll let myself out.
generalTopic := "test/general"
generalDeniedTopic := "test/general_denied"
Convey("All users but not present ones should have a record", func() {
_, ok := files.Users[user1]
So(ok, ShouldBeTrue)
_, ok = files.Users[user2]
So(ok, ShouldBeTrue)
_, ok = files.Users[user3]
So(ok, ShouldBeTrue)
_, ok = files.Users[user4]
So(ok, ShouldBeFalse)
_, ok = files.Users[elton]
So(ok, ShouldBeTrue)
})
Convey("All users should be able to read the general topic", func() {
authenticated, err := files.CheckAcl(user1, generalTopic, clientID, 1)
So(err, ShouldBeNil)
So(authenticated, ShouldBeTrue)
authenticated, err = files.CheckAcl(user2, generalTopic, clientID, 1)
So(err, ShouldBeNil)
So(authenticated, ShouldBeTrue)
authenticated, err = files.CheckAcl(user3, generalTopic, clientID, 1)
So(err, ShouldBeNil)
So(authenticated, ShouldBeTrue)
authenticated, err = files.CheckAcl(elton, generalTopic, clientID, 1)
So(err, ShouldBeNil)
So(authenticated, ShouldBeTrue)
})
Convey("No user should be able to read the general denied topic", func() {
authenticated, err := files.CheckAcl(user1, generalDeniedTopic, clientID, 1)
So(err, ShouldBeNil)
So(authenticated, ShouldBeFalse)
authenticated, err = files.CheckAcl(user2, generalDeniedTopic, clientID, 1)
So(err, ShouldBeNil)
So(authenticated, ShouldBeFalse)
authenticated, err = files.CheckAcl(user3, generalDeniedTopic, clientID, 1)
So(err, ShouldBeNil)
So(authenticated, ShouldBeFalse)
authenticated, err = files.CheckAcl(elton, generalDeniedTopic, clientID, 1)
So(err, ShouldBeNil)
So(authenticated, ShouldBeFalse)
})
Convey("Given a username and a correct password, it should correctly authenticate it", func() {
authenticated, err := files.GetUser(user1, user1, clientID)
So(err, ShouldBeNil)
So(authenticated, ShouldBeTrue)
authenticated, err = files.GetUser(user2, user2, clientID)
So(err, ShouldBeNil)
So(authenticated, ShouldBeTrue)
authenticated, err = files.GetUser(user3, user3, clientID)
So(err, ShouldBeNil)
So(authenticated, ShouldBeTrue)
authenticated, err = files.GetUser(elton, elton, clientID)
So(err, ShouldBeNil)
So(authenticated, ShouldBeTrue)
})
Convey("Given a username and an incorrect password, it should not authenticate it", func() {
authenticated, err := files.GetUser(user1, user2, clientID)
So(err, ShouldBeNil)
So(authenticated, ShouldBeFalse)
})
Convey("Given a wrong username, it should not authenticate it and not return error", func() {
authenticated, err := files.GetUser(user4, "whatever_password", "")
So(err, ShouldBeNil)
So(authenticated, ShouldBeFalse)
})
//There are no superusers for files
Convey("For any user superuser should return false", func() {
superuser, err := files.GetSuperuser(user1)
So(err, ShouldBeNil)
So(superuser, ShouldBeFalse)
Convey("Including non-present username", func() {
superuser, err := files.GetSuperuser(user4)
So(err, ShouldBeNil)
So(superuser, ShouldBeFalse)
})
})
testTopic1 := `test/topic/1`
testTopic2 := `test/topic/2`
testTopic3 := `test/other/1`
testTopic4 := `other/1`
readWriteTopic := "readwrite/topic"
spaceTopic := "test/space"
multiSpaceTopic := "test/multiple spaces in/topic"
lotsOfSpacesTopic := "test/lots of spaces in/topic and borders"
deniedTopic := "test/denied"
Convey("Topics for non existing users should be ignored", func() {
for record := range files.AclRecords {
So(record, ShouldNotEqual, "test/not_present")
}
for _, user := range files.Users {
for record := range user.AclRecords {
So(record, ShouldNotEqual, "test/not_present")
}
}
})
Convey("User 1 should be able to publish and not subscribe to test topic 1, and only subscribe but not publish to topic 2", func() {
tt1, err1 := files.CheckAcl(user1, testTopic1, clientID, 2)
tt2, err2 := files.CheckAcl(user1, testTopic1, clientID, 1)
tt3, err3 := files.CheckAcl(user1, testTopic2, clientID, 2)
tt4, err4 := files.CheckAcl(user1, testTopic2, clientID, 1)
So(err1, ShouldBeNil)
So(err2, ShouldBeNil)
So(err3, ShouldBeNil)
So(err4, ShouldBeNil)
So(tt1, ShouldBeTrue)
So(tt2, ShouldBeFalse)
So(tt3, ShouldBeFalse)
So(tt4, ShouldBeTrue)
})
Convey("User 1 should be able to subscribe or publish to a readwrite topic rule", func() {
tt1, err1 := files.CheckAcl(user1, readWriteTopic, clientID, 2)
tt2, err2 := files.CheckAcl(user1, readWriteTopic, clientID, 1)
So(err1, ShouldBeNil)
So(err2, ShouldBeNil)
So(tt1, ShouldBeTrue)
So(tt2, ShouldBeTrue)
})
Convey("User 2 should be able to read any test/topic/X but not any/other", func() {
tt1, err1 := files.CheckAcl(user2, testTopic1, clientID, 1)
tt2, err2 := files.CheckAcl(user2, testTopic2, clientID, 1)
tt3, err3 := files.CheckAcl(user2, testTopic3, clientID, 1)
So(err1, ShouldBeNil)
So(err2, ShouldBeNil)
So(err3, ShouldBeNil)
So(tt1, ShouldBeTrue)
So(tt2, ShouldBeTrue)
So(tt3, ShouldBeFalse)
})
Convey("User 3 should be able to read any test/X but not other/... nor test/denied\n\n", func() {
fmt.Printf("\n\nUser 3 acls: %#v", files.Users[user3].AclRecords)
tt1, err1 := files.CheckAcl(user3, testTopic1, clientID, 1)
tt2, err2 := files.CheckAcl(user3, testTopic2, clientID, 1)
tt3, err3 := files.CheckAcl(user3, testTopic3, clientID, 1)
tt4, err4 := files.CheckAcl(user3, testTopic4, clientID, 1)
tt5, err5 := files.CheckAcl(user3, deniedTopic, clientID, 1)
So(err1, ShouldBeNil)
So(err2, ShouldBeNil)
So(err3, ShouldBeNil)
So(err4, ShouldBeNil)
So(err5, ShouldBeNil)
So(tt1, ShouldBeTrue)
So(tt2, ShouldBeTrue)
So(tt3, ShouldBeTrue)
So(tt4, ShouldBeFalse)
So(tt5, ShouldBeFalse)
})
Convey("User 4 should not be able to read since it's not in the passwords file", func() {
tt1, err1 := files.CheckAcl(user4, testTopic1, clientID, 1)
So(err1, ShouldBeNil)
So(tt1, ShouldBeFalse)
})
Convey("Elton Bowie should be able to read and write to `test/space`, and only read from other topics", func() {
tt1, err1 := files.CheckAcl(elton, spaceTopic, clientID, 2)
tt2, err2 := files.CheckAcl(elton, multiSpaceTopic, clientID, 1)
tt3, err3 := files.CheckAcl(elton, multiSpaceTopic, clientID, 2)
tt4, err4 := files.CheckAcl(elton, lotsOfSpacesTopic, clientID, 1)
tt5, err5 := files.CheckAcl(elton, lotsOfSpacesTopic, clientID, 2)
So(err1, ShouldBeNil)
So(err2, ShouldBeNil)
So(err3, ShouldBeNil)
So(err4, ShouldBeNil)
So(err5, ShouldBeNil)
So(tt1, ShouldBeTrue)
So(tt2, ShouldBeTrue)
So(tt3, ShouldBeFalse)
So(tt4, ShouldBeTrue)
So(tt5, ShouldBeFalse)
})
//Now check against patterns.
Convey("Given a topic that mentions username, acl check should pass", func() {
tt1, err1 := files.CheckAcl(user1, "test/test1", clientID, 1)
So(err1, ShouldBeNil)
So(tt1, ShouldBeTrue)
tt2, err2 := files.CheckAcl(elton, "test/test with space", clientID, 1)
So(err2, ShouldBeNil)
So(tt2, ShouldBeTrue)
})
Convey("Given a topic that mentions clientid, acl check should pass", func() {
tt1, err1 := files.CheckAcl(user1, "test/test_client", clientID, 1)
So(err1, ShouldBeNil)
So(tt1, ShouldBeTrue)
})
//Halt files
files.Halt()
})
Convey("On SIGHUP files should be reloaded", t, func() {
pwFile, err := os.Create("../test-files/test-passwords")
So(err, ShouldBeNil)
aclFile, err := os.Create("../test-files/test-acls")
So(err, ShouldBeNil)
defer os.Remove(pwFile.Name())
defer os.Remove(aclFile.Name())
hasher := hashing.NewHasher(authOpts, "files")
user1 := "test1"
user2 := "test2"
pw1, err := hasher.Hash(user1)
So(err, ShouldBeNil)
pw2, err := hasher.Hash(user2)
So(err, ShouldBeNil)
pwFile.WriteString(fmt.Sprintf("\n%s:%s\n", user1, pw1))
aclFile.WriteString("\nuser test1")
aclFile.WriteString("\ntopic read test/#")
pwFile.Sync()
aclFile.Sync()
authOpts["password_path"] = pwFile.Name()
authOpts["acl_path"] = aclFile.Name()
files, err := NewFiles(authOpts, log.DebugLevel, hasher)
So(err, ShouldBeNil)
user, ok := files.Users[user1]
So(ok, ShouldBeTrue)
record := user.AclRecords[0]
So(record.Acc, ShouldEqual, MOSQ_ACL_READ)
So(record.Topic, ShouldEqual, "test/#")
_, ok = files.Users[user2]
So(ok, ShouldBeFalse)
// Now add second user and reload.
pwFile.WriteString(fmt.Sprintf("\n%s:%s\n", user2, pw2))
aclFile.WriteString("\nuser test2")
aclFile.WriteString("\ntopic write test/#")
files.signals <- syscall.SIGHUP
time.Sleep(200 * time.Millisecond)
user, ok = files.Users[user2]
So(ok, ShouldBeTrue)
record = user.AclRecords[0]
So(record.Acc, ShouldEqual, MOSQ_ACL_WRITE)
So(record.Topic, ShouldEqual, "test/#")
})
}