Skip to content

Commit

Permalink
Migrated to the official Mongo DB driver. Added auth test options for…
Browse files Browse the repository at this point in the history
… mongo and redis.
  • Loading branch information
iegomez committed Jun 4, 2019
1 parent 8687ab7 commit 0cb001e
Show file tree
Hide file tree
Showing 9 changed files with 139 additions and 80 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ dev-requirements:
go get -u github.com/smartystreets/goconvey

test:
go test ./backends -v -bench=none
go test ./backends -v -bench=none -count=1

benchmark:
go test ./backends -v -bench=. -run=^a
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -846,6 +846,11 @@ All this requirements are met with a fresh installation of Redis without any cus

After testing, db 2 will be flushed.

If you wish to test Redis auth, you may set the `requirepass` option at your `redis.conf` to match the password given in the test case:

```
requirepass go_auth_test
```

### MongoDB

Expand Down Expand Up @@ -913,7 +918,7 @@ All this requirements are met with a fresh installation of MongoDB without any c

As with `sqlite`, this backend constructs the collections and inserts relevant data, which are whiped out after testing is done, so no user actions are required.


If you wish to test Mongo's auth, you'll need to run mongo with the `--auth` flag and have a user `go_auth_test` with password `go_auth_test` with the `dbOwner` role over the `mosquitto_test` DB.

### Custom (experimental)

Expand Down
96 changes: 52 additions & 44 deletions backends/mongo.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package backends

import (
"context"
"fmt"
"strings"
"time"
Expand All @@ -11,8 +12,9 @@ import (

"github.com/iegomez/mosquitto-go-auth/common"

"gopkg.in/mgo.v2"
"gopkg.in/mgo.v2/bson"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)

type Mongo struct {
Expand All @@ -23,7 +25,7 @@ type Mongo struct {
DBName string
UsersCollection string
AclsCollection string
Conn *mgo.Session
Conn *mongo.Client
}

type MongoAcl struct {
Expand All @@ -42,7 +44,7 @@ func NewMongo(authOpts map[string]string, logLevel log.Level) (Mongo, error) {

log.SetLevel(logLevel)

var mongo = Mongo{
var m = Mongo{
Host: "localhost",
Port: "27017",
Username: "",
Expand All @@ -53,67 +55,70 @@ func NewMongo(authOpts map[string]string, logLevel log.Level) (Mongo, error) {
}

if mongoHost, ok := authOpts["mongo_host"]; ok {
mongo.Host = mongoHost
m.Host = mongoHost
}

if mongoPort, ok := authOpts["mongo_port"]; ok {
mongo.Port = mongoPort
m.Port = mongoPort
}

if mongoUsername, ok := authOpts["mongo_username"]; ok {
mongo.Username = mongoUsername
m.Username = mongoUsername
}

if mongoPassword, ok := authOpts["mongo_password"]; ok {
mongo.Password = mongoPassword
m.Password = mongoPassword
}

if mongoDBName, ok := authOpts["mongo_dbname"]; ok {
mongo.DBName = mongoDBName
m.DBName = mongoDBName
}

if usersCollection, ok := authOpts["mongo_users"]; ok {
mongo.UsersCollection = usersCollection
m.UsersCollection = usersCollection
}

if aclsCollection, ok := authOpts["mongo_acls"]; ok {
mongo.AclsCollection = aclsCollection
m.AclsCollection = aclsCollection
}

addr := fmt.Sprintf("%s:%s", mongo.Host, mongo.Port)
addr := fmt.Sprintf("mongodb://%s:%s", m.Host, m.Port)

mongoDBDialInfo := &mgo.DialInfo{
Addrs: []string{addr},
Timeout: 60 * time.Second,
Database: mongo.DBName,
to := 60 * time.Second
opts := options.ClientOptions{
ConnectTimeout: &to,
}

if mongo.Username != "" && mongo.Password != "" {
mongoDBDialInfo.Username = mongo.Username
mongoDBDialInfo.Password = mongo.Password
opts.ApplyURI(addr)

if m.Username != "" && m.Password != "" {
opts.Auth = &options.Credential{
AuthSource: m.DBName,
Username: m.Username,
Password: m.Password,
PasswordSet: true,
}
}

mongoSession, err := mgo.DialWithInfo(mongoDBDialInfo)
client, err := mongo.Connect(context.TODO(), &opts)
if err != nil {
return mongo, errors.Errorf("couldn't start mongo backend. error: %s\n", err)
return m, errors.Errorf("couldn't start mongo backend. error: %s\n", err)
}

mongoSession.SetMode(mgo.Monotonic, true)
m.Conn = client

mongo.Conn = mongoSession

return mongo, nil
return m, nil

}

//GetUser checks that the username exists and the given password hashes to the same password.
func (o Mongo) GetUser(username, password string) bool {

uc := o.Conn.DB(o.DBName).C(o.UsersCollection)
uc := o.Conn.Database(o.DBName).Collection(o.UsersCollection)

var user MongoUser

err := uc.Find(bson.M{"username": username}).One(&user)
err := uc.FindOne(context.TODO(), bson.M{"username": username}).Decode(&user)
if err != nil {
log.Debugf("Mongo get user error: %s", err)
return false
Expand All @@ -130,11 +135,11 @@ func (o Mongo) GetUser(username, password string) bool {
//GetSuperuser checks that the key username:su exists and has value "true".
func (o Mongo) GetSuperuser(username string) bool {

uc := o.Conn.DB(o.DBName).C(o.UsersCollection)
uc := o.Conn.Database(o.DBName).Collection(o.UsersCollection)

var user MongoUser

err := uc.Find(bson.M{"username": username}).One(&user)
err := uc.FindOne(context.TODO(), bson.M{"username": username}).Decode(&user)
if err != nil {
log.Debugf("Mongo get superuser error: %s", err)
return false
Expand All @@ -148,11 +153,11 @@ func (o Mongo) GetSuperuser(username string) bool {
func (o Mongo) CheckAcl(username, topic, clientid string, acc int32) bool {

//Get user and check his acls.
uc := o.Conn.DB(o.DBName).C(o.UsersCollection)
uc := o.Conn.Database(o.DBName).Collection(o.UsersCollection)

var user MongoUser

err := uc.Find(bson.M{"username": username}).One(&user)
err := uc.FindOne(context.TODO(), bson.M{"username": username}).Decode(&user)
if err != nil {
log.Debugf("Mongo get superuser error: %s", err)
return false
Expand All @@ -166,24 +171,27 @@ func (o Mongo) CheckAcl(username, topic, clientid string, acc int32) bool {

//Now check common acls.

ac := o.Conn.DB(o.DBName).C(o.AclsCollection)

var acls []MongoAcl

//aErr := ac.Find(bson.M{"$or": []bson.M{bson.M{"acc": acc}, bson.M{"acc": 3}}}).All(&acls)
aErr := ac.Find(bson.M{"acc": bson.M{"$in": []int32{acc, 3}}}).All(&acls)
//aErr := ac.
ac := o.Conn.Database(o.DBName).Collection(o.AclsCollection)
cur, aErr := ac.Find(context.TODO(), bson.M{"acc": bson.M{"$in": []int32{acc, 3}}})

if aErr != nil {
log.Debugf("Mongo check acl error: %s", err)
return false
}

for _, acl := range acls {
aclTopic := strings.Replace(acl.Topic, "%c", clientid, -1)
aclTopic = strings.Replace(aclTopic, "%u", username, -1)
if common.TopicsMatch(aclTopic, topic) {
return true
defer cur.Close(context.TODO())

for cur.Next(context.TODO()) {
var acl MongoAcl
err = cur.Decode(&acl)
if err == nil {
aclTopic := strings.Replace(acl.Topic, "%c", clientid, -1)
aclTopic = strings.Replace(aclTopic, "%u", username, -1)
if common.TopicsMatch(aclTopic, topic) {
return true
}
} else {
log.Errorf("mongo cursor decode error: %s", err)
}
}

Expand All @@ -199,6 +207,6 @@ func (o Mongo) GetName() string {
//Halt closes the mongo session.
func (o Mongo) Halt() {
if o.Conn != nil {
o.Conn.Close()
o.Conn.Disconnect(context.TODO())
}
}
20 changes: 12 additions & 8 deletions backends/mongo_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package backends

import (
"context"
"testing"

log "github.com/sirupsen/logrus"
Expand All @@ -13,16 +14,19 @@ func TestMongo(t *testing.T) {
authOpts := make(map[string]string)
authOpts["mongo_host"] = "localhost"
authOpts["mongo_port"] = "27017"
authOpts["mongo_username"] = "go_auth_test"
authOpts["mongo_password"] = "go_auth_test"
authOpts["mongo_dbname"] = "mosquitto_test"

Convey("Given valid params NewMongo should return a Mongo backend instance", t, func() {
mongo, err := NewMongo(authOpts, log.DebugLevel)
So(err, ShouldBeNil)

//Drop DB and recreate it
mongo.Conn.DB(mongo.DBName).DropDatabase()
mongoDb := mongo.Conn.DB(mongo.DBName)
usersColl := mongoDb.C(mongo.UsersCollection)
aclsColl := mongoDb.C(mongo.AclsCollection)
mongo.Conn.Database(mongo.DBName).Drop(context.TODO())
mongoDb := mongo.Conn.Database(mongo.DBName)
usersColl := mongoDb.Collection(mongo.UsersCollection)
aclsColl := mongoDb.Collection(mongo.AclsCollection)

//Insert a user to test auth
username := "test"
Expand Down Expand Up @@ -56,7 +60,7 @@ func TestMongo(t *testing.T) {
}

//mongo.Conn.Set(username, userPassHash, 0)
usersColl.Insert(&testUser)
usersColl.InsertOne(context.TODO(), &testUser)

Convey("Given a username and a correct password, it should correctly authenticate it", func() {

Expand Down Expand Up @@ -112,8 +116,8 @@ func TestMongo(t *testing.T) {
Acc: 1,
}

aclsColl.Insert(&userAcl)
aclsColl.Insert(&clientAcl)
aclsColl.InsertOne(context.TODO(), &userAcl)
aclsColl.InsertOne(context.TODO(), &clientAcl)

Convey("Given a topic that mentions username and subscribes to it, acl check should pass", func() {
tt1 := mongo.CheckAcl(username, "pattern/test", clientID, 1)
Expand Down Expand Up @@ -159,7 +163,7 @@ func TestMongo(t *testing.T) {
})

//Empty db
mongoDb.DropDatabase()
mongoDb.Drop(context.TODO())

mongo.Halt()

Expand Down
13 changes: 6 additions & 7 deletions backends/redis.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,9 @@ func NewRedis(authOpts map[string]string, logLevel log.Level) (Redis, error) {
log.SetLevel(logLevel)

var redis = Redis{
Host: "localhost",
Port: "6379",
Password: "",
DB: 1,
Host: "localhost",
Port: "6379",
DB: 1,
}

if redisHost, ok := authOpts["redis_host"]; ok {
Expand All @@ -56,13 +55,13 @@ func NewRedis(authOpts map[string]string, logLevel log.Level) (Redis, error) {
//Try to start redis.
goredisClient := goredis.NewClient(&goredis.Options{
Addr: addr,
Password: redis.Password, // no password set
DB: int(redis.DB), // use default DB
Password: redis.Password,
DB: int(redis.DB),
})

for {
if _, err := goredisClient.Ping().Result(); err != nil {
log.Printf("ping redis error, will retry in 2s: %s", err)
log.Errorf("ping redis error, will retry in 2s: %s", err)
time.Sleep(2 * time.Second)
} else {
break
Expand Down
7 changes: 4 additions & 3 deletions backends/redis_benchmark_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,10 @@ var redis Redis

func init() {
var authOpts = map[string]string{
"redis_host": "localhost",
"redis_port": "6379",
"redis_db": "2",
"redis_host": "localhost",
"redis_port": "6379",
"redis_db": "2",
"redis_password": "go_auth_test",
}
var err error
redis, err = NewRedis(authOpts, log.ErrorLevel)
Expand Down
1 change: 1 addition & 0 deletions backends/redis_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ func TestRedis(t *testing.T) {
authOpts["redis_host"] = "localhost"
authOpts["redis_port"] = "6379"
authOpts["redis_db"] = "2"
authOpts["redis_password"] = "go_auth_test"

Convey("Given valid params NewRedis should return a Redis backend instance", t, func() {
redis, err := NewRedis(authOpts, log.DebugLevel)
Expand Down
20 changes: 13 additions & 7 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,24 @@ require (
github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/go-redis/redis v6.14.1+incompatible
github.com/go-sql-driver/mysql v1.4.0
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e
github.com/go-stack/stack v1.8.0 // indirect
github.com/golang/snappy v0.0.1 // indirect
github.com/google/go-cmp v0.3.0 // indirect
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e // indirect
github.com/jmoiron/sqlx v0.0.0-20180614180643-0dae4fefe7c0
github.com/jtolds/gls v4.20.0+incompatible
github.com/konsorten/go-windows-terminal-sequences v0.0.0-20180402223658-b729f2633dfe
github.com/lib/pq v1.0.0
github.com/mattn/go-sqlite3 v1.9.0
github.com/onsi/ginkgo v1.8.0 // indirect
github.com/onsi/gomega v1.5.0 // indirect
github.com/pkg/errors v0.8.0
github.com/sirupsen/logrus v1.1.0
github.com/smartystreets/assertions v0.0.0-20190215210624-980c5ac6f3ac
github.com/smartystreets/assertions v0.0.0-20190215210624-980c5ac6f3ac // indirect
github.com/smartystreets/goconvey v0.0.0-20190306220146-200a235640ff
github.com/tidwall/pretty v0.0.0-20190325153808-1166b9ac2b65 // indirect
github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c // indirect
github.com/xdg/stringprep v1.0.0 // indirect
go.mongodb.org/mongo-driver v1.0.0
golang.org/x/crypto v0.0.0-20181001203147-e3636079e1a4
golang.org/x/sys v0.0.0-20181003145944-af653ce8b74f
google.golang.org/appengine v1.2.0
gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce
golang.org/x/sys v0.0.0-20181003145944-af653ce8b74f // indirect
google.golang.org/appengine v1.2.0 // indirect
)
Loading

0 comments on commit 0cb001e

Please sign in to comment.