From a02007ce6b0e25ee274fa6afbf3442a610c81986 Mon Sep 17 00:00:00 2001 From: joyz Date: Sun, 27 Oct 2019 08:58:41 +0800 Subject: [PATCH] update --- plugins/auth/acl/acl.go | 28 +++++++ plugins/auth/acl/aclcheck.go | 148 ++++++++++++++++++++++++++++++++++ plugins/auth/acl/aclconfig.go | 114 ++++++++++++++++++++++++++ plugins/auth/acl/spilt.go | 52 ++++++++++++ 4 files changed, 342 insertions(+) create mode 100644 plugins/auth/acl/acl.go create mode 100644 plugins/auth/acl/aclcheck.go create mode 100644 plugins/auth/acl/aclconfig.go create mode 100644 plugins/auth/acl/spilt.go diff --git a/plugins/auth/acl/acl.go b/plugins/auth/acl/acl.go new file mode 100644 index 0000000..478074b --- /dev/null +++ b/plugins/auth/acl/acl.go @@ -0,0 +1,28 @@ +package acl + +type aclAuth struct { + config *ACLConfig +} + +func Init() *aclAuth { + aclConfig, err := AclConfigLoad("./plugins/auth/authhttp/http.json") + if err != nil { + panic(err) + } + return &aclAuth{ + config: aclConfig, + } +} + +func (a *aclAuth) CheckConnect(clientID, username, password string) bool { + return checkTopicAuth(a.config, clientID, username, password) +} + +func (a *aclAuth) CheckACL(action, username, topic string) bool { + return checkTopicAuth(a.config, action, clientID, username, password) +} + +// type Auth interface { +// CheckACL(action, username, topic string) bool +// CheckConnect(clientID, username, password string) bool +// } diff --git a/plugins/auth/acl/aclcheck.go b/plugins/auth/acl/aclcheck.go new file mode 100644 index 0000000..8bf77ed --- /dev/null +++ b/plugins/auth/acl/aclcheck.go @@ -0,0 +1,148 @@ +package acl + +import "strings" + +func checkTopicAuth(ACLInfo *ACLConfig, typ int, ip, username, clientid, topic string) bool { + for _, info := range ACLInfo.Info { + ctyp := info.Typ + switch ctyp { + case CLIENTID: + if match, auth := info.checkWithClientID(typ, clientid, topic); match { + return auth + } + case USERNAME: + if match, auth := info.checkWithUsername(typ, username, topic); match { + return auth + } + case IP: + if match, auth := info.checkWithIP(typ, ip, topic); match { + return auth + } + } + } + return false +} + +func (a *AuthInfo) checkWithClientID(typ int, clientid, topic string) (bool, bool) { + auth := false + match := false + if a.Val == "*" || a.Val == clientid { + for _, tp := range a.Topics { + des := strings.Replace(tp, "%c", clientid, -1) + if typ == PUB { + if pubTopicMatch(topic, des) { + match = true + auth = a.checkAuth(PUB) + } + } else if typ == SUB { + if subTopicMatch(topic, des) { + match = true + auth = a.checkAuth(SUB) + } + } + } + } + return match, auth +} + +func (a *AuthInfo) checkWithUsername(typ int, username, topic string) (bool, bool) { + auth := false + match := false + if a.Val == "*" || a.Val == username { + for _, tp := range a.Topics { + des := strings.Replace(tp, "%u", username, -1) + if typ == PUB { + if pubTopicMatch(topic, des) { + match = true + auth = a.checkAuth(PUB) + } + } else if typ == SUB { + if subTopicMatch(topic, des) { + match = true + auth = a.checkAuth(SUB) + } + } + } + } + return match, auth +} + +func (a *AuthInfo) checkWithIP(typ int, ip, topic string) (bool, bool) { + auth := false + match := false + if a.Val == "*" || a.Val == ip { + for _, tp := range a.Topics { + des := tp + if typ == PUB { + if pubTopicMatch(topic, des) { + auth = a.checkAuth(PUB) + match = true + } + } else if typ == SUB { + if subTopicMatch(topic, des) { + auth = a.checkAuth(SUB) + match = true + } + } + } + } + return match, auth +} + +func (a *AuthInfo) checkAuth(typ int) bool { + auth := false + if typ == PUB { + if a.Auth == ALLOW && (a.PubSub == PUB || a.PubSub == PUBSUB) { + auth = true + } else if a.Auth == DENY && a.PubSub == SUB { + auth = true + } + } else if typ == SUB { + if a.Auth == ALLOW && (a.PubSub == SUB || a.PubSub == PUBSUB) { + auth = true + } else if a.Auth == DENY && a.PubSub == PUB { + auth = true + } + } + return auth +} + +func pubTopicMatch(pub, des string) bool { + dest, _ := SubscribeTopicSpilt(des) + topic, _ := PublishTopicSpilt(pub) + for i, t := range dest { + if i > len(topic)-1 { + return false + } + if t == "#" { + return true + } + if t == "+" || t == topic[i] { + continue + } + if t != topic[i] { + return false + } + } + return true +} + +func subTopicMatch(pub, des string) bool { + dest, _ := SubscribeTopicSpilt(des) + topic, _ := SubscribeTopicSpilt(pub) + for i, t := range dest { + if i > len(topic)-1 { + return false + } + if t == "#" { + return true + } + if t == "+" || "+" == topic[i] || t == topic[i] { + continue + } + if t != topic[i] { + return false + } + } + return true +} diff --git a/plugins/auth/acl/aclconfig.go b/plugins/auth/acl/aclconfig.go new file mode 100644 index 0000000..9cfe14a --- /dev/null +++ b/plugins/auth/acl/aclconfig.go @@ -0,0 +1,114 @@ +package acl + +import ( + "bufio" + "errors" + "io" + "os" + "strconv" + "strings" +) + +const ( + PUB = 1 + SUB = 2 + PUBSUB = 3 + CLIENTID = "clientid" + USERNAME = "username" + IP = "ip" + ALLOW = "allow" + DENY = "deny" +) + +type AuthInfo struct { + Auth string + Typ string + Val string + PubSub int + Topics []string +} + +type ACLConfig struct { + File string + Info []*AuthInfo +} + +func AclConfigLoad(file string) (*ACLConfig, error) { + if file == "" { + file = "./conf/acl.conf" + } + aclconifg := &ACLConfig{ + File: file, + Info: make([]*AuthInfo, 0, 4), + } + err := aclconifg.Prase() + if err != nil { + return nil, err + } + return aclconifg, err +} + +func (c *ACLConfig) Prase() error { + f, err := os.Open(c.File) + defer f.Close() + if err != nil { + return err + } + buf := bufio.NewReader(f) + var parseErr error + for { + line, err := buf.ReadString('\n') + line = strings.TrimSpace(line) + if isCommentOut(line) { + continue + } + if line == "" { + return parseErr + } + // fmt.Println(line) + tmpArr := strings.Fields(line) + if len(tmpArr) != 5 { + parseErr = errors.New("\"" + line + "\" format is error") + break + } + if tmpArr[0] != ALLOW && tmpArr[0] != DENY { + parseErr = errors.New("\"" + line + "\" format is error") + break + } + if tmpArr[1] != CLIENTID && tmpArr[1] != USERNAME && tmpArr[1] != IP { + parseErr = errors.New("\"" + line + "\" format is error") + break + } + var pubsub int + pubsub, err = strconv.Atoi(tmpArr[3]) + if err != nil { + parseErr = errors.New("\"" + line + "\" format is error") + break + } + topicStr := strings.Replace(tmpArr[4], " ", "", -1) + topicStr = strings.Replace(topicStr, "\n", "", -1) + topics := strings.Split(topicStr, ",") + tmpAuth := &AuthInfo{ + Auth: tmpArr[0], + Typ: tmpArr[1], + Val: tmpArr[2], + Topics: topics, + PubSub: pubsub, + } + c.Info = append(c.Info, tmpAuth) + if err != nil { + if err != io.EOF { + parseErr = err + } + break + } + } + return parseErr +} +func isCommentOut(line string) bool { + if strings.HasPrefix(line, "#") || strings.HasPrefix(line, ";") || strings.HasPrefix(line, "//") || strings.HasPrefix(line, "*") { + return true + } else { + return false + } +} diff --git a/plugins/auth/acl/spilt.go b/plugins/auth/acl/spilt.go new file mode 100644 index 0000000..3df0c9d --- /dev/null +++ b/plugins/auth/acl/spilt.go @@ -0,0 +1,52 @@ +package acl + +import ( + "bytes" + "errors" + "strings" +) + +func SubscribeTopicSpilt(topic string) ([]string, error) { + subject := []byte(topic) + if bytes.IndexByte(subject, '#') != -1 { + if bytes.IndexByte(subject, '#') != len(subject)-1 { + return nil, errors.New("Topic format error with index of #") + } + } + re := strings.Split(topic, "/") + for i, v := range re { + if i != 0 && i != (len(re)-1) { + if v == "" { + return nil, errors.New("Topic format error with index of //") + } + if strings.Contains(v, "+") && v != "+" { + return nil, errors.New("Topic format error with index of +") + } + } else { + if v == "" { + re[i] = "/" + } + } + } + return re, nil + +} + +func PublishTopicSpilt(topic string) ([]string, error) { + subject := []byte(topic) + if bytes.IndexByte(subject, '#') != -1 || bytes.IndexByte(subject, '+') != -1 { + return nil, errors.New("Publish Topic format error with + and #") + } + re := strings.Split(topic, "/") + for i, v := range re { + if v == "" { + if i != 0 && i != (len(re)-1) { + return nil, errors.New("Topic format error with index of //") + } else { + re[i] = "/" + } + } + + } + return re, nil +}