mosquitto-go-auth/backends/sqlite_test.go

419 lines
13 KiB
Go

package backends
import (
"os"
"testing"
. "github.com/iegomez/mosquitto-go-auth/backends/constants"
"github.com/iegomez/mosquitto-go-auth/hashing"
log "github.com/sirupsen/logrus"
. "github.com/smartystreets/goconvey/convey"
)
var userSchema = `
DROP TABLE IF EXISTS test_user;
CREATE TABLE test_user (
id INTEGER PRIMARY KEY,
username varchar(100) not null,
password_hash varchar(200) not null,
is_admin integer not null
);`
var aclSchema = `
DROP TABLE IF EXISTS test_acl;
create table test_acl(
id INTEGER PRIMARY KEY,
test_user_id INTEGER not null,
topic varchar(200) not null,
rw integer not null,
foreign key(test_user_id) references test_user(id)
);
`
func TestFileSqlite(t *testing.T) {
//Initialize Sqlite without mandatory values (fail).
authOpts := make(map[string]string)
Convey("If mandatory params are not set initialization should fail", t, func() {
_, err := NewSqlite(authOpts, log.DebugLevel, hashing.NewHasher(authOpts, "sqlite"))
So(err, ShouldBeError)
})
_, err := os.Stat("../test-files/sqlite_test.db")
if os.IsNotExist(err) {
_, err = os.Create("../test-files/sqlite_test.db")
if err != nil {
log.Errorf("file error: %s", err)
os.Exit(1)
}
}
//Initialize Sqlite with some test values (omit tls).
authOpts["sqlite_source"] = "../test-files/sqlite_test.db"
authOpts["sqlite_userquery"] = "SELECT password_hash FROM test_user WHERE username = ? limit 1"
authOpts["sqlite_superquery"] = "select count(*) from test_user where username = ? and is_admin = 1"
authOpts["sqlite_aclquery"] = "SELECT test_acl.topic FROM test_acl, test_user WHERE test_user.username = ? AND test_acl.test_user_id = test_user.id AND rw >= ?"
Convey("Given valid params NewSqlite should return a Sqlite backend instance", t, func() {
sqlite, err := NewSqlite(authOpts, log.DebugLevel, hashing.NewHasher(authOpts, "sqlite"))
So(err, ShouldBeNil)
//Create schemas
sqlite.DB.MustExec(userSchema)
sqlite.DB.MustExec(aclSchema)
//Empty db
sqlite.DB.MustExec("delete from test_user where 1 = 1")
sqlite.DB.MustExec("delete from test_acl where 1 = 1")
//Insert a user to test auth
username := "test"
userPass := "testpw"
//Hash generated by the pw utility
userPassHash := "PBKDF2$sha512$100000$os24lcPr9cJt2QDVWssblQ==$BK1BQ2wbwU1zNxv3Ml3wLuu5//hPop3/LvaPYjjCwdBvnpwusnukJPpcXQzyyjOlZdieXTx6sXAcX4WnZRZZnw=="
wrongUsername := "not_present"
insertQuery := "INSERT INTO test_user(username, password_hash, is_admin) values(?, ?, ?)"
userID := int64(0)
res, err := sqlite.DB.Exec(insertQuery, username, userPassHash, MOSQ_ACL_READ)
So(err, ShouldBeNil)
userID, err = res.LastInsertId()
So(err, ShouldBeNil)
So(userID, ShouldBeGreaterThan, 0)
Convey("Given a username and a correct password, it should correctly authenticate it", func() {
authenticated, err := sqlite.GetUser(username, userPass, "")
So(err, ShouldBeNil)
So(authenticated, ShouldBeTrue)
})
Convey("Given a username and an incorrect password, it should not authenticate it", func() {
authenticated, err := sqlite.GetUser(username, "wrong_password", "")
So(err, ShouldBeNil)
So(authenticated, ShouldBeFalse)
})
Convey("Given wrongusername, it should not authenticate it and don't return error", func() {
authenticated, err := sqlite.GetUser(wrongUsername, "whatever_password", "")
So(err, ShouldBeNil)
So(authenticated, ShouldBeFalse)
})
Convey("Given a username that is admin, super user should pass", func() {
superuser, err := sqlite.GetSuperuser(username)
So(err, ShouldBeNil)
So(superuser, ShouldBeTrue)
})
Convey("Given wrongusername, super check should no pass and don't return error", func() {
authenticated, err := sqlite.GetSuperuser(wrongUsername)
So(err, ShouldBeNil)
So(authenticated, ShouldBeFalse)
})
//Now create some acls and test topics
strictAcl := "test/topic/1"
singleLevelAcl := "test/topic/+"
hierarchyAcl := "test/#"
userPattern := "test/%u"
clientPattern := "test/%c"
clientID := "test_client"
var aclID int64
aclQuery := "INSERT INTO test_acl(test_user_id, topic, rw) values(?, ?, ?)"
res, err = sqlite.DB.Exec(aclQuery, userID, strictAcl, MOSQ_ACL_READ)
So(err, ShouldBeNil)
aclID, err = res.LastInsertId()
So(err, ShouldBeNil)
So(aclID, ShouldBeGreaterThan, 0)
Convey("Given only strict acl in db, an exact match should work and and inexact one not", func() {
testTopic1 := `test/topic/1`
testTopic2 := `test/topic/2`
tt1, err1 := sqlite.CheckAcl(username, testTopic1, clientID, MOSQ_ACL_READ)
tt2, err2 := sqlite.CheckAcl(username, testTopic2, clientID, MOSQ_ACL_READ)
So(err1, ShouldBeNil)
So(err2, ShouldBeNil)
So(tt1, ShouldBeTrue)
So(tt2, ShouldBeFalse)
})
Convey("Given read only privileges, a pub check should fail", func() {
testTopic1 := "test/topic/1"
tt1, err1 := sqlite.CheckAcl(username, testTopic1, clientID, MOSQ_ACL_WRITE)
So(err1, ShouldBeNil)
So(tt1, ShouldBeFalse)
})
Convey("Given wildcard subscriptions against strict db acl, acl checks should fail", func() {
tt1, err1 := sqlite.CheckAcl(username, singleLevelAcl, clientID, MOSQ_ACL_READ)
tt2, err2 := sqlite.CheckAcl(username, hierarchyAcl, clientID, MOSQ_ACL_READ)
So(err1, ShouldBeNil)
So(err2, ShouldBeNil)
So(tt1, ShouldBeFalse)
So(tt2, ShouldBeFalse)
})
//Now check against patterns.
_, err = sqlite.DB.Exec(aclQuery, userID, userPattern, MOSQ_ACL_READ)
So(err, ShouldBeNil)
Convey("Given a topic that mentions username, acl check should pass", func() {
tt1, err1 := sqlite.CheckAcl(username, "test/test", clientID, MOSQ_ACL_READ)
So(err1, ShouldBeNil)
So(tt1, ShouldBeTrue)
})
_, err = sqlite.DB.Exec(aclQuery, userID, clientPattern, MOSQ_ACL_READ)
So(err, ShouldBeNil)
Convey("Given a topic that mentions clientid, acl check should pass", func() {
tt1, err1 := sqlite.CheckAcl(username, "test/test_client", clientID, MOSQ_ACL_READ)
So(err1, ShouldBeNil)
So(tt1, ShouldBeTrue)
})
//Now insert single level topic to check against.
_, err = sqlite.DB.Exec(aclQuery, userID, singleLevelAcl, MOSQ_ACL_READ)
So(err, ShouldBeNil)
Convey("Given a topic not strictly present that matches a db single level wildcard, acl check should pass", func() {
tt1, err1 := sqlite.CheckAcl(username, "test/topic/whatever", clientID, MOSQ_ACL_READ)
So(err1, ShouldBeNil)
So(tt1, ShouldBeTrue)
})
//Now insert hierarchy wildcard to check against.
_, err = sqlite.DB.Exec(aclQuery, userID, hierarchyAcl, MOSQ_ACL_READ)
So(err, ShouldBeNil)
Convey("Given a topic not strictly present that matches a hierarchy wildcard, acl check should pass", func() {
tt1, err1 := sqlite.CheckAcl(username, "test/what/ever", clientID, MOSQ_ACL_READ)
So(err1, ShouldBeNil)
So(tt1, ShouldBeTrue)
})
Convey("Given a bad username, acl check should not return error", func() {
tt1, err1 := sqlite.CheckAcl(wrongUsername, "test/test", clientID, MOSQ_ACL_READ)
So(err1, ShouldBeNil)
So(tt1, ShouldBeFalse)
})
//Empty db
sqlite.DB.MustExec("delete from test_user where 1 = 1")
sqlite.DB.MustExec("delete from test_acl where 1 = 1")
sqlite.DB.Close()
//Delete the db
os.Remove("../test-files/sqlite_test.db")
sqlite.Halt()
})
}
func TestMemorySqlite(t *testing.T) {
//Initialize Sqlite without mandatory values (fail).
authOpts := make(map[string]string)
Convey("If mandatory params are not set initialization should fail", t, func() {
_, err := NewSqlite(authOpts, log.DebugLevel, hashing.NewHasher(authOpts, "sqlite"))
So(err, ShouldBeError)
})
//Initialize Sqlite with some test values (omit tls).
authOpts["sqlite_source"] = "memory"
authOpts["sqlite_userquery"] = "SELECT password_hash FROM test_user WHERE username = ? limit 1"
authOpts["sqlite_superquery"] = "select count(*) from test_user where username = ? and is_admin = 1"
authOpts["sqlite_aclquery"] = "SELECT test_acl.topic FROM test_acl, test_user WHERE test_user.username = ? AND test_acl.test_user_id = test_user.id AND rw >= ?"
Convey("Given valid params NewSqlite should return a Sqlite backend instance", t, func() {
sqlite, err := NewSqlite(authOpts, log.DebugLevel, hashing.NewHasher(authOpts, "sqlite"))
So(err, ShouldBeNil)
//Create schemas
sqlite.DB.MustExec(userSchema)
sqlite.DB.MustExec(aclSchema)
//Empty db
sqlite.DB.MustExec("delete from test_user where 1 = 1")
sqlite.DB.MustExec("delete from test_acl where 1 = 1")
//Insert a user to test auth
username := "test"
userPass := "testpw"
//Hash generated by the pw utility
userPassHash := "PBKDF2$sha512$100000$os24lcPr9cJt2QDVWssblQ==$BK1BQ2wbwU1zNxv3Ml3wLuu5//hPop3/LvaPYjjCwdBvnpwusnukJPpcXQzyyjOlZdieXTx6sXAcX4WnZRZZnw=="
insertQuery := "INSERT INTO test_user(username, password_hash, is_admin) values(?, ?, ?)"
var userID int64
res, err := sqlite.DB.Exec(insertQuery, username, userPassHash, MOSQ_ACL_READ)
So(err, ShouldBeNil)
userID, err = res.LastInsertId()
So(err, ShouldBeNil)
So(userID, ShouldBeGreaterThan, 0)
Convey("Given a username and a correct password, it should correctly authenticate it", func() {
authenticated, err := sqlite.GetUser(username, userPass, "")
So(err, ShouldBeNil)
So(authenticated, ShouldBeTrue)
})
Convey("Given a username and an incorrect password, it should not authenticate it", func() {
authenticated, err := sqlite.GetUser(username, "wrong_password", "")
So(err, ShouldBeNil)
So(authenticated, ShouldBeFalse)
})
Convey("Given a username that is admin, super user should pass", func() {
superuser, err := sqlite.GetSuperuser(username)
So(err, ShouldBeNil)
So(superuser, ShouldBeTrue)
})
//Now create some acls and test topics
strictAcl := "test/topic/1"
singleLevelAcl := "test/topic/+"
hierarchyAcl := "test/#"
userPattern := "test/%u"
clientPattern := "test/%c"
clientID := "test_client"
var aclID int64
aclQuery := "INSERT INTO test_acl(test_user_id, topic, rw) values(?, ?, ?)"
res, err = sqlite.DB.Exec(aclQuery, userID, strictAcl, MOSQ_ACL_READ)
So(err, ShouldBeNil)
aclID, err = res.LastInsertId()
So(err, ShouldBeNil)
So(aclID, ShouldBeGreaterThan, 0)
Convey("Given only strict acl in db, an exact match should work and and inexact one not", func() {
testTopic1 := `test/topic/1`
testTopic2 := `test/topic/2`
tt1, err1 := sqlite.CheckAcl(username, testTopic1, clientID, MOSQ_ACL_READ)
tt2, err2 := sqlite.CheckAcl(username, testTopic2, clientID, MOSQ_ACL_READ)
So(err1, ShouldBeNil)
So(err2, ShouldBeNil)
So(tt1, ShouldBeTrue)
So(tt2, ShouldBeFalse)
})
Convey("Given read only privileges, a pub check should fail", func() {
testTopic1 := "test/topic/1"
tt1, err1 := sqlite.CheckAcl(username, testTopic1, clientID, MOSQ_ACL_WRITE)
So(err1, ShouldBeNil)
So(tt1, ShouldBeFalse)
})
Convey("Given wildcard subscriptions against strict db acl, acl checks should fail", func() {
tt1, err1 := sqlite.CheckAcl(username, singleLevelAcl, clientID, MOSQ_ACL_READ)
tt2, err2 := sqlite.CheckAcl(username, hierarchyAcl, clientID, MOSQ_ACL_READ)
So(err1, ShouldBeNil)
So(err2, ShouldBeNil)
So(tt1, ShouldBeFalse)
So(tt2, ShouldBeFalse)
})
//Now check against patterns.
_, err = sqlite.DB.Exec(aclQuery, userID, userPattern, MOSQ_ACL_READ)
So(err, ShouldBeNil)
Convey("Given a topic that mentions username, acl check should pass", func() {
tt1, err1 := sqlite.CheckAcl(username, "test/test", clientID, MOSQ_ACL_READ)
So(err1, ShouldBeNil)
So(tt1, ShouldBeTrue)
})
_, err = sqlite.DB.Exec(aclQuery, userID, clientPattern, MOSQ_ACL_READ)
So(err, ShouldBeNil)
Convey("Given a topic that mentions clientid, acl check should pass", func() {
tt1, err1 := sqlite.CheckAcl(username, "test/test_client", clientID, MOSQ_ACL_READ)
So(err1, ShouldBeNil)
So(tt1, ShouldBeTrue)
})
//Now insert single level topic to check against.
_, err = sqlite.DB.Exec(aclQuery, userID, singleLevelAcl, MOSQ_ACL_READ)
So(err, ShouldBeNil)
Convey("Given a topic not strictly present that matches a db single level wildcard, acl check should pass", func() {
tt1, err1 := sqlite.CheckAcl(username, "test/topic/whatever", clientID, MOSQ_ACL_READ)
So(err1, ShouldBeNil)
So(tt1, ShouldBeTrue)
})
//Now insert hierarchy wildcard to check against.
_, err = sqlite.DB.Exec(aclQuery, userID, hierarchyAcl, MOSQ_ACL_READ)
So(err, ShouldBeNil)
Convey("Given a topic not strictly present that matches a hierarchy wildcard, acl check should pass", func() {
tt1, err1 := sqlite.CheckAcl(username, "test/what/ever", clientID, MOSQ_ACL_READ)
So(err1, ShouldBeNil)
So(tt1, ShouldBeTrue)
})
//Empty db
sqlite.DB.MustExec("delete from test_user where 1 = 1")
sqlite.DB.MustExec("delete from test_acl where 1 = 1")
sqlite.Halt()
})
}