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() }) }