mosquitto-go-auth/backends/mongo.go

213 lines
4.5 KiB
Go

package backends
import (
"context"
"fmt"
"strings"
"time"
log "github.com/sirupsen/logrus"
"github.com/pkg/errors"
"github.com/iegomez/mosquitto-go-auth/common"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
type Mongo struct {
Host string
Port string
Username string
Password string
DBName string
UsersCollection string
AclsCollection string
Conn *mongo.Client
}
type MongoAcl struct {
Topic string `bson:"topic"`
Acc int32 `bson:"acc"`
}
type MongoUser struct {
Username string `bson:"username"`
PasswordHash string `bson:"password"`
Superuser bool `bson:"superuser"`
Acls []MongoAcl `bson:"acls"`
}
func NewMongo(authOpts map[string]string, logLevel log.Level) (Mongo, error) {
log.SetLevel(logLevel)
var m = Mongo{
Host: "localhost",
Port: "27017",
Username: "",
Password: "",
DBName: "mosquitto",
UsersCollection: "users",
AclsCollection: "acls",
}
if mongoHost, ok := authOpts["mongo_host"]; ok {
m.Host = mongoHost
}
if mongoPort, ok := authOpts["mongo_port"]; ok {
m.Port = mongoPort
}
if mongoUsername, ok := authOpts["mongo_username"]; ok {
m.Username = mongoUsername
}
if mongoPassword, ok := authOpts["mongo_password"]; ok {
m.Password = mongoPassword
}
if mongoDBName, ok := authOpts["mongo_dbname"]; ok {
m.DBName = mongoDBName
}
if usersCollection, ok := authOpts["mongo_users"]; ok {
m.UsersCollection = usersCollection
}
if aclsCollection, ok := authOpts["mongo_acls"]; ok {
m.AclsCollection = aclsCollection
}
addr := fmt.Sprintf("mongodb://%s:%s", m.Host, m.Port)
to := 60 * time.Second
opts := options.ClientOptions{
ConnectTimeout: &to,
}
opts.ApplyURI(addr)
if m.Username != "" && m.Password != "" {
opts.Auth = &options.Credential{
AuthSource: m.DBName,
Username: m.Username,
Password: m.Password,
PasswordSet: true,
}
}
client, err := mongo.Connect(context.TODO(), &opts)
if err != nil {
return m, errors.Errorf("couldn't start mongo backend. error: %s\n", err)
}
m.Conn = client
return m, nil
}
//GetUser checks that the username exists and the given password hashes to the same password.
func (o Mongo) GetUser(username, password, clientid string) bool {
uc := o.Conn.Database(o.DBName).Collection(o.UsersCollection)
var user MongoUser
err := uc.FindOne(context.TODO(), bson.M{"username": username}).Decode(&user)
if err != nil {
log.Debugf("Mongo get user error: %s", err)
return false
}
if common.HashCompare(password, user.PasswordHash) {
return true
}
return false
}
//GetSuperuser checks that the key username:su exists and has value "true".
func (o Mongo) GetSuperuser(username string) bool {
uc := o.Conn.Database(o.DBName).Collection(o.UsersCollection)
var user MongoUser
err := uc.FindOne(context.TODO(), bson.M{"username": username}).Decode(&user)
if err != nil {
log.Debugf("Mongo get superuser error: %s", err)
return false
}
return user.Superuser
}
//CheckAcl gets all acls for the username and tries to match against topic, acc, and username/clientid if needed.
func (o Mongo) CheckAcl(username, topic, clientid string, acc int32) bool {
//Get user and check his acls.
uc := o.Conn.Database(o.DBName).Collection(o.UsersCollection)
var user MongoUser
err := uc.FindOne(context.TODO(), bson.M{"username": username}).Decode(&user)
if err != nil {
log.Debugf("Mongo get superuser error: %s", err)
return false
}
for _, acl := range user.Acls {
if (acl.Acc == acc || acl.Acc == 3) && common.TopicsMatch(acl.Topic, topic) {
return true
}
}
//Now check common acls.
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
}
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)
}
}
return false
}
//GetName returns the backend's name
func (o Mongo) GetName() string {
return "Mongo"
}
//Halt closes the mongo session.
func (o Mongo) Halt() {
if o.Conn != nil {
o.Conn.Disconnect(context.TODO())
}
}