362 lines
10 KiB
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/#")
|
|
})
|
|
}
|