Refactor backends to take all the init and checking logic out of the main package, add options to enable registering specific checks per backend.
This commit is contained in:
parent
82ca3fc6a1
commit
ca22c6f9fa
21
README.md
21
README.md
|
@ -443,6 +443,21 @@ You may run all tests (see Testing X for each backend's testing requirements) li
|
|||
make test
|
||||
```
|
||||
|
||||
### Registering checks
|
||||
|
||||
Backends may register which checks they'll run, enabling the option to e.g. only check user auth through an HTTP backend while delegating ACL to another backend, e.g. Files.
|
||||
By default, when the option is not present, all checks for that backend will be enabled (unless `superuser` is globally disabled in the case of `superuser` checks).
|
||||
For `user` and `acl` checks, at least one backend needs to be registered, either explicitly or by default.
|
||||
|
||||
You may register which checks a backend will perform with the option `auth_opt_backend_register` followed by comma separated values of the registered checks, e.g.:
|
||||
```
|
||||
auth_opt_http_register user
|
||||
auth_opt_files_register user, acl
|
||||
auth_opt_redis_register superuser
|
||||
```
|
||||
|
||||
Possible values for checks are `user`, `superuser` and `acl`. Any other value will result in an error initializing the plugin.
|
||||
|
||||
|
||||
### Files
|
||||
|
||||
|
@ -1254,15 +1269,15 @@ func Init(authOpts map[string]string, logLevel log.Level) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func GetUser(username, password, clientid string) bool {
|
||||
func GetUser(username, password, clientid string) (bool, error) {
|
||||
return false
|
||||
}
|
||||
|
||||
func GetSuperuser(username string) bool {
|
||||
func GetSuperuser(username string) (bool, error) {
|
||||
return false
|
||||
}
|
||||
|
||||
func CheckAcl(username, topic, clientid string, acc int) bool {
|
||||
func CheckAcl(username, topic, clientid string, acc int) (bool, error) {
|
||||
return false
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,461 @@
|
|||
package backends
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/iegomez/mosquitto-go-auth/hashing"
|
||||
"github.com/pkg/errors"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type Backend interface {
|
||||
GetUser(username, password, clientid string) (bool, error)
|
||||
GetSuperuser(username string) (bool, error)
|
||||
CheckAcl(username, topic, clientId string, acc int32) (bool, error)
|
||||
GetName() string
|
||||
Halt()
|
||||
}
|
||||
|
||||
type Backends struct {
|
||||
backends map[string]Backend
|
||||
|
||||
aclCheckers []string
|
||||
userCheckers []string
|
||||
superuserCheckers []string
|
||||
|
||||
checkPrefix bool
|
||||
prefixes map[string]string
|
||||
|
||||
disableSuperuser bool
|
||||
}
|
||||
|
||||
const (
|
||||
//backends
|
||||
postgresBackend = "postgres"
|
||||
jwtBackend = "jwt"
|
||||
redisBackend = "redis"
|
||||
httpBackend = "http"
|
||||
filesBackend = "files"
|
||||
mysqlBackend = "mysql"
|
||||
sqliteBackend = "sqlite"
|
||||
mongoBackend = "mongo"
|
||||
pluginBackend = "plugin"
|
||||
grpcBackend = "grpc"
|
||||
jsBackend = "js"
|
||||
|
||||
//checks
|
||||
aclCheck = "acl"
|
||||
userCheck = "user"
|
||||
superuserCheck = "superuser"
|
||||
)
|
||||
|
||||
// AllowedBackendsOptsPrefix serves as a check for allowed backends and a map from backend to expected opts prefix.
|
||||
var AllowedBackendsOptsPrefix = map[string]string{
|
||||
postgresBackend: "pg",
|
||||
jwtBackend: "jwt",
|
||||
redisBackend: "redis",
|
||||
httpBackend: "http",
|
||||
filesBackend: "files",
|
||||
mysqlBackend: "mysql",
|
||||
sqliteBackend: "sqlite",
|
||||
mongoBackend: "mongo",
|
||||
pluginBackend: "plugin",
|
||||
grpcBackend: "grpc",
|
||||
jsBackend: "js",
|
||||
}
|
||||
|
||||
// Initialize sets general options, tries to build the backends and register their checkers.
|
||||
func Initialize(authOpts map[string]string, logLevel log.Level, backends []string) *Backends {
|
||||
|
||||
b := &Backends{
|
||||
backends: make(map[string]Backend),
|
||||
aclCheckers: make([]string, 0),
|
||||
userCheckers: make([]string, 0),
|
||||
superuserCheckers: make([]string, 0),
|
||||
checkPrefix: false,
|
||||
prefixes: make(map[string]string),
|
||||
}
|
||||
|
||||
//Disable superusers for all backends if option is set.
|
||||
if authOpts["disable_superuser"] == "true" {
|
||||
b.disableSuperuser = true
|
||||
}
|
||||
|
||||
for _, bename := range backends {
|
||||
var beIface Backend
|
||||
var err error
|
||||
|
||||
/*
|
||||
TODO: this could be nicer if we had a initializer map, e.g.:
|
||||
var initializers map[string] func(authOpts map[string]string, logLevel log.Level, hasher hashing.Hasher) (Backend, error)
|
||||
|
||||
Sadly, not all backends require a hasher and I'm not sure about changing them to accept a dummy one just for the sake of this.
|
||||
But I'll keep this comment for further thought and to remind me about changing those that don't return a pointer to do so.
|
||||
*/
|
||||
hasher := hashing.NewHasher(authOpts, AllowedBackendsOptsPrefix[bename])
|
||||
switch bename {
|
||||
case postgresBackend:
|
||||
beIface, err = NewPostgres(authOpts, logLevel, hasher)
|
||||
if err != nil {
|
||||
log.Fatalf("backend register error: couldn't initialize %s backend with error %s.", bename, err)
|
||||
} else {
|
||||
log.Infof("backend registered: %s", beIface.GetName())
|
||||
b.backends[postgresBackend] = beIface.(Postgres)
|
||||
}
|
||||
case jwtBackend:
|
||||
beIface, err = NewJWT(authOpts, logLevel, hasher)
|
||||
if err != nil {
|
||||
log.Fatalf("Backend register error: couldn't initialize %s backend with error %s.", bename, err)
|
||||
} else {
|
||||
log.Infof("Backend registered: %s", beIface.GetName())
|
||||
b.backends[jwtBackend] = beIface.(*JWT)
|
||||
}
|
||||
case filesBackend:
|
||||
beIface, err = NewFiles(authOpts, logLevel, hasher)
|
||||
if err != nil {
|
||||
log.Fatalf("Backend register error: couldn't initialize %s backend with error %s.", bename, err)
|
||||
} else {
|
||||
log.Infof("Backend registered: %s", beIface.GetName())
|
||||
b.backends[filesBackend] = beIface.(*Files)
|
||||
}
|
||||
case redisBackend:
|
||||
beIface, err = NewRedis(authOpts, logLevel, hasher)
|
||||
if err != nil {
|
||||
log.Fatalf("Backend register error: couldn't initialize %s backend with error %s.", bename, err)
|
||||
} else {
|
||||
log.Infof("Backend registered: %s", beIface.GetName())
|
||||
b.backends[redisBackend] = beIface.(Redis)
|
||||
}
|
||||
case mysqlBackend:
|
||||
beIface, err = NewMysql(authOpts, logLevel, hasher)
|
||||
if err != nil {
|
||||
log.Fatalf("Backend register error: couldn't initialize %s backend with error %s.", bename, err)
|
||||
} else {
|
||||
log.Infof("Backend registered: %s", beIface.GetName())
|
||||
b.backends[mysqlBackend] = beIface.(Mysql)
|
||||
}
|
||||
case httpBackend:
|
||||
beIface, err = NewHTTP(authOpts, logLevel)
|
||||
if err != nil {
|
||||
log.Fatalf("Backend register error: couldn't initialize %s backend with error %s.", bename, err)
|
||||
} else {
|
||||
log.Infof("Backend registered: %s", beIface.GetName())
|
||||
b.backends[httpBackend] = beIface.(HTTP)
|
||||
}
|
||||
case sqliteBackend:
|
||||
beIface, err = NewSqlite(authOpts, logLevel, hasher)
|
||||
if err != nil {
|
||||
log.Fatalf("Backend register error: couldn't initialize %s backend with error %s.", bename, err)
|
||||
} else {
|
||||
log.Infof("Backend registered: %s", beIface.GetName())
|
||||
b.backends[sqliteBackend] = beIface.(Sqlite)
|
||||
}
|
||||
case mongoBackend:
|
||||
beIface, err = NewMongo(authOpts, logLevel, hasher)
|
||||
if err != nil {
|
||||
log.Fatalf("Backend register error: couldn't initialize %s backend with error %s.", bename, err)
|
||||
} else {
|
||||
log.Infof("Backend registered: %s", beIface.GetName())
|
||||
b.backends[mongoBackend] = beIface.(Mongo)
|
||||
}
|
||||
case grpcBackend:
|
||||
beIface, err = NewGRPC(authOpts, logLevel)
|
||||
if err != nil {
|
||||
log.Fatalf("Backend register error: couldn't initialize %s backend with error %s.", bename, err)
|
||||
} else {
|
||||
log.Infof("Backend registered: %s", beIface.GetName())
|
||||
b.backends[grpcBackend] = beIface.(GRPC)
|
||||
}
|
||||
case jsBackend:
|
||||
beIface, err = NewJavascript(authOpts, logLevel)
|
||||
if err != nil {
|
||||
log.Fatalf("Backend register error: couldn't initialize %s backend with error %s.", bename, err)
|
||||
} else {
|
||||
log.Infof("Backend registered: %s", beIface.GetName())
|
||||
b.backends[jsBackend] = beIface.(*Javascript)
|
||||
}
|
||||
case pluginBackend:
|
||||
beIface, err = NewCustomPlugin(authOpts, logLevel)
|
||||
if err != nil {
|
||||
log.Fatalf("Backend register error: couldn't initialize %s backend with error %s.", bename, err)
|
||||
} else {
|
||||
log.Infof("Backend registered: %s", beIface.GetName())
|
||||
b.backends[pluginBackend] = beIface.(*CustomPlugin)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
b.setCheckers(authOpts)
|
||||
b.setPrefixes(authOpts, backends)
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *Backends) setCheckers(authOpts map[string]string) error {
|
||||
// We'll register which plugins will perform checks for user, superuser and acls.
|
||||
// At least one backend must be registered for user and acl checks.
|
||||
// When option auth_opt_backend_register is missing for the backend, we register all checks.
|
||||
for name := range b.backends {
|
||||
opt := fmt.Sprintf("%s_register", AllowedBackendsOptsPrefix[name])
|
||||
options, ok := authOpts[opt]
|
||||
|
||||
if ok {
|
||||
checkers := strings.Fields(options)
|
||||
for _, check := range checkers {
|
||||
switch check {
|
||||
case aclCheck:
|
||||
b.aclCheckers = append(b.aclCheckers, name)
|
||||
case userCheck:
|
||||
b.userCheckers = append(b.userCheckers, name)
|
||||
case superuserCheck:
|
||||
b.superuserCheckers = append(b.superuserCheckers, name)
|
||||
default:
|
||||
return fmt.Errorf("unsupported check %s found for backend %s, skipping registration", check, name)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
b.aclCheckers = append(b.aclCheckers, name)
|
||||
b.userCheckers = append(b.userCheckers, name)
|
||||
b.superuserCheckers = append(b.superuserCheckers, name)
|
||||
}
|
||||
}
|
||||
|
||||
if len(b.userCheckers) == 0 {
|
||||
return errors.New("no backend registered user checks")
|
||||
}
|
||||
|
||||
if len(b.aclCheckers) == 0 {
|
||||
return errors.New("no backend registered ACL checks")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// setPrefixes sets options for prefixes handling.
|
||||
func (b *Backends) setPrefixes(authOpts map[string]string, backends []string) {
|
||||
if checkPrefix, ok := authOpts["check_prefix"]; ok && strings.Replace(checkPrefix, " ", "", -1) == "true" {
|
||||
//Check that backends match prefixes.
|
||||
if prefixesStr, ok := authOpts["prefixes"]; ok {
|
||||
prefixes := strings.Split(strings.Replace(prefixesStr, " ", "", -1), ",")
|
||||
if len(prefixes) == len(backends) {
|
||||
//Set prefixes
|
||||
for i, backend := range backends {
|
||||
b.prefixes[prefixes[i]] = backend
|
||||
}
|
||||
log.Infof("prefixes enabled for backends %s with prefixes %s.", authOpts["backends"], authOpts["prefixes"])
|
||||
b.checkPrefix = true
|
||||
} else {
|
||||
log.Errorf("Error: got %d backends and %d prefixes, defaulting to prefixes disabled.", len(backends), len(prefixes))
|
||||
b.checkPrefix = false
|
||||
}
|
||||
|
||||
} else {
|
||||
log.Warn("Error: prefixes enabled but no options given, defaulting to prefixes disabled.")
|
||||
b.checkPrefix = false
|
||||
}
|
||||
} else {
|
||||
b.checkPrefix = false
|
||||
}
|
||||
}
|
||||
|
||||
// checkPrefix checks if a username contains a valid prefix. If so, returns ok and the suitable backend name; else, !ok and empty string.
|
||||
func (b *Backends) lookupPrefix(username string) (bool, string) {
|
||||
if strings.Index(username, "_") > 0 {
|
||||
userPrefix := username[0:strings.Index(username, "_")]
|
||||
if prefix, ok := b.prefixes[userPrefix]; ok {
|
||||
log.Debugf("Found prefix for user %s, using backend %s.", username, prefix)
|
||||
return true, prefix
|
||||
}
|
||||
}
|
||||
return false, ""
|
||||
}
|
||||
|
||||
// getPrefixForBackend retrieves the user provided prefix for a given backend.
|
||||
func (b *Backends) getPrefixForBackend(backend string) string {
|
||||
for k, v := range b.prefixes {
|
||||
if v == backend {
|
||||
return k
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func checkRegistered(bename string, checkers []string) bool {
|
||||
for _, b := range checkers {
|
||||
if b == bename {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// AuthUnpwdCheck checks user authentication.
|
||||
func (b *Backends) AuthUnpwdCheck(username, password, clientid string) (bool, error) {
|
||||
var authenticated bool
|
||||
var err error
|
||||
|
||||
//If prefixes are enabled, check if username has a valid prefix and use the correct backend if so.
|
||||
if b.checkPrefix {
|
||||
validPrefix, bename := b.lookupPrefix(username)
|
||||
|
||||
if !checkRegistered(bename, b.userCheckers) {
|
||||
return false, fmt.Errorf("backends %s not registered to check users", bename)
|
||||
}
|
||||
|
||||
if validPrefix {
|
||||
// If the backend is JWT and the token was prefixed, then strip the token. If the token was passed without a prefix it will be handled in the common case.
|
||||
if bename == jwtBackend {
|
||||
prefix := b.getPrefixForBackend(bename)
|
||||
username = strings.TrimPrefix(username, prefix+"_")
|
||||
}
|
||||
var backend = b.backends[bename]
|
||||
|
||||
authenticated, err = backend.GetUser(username, password, clientid)
|
||||
if authenticated && err == nil {
|
||||
log.Debugf("user %s authenticated with backend %s", username, backend.GetName())
|
||||
}
|
||||
} else {
|
||||
//If there's no valid prefix, check all backends.
|
||||
authenticated, err = b.checkAuth(username, password, clientid)
|
||||
}
|
||||
} else {
|
||||
authenticated, err = b.checkAuth(username, password, clientid)
|
||||
}
|
||||
|
||||
return authenticated, err
|
||||
}
|
||||
|
||||
func (b *Backends) checkAuth(username, password, clientid string) (bool, error) {
|
||||
var err error
|
||||
authenticated := false
|
||||
|
||||
for _, bename := range b.userCheckers {
|
||||
var backend = b.backends[bename]
|
||||
|
||||
log.Debugf("checking user %s with backend %s", username, backend.GetName())
|
||||
|
||||
if ok, getUserErr := backend.GetUser(username, password, clientid); ok && getUserErr == nil {
|
||||
authenticated = true
|
||||
log.Debugf("user %s authenticated with backend %s", username, backend.GetName())
|
||||
break
|
||||
} else if getUserErr != nil && err == nil {
|
||||
err = getUserErr
|
||||
}
|
||||
}
|
||||
|
||||
// If authenticated is true, it means at least one backend didn't fail and
|
||||
// accepted the user. If so, honor the backend and clear the error.
|
||||
if authenticated {
|
||||
err = nil
|
||||
}
|
||||
|
||||
return authenticated, err
|
||||
}
|
||||
|
||||
// AuthAclCheck checks user/topic/acc authentication.
|
||||
func (b *Backends) AuthAclCheck(clientid, username, topic string, acc int) (bool, error) {
|
||||
var aclCheck bool
|
||||
var err error
|
||||
|
||||
//If prefixes are enabled, check if username has a valid prefix and use the correct backend if so.
|
||||
//Else, check all backends.
|
||||
if b.checkPrefix {
|
||||
validPrefix, bename := b.lookupPrefix(username)
|
||||
if validPrefix {
|
||||
// If the backend is JWT and the token was prefixed, then strip the token. If the token was passed without a prefix then it be handled in the common case.
|
||||
if bename == jwtBackend {
|
||||
prefix := b.getPrefixForBackend(bename)
|
||||
username = strings.TrimPrefix(username, prefix+"_")
|
||||
}
|
||||
var backend = b.backends[bename]
|
||||
|
||||
// Short circuit checks when superusers are disabled.
|
||||
if !b.disableSuperuser {
|
||||
log.Debugf("Superuser check with backend %s", backend.GetName())
|
||||
if !checkRegistered(bename, b.superuserCheckers) {
|
||||
return false, fmt.Errorf("backends %s not registered to check superusers", bename)
|
||||
}
|
||||
|
||||
aclCheck, err = backend.GetSuperuser(username)
|
||||
|
||||
if aclCheck && err == nil {
|
||||
log.Debugf("superuser %s acl authenticated with backend %s", username, backend.GetName())
|
||||
}
|
||||
}
|
||||
//If not superuser, check acl.
|
||||
if !aclCheck {
|
||||
if !checkRegistered(bename, b.aclCheckers) {
|
||||
return false, fmt.Errorf("backends %s not registered to check superusers", bename)
|
||||
}
|
||||
|
||||
log.Debugf("Acl check with backend %s", backend.GetName())
|
||||
if ok, checkACLErr := backend.CheckAcl(username, topic, clientid, int32(acc)); ok && checkACLErr == nil {
|
||||
aclCheck = true
|
||||
log.Debugf("user %s acl authenticated with backend %s", username, backend.GetName())
|
||||
} else if checkACLErr != nil && err == nil {
|
||||
err = checkACLErr
|
||||
}
|
||||
}
|
||||
} else {
|
||||
//If there's no valid prefix, check all backends.
|
||||
aclCheck, err = b.checkAcl(username, topic, clientid, acc)
|
||||
}
|
||||
} else {
|
||||
aclCheck, err = b.checkAcl(username, topic, clientid, acc)
|
||||
}
|
||||
|
||||
log.Debugf("Acl is %t for user %s", aclCheck, username)
|
||||
return aclCheck, err
|
||||
}
|
||||
|
||||
func (b *Backends) checkAcl(username, topic, clientid string, acc int) (bool, error) {
|
||||
//Check superusers first
|
||||
var err error
|
||||
aclCheck := false
|
||||
if !b.disableSuperuser {
|
||||
for _, bename := range b.superuserCheckers {
|
||||
var backend = b.backends[bename]
|
||||
|
||||
log.Debugf("Superuser check with backend %s", backend.GetName())
|
||||
if ok, getSuperuserErr := backend.GetSuperuser(username); ok && getSuperuserErr == nil {
|
||||
log.Debugf("superuser %s acl authenticated with backend %s", username, backend.GetName())
|
||||
aclCheck = true
|
||||
break
|
||||
} else if getSuperuserErr != nil && err == nil {
|
||||
err = getSuperuserErr
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !aclCheck {
|
||||
for _, bename := range b.aclCheckers {
|
||||
var backend = b.backends[bename]
|
||||
|
||||
log.Debugf("Acl check with backend %s", backend.GetName())
|
||||
if ok, checkACLErr := backend.CheckAcl(username, topic, clientid, int32(acc)); ok && checkACLErr == nil {
|
||||
log.Debugf("user %s acl authenticated with backend %s", username, backend.GetName())
|
||||
aclCheck = true
|
||||
break
|
||||
} else if checkACLErr != nil && err == nil {
|
||||
err = checkACLErr
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If aclCheck is true, it means at least one backend didn't fail and
|
||||
// accepted the access. In this case trust this backend and clear the error.
|
||||
if aclCheck {
|
||||
err = nil
|
||||
}
|
||||
|
||||
return aclCheck, err
|
||||
}
|
||||
|
||||
func (b *Backends) Halt() {
|
||||
//Halt every registered backend.
|
||||
for _, v := range b.backends {
|
||||
v.Halt()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,131 @@
|
|||
package backends
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"plugin"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type CustomPlugin struct {
|
||||
plugin *plugin.Plugin
|
||||
init func(map[string]string, log.Level) error
|
||||
getName func() string
|
||||
getUser func(username, password, clientid string) (bool, error)
|
||||
getSuperuser func(username string) (bool, error)
|
||||
checkAcl func(username, topic, clientid string, acc int32) (bool, error)
|
||||
halt func()
|
||||
}
|
||||
|
||||
func NewCustomPlugin(authOpts map[string]string, logLevel log.Level) (*CustomPlugin, error) {
|
||||
plug, err := plugin.Open(authOpts["plugin_path"])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Could not init custom plugin: %s", err)
|
||||
}
|
||||
|
||||
customPlugin := &CustomPlugin{
|
||||
plugin: plug,
|
||||
}
|
||||
|
||||
// Damn, this is gonna be tedious, freaking error handling!
|
||||
plInit, err := plug.Lookup("Init")
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Couldn't find func Init in plugin: %s", err)
|
||||
}
|
||||
|
||||
initFunc := plInit.(func(authOpts map[string]string, logLevel log.Level) error)
|
||||
|
||||
err = initFunc(authOpts, logLevel)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Couldn't init plugin: %s", err)
|
||||
}
|
||||
|
||||
customPlugin.init = initFunc
|
||||
|
||||
plName, err := plug.Lookup("GetName")
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Couldn't find func GetName in plugin: %s", err)
|
||||
}
|
||||
|
||||
nameFunc := plName.(func() string)
|
||||
customPlugin.getName = nameFunc
|
||||
|
||||
plGetUser, err := plug.Lookup("GetUser")
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("couldn't find func GetUser in plugin: %s", err)
|
||||
}
|
||||
|
||||
getUserFunc, ok := plGetUser.(func(username, password, clientid string) (bool, error))
|
||||
if !ok {
|
||||
// Here and in other places, we do this for backwards compatibility in case the custom plugin so was created before error was returned.
|
||||
tmp := plGetUser.(func(username, password, clientid string) bool)
|
||||
getUserFunc = func(username, password, clientid string) (bool, error) {
|
||||
return tmp(username, password, clientid), nil
|
||||
}
|
||||
}
|
||||
customPlugin.getUser = getUserFunc
|
||||
|
||||
plGetSuperuser, err := plug.Lookup("GetSuperuser")
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("couldn't find func GetSuperuser in plugin: %s", err)
|
||||
}
|
||||
|
||||
getSuperuserFunc, ok := plGetSuperuser.(func(username string) (bool, error))
|
||||
if !ok {
|
||||
tmp := plGetSuperuser.(func(username string) bool)
|
||||
getSuperuserFunc = func(username string) (bool, error) {
|
||||
return tmp(username), nil
|
||||
}
|
||||
}
|
||||
customPlugin.getSuperuser = getSuperuserFunc
|
||||
|
||||
plCheckAcl, err := plug.Lookup("CheckAcl")
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("couldn't find func CheckAcl in plugin: %s", err)
|
||||
}
|
||||
|
||||
checkAclFunc, ok := plCheckAcl.(func(username, topic, clientid string, acc int32) (bool, error))
|
||||
if !ok {
|
||||
tmp := plCheckAcl.(func(username, topic, clientid string, acc int32) bool)
|
||||
checkAclFunc = func(username, topic, clientid string, acc int32) (bool, error) {
|
||||
return tmp(username, topic, clientid, acc), nil
|
||||
}
|
||||
}
|
||||
customPlugin.checkAcl = checkAclFunc
|
||||
|
||||
plHalt, err := plug.Lookup("Halt")
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Couldn't find func Halt in plugin: %s", err)
|
||||
}
|
||||
|
||||
haltFunc := plHalt.(func())
|
||||
customPlugin.halt = haltFunc
|
||||
|
||||
return customPlugin, nil
|
||||
}
|
||||
|
||||
func (o *CustomPlugin) GetUser(username, password, clientid string) (bool, error) {
|
||||
return o.getUser(username, password, clientid)
|
||||
}
|
||||
|
||||
func (o *CustomPlugin) GetSuperuser(username string) (bool, error) {
|
||||
return o.getSuperuser(username)
|
||||
}
|
||||
|
||||
func (o *CustomPlugin) CheckAcl(username, topic, clientid string, acc int32) (bool, error) {
|
||||
return o.checkAcl(username, topic, clientid, acc)
|
||||
}
|
||||
|
||||
func (o *CustomPlugin) GetName() string {
|
||||
return o.getName()
|
||||
}
|
||||
|
||||
func (o *CustomPlugin) Halt() {
|
||||
o.halt()
|
||||
}
|
543
go-auth.go
543
go-auth.go
|
@ -5,7 +5,6 @@ import "C"
|
|||
import (
|
||||
"context"
|
||||
"os"
|
||||
"plugin"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
@ -16,70 +15,25 @@ import (
|
|||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type Backend interface {
|
||||
GetUser(username, password, clientid string) (bool, error)
|
||||
GetSuperuser(username string) (bool, error)
|
||||
CheckAcl(username, topic, clientId string, acc int32) (bool, error)
|
||||
GetName() string
|
||||
Halt()
|
||||
}
|
||||
|
||||
type AuthPlugin struct {
|
||||
backends map[string]Backend
|
||||
customPlugin *plugin.Plugin
|
||||
PInit func(map[string]string, log.Level) error
|
||||
customPluginGetName func() string
|
||||
customPluginGetUser func(username, password, clientid string) (bool, error)
|
||||
customPluginGetSuperuser func(username string) (bool, error)
|
||||
customPluginCheckAcl func(username, topic, clientid string, acc int) (bool, error)
|
||||
customPluginHalt func()
|
||||
useCache bool
|
||||
checkPrefix bool
|
||||
retryCount int
|
||||
prefixes map[string]string
|
||||
logLevel log.Level
|
||||
logDest string
|
||||
logFile string
|
||||
disableSuperuser bool
|
||||
ctx context.Context
|
||||
cache cache.Store
|
||||
hasher hashing.HashComparer
|
||||
backends *bes.Backends
|
||||
useCache bool
|
||||
logLevel log.Level
|
||||
logDest string
|
||||
logFile string
|
||||
ctx context.Context
|
||||
cache cache.Store
|
||||
hasher hashing.HashComparer
|
||||
retryCount int
|
||||
}
|
||||
|
||||
// errors to signal mosquitto
|
||||
const (
|
||||
//backends
|
||||
postgresBackend = "postgres"
|
||||
jwtBackend = "jwt"
|
||||
redisBackend = "redis"
|
||||
httpBackend = "http"
|
||||
filesBackend = "files"
|
||||
mysqlBackend = "mysql"
|
||||
sqliteBackend = "sqlite"
|
||||
mongoBackend = "mongo"
|
||||
pluginBackend = "plugin"
|
||||
grpcBackend = "grpc"
|
||||
jsBackend = "js"
|
||||
|
||||
AuthRejected = 0
|
||||
AuthGranted = 1
|
||||
AuthError = 2
|
||||
)
|
||||
|
||||
// Serves s a check for allowed backends and a map from backend to expected opts prefix.
|
||||
var allowedBackendsOptsPrefix = map[string]string{
|
||||
postgresBackend: "pg",
|
||||
jwtBackend: "jwt",
|
||||
redisBackend: "redis",
|
||||
httpBackend: "http",
|
||||
filesBackend: "files",
|
||||
mysqlBackend: "mysql",
|
||||
sqliteBackend: "sqlite",
|
||||
mongoBackend: "mongo",
|
||||
pluginBackend: "plugin",
|
||||
grpcBackend: "grpc",
|
||||
jsBackend: "js",
|
||||
}
|
||||
|
||||
var backends []string //List of selected backends.
|
||||
var authOpts map[string]string //Options passed by mosquitto.
|
||||
var authPlugin AuthPlugin //General struct with options and conf.
|
||||
|
@ -90,14 +44,10 @@ func AuthPluginInit(keys []string, values []string, authOptsNum int) {
|
|||
FullTimestamp: true,
|
||||
})
|
||||
|
||||
cmBackends := make(map[string]Backend)
|
||||
|
||||
//Initialize auth plugin struct with default and given values.
|
||||
authPlugin = AuthPlugin{
|
||||
checkPrefix: false,
|
||||
prefixes: make(map[string]string),
|
||||
logLevel: log.InfoLevel,
|
||||
ctx: context.Background(),
|
||||
logLevel: log.InfoLevel,
|
||||
ctx: context.Background(),
|
||||
}
|
||||
|
||||
//First, get backends
|
||||
|
@ -109,7 +59,7 @@ func AuthPluginInit(keys []string, values []string, authOptsNum int) {
|
|||
if len(backends) > 0 {
|
||||
backendsCheck := true
|
||||
for _, backend := range backends {
|
||||
if _, ok := allowedBackendsOptsPrefix[backend]; !ok {
|
||||
if _, ok := bes.AllowedBackendsOptsPrefix[backend]; !ok {
|
||||
backendsCheck = false
|
||||
log.Errorf("backend not allowed: %s", backend)
|
||||
}
|
||||
|
@ -135,11 +85,6 @@ func AuthPluginInit(keys []string, values []string, authOptsNum int) {
|
|||
log.Fatal("backends error")
|
||||
}
|
||||
|
||||
//Disable superusers for all backends if option is set.
|
||||
if authOpts["disable_superuser"] == "true" {
|
||||
authPlugin.disableSuperuser = true
|
||||
}
|
||||
|
||||
//Check if log level is given. Set level if any valid option is given.
|
||||
if logLevel, ok := authOpts["log_level"]; ok {
|
||||
logLevel = strings.Replace(logLevel, " ", "", -1)
|
||||
|
@ -179,199 +124,7 @@ func AuthPluginInit(keys []string, values []string, authOptsNum int) {
|
|||
}
|
||||
}
|
||||
|
||||
//Initialize backends
|
||||
for _, bename := range backends {
|
||||
var beIface Backend
|
||||
var err error
|
||||
if bename == pluginBackend {
|
||||
plug, err := plugin.Open(authOpts["plugin_path"])
|
||||
if err != nil {
|
||||
log.Errorf("Could not init custom plugin: %s", err)
|
||||
authPlugin.customPlugin = nil
|
||||
} else {
|
||||
authPlugin.customPlugin = plug
|
||||
|
||||
plInit, err := authPlugin.customPlugin.Lookup("Init")
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("Couldn't find func Init in plugin: %s", err)
|
||||
authPlugin.customPlugin = nil
|
||||
continue
|
||||
}
|
||||
|
||||
initFunc := plInit.(func(authOpts map[string]string, logLevel log.Level) error)
|
||||
|
||||
err = initFunc(authOpts, authPlugin.logLevel)
|
||||
if err != nil {
|
||||
log.Errorf("Couldn't init plugin: %s", err)
|
||||
authPlugin.customPlugin = nil
|
||||
continue
|
||||
}
|
||||
|
||||
authPlugin.PInit = initFunc
|
||||
|
||||
plName, err := authPlugin.customPlugin.Lookup("GetName")
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("Couldn't find func GetName in plugin: %s", err)
|
||||
authPlugin.customPlugin = nil
|
||||
continue
|
||||
}
|
||||
|
||||
nameFunc := plName.(func() string)
|
||||
authPlugin.customPluginGetName = nameFunc
|
||||
|
||||
plGetUser, err := authPlugin.customPlugin.Lookup("GetUser")
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("couldn't find func GetUser in plugin: %s", err)
|
||||
authPlugin.customPlugin = nil
|
||||
continue
|
||||
}
|
||||
|
||||
getUserFunc, ok := plGetUser.(func(username, password, clientid string) (bool, error))
|
||||
if !ok {
|
||||
tmp := plGetUser.(func(username, password, clientid string) bool)
|
||||
getUserFunc = func(username, password, clientid string) (bool, error) {
|
||||
return tmp(username, password, clientid), nil
|
||||
}
|
||||
}
|
||||
authPlugin.customPluginGetUser = getUserFunc
|
||||
|
||||
plGetSuperuser, err := authPlugin.customPlugin.Lookup("GetSuperuser")
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("couldn't find func GetSuperuser in plugin: %s", err)
|
||||
authPlugin.customPlugin = nil
|
||||
continue
|
||||
}
|
||||
|
||||
getSuperuserFunc, ok := plGetSuperuser.(func(username string) (bool, error))
|
||||
if !ok {
|
||||
tmp := plGetSuperuser.(func(username string) bool)
|
||||
getSuperuserFunc = func(username string) (bool, error) {
|
||||
return tmp(username), nil
|
||||
}
|
||||
}
|
||||
authPlugin.customPluginGetSuperuser = getSuperuserFunc
|
||||
|
||||
plCheckAcl, err := authPlugin.customPlugin.Lookup("CheckAcl")
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("couldn't find func CheckAcl in plugin: %s", err)
|
||||
authPlugin.customPlugin = nil
|
||||
continue
|
||||
}
|
||||
|
||||
checkAclFunc, ok := plCheckAcl.(func(username, topic, clientid string, acc int) (bool, error))
|
||||
if !ok {
|
||||
tmp := plCheckAcl.(func(username, topic, clientid string, acc int) bool)
|
||||
checkAclFunc = func(username, topic, clientid string, acc int) (bool, error) {
|
||||
return tmp(username, topic, clientid, acc), nil
|
||||
}
|
||||
}
|
||||
authPlugin.customPluginCheckAcl = checkAclFunc
|
||||
|
||||
plHalt, err := authPlugin.customPlugin.Lookup("Halt")
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("Couldn't find func Halt in plugin: %s", err)
|
||||
authPlugin.customPlugin = nil
|
||||
continue
|
||||
}
|
||||
|
||||
haltFunc := plHalt.(func())
|
||||
authPlugin.customPluginHalt = haltFunc
|
||||
|
||||
log.Infof("Backend registered: %s", authPlugin.customPluginGetName())
|
||||
|
||||
}
|
||||
} else {
|
||||
hasher := hashing.NewHasher(authOpts, allowedBackendsOptsPrefix[bename])
|
||||
switch bename {
|
||||
case postgresBackend:
|
||||
beIface, err = bes.NewPostgres(authOpts, authPlugin.logLevel, hasher)
|
||||
if err != nil {
|
||||
log.Fatalf("backend register error: couldn't initialize %s backend with error %s.", bename, err)
|
||||
} else {
|
||||
log.Infof("backend registered: %s", beIface.GetName())
|
||||
cmBackends[postgresBackend] = beIface.(bes.Postgres)
|
||||
}
|
||||
case jwtBackend:
|
||||
beIface, err = bes.NewJWT(authOpts, authPlugin.logLevel, hasher)
|
||||
if err != nil {
|
||||
log.Fatalf("Backend register error: couldn't initialize %s backend with error %s.", bename, err)
|
||||
} else {
|
||||
log.Infof("Backend registered: %s", beIface.GetName())
|
||||
cmBackends[jwtBackend] = beIface.(*bes.JWT)
|
||||
}
|
||||
case filesBackend:
|
||||
beIface, err = bes.NewFiles(authOpts, authPlugin.logLevel, hasher)
|
||||
if err != nil {
|
||||
log.Fatalf("Backend register error: couldn't initialize %s backend with error %s.", bename, err)
|
||||
} else {
|
||||
log.Infof("Backend registered: %s", beIface.GetName())
|
||||
cmBackends[filesBackend] = beIface.(*bes.Files)
|
||||
}
|
||||
case redisBackend:
|
||||
beIface, err = bes.NewRedis(authOpts, authPlugin.logLevel, hasher)
|
||||
if err != nil {
|
||||
log.Fatalf("Backend register error: couldn't initialize %s backend with error %s.", bename, err)
|
||||
} else {
|
||||
log.Infof("Backend registered: %s", beIface.GetName())
|
||||
cmBackends[redisBackend] = beIface.(bes.Redis)
|
||||
}
|
||||
case mysqlBackend:
|
||||
beIface, err = bes.NewMysql(authOpts, authPlugin.logLevel, hasher)
|
||||
if err != nil {
|
||||
log.Fatalf("Backend register error: couldn't initialize %s backend with error %s.", bename, err)
|
||||
} else {
|
||||
log.Infof("Backend registered: %s", beIface.GetName())
|
||||
cmBackends[mysqlBackend] = beIface.(bes.Mysql)
|
||||
}
|
||||
case httpBackend:
|
||||
beIface, err = bes.NewHTTP(authOpts, authPlugin.logLevel)
|
||||
if err != nil {
|
||||
log.Fatalf("Backend register error: couldn't initialize %s backend with error %s.", bename, err)
|
||||
} else {
|
||||
log.Infof("Backend registered: %s", beIface.GetName())
|
||||
cmBackends[httpBackend] = beIface.(bes.HTTP)
|
||||
}
|
||||
case sqliteBackend:
|
||||
beIface, err = bes.NewSqlite(authOpts, authPlugin.logLevel, hasher)
|
||||
if err != nil {
|
||||
log.Fatalf("Backend register error: couldn't initialize %s backend with error %s.", bename, err)
|
||||
} else {
|
||||
log.Infof("Backend registered: %s", beIface.GetName())
|
||||
cmBackends[sqliteBackend] = beIface.(bes.Sqlite)
|
||||
}
|
||||
case mongoBackend:
|
||||
beIface, err = bes.NewMongo(authOpts, authPlugin.logLevel, hasher)
|
||||
if err != nil {
|
||||
log.Fatalf("Backend register error: couldn't initialize %s backend with error %s.", bename, err)
|
||||
} else {
|
||||
log.Infof("Backend registered: %s", beIface.GetName())
|
||||
cmBackends[mongoBackend] = beIface.(bes.Mongo)
|
||||
}
|
||||
case grpcBackend:
|
||||
beIface, err = bes.NewGRPC(authOpts, authPlugin.logLevel)
|
||||
if err != nil {
|
||||
log.Fatalf("Backend register error: couldn't initialize %s backend with error %s.", bename, err)
|
||||
} else {
|
||||
log.Infof("Backend registered: %s", beIface.GetName())
|
||||
cmBackends[grpcBackend] = beIface.(bes.GRPC)
|
||||
}
|
||||
case jsBackend:
|
||||
beIface, err = bes.NewJavascript(authOpts, authPlugin.logLevel)
|
||||
if err != nil {
|
||||
log.Fatalf("Backend register error: couldn't initialize %s backend with error %s.", bename, err)
|
||||
} else {
|
||||
log.Infof("Backend registered: %s", beIface.GetName())
|
||||
cmBackends[jsBackend] = beIface.(*bes.Javascript)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
authPlugin.backends = bes.Initialize(authOpts, authPlugin.logLevel, backends)
|
||||
|
||||
if cache, ok := authOpts["cache"]; ok && strings.Replace(cache, " ", "", -1) == "true" {
|
||||
log.Info("redisCache activated")
|
||||
|
@ -384,31 +137,6 @@ func AuthPluginInit(keys []string, values []string, authOptsNum int) {
|
|||
if authPlugin.useCache {
|
||||
setCache(authOpts)
|
||||
}
|
||||
|
||||
if checkPrefix, ok := authOpts["check_prefix"]; ok && strings.Replace(checkPrefix, " ", "", -1) == "true" {
|
||||
//Check that backends match prefixes.
|
||||
if prefixesStr, ok := authOpts["prefixes"]; ok {
|
||||
prefixes := strings.Split(strings.Replace(prefixesStr, " ", "", -1), ",")
|
||||
if len(prefixes) == len(backends) {
|
||||
//Set prefixes
|
||||
for i, backend := range backends {
|
||||
authPlugin.prefixes[prefixes[i]] = backend
|
||||
}
|
||||
log.Infof("prefixes enabled for backends %s with prefixes %s.", authOpts["backends"], authOpts["prefixes"])
|
||||
authPlugin.checkPrefix = true
|
||||
} else {
|
||||
log.Errorf("Error: got %d backends and %d prefixes, defaulting to prefixes disabled.", len(backends), len(prefixes))
|
||||
authPlugin.checkPrefix = false
|
||||
}
|
||||
|
||||
} else {
|
||||
log.Warn("Error: prefixes enabled but no options given, defaulting to prefixes disabled.")
|
||||
authPlugin.checkPrefix = false
|
||||
}
|
||||
} else {
|
||||
authPlugin.checkPrefix = false
|
||||
}
|
||||
authPlugin.backends = cmBackends
|
||||
}
|
||||
|
||||
func setCache(authOpts map[string]string) {
|
||||
|
@ -602,50 +330,7 @@ func authUnpwdCheck(username, password, clientid string) (bool, error) {
|
|||
}
|
||||
}
|
||||
|
||||
//If prefixes are enabled, check if username has a valid prefix and use the correct backend if so.
|
||||
if authPlugin.checkPrefix {
|
||||
validPrefix, bename := CheckPrefix(username)
|
||||
if validPrefix {
|
||||
if bename == pluginBackend {
|
||||
authenticated, err = CheckPluginAuth(username, password, clientid)
|
||||
} else {
|
||||
// If the backend is JWT and the token was prefixed, then strip the token. If the token was passed without a prefix it will be handled in the common case.
|
||||
if bename == jwtBackend {
|
||||
prefix := getPrefixForBackend(bename)
|
||||
username = strings.TrimPrefix(username, prefix+"_")
|
||||
}
|
||||
var backend = authPlugin.backends[bename]
|
||||
|
||||
authenticated, err = backend.GetUser(username, password, clientid)
|
||||
if authenticated && err == nil {
|
||||
log.Debugf("user %s authenticated with backend %s", username, backend.GetName())
|
||||
}
|
||||
}
|
||||
} else {
|
||||
//If there's no valid prefix, check all backends.
|
||||
authenticated, err = CheckBackendsAuth(username, password, clientid)
|
||||
//If not authenticated, check for a present plugin
|
||||
if !authenticated {
|
||||
if ok, checkAuthErr := CheckPluginAuth(username, password, clientid); ok && checkAuthErr == nil {
|
||||
authenticated = true
|
||||
err = nil
|
||||
} else if checkAuthErr != nil && err == nil {
|
||||
err = checkAuthErr
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
authenticated, err = CheckBackendsAuth(username, password, clientid)
|
||||
//If not authenticated, check for a present plugin
|
||||
if !authenticated {
|
||||
if ok, checkAuthErr := CheckPluginAuth(username, password, clientid); ok && checkAuthErr == nil {
|
||||
authenticated = true
|
||||
err = nil
|
||||
} else if checkAuthErr != nil && err == nil {
|
||||
err = checkAuthErr
|
||||
}
|
||||
}
|
||||
}
|
||||
authenticated, err = authPlugin.backends.AuthUnpwdCheck(username, password, clientid)
|
||||
|
||||
if authPlugin.useCache && err == nil {
|
||||
authGranted := "false"
|
||||
|
@ -698,63 +383,8 @@ func authAclCheck(clientid, username, topic string, acc int) (bool, error) {
|
|||
return granted, nil
|
||||
}
|
||||
}
|
||||
//If prefixes are enabled, check if username has a valid prefix and use the correct backend if so.
|
||||
//Else, check all backends.
|
||||
if authPlugin.checkPrefix {
|
||||
validPrefix, bename := CheckPrefix(username)
|
||||
if validPrefix {
|
||||
if bename == pluginBackend {
|
||||
aclCheck, err = CheckPluginAcl(username, topic, clientid, acc)
|
||||
} else {
|
||||
// If the backend is JWT and the token was prefixed, then strip the token. If the token was passed without a prefix then it be handled in the common case.
|
||||
if bename == jwtBackend {
|
||||
prefix := getPrefixForBackend(bename)
|
||||
username = strings.TrimPrefix(username, prefix+"_")
|
||||
}
|
||||
var backend = authPlugin.backends[bename]
|
||||
log.Debugf("Superuser check with backend %s", backend.GetName())
|
||||
// Short circuit checks when superusers are disabled.
|
||||
if !authPlugin.disableSuperuser {
|
||||
aclCheck, err = backend.GetSuperuser(username)
|
||||
|
||||
if aclCheck && err == nil {
|
||||
log.Debugf("superuser %s acl authenticated with backend %s", username, backend.GetName())
|
||||
}
|
||||
}
|
||||
//If not superuser, check acl.
|
||||
if !aclCheck {
|
||||
log.Debugf("Acl check with backend %s", backend.GetName())
|
||||
if ok, checkACLErr := backend.CheckAcl(username, topic, clientid, int32(acc)); ok && checkACLErr == nil {
|
||||
aclCheck = true
|
||||
log.Debugf("user %s acl authenticated with backend %s", username, backend.GetName())
|
||||
} else if checkACLErr != nil && err == nil {
|
||||
err = checkACLErr
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
//If there's no valid prefix, check all backends.
|
||||
aclCheck, err = CheckBackendsAcl(username, topic, clientid, acc)
|
||||
//If acl hasn't passed, check for plugin.
|
||||
if !aclCheck {
|
||||
if ok, checkACLErr := CheckPluginAcl(username, topic, clientid, acc); ok && checkACLErr == nil {
|
||||
aclCheck = true
|
||||
} else if checkACLErr != nil && err == nil {
|
||||
err = checkACLErr
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
aclCheck, err = CheckBackendsAcl(username, topic, clientid, acc)
|
||||
//If acl hasn't passed, check for plugin.
|
||||
if !aclCheck {
|
||||
if ok, checkACLErr := CheckPluginAcl(username, topic, clientid, acc); ok && checkACLErr == nil {
|
||||
aclCheck = true
|
||||
} else if checkACLErr != nil && err == nil {
|
||||
err = checkACLErr
|
||||
}
|
||||
}
|
||||
}
|
||||
aclCheck, err = authPlugin.backends.AuthAclCheck(clientid, username, topic, acc)
|
||||
|
||||
if authPlugin.useCache && err == nil {
|
||||
authGranted := "false"
|
||||
|
@ -777,138 +407,6 @@ func AuthPskKeyGet() bool {
|
|||
return true
|
||||
}
|
||||
|
||||
//checkPrefix checks if a username contains a valid prefix. If so, returns ok and the suitable backend name; else, !ok and empty string.
|
||||
func CheckPrefix(username string) (bool, string) {
|
||||
if strings.Index(username, "_") > 0 {
|
||||
userPrefix := username[0:strings.Index(username, "_")]
|
||||
if prefix, ok := authPlugin.prefixes[userPrefix]; ok {
|
||||
log.Debugf("Found prefix for user %s, using backend %s.", username, prefix)
|
||||
return true, prefix
|
||||
}
|
||||
}
|
||||
return false, ""
|
||||
}
|
||||
|
||||
//getPrefixForBackend retrieves the user provided prefix for a given backend.
|
||||
func getPrefixForBackend(backend string) string {
|
||||
for k, v := range authPlugin.prefixes {
|
||||
if v == backend {
|
||||
return k
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
//CheckBackendsAuth checks for all backends if a username is authenticated and sets the authenticated param.
|
||||
func CheckBackendsAuth(username, password, clientid string) (bool, error) {
|
||||
var err error
|
||||
authenticated := false
|
||||
|
||||
for _, bename := range backends {
|
||||
|
||||
if bename == pluginBackend {
|
||||
continue
|
||||
}
|
||||
|
||||
var backend = authPlugin.backends[bename]
|
||||
|
||||
log.Debugf("checking user %s with backend %s", username, backend.GetName())
|
||||
|
||||
if ok, getUserErr := backend.GetUser(username, password, clientid); ok && getUserErr == nil {
|
||||
authenticated = true
|
||||
log.Debugf("user %s authenticated with backend %s", username, backend.GetName())
|
||||
break
|
||||
} else if getUserErr != nil && err == nil {
|
||||
err = getUserErr
|
||||
}
|
||||
}
|
||||
|
||||
// If authenticated is true, it means at least one backend didn't failed and
|
||||
// accepted the user. In this case trust this backend and clear the error.
|
||||
if authenticated {
|
||||
err = nil
|
||||
}
|
||||
|
||||
return authenticated, err
|
||||
|
||||
}
|
||||
|
||||
//CheckBackendsAcl checks for all backends if a username is superuser or has acl rights and sets the aclCheck param.
|
||||
func CheckBackendsAcl(username, topic, clientid string, acc int) (bool, error) {
|
||||
//Check superusers first
|
||||
var err error
|
||||
aclCheck := false
|
||||
if !authPlugin.disableSuperuser {
|
||||
for _, bename := range backends {
|
||||
if bename == pluginBackend {
|
||||
continue
|
||||
}
|
||||
var backend = authPlugin.backends[bename]
|
||||
log.Debugf("Superuser check with backend %s", backend.GetName())
|
||||
if ok, getSuperuserErr := backend.GetSuperuser(username); ok && getSuperuserErr == nil {
|
||||
log.Debugf("superuser %s acl authenticated with backend %s", username, backend.GetName())
|
||||
aclCheck = true
|
||||
break
|
||||
} else if getSuperuserErr != nil && err == nil {
|
||||
err = getSuperuserErr
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !aclCheck {
|
||||
for _, bename := range backends {
|
||||
if bename == pluginBackend {
|
||||
continue
|
||||
}
|
||||
var backend = authPlugin.backends[bename]
|
||||
log.Debugf("Acl check with backend %s", backend.GetName())
|
||||
if ok, checkACLErr := backend.CheckAcl(username, topic, clientid, int32(acc)); ok && checkACLErr == nil {
|
||||
log.Debugf("user %s acl authenticated with backend %s", username, backend.GetName())
|
||||
aclCheck = true
|
||||
break
|
||||
} else if checkACLErr != nil && err == nil {
|
||||
err = checkACLErr
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If aclCheck is true, it means at least one backend didn't fail and
|
||||
// accepted the access. In this case trust this backend and clear the error.
|
||||
if aclCheck {
|
||||
err = nil
|
||||
}
|
||||
|
||||
return aclCheck, err
|
||||
}
|
||||
|
||||
//CheckPluginAuth checks that the plugin is not nil and returns the plugins auth response.
|
||||
func CheckPluginAuth(username, password, clientid string) (bool, error) {
|
||||
if authPlugin.customPlugin == nil {
|
||||
return false, nil
|
||||
}
|
||||
return authPlugin.customPluginGetUser(username, password, clientid)
|
||||
}
|
||||
|
||||
//CheckPluginAcl checks that the plugin is not nil and returns the superuser/acl response.
|
||||
func CheckPluginAcl(username, topic, clientid string, acc int) (bool, error) {
|
||||
var aclCheck bool
|
||||
var err error
|
||||
if authPlugin.customPlugin == nil {
|
||||
return false, nil
|
||||
}
|
||||
//If superuser, authorize it unless superusers are disabled.
|
||||
if !authPlugin.disableSuperuser {
|
||||
aclCheck, err = authPlugin.customPluginGetSuperuser(username)
|
||||
}
|
||||
|
||||
if !aclCheck && err == nil {
|
||||
//Check against the plugin's check acl function.
|
||||
aclCheck, err = authPlugin.customPluginCheckAcl(username, topic, clientid, acc)
|
||||
}
|
||||
|
||||
return aclCheck, err
|
||||
}
|
||||
|
||||
//export AuthPluginCleanup
|
||||
func AuthPluginCleanup() {
|
||||
log.Info("Cleaning up plugin")
|
||||
|
@ -917,14 +415,7 @@ func AuthPluginCleanup() {
|
|||
authPlugin.cache.Close()
|
||||
}
|
||||
|
||||
//Halt every registered backend.
|
||||
for _, v := range authPlugin.backends {
|
||||
v.Halt()
|
||||
}
|
||||
|
||||
if authPlugin.customPlugin != nil {
|
||||
authPlugin.customPluginHalt()
|
||||
}
|
||||
authPlugin.backends.Halt()
|
||||
}
|
||||
|
||||
func main() {}
|
||||
|
|
|
@ -11,19 +11,19 @@ func Init(authOpts map[string]string, logLevel log.Level) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func GetUser(username, password, clientid string) bool {
|
||||
func GetUser(username, password, clientid string) (bool, error) {
|
||||
log.Debugf("Checking get user with custom plugin.")
|
||||
return false
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func GetSuperuser(username string) bool {
|
||||
func GetSuperuser(username string) (bool, error) {
|
||||
log.Debugf("Checking get superuser with custom plugin.")
|
||||
return false
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func CheckAcl(username, topic, clientid string, acc int) bool {
|
||||
func CheckAcl(username, topic, clientid string, acc int) (bool, error) {
|
||||
log.Debugf("Checking acl with custom plugin.")
|
||||
return false
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func GetName() string {
|
||||
|
|
Loading…
Reference in New Issue