mirror of
https://github.com/EchoVault/SugarDB.git
synced 2025-11-03 02:13:32 +08:00
Created DefaultConfig to be used when embedding echovaule. Moved ACL and PubSub to internal packages with only the associated commands in the modules folder. Initialise ACL and PubSub when creating new EchoVault instance which removed the need to pass WithACL and WithPubSub options.
This commit is contained in:
311
internal/acl/user.go
Normal file
311
internal/acl/user.go
Normal file
@@ -0,0 +1,311 @@
|
||||
// Copyright 2024 Kelvin Clement Mwinuka
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package acl
|
||||
|
||||
import (
|
||||
"slices"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
PasswordPlainText = "plaintext"
|
||||
PasswordSHA256 = "SHA256"
|
||||
)
|
||||
|
||||
type Password struct {
|
||||
PasswordType string `json:"PasswordType" yaml:"PasswordType"` // plaintext, SHA256
|
||||
PasswordValue string `json:"PasswordValue" yaml:"PasswordValue"`
|
||||
}
|
||||
|
||||
type User struct {
|
||||
Username string `json:"Username" yaml:"Username"`
|
||||
Enabled bool `json:"Enabled" yaml:"Enabled"`
|
||||
NoPassword bool `json:"NoPassword" yaml:"NoPassword"`
|
||||
NoKeys bool `json:"NoKeys" yaml:"NoKeys"`
|
||||
|
||||
Passwords []Password `json:"Passwords" yaml:"Passwords"`
|
||||
|
||||
IncludedCategories []string `json:"IncludedCategories" yaml:"IncludedCategories"`
|
||||
ExcludedCategories []string `json:"ExcludedCategories" yaml:"ExcludedCategories"`
|
||||
|
||||
IncludedCommands []string `json:"IncludedCommands" yaml:"IncludedCommands"`
|
||||
ExcludedCommands []string `json:"ExcludedCommands" yaml:"ExcludedCommands"`
|
||||
|
||||
IncludedReadKeys []string `json:"IncludedReadKeys" yaml:"IncludedReadKeys"`
|
||||
IncludedWriteKeys []string `json:"IncludedWriteKeys" yaml:"IncludedWriteKeys"`
|
||||
|
||||
IncludedPubSubChannels []string `json:"IncludedPubSubChannels" yaml:"IncludedPubSubChannels"`
|
||||
ExcludedPubSubChannels []string `json:"ExcludedPubSubChannels" yaml:"ExcludedPubSubChannels"`
|
||||
}
|
||||
|
||||
func (user *User) Normalise() {
|
||||
user.IncludedCategories = RemoveDuplicateEntries(user.IncludedCategories, "allCategories")
|
||||
if len(user.IncludedCategories) == 0 {
|
||||
user.IncludedCategories = []string{"*"}
|
||||
}
|
||||
user.ExcludedCategories = RemoveDuplicateEntries(user.ExcludedCategories, "allCategories")
|
||||
if slices.Contains(user.ExcludedCategories, "*") {
|
||||
user.IncludedCategories = []string{}
|
||||
}
|
||||
|
||||
user.IncludedCommands = RemoveDuplicateEntries(user.IncludedCommands, "allCommands")
|
||||
if len(user.IncludedCommands) == 0 {
|
||||
user.IncludedCommands = []string{"*"}
|
||||
}
|
||||
user.ExcludedCommands = RemoveDuplicateEntries(user.ExcludedCommands, "allCommands")
|
||||
if slices.Contains(user.ExcludedCommands, "*") {
|
||||
user.IncludedCommands = []string{}
|
||||
}
|
||||
|
||||
user.IncludedReadKeys = RemoveDuplicateEntries(user.IncludedReadKeys, "allKeys")
|
||||
if len(user.IncludedReadKeys) == 0 && !user.NoKeys {
|
||||
user.IncludedReadKeys = []string{"*"}
|
||||
}
|
||||
user.IncludedWriteKeys = RemoveDuplicateEntries(user.IncludedWriteKeys, "allKeys")
|
||||
if len(user.IncludedWriteKeys) == 0 && !user.NoKeys {
|
||||
user.IncludedWriteKeys = []string{"*"}
|
||||
}
|
||||
|
||||
user.IncludedPubSubChannels = RemoveDuplicateEntries(user.IncludedPubSubChannels, "allChannels")
|
||||
if len(user.IncludedPubSubChannels) == 0 {
|
||||
user.IncludedPubSubChannels = []string{"*"}
|
||||
}
|
||||
user.ExcludedPubSubChannels = RemoveDuplicateEntries(user.ExcludedPubSubChannels, "allChannels")
|
||||
if slices.Contains(user.ExcludedPubSubChannels, "*") {
|
||||
user.IncludedPubSubChannels = []string{}
|
||||
}
|
||||
}
|
||||
|
||||
func RemoveDuplicateEntries(entries []string, allAlias string) (res []string) {
|
||||
entriesMap := make(map[string]int)
|
||||
for _, entry := range entries {
|
||||
if entry == allAlias {
|
||||
entriesMap["*"] += 1
|
||||
continue
|
||||
}
|
||||
entriesMap[entry] += 1
|
||||
}
|
||||
for key, _ := range entriesMap {
|
||||
if key == "*" {
|
||||
res = []string{"*"}
|
||||
return
|
||||
}
|
||||
res = append(res, key)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (user *User) UpdateUser(cmd []string) error {
|
||||
for _, str := range cmd {
|
||||
// Parse enabled
|
||||
if strings.EqualFold(str, "on") {
|
||||
user.Enabled = true
|
||||
}
|
||||
if strings.EqualFold(str, "off") {
|
||||
user.Enabled = false
|
||||
}
|
||||
// Parse passwords
|
||||
if str[0] == '>' || str[0] == '#' {
|
||||
user.Passwords = append(user.Passwords, Password{
|
||||
PasswordType: GetPasswordType(str),
|
||||
PasswordValue: str[1:],
|
||||
})
|
||||
user.NoPassword = false
|
||||
continue
|
||||
}
|
||||
if str[0] == '<' {
|
||||
user.Passwords = slices.DeleteFunc(user.Passwords, func(password Password) bool {
|
||||
if strings.EqualFold(password.PasswordType, PasswordSHA256) {
|
||||
return false
|
||||
}
|
||||
return password.PasswordValue == str[1:]
|
||||
})
|
||||
continue
|
||||
}
|
||||
if str[0] == '!' {
|
||||
user.Passwords = slices.DeleteFunc(user.Passwords, func(password Password) bool {
|
||||
if strings.EqualFold(password.PasswordType, PasswordPlainText) {
|
||||
return false
|
||||
}
|
||||
return password.PasswordValue == str[1:]
|
||||
})
|
||||
continue
|
||||
}
|
||||
// Parse categories
|
||||
if strings.EqualFold(str, "nocommands") {
|
||||
user.ExcludedCategories = []string{"*"}
|
||||
user.ExcludedCommands = []string{"*"}
|
||||
continue
|
||||
}
|
||||
if strings.EqualFold(str, "allCategories") {
|
||||
user.IncludedCategories = []string{"*"}
|
||||
continue
|
||||
}
|
||||
if len(str) > 3 && str[1] == '@' {
|
||||
if str[0] == '+' {
|
||||
user.IncludedCategories = append(user.IncludedCategories, str[2:])
|
||||
continue
|
||||
}
|
||||
if str[0] == '-' {
|
||||
user.ExcludedCategories = append(user.ExcludedCategories, str[2:])
|
||||
continue
|
||||
}
|
||||
}
|
||||
// Parse keys
|
||||
if strings.EqualFold(str, "allKeys") {
|
||||
user.IncludedReadKeys = []string{"*"}
|
||||
user.IncludedWriteKeys = []string{"*"}
|
||||
user.NoKeys = false
|
||||
continue
|
||||
}
|
||||
if (len(str) > 1 && str[0] == '~') || len(str) > 4 && strings.EqualFold(str[0:4], "%RW~") {
|
||||
startIndex := strings.Index(str, "~") + 1
|
||||
user.IncludedReadKeys = append(user.IncludedReadKeys, str[startIndex:])
|
||||
user.IncludedWriteKeys = append(user.IncludedWriteKeys, str[startIndex:])
|
||||
user.NoKeys = false
|
||||
continue
|
||||
}
|
||||
if len(str) > 3 && strings.EqualFold(str[0:3], "%R~") {
|
||||
user.IncludedReadKeys = append(user.IncludedReadKeys, str[3:])
|
||||
user.NoKeys = false
|
||||
continue
|
||||
}
|
||||
if len(str) > 3 && strings.EqualFold(str[0:3], "%W~") {
|
||||
user.IncludedWriteKeys = append(user.IncludedWriteKeys, str[3:])
|
||||
user.NoKeys = false
|
||||
continue
|
||||
}
|
||||
// Parse channels
|
||||
if strings.EqualFold(str, "allChannels") {
|
||||
user.IncludedPubSubChannels = []string{"*"}
|
||||
}
|
||||
if len(str) > 2 && str[1] == '&' {
|
||||
if str[0] == '+' {
|
||||
user.IncludedPubSubChannels = append(user.IncludedPubSubChannels, str[2:])
|
||||
continue
|
||||
}
|
||||
if str[0] == '-' {
|
||||
user.ExcludedPubSubChannels = append(user.ExcludedPubSubChannels, str[2:])
|
||||
continue
|
||||
}
|
||||
}
|
||||
// Parse commands
|
||||
if strings.EqualFold(str, "allCommands") {
|
||||
user.IncludedCommands = []string{"*"}
|
||||
user.ExcludedCommands = []string{}
|
||||
continue
|
||||
}
|
||||
if len(str) > 2 && !slices.Contains([]uint8{'&', '@'}, str[1]) {
|
||||
if str[0] == '+' {
|
||||
user.IncludedCommands = append(user.IncludedCommands, str[1:])
|
||||
continue
|
||||
}
|
||||
if str[0] == '-' {
|
||||
user.ExcludedCommands = append(user.ExcludedCommands, str[1:])
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If nopass is provided, delete all passwords
|
||||
for _, str := range cmd {
|
||||
if strings.EqualFold(str, "nopass") {
|
||||
user.Passwords = []Password{}
|
||||
user.NoPassword = true
|
||||
}
|
||||
}
|
||||
|
||||
for _, str := range cmd {
|
||||
// If resetpass is provided, delete all passwords and set NoPassword to false
|
||||
if strings.EqualFold(str, "resetpass") {
|
||||
user.Passwords = []Password{}
|
||||
user.NoPassword = false
|
||||
}
|
||||
// If nocommands is provided, disable all commands for this user
|
||||
if strings.EqualFold(str, "nocommands") {
|
||||
user.IncludedCommands = []string{}
|
||||
user.ExcludedCommands = []string{"*"}
|
||||
user.IncludedCategories = []string{}
|
||||
user.ExcludedCategories = []string{"*"}
|
||||
}
|
||||
// If resetkeys is provided, reset all keys that the user can access
|
||||
if strings.EqualFold(str, "resetkeys") {
|
||||
user.IncludedReadKeys = []string{}
|
||||
user.IncludedWriteKeys = []string{}
|
||||
user.NoKeys = true
|
||||
}
|
||||
// If resetchannels is provided, remove all the pub/sub channels that the user can access
|
||||
if strings.EqualFold(str, "resetchannels") {
|
||||
user.IncludedPubSubChannels = []string{}
|
||||
user.ExcludedPubSubChannels = []string{"*"}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (user *User) Merge(new *User) {
|
||||
user.Enabled = new.Enabled
|
||||
user.NoKeys = new.NoKeys
|
||||
user.NoPassword = new.NoPassword
|
||||
user.Passwords = append(user.Passwords, new.Passwords...)
|
||||
user.IncludedCategories = append(user.IncludedCategories, new.IncludedCategories...)
|
||||
user.ExcludedCategories = append(user.ExcludedCategories, new.ExcludedCategories...)
|
||||
user.IncludedCommands = append(user.IncludedCommands, new.IncludedCommands...)
|
||||
user.ExcludedCommands = append(user.ExcludedCommands, new.ExcludedCommands...)
|
||||
user.IncludedReadKeys = append(user.IncludedReadKeys, new.IncludedReadKeys...)
|
||||
user.IncludedWriteKeys = append(user.IncludedWriteKeys, new.IncludedWriteKeys...)
|
||||
user.IncludedPubSubChannels = append(user.IncludedPubSubChannels, new.IncludedPubSubChannels...)
|
||||
user.ExcludedPubSubChannels = append(user.ExcludedPubSubChannels, new.ExcludedPubSubChannels...)
|
||||
user.Normalise()
|
||||
}
|
||||
|
||||
func (user *User) Replace(new *User) {
|
||||
user.Enabled = new.Enabled
|
||||
user.NoKeys = new.NoKeys
|
||||
user.NoPassword = new.NoPassword
|
||||
user.Passwords = new.Passwords
|
||||
user.IncludedCategories = new.IncludedCategories
|
||||
user.ExcludedCategories = new.ExcludedCategories
|
||||
user.IncludedCommands = new.IncludedCommands
|
||||
user.ExcludedCommands = new.ExcludedCommands
|
||||
user.IncludedReadKeys = new.IncludedReadKeys
|
||||
user.IncludedWriteKeys = new.IncludedWriteKeys
|
||||
user.IncludedPubSubChannels = new.IncludedPubSubChannels
|
||||
user.ExcludedPubSubChannels = new.ExcludedPubSubChannels
|
||||
}
|
||||
|
||||
func CreateUser(username string) *User {
|
||||
return &User{
|
||||
Username: username,
|
||||
Enabled: true,
|
||||
NoPassword: false,
|
||||
Passwords: []Password{},
|
||||
IncludedCategories: []string{},
|
||||
ExcludedCategories: []string{},
|
||||
IncludedCommands: []string{},
|
||||
ExcludedCommands: []string{},
|
||||
IncludedReadKeys: []string{},
|
||||
IncludedWriteKeys: []string{},
|
||||
IncludedPubSubChannels: []string{},
|
||||
ExcludedPubSubChannels: []string{},
|
||||
}
|
||||
}
|
||||
|
||||
func GetPasswordType(password string) string {
|
||||
if password[0] == '#' {
|
||||
return PasswordSHA256
|
||||
}
|
||||
return PasswordPlainText
|
||||
}
|
||||
Reference in New Issue
Block a user