mosquitto-go-auth/backends/jwt_test.go

1742 lines
49 KiB
Go

package backends
import (
"encoding/json"
"io/ioutil"
"net/http"
"net/http/httptest"
"path/filepath"
"strconv"
"strings"
"testing"
"time"
"github.com/golang-jwt/jwt"
. "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 username = "test"
// Hash generated by the pw utility
var userPassHash = "PBKDF2$sha512$100000$os24lcPr9cJt2QDVWssblQ==$BK1BQ2wbwU1zNxv3Ml3wLuu5//hPop3/LvaPYjjCwdBvnpwusnukJPpcXQzyyjOlZdieXTx6sXAcX4WnZRZZnw=="
var jwtSecret = "some_jwt_secret"
// Generate the token.
var now = time.Now()
var nowSecondsSinceEpoch = now.Unix()
var expSecondsSinceEpoch int64 = nowSecondsSinceEpoch + int64(time.Hour*24/time.Second)
var jwtToken = jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"iss": "jwt-test",
"aud": "jwt-test",
"nbf": nowSecondsSinceEpoch,
"exp": expSecondsSinceEpoch,
"sub": "user",
"username": username,
})
var wrongJwtToken = jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"iss": "jwt-test",
"aud": "jwt-test",
"nbf": nowSecondsSinceEpoch,
"exp": expSecondsSinceEpoch,
"sub": "user",
"username": "wrong_user",
})
var expiredToken = jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"iss": "jwt-test",
"aud": "jwt-test",
"nbf": nowSecondsSinceEpoch,
"exp": nowSecondsSinceEpoch - int64(time.Hour*24/time.Second),
"sub": "user",
"username": username,
})
var notPresentJwtToken = jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"iss": "jwt-test",
"aud": "jwt-test",
"nbf": nowSecondsSinceEpoch,
"exp": expSecondsSinceEpoch,
"sub": "user",
"username": "not_present",
})
var tkOptions = tokenOptions{
secret: jwtSecret,
userFieldKey: "username",
}
func TestJWTClaims(t *testing.T) {
Convey("When getting claims", t, func() {
Convey("Correct token should give no error", func() {
token, err := jwtToken.SignedString([]byte(jwtSecret))
So(err, ShouldBeNil)
_, err = getJWTClaims(jwtSecret, token, false)
So(err, ShouldBeNil)
})
Convey("A token signed with a different secret should give an error", func() {
token, err := jwtToken.SignedString([]byte("wrong-secret"))
So(err, ShouldBeNil)
_, err = getJWTClaims(jwtSecret, token, false)
So(err, ShouldNotBeNil)
})
Convey("Wrong user token should give no error", func() {
token, err := wrongJwtToken.SignedString([]byte(jwtSecret))
So(err, ShouldBeNil)
_, err = getJWTClaims(jwtSecret, token, false)
So(err, ShouldBeNil)
})
Convey("Expired token should give an error when getting claims", func() {
token, err := expiredToken.SignedString([]byte(jwtSecret))
So(err, ShouldBeNil)
_, err = getJWTClaims(jwtSecret, token, false)
So(err, ShouldNotBeNil)
})
Convey("When skipping expiration, expired token should not give an error", func() {
token, err := expiredToken.SignedString([]byte(jwtSecret))
So(err, ShouldBeNil)
_, err = getJWTClaims(jwtSecret, token, true)
So(err, ShouldBeNil)
})
})
}
func TestJsJWTChecker(t *testing.T) {
authOpts := make(map[string]string)
authOpts["jwt_js_user_script_path"] = "../test-files/jwt/user_script.js"
authOpts["jwt_js_superuser_script_path"] = "../test-files/jwt/superuser_script.js"
authOpts["jwt_js_acl_script_path"] = "../test-files/jwt/acl_script.js"
Convey("Creating a js checker should succeed", t, func() {
checker, err := NewJsJWTChecker(authOpts, tkOptions)
So(err, ShouldBeNil)
userResponse, err := checker.GetUser("correct")
So(err, ShouldBeNil)
So(userResponse, ShouldBeTrue)
userResponse, err = checker.GetUser("bad")
So(err, ShouldBeNil)
So(userResponse, ShouldBeFalse)
superuserResponse, err := checker.GetSuperuser("admin")
So(err, ShouldBeNil)
So(superuserResponse, ShouldBeTrue)
superuserResponse, err = checker.GetSuperuser("non-admin")
So(err, ShouldBeNil)
So(superuserResponse, ShouldBeFalse)
aclResponse, err := checker.CheckAcl("correct", "test/topic", "id", 1)
So(err, ShouldBeNil)
So(aclResponse, ShouldBeTrue)
aclResponse, err = checker.CheckAcl("incorrect", "test/topic", "id", 1)
So(err, ShouldBeNil)
So(aclResponse, ShouldBeFalse)
aclResponse, err = checker.CheckAcl("correct", "bad/topic", "id", 1)
So(err, ShouldBeNil)
So(aclResponse, ShouldBeFalse)
aclResponse, err = checker.CheckAcl("correct", "test/topic", "wrong-id", 1)
So(err, ShouldBeNil)
So(aclResponse, ShouldBeFalse)
aclResponse, err = checker.CheckAcl("correct", "test/topic", "id", 2)
So(err, ShouldBeNil)
So(aclResponse, ShouldBeFalse)
Convey("Tokens may be pre-parsed and passed to the scripts", func() {
jsTokenOptions := tokenOptions{
parseToken: true,
secret: jwtSecret,
userFieldKey: "username",
}
authOpts["jwt_js_user_script_path"] = "../test-files/jwt/parsed_user_script.js"
authOpts["jwt_js_pass_claims"] = "true"
checker, err = NewJsJWTChecker(authOpts, jsTokenOptions)
So(err, ShouldBeNil)
token, err := jwtToken.SignedString([]byte(jwtSecret))
So(err, ShouldBeNil)
userResponse, err := checker.GetUser(token)
So(err, ShouldBeNil)
So(userResponse, ShouldBeTrue)
})
})
}
func TestFilesJWTChecker(t *testing.T) {
// The bulk of files testing is done in the internal files checker.
// Neverthelss, we'll check that tokens are effectively parsed and correct usernames get the expected access.
authOpts := make(map[string]string)
logLevel := log.DebugLevel
hasher := hashing.NewHasher(authOpts, "files")
Convey("Given empty opts NewFilesJWTChecker should fail", t, func() {
_, err := NewFilesJWTChecker(authOpts, logLevel, hasher, tkOptions)
So(err, ShouldNotBeNil)
})
Convey("When files backend is set, missing acl path should make NewFilesJWTChecker fail", t, func() {
authOpts["backends"] = "files"
_, err := NewFilesJWTChecker(authOpts, logLevel, hasher, tkOptions)
So(err, ShouldNotBeNil)
})
Convey("When acl path is given, NewFilesJWTChecker should succeed", t, func() {
pwPath, err := filepath.Abs("../test-files/acls")
So(err, ShouldBeNil)
authOpts["backends"] = "files"
authOpts["jwt_acl_path"] = pwPath
filesChecker, err := NewFilesJWTChecker(authOpts, logLevel, hasher, tkOptions)
So(err, ShouldBeNil)
token, err := notPresentJwtToken.SignedString([]byte(jwtSecret))
So(err, ShouldBeNil)
Convey("Access should be granted for ACL mentioned users", func() {
tt, err := filesChecker.CheckAcl(token, "test/not_present", "id", 1)
So(err, ShouldBeNil)
So(tt, ShouldBeTrue)
})
Convey("Access should be granted for general ACL rules on non mentioned users", func() {
tt1, err1 := filesChecker.CheckAcl(token, "test/general", "id", 1)
tt2, err2 := filesChecker.CheckAcl(token, "test/general_denied", "id", 1)
So(err1, ShouldBeNil)
So(tt1, ShouldBeTrue)
So(err2, ShouldBeNil)
So(tt2, ShouldBeFalse)
})
})
}
func TestLocalPostgresJWT(t *testing.T) {
Convey("Creating a token should return a nil error", t, func() {
token, err := jwtToken.SignedString([]byte(jwtSecret))
So(err, ShouldBeNil)
// Initialize JWT in local mode.
authOpts := make(map[string]string)
authOpts["jwt_mode"] = "local"
authOpts["jwt_db"] = "postgres"
authOpts["jwt_secret"] = jwtSecret
authOpts["jwt_userfield"] = "Username"
authOpts["jwt_userquery"] = "select count(*) from test_user where username = $1 limit 1"
// Give necessary postgres options.
authOpts["jwt_pg_host"] = "localhost"
authOpts["jwt_pg_port"] = "5432"
authOpts["jwt_pg_sslmode"] = "disable"
authOpts["jwt_pg_dbname"] = "go_auth_test"
authOpts["jwt_pg_user"] = "go_auth_test"
authOpts["jwt_pg_password"] = "go_auth_test"
authOpts["jwt_pg_superquery"] = "select count(*) from test_user where username = $1 and is_admin = true"
authOpts["jwt_pg_aclquery"] = "SELECT test_acl.topic FROM test_acl, test_user WHERE test_user.username = $1 AND test_acl.test_user_id = test_user.id AND rw >= $2"
// Set regular PG options just to create a PG instance and create the records.
pgAuthOpts := make(map[string]string)
pgAuthOpts["pg_host"] = "localhost"
pgAuthOpts["pg_port"] = "5432"
pgAuthOpts["pg_sslmode"] = "disable"
pgAuthOpts["pg_dbname"] = "go_auth_test"
pgAuthOpts["pg_user"] = "go_auth_test"
pgAuthOpts["pg_password"] = "go_auth_test"
pgAuthOpts["pg_userquery"] = "mock"
pgAuthOpts["pg_superquery"] = "mock"
pgAuthOpts["pg_aclquery"] = "mock"
db, err := NewPostgres(pgAuthOpts, log.DebugLevel, hashing.NewHasher(pgAuthOpts, ""))
So(err, ShouldBeNil)
Convey("Given correct option NewJWT returns an instance of jwt backend", func() {
jwt, err := NewLocalJWTChecker(authOpts, log.DebugLevel, hashing.NewHasher(authOpts, ""), tkOptions)
So(err, ShouldBeNil)
//Empty db
db.DB.MustExec("delete from test_user where 1 = 1")
db.DB.MustExec("delete from test_acl where 1 = 1")
//Now test everything.
insertQuery := "INSERT INTO test_user(username, password_hash, is_admin) values($1, $2, $3) returning id"
userID := 0
err = db.DB.Get(&userID, insertQuery, username, userPassHash, true)
So(err, ShouldBeNil)
So(userID, ShouldBeGreaterThan, 0)
Convey("Given a correct token, it should correctly authenticate it", func() {
authenticated, err := jwt.GetUser(token)
So(err, ShouldBeNil)
So(authenticated, ShouldBeTrue)
})
Convey("Given an incorrect token, it should not authenticate it", func() {
wrongToken, err := wrongJwtToken.SignedString([]byte(jwtSecret))
So(err, ShouldBeNil)
authenticated, err := jwt.GetUser(wrongToken)
So(err, ShouldBeNil)
So(authenticated, ShouldBeFalse)
})
Convey("Given a token that is admin, super user should pass", func() {
superuser, err := jwt.GetSuperuser(token)
So(err, ShouldBeNil)
So(superuser, ShouldBeTrue)
Convey("But disabling superusers by removing superuri should now return false", func() {
authOpts["jwt_pg_superquery"] = ""
jwt, err := NewLocalJWTChecker(authOpts, log.DebugLevel, hashing.NewHasher(authOpts, ""), tkOptions)
So(err, ShouldBeNil)
superuser, err := jwt.GetSuperuser(token)
So(err, ShouldBeNil)
So(superuser, ShouldBeFalse)
})
})
//Now create some acls and test topics
strictACL := "test/topic/1"
singleLevelACL := "test/topic/+"
hierarchyACL := "test/#"
clientID := "test_client"
aclID := 0
aclQuery := "INSERT INTO test_acl(test_user_id, topic, rw) values($1, $2, $3) returning id"
err = db.DB.Get(&aclID, aclQuery, userID, strictACL, MOSQ_ACL_READ)
So(err, ShouldBeNil)
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 := jwt.CheckAcl(token, testTopic1, clientID, MOSQ_ACL_READ)
tt2, err2 := jwt.CheckAcl(token, 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 := jwt.CheckAcl(token, 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 := jwt.CheckAcl(token, singleLevelACL, clientID, MOSQ_ACL_READ)
tt2, err2 := jwt.CheckAcl(token, hierarchyACL, clientID, MOSQ_ACL_READ)
So(err1, ShouldBeNil)
So(err2, ShouldBeNil)
So(tt1, ShouldBeFalse)
So(tt2, ShouldBeFalse)
})
//Now insert single level topic to check against.
err = db.DB.Get(&aclID, 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 := jwt.CheckAcl(token, "test/topic/whatever", clientID, MOSQ_ACL_READ)
So(err1, ShouldBeNil)
So(tt1, ShouldBeTrue)
})
//Now insert hierarchy wildcard to check against.
err = db.DB.Get(&aclID, 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 := jwt.CheckAcl(token, "test/what/ever", clientID, MOSQ_ACL_READ)
So(err1, ShouldBeNil)
So(tt1, ShouldBeTrue)
})
Convey("Deleting superuser and acl queries should work fine", func() {
authOpts["jwt_pg_superquery"] = ""
authOpts["jwt_pg_aclquery"] = ""
jwt, err := NewLocalJWTChecker(authOpts, log.DebugLevel, hashing.NewHasher(authOpts, ""), tkOptions)
So(err, ShouldBeNil)
Convey("So checking against them should give false and true for any user", func() {
tt1, err1 := jwt.CheckAcl(token, singleLevelACL, clientID, MOSQ_ACL_READ)
tt2, err2 := jwt.CheckAcl(token, hierarchyACL, clientID, MOSQ_ACL_READ)
So(err1, ShouldBeNil)
So(err2, ShouldBeNil)
So(tt1, ShouldBeTrue)
So(tt2, ShouldBeTrue)
superuser, err := jwt.GetSuperuser(token)
So(err, ShouldBeNil)
So(superuser, ShouldBeFalse)
})
})
//Empty db
db.DB.MustExec("delete from test_user where 1 = 1")
db.DB.MustExec("delete from test_acl where 1 = 1")
jwt.Halt()
})
})
}
func TestLocalMysqlJWT(t *testing.T) {
Convey("Creating a token should return a nil error", t, func() {
token, err := jwtToken.SignedString([]byte(jwtSecret))
So(err, ShouldBeNil)
// Initialize JWT in local mode.
authOpts := make(map[string]string)
authOpts["jwt_mode"] = "local"
authOpts["jwt_db"] = "mysql"
authOpts["jwt_secret"] = jwtSecret
authOpts["jwt_userfield"] = "Username"
authOpts["jwt_userquery"] = "select count(*) from test_user where username = ? limit 1"
// Give necessary postgres options.
authOpts["jwt_mysql_host"] = "localhost"
authOpts["jwt_mysql_port"] = "3306"
authOpts["jwt_mysql_dbname"] = "go_auth_test"
authOpts["jwt_mysql_user"] = "go_auth_test"
authOpts["jwt_mysql_password"] = "go_auth_test"
authOpts["jwt_mysql_allow_native_passwords"] = "true"
authOpts["jwt_mysql_superquery"] = "select count(*) from test_user where username = ? and is_admin = true"
authOpts["jwt_mysql_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 >= ?"
// Set options for our MySQL instance used to create test records.
mysqlAuthOpts := make(map[string]string)
mysqlAuthOpts["mysql_host"] = "localhost"
mysqlAuthOpts["mysql_port"] = "3306"
mysqlAuthOpts["mysql_dbname"] = "go_auth_test"
mysqlAuthOpts["mysql_user"] = "go_auth_test"
mysqlAuthOpts["mysql_password"] = "go_auth_test"
mysqlAuthOpts["mysql_allow_native_passwords"] = "true"
mysqlAuthOpts["mysql_userquery"] = "mock"
mysqlAuthOpts["mysql_superquery"] = "mock"
mysqlAuthOpts["mysql_aclquery"] = "mock"
db, err := NewMysql(mysqlAuthOpts, log.DebugLevel, hashing.NewHasher(mysqlAuthOpts, ""))
So(err, ShouldBeNil)
Convey("Given correct option NewJWT returns an instance of jwt backend", func() {
jwt, err := NewLocalJWTChecker(authOpts, log.DebugLevel, hashing.NewHasher(authOpts, ""), tkOptions)
So(err, ShouldBeNil)
//Empty db
db.DB.MustExec("delete from test_user where 1 = 1")
db.DB.MustExec("delete from test_acl where 1 = 1")
//Now test everything.
insertQuery := "INSERT INTO test_user(username, password_hash, is_admin) values(?, ?, ?)"
userID := int64(0)
res, err := db.DB.Exec(insertQuery, username, userPassHash, true)
So(err, ShouldBeNil)
userID, err = res.LastInsertId()
So(err, ShouldBeNil)
So(userID, ShouldBeGreaterThan, 0)
Convey("Given a correct token, it should correctly authenticate it", func() {
authenticated, err := jwt.GetUser(token)
So(err, ShouldBeNil)
So(authenticated, ShouldBeTrue)
})
Convey("Given an incorrect token, it should not authenticate it", func() {
wrongToken, err := wrongJwtToken.SignedString([]byte(jwtSecret))
So(err, ShouldBeNil)
authenticated, err := jwt.GetUser(wrongToken)
So(err, ShouldBeNil)
So(authenticated, ShouldBeFalse)
})
Convey("Given a token that is admin, super user should pass", func() {
superuser, err := jwt.GetSuperuser(token)
So(err, ShouldBeNil)
So(superuser, ShouldBeTrue)
Convey("But disabling superusers by removing superuri should now return false", func() {
authOpts["jwt_mysql_superquery"] = ""
jwt, err := NewLocalJWTChecker(authOpts, log.DebugLevel, hashing.NewHasher(authOpts, ""), tkOptions)
So(err, ShouldBeNil)
superuser, err := jwt.GetSuperuser(token)
So(err, ShouldBeNil)
So(superuser, ShouldBeFalse)
})
})
strictACL := "test/topic/1"
singleLevelACL := "test/topic/+"
hierarchyACL := "test/#"
clientID := "test_client"
aclID := int64(0)
aclQuery := "INSERT INTO test_acl(test_user_id, topic, rw) values(?, ?, ?)"
res, err = db.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 := jwt.CheckAcl(token, testTopic1, clientID, MOSQ_ACL_READ)
tt2, err2 := jwt.CheckAcl(token, 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 := jwt.CheckAcl(token, 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 := jwt.CheckAcl(token, singleLevelACL, clientID, MOSQ_ACL_READ)
tt2, err2 := jwt.CheckAcl(token, hierarchyACL, clientID, MOSQ_ACL_READ)
So(err1, ShouldBeNil)
So(err2, ShouldBeNil)
So(tt1, ShouldBeFalse)
So(tt2, ShouldBeFalse)
})
//Now insert single level topic to check against.
_, err = db.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 := jwt.CheckAcl(token, "test/topic/whatever", clientID, MOSQ_ACL_READ)
So(err1, ShouldBeNil)
So(tt1, ShouldBeTrue)
})
//Now insert hierarchy wildcard to check against.
_, err = db.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 := jwt.CheckAcl(token, "test/what/ever", clientID, MOSQ_ACL_READ)
So(err1, ShouldBeNil)
So(tt1, ShouldBeTrue)
})
Convey("Deleting superuser and acl queries should work fine", func() {
authOpts["jwt_mysql_superquery"] = ""
authOpts["jwt_mysql_aclquery"] = ""
jwt, err := NewLocalJWTChecker(authOpts, log.DebugLevel, hashing.NewHasher(authOpts, ""), tkOptions)
So(err, ShouldBeNil)
Convey("So checking against them should give false and true for any user", func() {
tt1, err1 := jwt.CheckAcl(token, singleLevelACL, clientID, MOSQ_ACL_READ)
tt2, err2 := jwt.CheckAcl(token, hierarchyACL, clientID, MOSQ_ACL_READ)
So(err1, ShouldBeNil)
So(err2, ShouldBeNil)
So(tt1, ShouldBeTrue)
So(tt2, ShouldBeTrue)
superuser, err := jwt.GetSuperuser(token)
So(err, ShouldBeNil)
So(superuser, ShouldBeFalse)
})
})
//Empty db
db.DB.MustExec("delete from test_user where 1 = 1")
db.DB.MustExec("delete from test_acl where 1 = 1")
jwt.Halt()
})
})
}
func TestJWTAllJsonServer(t *testing.T) {
topic := "test/topic"
var acc = int64(1)
clientID := "test_client"
version := "2.0.0"
token, _ := jwtToken.SignedString([]byte(jwtSecret))
wrongToken, _ := wrongJwtToken.SignedString([]byte(jwtSecret))
mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
httpResponse := &HTTPResponse{
Ok: true,
Error: "",
}
var jsonResponse []byte
w.WriteHeader(http.StatusOK)
w.Header().Set("Content-Type", "application/json")
gToken := r.Header.Get("Authorization")
gToken = strings.TrimPrefix(gToken, "Bearer ")
if token != gToken {
httpResponse.Ok = false
httpResponse.Error = "Wrong token."
} else {
switch r.URL.Path {
case "/user", "/superuser":
httpResponse.Ok = true
httpResponse.Error = ""
case "/acl":
var data interface{}
var params map[string]interface{}
body, _ := ioutil.ReadAll(r.Body)
defer r.Body.Close()
err := json.Unmarshal(body, &data)
if err != nil {
httpResponse.Ok = false
httpResponse.Error = "Json unmarshal error"
break
}
params = data.(map[string]interface{})
paramsAcc := int64(params["acc"].(float64))
if params["topic"].(string) == topic && params["clientid"].(string) == clientID && paramsAcc <= acc {
httpResponse.Ok = true
httpResponse.Error = ""
break
}
httpResponse.Ok = false
httpResponse.Error = "Acl check failed."
}
}
jsonResponse, err := json.Marshal(httpResponse)
if err != nil {
w.Write([]byte("error"))
}
w.Write(jsonResponse)
}))
defer mockServer.Close()
authOpts := make(map[string]string)
authOpts["jwt_mode"] = "remote"
authOpts["jwt_params_mode"] = "json"
authOpts["jwt_response_mode"] = "json"
authOpts["jwt_port"] = ""
authOpts["jwt_getuser_uri"] = "/user"
authOpts["jwt_superuser_uri"] = "/superuser"
authOpts["jwt_aclcheck_uri"] = "/acl"
parseTkOptions := tkOptions
parseTkOptions.parseToken = true
Convey("Given inconsistent auth options, NewRemoteJWTChecker should fail", t, func() {
Convey("Given jwt_host is not set, jwt_host_whitelist should be set and valid", func() {
authOpts["jwt_host_whitelist"] = ""
_, err := NewRemoteJWTChecker(authOpts, parseTkOptions, version)
So(err, ShouldNotBeNil)
authOpts["jwt_host_whitelist"] = "good-host:8000, bad_host"
_, err = NewRemoteJWTChecker(authOpts, parseTkOptions, version)
So(err, ShouldNotBeNil)
})
authOpts["jwt_host_whitelist"] = "*"
Convey("Given jwt_host is not set, jwt_parse_token should be true", func() {
_, err := NewRemoteJWTChecker(authOpts, tkOptions, version)
So(err, ShouldNotBeNil)
})
})
Convey("Given consistent auth options, NewRemoteJWTChecker should be created", t, func() {
authOpts["jwt_host_whitelist"] = "good-host:8000, 10.0.0.1:10, some.good.host, 10.0.0.2"
_, err := NewRemoteJWTChecker(authOpts, parseTkOptions, version)
So(err, ShouldBeNil)
})
authOpts["jwt_host"] = strings.Replace(mockServer.URL, "http://", "", -1)
Convey("Given correct options an http backend instance should be returned", t, func() {
hb, err := NewJWT(authOpts, log.DebugLevel, hashing.NewHasher(authOpts, ""), version)
So(err, ShouldBeNil)
Convey("Given correct password/username, get user should return true", func() {
authenticated, err := hb.GetUser(token, "", "")
So(err, ShouldBeNil)
So(authenticated, ShouldBeTrue)
})
Convey("Given incorrect password/username, get user should return false", func() {
authenticated, err := hb.GetUser(wrongToken, "", "")
So(err, ShouldBeNil)
So(authenticated, ShouldBeFalse)
})
Convey("Given correct username, get superuser should return true", func() {
authenticated, err := hb.GetSuperuser(token)
So(err, ShouldBeNil)
So(authenticated, ShouldBeTrue)
Convey("But disabling superusers by removing superuri should now return false", func() {
authOpts["jwt_superuser_uri"] = ""
hb, err := NewJWT(authOpts, log.DebugLevel, hashing.NewHasher(authOpts, ""), version)
So(err, ShouldBeNil)
superuser, err := hb.GetSuperuser(username)
So(err, ShouldBeNil)
So(superuser, ShouldBeFalse)
})
})
Convey("Given incorrect username, get superuser should return false", func() {
authenticated, err := hb.GetSuperuser(wrongToken)
So(err, ShouldBeNil)
So(authenticated, ShouldBeFalse)
})
Convey("Given correct topic, username, client id and acc, acl check should return true", func() {
authenticated, err := hb.CheckAcl(token, topic, clientID, MOSQ_ACL_READ)
So(err, ShouldBeNil)
So(authenticated, ShouldBeTrue)
})
Convey("Given an acc that requires more privileges than the user has, check acl should return false", func() {
authenticated, err := hb.CheckAcl(token, topic, clientID, MOSQ_ACL_WRITE)
So(err, ShouldBeNil)
So(authenticated, ShouldBeFalse)
})
Convey("Given a topic not present in acls, check acl should return false", func() {
authenticated, err := hb.CheckAcl(token, "fake/topic", clientID, MOSQ_ACL_READ)
So(err, ShouldBeNil)
So(authenticated, ShouldBeFalse)
})
Convey("Given a clientID that doesn't match, check acl should return false", func() {
authenticated, err := hb.CheckAcl(token, topic, "fake_client_id", MOSQ_ACL_READ)
So(err, ShouldBeNil)
So(authenticated, ShouldBeFalse)
})
hb.Halt()
})
}
func TestJWTJsonStatusOnlyServer(t *testing.T) {
topic := "test/topic"
var acc = int64(1)
clientID := "test_client"
token, _ := jwtToken.SignedString([]byte(jwtSecret))
wrongToken, _ := wrongJwtToken.SignedString([]byte(jwtSecret))
version := "2.0.0"
mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var data interface{}
var params map[string]interface{}
body, _ := ioutil.ReadAll(r.Body)
defer r.Body.Close()
err := json.Unmarshal(body, &data)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
}
gToken := r.Header.Get("Authorization")
gToken = strings.TrimPrefix(gToken, "Bearer ")
if token != gToken {
w.WriteHeader(http.StatusNotFound)
return
}
switch r.URL.Path {
case "/user", "/superuser":
w.WriteHeader(http.StatusOK)
case "/acl":
params = data.(map[string]interface{})
paramsAcc := int64(params["acc"].(float64))
if params["topic"].(string) == topic && params["clientid"].(string) == clientID && paramsAcc <= acc {
w.WriteHeader(http.StatusOK)
break
}
w.WriteHeader(http.StatusNotFound)
}
}))
defer mockServer.Close()
authOpts := make(map[string]string)
authOpts["jwt_mode"] = "remote"
authOpts["jwt_params_mode"] = "json"
authOpts["jwt_response_mode"] = "status"
authOpts["jwt_host"] = strings.Replace(mockServer.URL, "http://", "", -1)
authOpts["jwt_port"] = ""
authOpts["jwt_getuser_uri"] = "/user"
authOpts["jwt_superuser_uri"] = "/superuser"
authOpts["jwt_aclcheck_uri"] = "/acl"
Convey("Given correct options an http backend instance should be returned", t, func() {
hb, err := NewJWT(authOpts, log.DebugLevel, hashing.NewHasher(authOpts, ""), version)
So(err, ShouldBeNil)
So(hb.GetName(), ShouldEqual, "JWT remote")
Convey("Given correct password/username, get user should return true", func() {
authenticated, err := hb.GetUser(token, "", "")
So(err, ShouldBeNil)
So(authenticated, ShouldBeTrue)
})
Convey("Given incorrect password/username, get user should return false", func() {
authenticated, err := hb.GetUser(wrongToken, "", "")
So(err, ShouldBeNil)
So(authenticated, ShouldBeFalse)
})
Convey("Given correct username, get superuser should return true", func() {
authenticated, err := hb.GetSuperuser(token)
So(err, ShouldBeNil)
So(authenticated, ShouldBeTrue)
Convey("But disabling superusers by removing superuri should now return false", func() {
authOpts["jwt_superuser_uri"] = ""
hb, err := NewJWT(authOpts, log.DebugLevel, hashing.NewHasher(authOpts, ""), version)
So(err, ShouldBeNil)
superuser, err := hb.GetSuperuser(username)
So(err, ShouldBeNil)
So(superuser, ShouldBeFalse)
})
})
Convey("Given incorrect username, get superuser should return false", func() {
authenticated, err := hb.GetSuperuser(wrongToken)
So(err, ShouldBeNil)
So(authenticated, ShouldBeFalse)
})
Convey("Given correct topic, username, client id and acc, acl check should return true", func() {
authenticated, err := hb.CheckAcl(token, topic, clientID, MOSQ_ACL_READ)
So(err, ShouldBeNil)
So(authenticated, ShouldBeTrue)
})
Convey("Given an acc that requires more privileges than the user has, check acl should return false", func() {
authenticated, err := hb.CheckAcl(token, topic, clientID, MOSQ_ACL_WRITE)
So(err, ShouldBeNil)
So(authenticated, ShouldBeFalse)
})
Convey("Given a topic not present in acls, check acl should return false", func() {
authenticated, err := hb.CheckAcl(token, "fake/topic", clientID, MOSQ_ACL_READ)
So(err, ShouldBeNil)
So(authenticated, ShouldBeFalse)
})
Convey("Given a clientID that doesn't match, check acl should return false", func() {
authenticated, err := hb.CheckAcl(token, topic, "fake_client_id", MOSQ_ACL_READ)
So(err, ShouldBeNil)
So(authenticated, ShouldBeFalse)
})
hb.Halt()
})
}
func TestJWTJsonTextResponseServer(t *testing.T) {
topic := "test/topic"
var acc = int64(1)
clientID := "test_client"
token, _ := jwtToken.SignedString([]byte(jwtSecret))
wrongToken, _ := wrongJwtToken.SignedString([]byte(jwtSecret))
version := "2.0.0"
mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var data interface{}
var params map[string]interface{}
body, _ := ioutil.ReadAll(r.Body)
defer r.Body.Close()
err := json.Unmarshal(body, &data)
w.WriteHeader(http.StatusOK)
if err != nil {
w.Write([]byte(err.Error()))
}
gToken := r.Header.Get("Authorization")
gToken = strings.TrimPrefix(gToken, "Bearer ")
if token != gToken {
w.Write([]byte("Wrong credentials."))
return
}
switch r.URL.Path {
case "/user", "/superuser":
w.Write([]byte("ok"))
case "/acl":
params = data.(map[string]interface{})
paramsAcc := int64(params["acc"].(float64))
if params["topic"].(string) == topic && params["clientid"].(string) == clientID && paramsAcc <= acc {
w.Write([]byte("ok"))
break
}
w.Write([]byte("Acl check failed."))
}
}))
defer mockServer.Close()
authOpts := make(map[string]string)
authOpts["jwt_mode"] = "remote"
authOpts["jwt_params_mode"] = "json"
authOpts["jwt_response_mode"] = "text"
authOpts["jwt_host"] = strings.Replace(mockServer.URL, "http://", "", -1)
authOpts["jwt_port"] = ""
authOpts["jwt_getuser_uri"] = "/user"
authOpts["jwt_superuser_uri"] = "/superuser"
authOpts["jwt_aclcheck_uri"] = "/acl"
Convey("Given correct options an http backend instance should be returned", t, func() {
hb, err := NewJWT(authOpts, log.DebugLevel, hashing.NewHasher(authOpts, ""), version)
So(err, ShouldBeNil)
So(hb.GetName(), ShouldEqual, "JWT remote")
Convey("Given correct password/username, get user should return true", func() {
authenticated, err := hb.GetUser(token, "", "")
So(err, ShouldBeNil)
So(authenticated, ShouldBeTrue)
})
Convey("Given incorrect password/username, get user should return false", func() {
authenticated, err := hb.GetUser(wrongToken, "", "")
So(err, ShouldBeNil)
So(authenticated, ShouldBeFalse)
})
Convey("Given correct username, get superuser should return true", func() {
authenticated, err := hb.GetSuperuser(token)
So(err, ShouldBeNil)
So(authenticated, ShouldBeTrue)
Convey("But disabling superusers by removing superuri should now return false", func() {
authOpts["jwt_superuser_uri"] = ""
hb, err := NewJWT(authOpts, log.DebugLevel, hashing.NewHasher(authOpts, ""), version)
So(err, ShouldBeNil)
superuser, err := hb.GetSuperuser(username)
So(err, ShouldBeNil)
So(superuser, ShouldBeFalse)
})
})
Convey("Given incorrect username, get superuser should return false", func() {
authenticated, err := hb.GetSuperuser(wrongToken)
So(err, ShouldBeNil)
So(authenticated, ShouldBeFalse)
})
Convey("Given correct topic, username, client id and acc, acl check should return true", func() {
authenticated, err := hb.CheckAcl(token, topic, clientID, MOSQ_ACL_READ)
So(err, ShouldBeNil)
So(authenticated, ShouldBeTrue)
})
Convey("Given an acc that requires more privileges than the user has, check acl should return false", func() {
authenticated, err := hb.CheckAcl(token, topic, clientID, MOSQ_ACL_WRITE)
So(err, ShouldBeNil)
So(authenticated, ShouldBeFalse)
})
Convey("Given a topic not present in acls, check acl should return false", func() {
authenticated, err := hb.CheckAcl(token, "fake/topic", clientID, MOSQ_ACL_READ)
So(err, ShouldBeNil)
So(authenticated, ShouldBeFalse)
})
Convey("Given a clientID that doesn't match, check acl should return false", func() {
authenticated, err := hb.CheckAcl(token, topic, "fake_client_id", MOSQ_ACL_READ)
So(err, ShouldBeNil)
So(authenticated, ShouldBeFalse)
})
hb.Halt()
})
}
func TestJWTFormJsonResponseServer(t *testing.T) {
topic := "test/topic"
var acc = int64(1)
clientID := "test_client"
token, _ := jwtToken.SignedString([]byte(jwtSecret))
wrongToken, _ := wrongJwtToken.SignedString([]byte(jwtSecret))
version := "2.0.0"
mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
httpResponse := &HTTPResponse{
Ok: true,
Error: "",
}
err := r.ParseForm()
if err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}
var params = r.Form
w.WriteHeader(http.StatusOK)
w.Header().Set("Content-Type", "application/json")
gToken := r.Header.Get("Authorization")
gToken = strings.TrimPrefix(gToken, "Bearer ")
if token != gToken {
httpResponse.Ok = false
httpResponse.Error = "Wrong credentials."
} else {
switch r.URL.Path {
case "/user", "/superuser":
httpResponse.Ok = true
httpResponse.Error = ""
case "/acl":
paramsAcc, _ := strconv.ParseInt(params["acc"][0], 10, 64)
if params["topic"][0] == topic && params["clientid"][0] == clientID && paramsAcc <= acc {
httpResponse.Ok = true
httpResponse.Error = ""
break
}
httpResponse.Ok = false
httpResponse.Error = "Acl check failed."
}
}
jsonResponse, err := json.Marshal(httpResponse)
if err != nil {
w.Write([]byte("error"))
}
w.Write(jsonResponse)
}))
defer mockServer.Close()
authOpts := make(map[string]string)
authOpts["jwt_mode"] = "remote"
authOpts["jwt_params_mode"] = "form"
authOpts["jwt_response_mode"] = "json"
authOpts["jwt_host"] = strings.Replace(mockServer.URL, "http://", "", -1)
authOpts["jwt_port"] = ""
authOpts["jwt_getuser_uri"] = "/user"
authOpts["jwt_superuser_uri"] = "/superuser"
authOpts["jwt_aclcheck_uri"] = "/acl"
Convey("Given correct options an http backend instance should be returned", t, func() {
hb, err := NewJWT(authOpts, log.DebugLevel, hashing.NewHasher(authOpts, ""), version)
So(err, ShouldBeNil)
So(hb.GetName(), ShouldEqual, "JWT remote")
Convey("Given correct password/username, get user should return true", func() {
authenticated, err := hb.GetUser(token, "", "")
So(err, ShouldBeNil)
So(authenticated, ShouldBeTrue)
})
Convey("Given incorrect password/username, get user should return false", func() {
authenticated, err := hb.GetUser(wrongToken, "", "")
So(err, ShouldBeNil)
So(authenticated, ShouldBeFalse)
})
Convey("Given correct username, get superuser should return true", func() {
authenticated, err := hb.GetSuperuser(token)
So(err, ShouldBeNil)
So(authenticated, ShouldBeTrue)
Convey("But disabling superusers by removing superuri should now return false", func() {
authOpts["jwt_superuser_uri"] = ""
hb, err := NewJWT(authOpts, log.DebugLevel, hashing.NewHasher(authOpts, ""), version)
So(err, ShouldBeNil)
superuser, err := hb.GetSuperuser(username)
So(err, ShouldBeNil)
So(superuser, ShouldBeFalse)
})
})
Convey("Given incorrect username, get superuser should return false", func() {
authenticated, err := hb.GetSuperuser(wrongToken)
So(err, ShouldBeNil)
So(authenticated, ShouldBeFalse)
})
Convey("Given correct topic, username, client id and acc, acl check should return true", func() {
authenticated, err := hb.CheckAcl(token, topic, clientID, MOSQ_ACL_READ)
So(err, ShouldBeNil)
So(authenticated, ShouldBeTrue)
})
Convey("Given an acc that requires more privileges than the user has, check acl should return false", func() {
authenticated, err := hb.CheckAcl(token, topic, clientID, MOSQ_ACL_WRITE)
So(err, ShouldBeNil)
So(authenticated, ShouldBeFalse)
})
Convey("Given a topic not present in acls, check acl should return false", func() {
authenticated, err := hb.CheckAcl(token, "fake/topic", clientID, MOSQ_ACL_READ)
So(err, ShouldBeNil)
So(authenticated, ShouldBeFalse)
})
Convey("Given a clientID that doesn't match, check acl should return false", func() {
authenticated, err := hb.CheckAcl(token, topic, "fake_client_id", MOSQ_ACL_READ)
So(err, ShouldBeNil)
So(authenticated, ShouldBeFalse)
})
hb.Halt()
})
}
func TestJWTFormStatusOnlyServer(t *testing.T) {
topic := "test/topic"
var acc = int64(1)
clientID := "test_client"
token, _ := jwtToken.SignedString([]byte(jwtSecret))
wrongToken, _ := wrongJwtToken.SignedString([]byte(jwtSecret))
version := "2.0.0"
rightToken := token
mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
err := r.ParseForm()
if err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}
var params = r.Form
gToken := r.Header.Get("Authorization")
gToken = strings.TrimPrefix(gToken, "Bearer ")
if rightToken != gToken {
w.WriteHeader(http.StatusNotFound)
return
}
switch r.URL.Path {
case "/user", "/superuser":
w.WriteHeader(http.StatusOK)
case "/acl":
paramsAcc, _ := strconv.ParseInt(params["acc"][0], 10, 64)
if params["topic"][0] == topic && params["clientid"][0] == clientID && paramsAcc <= acc {
w.WriteHeader(http.StatusOK)
break
}
w.WriteHeader(http.StatusNotFound)
}
}))
defer mockServer.Close()
authOpts := make(map[string]string)
authOpts["jwt_mode"] = "remote"
authOpts["jwt_params_mode"] = "form"
authOpts["jwt_response_mode"] = "status"
authOpts["jwt_host"] = strings.Replace(mockServer.URL, "http://", "", -1)
authOpts["jwt_port"] = ""
authOpts["jwt_getuser_uri"] = "/user"
authOpts["jwt_superuser_uri"] = "/superuser"
authOpts["jwt_aclcheck_uri"] = "/acl"
Convey("Given correct options an http backend instance should be returned", t, func() {
hb, err := NewJWT(authOpts, log.DebugLevel, hashing.NewHasher(authOpts, ""), version)
So(err, ShouldBeNil)
So(hb.GetName(), ShouldEqual, "JWT remote")
Convey("Given correct password/username, get user should return true", func() {
authenticated, err := hb.GetUser(token, "", "")
So(err, ShouldBeNil)
So(authenticated, ShouldBeTrue)
})
Convey("Given incorrect password/username, get user should return false", func() {
authenticated, err := hb.GetUser(wrongToken, "", "")
So(err, ShouldBeNil)
So(authenticated, ShouldBeFalse)
})
Convey("Given correct username, get superuser should return true", func() {
authenticated, err := hb.GetSuperuser(token)
So(err, ShouldBeNil)
So(authenticated, ShouldBeTrue)
Convey("But disabling superusers by removing superuri should now return false", func() {
authOpts["jwt_superuser_uri"] = ""
hb, err := NewJWT(authOpts, log.DebugLevel, hashing.NewHasher(authOpts, ""), version)
So(err, ShouldBeNil)
superuser, err := hb.GetSuperuser(username)
So(err, ShouldBeNil)
So(superuser, ShouldBeFalse)
})
})
Convey("Given incorrect username, get superuser should return false", func() {
authenticated, err := hb.GetSuperuser(wrongToken)
So(err, ShouldBeNil)
So(authenticated, ShouldBeFalse)
})
Convey("Given correct topic, username, client id and acc, acl check should return true", func() {
authenticated, err := hb.CheckAcl(token, topic, clientID, MOSQ_ACL_READ)
So(err, ShouldBeNil)
So(authenticated, ShouldBeTrue)
})
Convey("Given an acc that requires more privileges than the user has, check acl should return false", func() {
authenticated, err := hb.CheckAcl(token, topic, clientID, MOSQ_ACL_WRITE)
So(err, ShouldBeNil)
So(authenticated, ShouldBeFalse)
})
Convey("Given a topic not present in acls, check acl should return false", func() {
authenticated, err := hb.CheckAcl(token, "fake/topic", clientID, MOSQ_ACL_READ)
So(err, ShouldBeNil)
So(authenticated, ShouldBeFalse)
})
Convey("Given a clientID that doesn't match, check acl should return false", func() {
authenticated, err := hb.CheckAcl(token, topic, "fake_client_id", MOSQ_ACL_READ)
So(err, ShouldBeNil)
So(authenticated, ShouldBeFalse)
})
hb.Halt()
})
serverHostAddr := strings.Replace(mockServer.URL, "http://", "", -1)
authOpts["jwt_host"] = ""
authOpts["jwt_parse_token"] = "true"
authOpts["jwt_secret"] = jwtSecret
tokenWithIss, _ := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"iss": serverHostAddr,
"aud": "jwt-test",
"nbf": nowSecondsSinceEpoch,
"exp": expSecondsSinceEpoch,
"sub": "user",
"username": username,
}).SignedString([]byte(jwtSecret))
wrongIssToken, _ := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"iss": serverHostAddr,
"aud": "jwt-test",
"nbf": nowSecondsSinceEpoch,
"exp": expSecondsSinceEpoch,
"sub": "user",
"username": "wrong_user",
}).SignedString([]byte(jwtSecret))
rightToken = tokenWithIss
Convey("Given empty jwt_host field and correct iss claim authorization should work", t, func() {
authOpts["jwt_host_whitelist"] = serverHostAddr + ", sometherhost"
hbWhitelistedHost, err := NewJWT(authOpts, log.DebugLevel, hashing.NewHasher(authOpts, ""), version)
So(err, ShouldBeNil)
Convey("Given correct password/username and iss host is whitelisted, get user should return true", func() {
authenticated, err := hbWhitelistedHost.GetUser(tokenWithIss, "", "")
So(err, ShouldBeNil)
So(authenticated, ShouldBeTrue)
})
Convey("Given incorrect password/username, get user should return false", func() {
authenticated, err := hbWhitelistedHost.GetUser(wrongIssToken, "", "")
So(err, ShouldBeNil)
So(authenticated, ShouldBeFalse)
})
authOpts["jwt_port"] = "12345"
hbWhitelistedHostBadConfigPort, err := NewJWT(authOpts, log.DebugLevel, hashing.NewHasher(authOpts, ""), version)
So(err, ShouldBeNil)
Convey("Given jwt_port is present in config, port from iss field should be used anyway", func() {
authenticated, err := hbWhitelistedHostBadConfigPort.GetUser(tokenWithIss, "", "")
So(err, ShouldBeNil)
So(authenticated, ShouldBeTrue)
})
authOpts["jwt_host_whitelist"] = "*"
hbAnyHost, err := NewJWT(authOpts, log.DebugLevel, hashing.NewHasher(authOpts, ""), version)
So(err, ShouldBeNil)
Convey("Given correct password/username and all hosts are allowed, get user should return true", func() {
authenticated, err := hbAnyHost.GetUser(tokenWithIss, "", "")
So(err, ShouldBeNil)
So(authenticated, ShouldBeTrue)
})
authOpts["jwt_host_whitelist"] = "otherhost1, otherhost2"
hbBadHost, err := NewJWT(authOpts, log.DebugLevel, hashing.NewHasher(authOpts, ""), version)
So(err, ShouldBeNil)
Convey("Given host from iss is not whitelisted, get user should fail even if the credentials are correct", func() {
authenticated, err := hbBadHost.GetUser(tokenWithIss, "", "")
So(err, ShouldNotBeNil)
So(authenticated, ShouldBeFalse)
})
})
}
func TestJWTFormTextResponseServer(t *testing.T) {
topic := "test/topic"
var acc = int64(1)
clientID := "test_client"
token, _ := jwtToken.SignedString([]byte(jwtSecret))
wrongToken, _ := wrongJwtToken.SignedString([]byte(jwtSecret))
version := "2.0.0"
mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
err := r.ParseForm()
if err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}
var params = r.Form
gToken := r.Header.Get("Authorization")
gToken = strings.TrimPrefix(gToken, "Bearer ")
if token != gToken {
w.Write([]byte("Wrong credentials."))
return
}
switch r.URL.Path {
case "/user", "/superuser":
w.Write([]byte("ok"))
case "/acl":
paramsAcc, _ := strconv.ParseInt(params["acc"][0], 10, 64)
if params["topic"][0] == topic && params["clientid"][0] == clientID && paramsAcc <= acc {
w.Write([]byte("ok"))
break
}
w.Write([]byte("Acl check failed."))
}
}))
defer mockServer.Close()
authOpts := make(map[string]string)
authOpts["jwt_mode"] = "remote"
authOpts["jwt_params_mode"] = "form"
authOpts["jwt_response_mode"] = "text"
authOpts["jwt_host"] = strings.Replace(mockServer.URL, "http://", "", -1)
authOpts["jwt_port"] = ""
authOpts["jwt_getuser_uri"] = "/user"
authOpts["jwt_superuser_uri"] = "/superuser"
authOpts["jwt_aclcheck_uri"] = "/acl"
Convey("Given correct options an http backend instance should be returned", t, func() {
hb, err := NewJWT(authOpts, log.DebugLevel, hashing.NewHasher(authOpts, ""), version)
So(err, ShouldBeNil)
Convey("Given correct password/username, get user should return true", func() {
authenticated, err := hb.GetUser(token, "", "")
So(err, ShouldBeNil)
So(authenticated, ShouldBeTrue)
})
Convey("Given incorrect password/username, get user should return false", func() {
authenticated, err := hb.GetUser(wrongToken, "", "")
So(err, ShouldBeNil)
So(authenticated, ShouldBeFalse)
})
Convey("Given correct username, get superuser should return true", func() {
authenticated, err := hb.GetSuperuser(token)
So(err, ShouldBeNil)
So(authenticated, ShouldBeTrue)
Convey("But disabling superusers by removing superuri should now return false", func() {
authOpts["jwt_superuser_uri"] = ""
hb, err := NewJWT(authOpts, log.DebugLevel, hashing.NewHasher(authOpts, ""), version)
So(err, ShouldBeNil)
superuser, err := hb.GetSuperuser(username)
So(err, ShouldBeNil)
So(superuser, ShouldBeFalse)
})
})
Convey("Given incorrect username, get superuser should return false", func() {
authenticated, err := hb.GetSuperuser(wrongToken)
So(err, ShouldBeNil)
So(authenticated, ShouldBeFalse)
})
Convey("Given correct topic, username, client id and acc, acl check should return true", func() {
authenticated, err := hb.CheckAcl(token, topic, clientID, MOSQ_ACL_READ)
So(err, ShouldBeNil)
So(authenticated, ShouldBeTrue)
})
Convey("Given an acc that requires more privileges than the user has, check acl should return false", func() {
authenticated, err := hb.CheckAcl(token, topic, clientID, MOSQ_ACL_WRITE)
So(err, ShouldBeNil)
So(authenticated, ShouldBeFalse)
})
Convey("Given a topic not present in acls, check acl should return false", func() {
authenticated, err := hb.CheckAcl(token, "fake/topic", clientID, MOSQ_ACL_READ)
So(err, ShouldBeNil)
So(authenticated, ShouldBeFalse)
})
Convey("Given a clientID that doesn't match, check acl should return false", func() {
authenticated, err := hb.CheckAcl(token, topic, "fake_client_id", MOSQ_ACL_READ)
So(err, ShouldBeNil)
So(authenticated, ShouldBeFalse)
})
hb.Halt()
})
}
func TestJWTHttpTimeout(t *testing.T) {
topic := "test/topic"
var acc = int64(1)
clientID := "test_client"
token, _ := jwtToken.SignedString([]byte(jwtSecret))
version := "2.0.0"
mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
err := r.ParseForm()
if err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}
var params = r.Form
gToken := r.Header.Get("Authorization")
gToken = strings.TrimPrefix(gToken, "Bearer ")
if token != gToken {
w.Write([]byte("Wrong credentials."))
return
}
switch r.URL.Path {
case "/user", "/superuser":
w.Write([]byte("ok"))
case "/acl":
time.Sleep(time.Duration(1200) * time.Millisecond)
paramsAcc, _ := strconv.ParseInt(params["acc"][0], 10, 64)
if params["topic"][0] == topic && params["clientid"][0] == clientID && paramsAcc <= acc {
w.Write([]byte("ok"))
break
}
w.Write([]byte("Acl check failed."))
}
}))
defer mockServer.Close()
authOpts := make(map[string]string)
authOpts["jwt_mode"] = "remote"
authOpts["jwt_params_mode"] = "form"
authOpts["jwt_response_mode"] = "text"
authOpts["jwt_host"] = strings.Replace(mockServer.URL, "http://", "", -1)
authOpts["jwt_port"] = ""
authOpts["jwt_getuser_uri"] = "/user"
authOpts["jwt_superuser_uri"] = "/superuser"
authOpts["jwt_aclcheck_uri"] = "/acl"
authOpts["jwt_http_timeout"] = "1"
Convey("Given correct options an http backend instance should be returned", t, func() {
hb, err := NewJWT(authOpts, log.DebugLevel, hashing.NewHasher(authOpts, ""), version)
So(err, ShouldBeNil)
Convey("JWT remote test timeout parameter: YES TIMEOUT", func() {
_, err := hb.CheckAcl(token, topic, clientID, MOSQ_ACL_READ)
So(err, ShouldBeError)
So(err.Error(), ShouldContainSubstring, "acl")
})
hb.Halt()
})
authOpts["jwt_http_timeout"] = "2"
Convey("Given correct options an http backend instance should be returned", t, func() {
hb, err := NewJWT(authOpts, log.DebugLevel, hashing.NewHasher(authOpts, ""), version)
So(err, ShouldBeNil)
Convey("JWT remote test timeout parameter: NO TIMEOUT", func() {
_, err := hb.CheckAcl(token, topic, clientID, MOSQ_ACL_READ)
So(err, ShouldBeNil)
})
hb.Halt()
})
}