forked from mirror/ledisdb
commit
f0dab4218f
|
@ -22,6 +22,7 @@ LedisDB now supports multiple different databases as backends.
|
||||||
+ Replication to guarantee data safety.
|
+ Replication to guarantee data safety.
|
||||||
+ Supplies tools to load, dump, and repair database.
|
+ Supplies tools to load, dump, and repair database.
|
||||||
+ Supports cluster, use [xcodis](https://github.com/siddontang/xcodis)
|
+ Supports cluster, use [xcodis](https://github.com/siddontang/xcodis)
|
||||||
|
+ Authentication (though, not via http)
|
||||||
|
|
||||||
## Build and Install
|
## Build and Install
|
||||||
|
|
||||||
|
|
|
@ -91,6 +91,8 @@ type SnapshotConfig struct {
|
||||||
type Config struct {
|
type Config struct {
|
||||||
m sync.RWMutex `toml:"-"`
|
m sync.RWMutex `toml:"-"`
|
||||||
|
|
||||||
|
AuthPassword string `toml:"auth_password"`
|
||||||
|
|
||||||
FileName string `toml:"-"`
|
FileName string `toml:"-"`
|
||||||
|
|
||||||
Addr string `toml:"addr"`
|
Addr string `toml:"addr"`
|
||||||
|
@ -168,6 +170,9 @@ func NewConfigDefault() *Config {
|
||||||
cfg.SlaveOf = ""
|
cfg.SlaveOf = ""
|
||||||
cfg.Readonly = false
|
cfg.Readonly = false
|
||||||
|
|
||||||
|
// Disable Auth by default, by setting password to blank
|
||||||
|
cfg.AuthPassword = ""
|
||||||
|
|
||||||
// default databases number
|
// default databases number
|
||||||
cfg.Databases = 16
|
cfg.Databases = 16
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,9 @@ slaveof = ""
|
||||||
# for readonly mode, only replication and flushall can write
|
# for readonly mode, only replication and flushall can write
|
||||||
readonly = false
|
readonly = false
|
||||||
|
|
||||||
|
# Authentication (for non-http connections). Connect, then use the AUTH command to authenticate.
|
||||||
|
# auth_password = "russellwashere"
|
||||||
|
|
||||||
# Choose which backend storage to use, now support:
|
# Choose which backend storage to use, now support:
|
||||||
#
|
#
|
||||||
# leveldb
|
# leveldb
|
||||||
|
|
|
@ -10,9 +10,49 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var testAppOnce sync.Once
|
var testAppOnce sync.Once
|
||||||
|
var testAppAuthOnce sync.Once
|
||||||
var testApp *App
|
var testApp *App
|
||||||
|
|
||||||
var testLedisClient *goredis.Client
|
var testLedisClient *goredis.Client
|
||||||
|
var testLedisClientAuth *goredis.Client
|
||||||
|
|
||||||
|
func getTestConnAuth(password string) *goredis.PoolConn {
|
||||||
|
startTestAppAuth(password)
|
||||||
|
conn, _ := testLedisClientAuth.Get()
|
||||||
|
return conn
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTestLedisClientAuth() {
|
||||||
|
testLedisClientAuth = goredis.NewClient("127.0.0.1:20000", "")
|
||||||
|
testLedisClientAuth.SetMaxIdleConns(4)
|
||||||
|
}
|
||||||
|
|
||||||
|
func startTestAppAuth(password string) {
|
||||||
|
f := func() {
|
||||||
|
newTestLedisClientAuth()
|
||||||
|
|
||||||
|
cfg := config.NewConfigDefault()
|
||||||
|
cfg.DataDir = "/tmp/testdb_auth"
|
||||||
|
os.RemoveAll(cfg.DataDir)
|
||||||
|
|
||||||
|
cfg.Addr = "127.0.0.1:20000"
|
||||||
|
cfg.HttpAddr = "127.0.0.1:20001"
|
||||||
|
cfg.AuthPassword = password
|
||||||
|
|
||||||
|
os.RemoveAll(cfg.DataDir)
|
||||||
|
|
||||||
|
var err error
|
||||||
|
testApp, err = NewApp(cfg)
|
||||||
|
if err != nil {
|
||||||
|
println(err.Error())
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
go testApp.Run()
|
||||||
|
}
|
||||||
|
|
||||||
|
testAppAuthOnce.Do(f)
|
||||||
|
}
|
||||||
|
|
||||||
func newTestLedisClient() {
|
func newTestLedisClient() {
|
||||||
testLedisClient = goredis.NewClient("127.0.0.1:16380", "")
|
testLedisClient = goredis.NewClient("127.0.0.1:16380", "")
|
||||||
|
@ -36,7 +76,7 @@ func startTestApp() {
|
||||||
cfg.Addr = "127.0.0.1:16380"
|
cfg.Addr = "127.0.0.1:16380"
|
||||||
cfg.HttpAddr = "127.0.0.1:21181"
|
cfg.HttpAddr = "127.0.0.1:21181"
|
||||||
|
|
||||||
os.RemoveAll("/tmp/testdb")
|
os.RemoveAll(cfg.DataDir)
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
testApp, err = NewApp(cfg)
|
testApp, err = NewApp(cfg)
|
||||||
|
|
|
@ -64,6 +64,8 @@ type client struct {
|
||||||
cmd string
|
cmd string
|
||||||
args [][]byte
|
args [][]byte
|
||||||
|
|
||||||
|
isAuthed bool
|
||||||
|
|
||||||
resp responseWriter
|
resp responseWriter
|
||||||
|
|
||||||
syncBuf bytes.Buffer
|
syncBuf bytes.Buffer
|
||||||
|
@ -86,6 +88,7 @@ func newClient(app *App) *client {
|
||||||
|
|
||||||
c.app = app
|
c.app = app
|
||||||
c.ldb = app.ldb
|
c.ldb = app.ldb
|
||||||
|
c.isAuthed = false || c.authEnabled()
|
||||||
c.db, _ = app.ldb.Select(0) //use default db
|
c.db, _ = app.ldb.Select(0) //use default db
|
||||||
|
|
||||||
return c
|
return c
|
||||||
|
@ -95,6 +98,10 @@ func (c *client) close() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *client) authEnabled() bool {
|
||||||
|
return len(c.app.cfg.AuthPassword) > 0
|
||||||
|
}
|
||||||
|
|
||||||
func (c *client) perform() {
|
func (c *client) perform() {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
|
@ -104,6 +111,8 @@ func (c *client) perform() {
|
||||||
err = ErrEmptyCommand
|
err = ErrEmptyCommand
|
||||||
} else if exeCmd, ok := regCmds[c.cmd]; !ok {
|
} else if exeCmd, ok := regCmds[c.cmd]; !ok {
|
||||||
err = ErrNotFound
|
err = ErrNotFound
|
||||||
|
} else if c.authEnabled() && !c.isAuthed && c.cmd != "auth" {
|
||||||
|
err = ErrNotAuthenticated
|
||||||
} else {
|
} else {
|
||||||
// if c.db.IsTransaction() {
|
// if c.db.IsTransaction() {
|
||||||
// if _, ok := txUnsupportedCmds[c.cmd]; ok {
|
// if _, ok := txUnsupportedCmds[c.cmd]; ok {
|
||||||
|
@ -120,7 +129,6 @@ func (c *client) perform() {
|
||||||
// }
|
// }
|
||||||
|
|
||||||
err = exeCmd(c)
|
err = exeCmd(c)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.app.access != nil {
|
if c.app.access != nil {
|
||||||
|
|
|
@ -14,6 +14,21 @@ func pingCommand(c *client) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func authCommand(c *client) error {
|
||||||
|
if len(c.args) != 1 {
|
||||||
|
return ErrCmdParams
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.app.cfg.AuthPassword == string(c.args[0]) {
|
||||||
|
c.isAuthed = true
|
||||||
|
c.resp.writeStatus(OK)
|
||||||
|
return nil
|
||||||
|
} else {
|
||||||
|
c.isAuthed = false
|
||||||
|
return ErrAuthenticationFailure
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func echoCommand(c *client) error {
|
func echoCommand(c *client) error {
|
||||||
if len(c.args) != 1 {
|
if len(c.args) != 1 {
|
||||||
return ErrCmdParams
|
return ErrCmdParams
|
||||||
|
@ -156,6 +171,7 @@ func configCommand(c *client) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
register("auth", authCommand)
|
||||||
register("ping", pingCommand)
|
register("ping", pingCommand)
|
||||||
register("echo", echoCommand)
|
register("echo", echoCommand)
|
||||||
register("select", selectCommand)
|
register("select", selectCommand)
|
||||||
|
|
|
@ -6,6 +6,50 @@ import (
|
||||||
"github.com/siddontang/goredis"
|
"github.com/siddontang/goredis"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestAuth(t *testing.T) {
|
||||||
|
c1 := getTestConn()
|
||||||
|
defer c1.Close()
|
||||||
|
|
||||||
|
// Should error, no params
|
||||||
|
_, err := c1.Do("AUTH")
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Should error, invalid pass
|
||||||
|
_, err = c1.Do("AUTH", "password")
|
||||||
|
if err.Error() != " authentication failure" {
|
||||||
|
t.Fatal("Expected authentication error:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
c2 := getTestConnAuth("password")
|
||||||
|
defer c2.Close()
|
||||||
|
|
||||||
|
// Login
|
||||||
|
_, err = c2.Do("AUTH", "password")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Should be ok doing a command
|
||||||
|
_, err = c2.Do("GET", "tmp_select_key")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log out by sending wrong pass
|
||||||
|
_, err = c2.Do("AUTH", "wrong password")
|
||||||
|
if err.Error() != " authentication failure" {
|
||||||
|
t.Fatal("Expected authentication error:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Should fail doing a command as we're logged out
|
||||||
|
_, err = c2.Do("GET", "tmp_select_key")
|
||||||
|
if err.Error() != " not authenticated" {
|
||||||
|
t.Fatal("Expected authentication error:", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestXSelect(t *testing.T) {
|
func TestXSelect(t *testing.T) {
|
||||||
c1 := getTestConn()
|
c1 := getTestConn()
|
||||||
defer c1.Close()
|
defer c1.Close()
|
||||||
|
|
|
@ -7,13 +7,15 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrEmptyCommand = errors.New("empty command")
|
ErrEmptyCommand = errors.New("empty command")
|
||||||
ErrNotFound = errors.New("command not found")
|
ErrNotFound = errors.New("command not found")
|
||||||
ErrCmdParams = errors.New("invalid command param")
|
ErrNotAuthenticated = errors.New("not authenticated")
|
||||||
ErrValue = errors.New("value is not an integer or out of range")
|
ErrAuthenticationFailure = errors.New("authentication failure")
|
||||||
ErrSyntax = errors.New("syntax error")
|
ErrCmdParams = errors.New("invalid command param")
|
||||||
ErrOffset = errors.New("offset bit is not an natural number")
|
ErrValue = errors.New("value is not an integer or out of range")
|
||||||
ErrBool = errors.New("value is not 0 or 1")
|
ErrSyntax = errors.New("syntax error")
|
||||||
|
ErrOffset = errors.New("offset bit is not an natural number")
|
||||||
|
ErrBool = errors.New("value is not 0 or 1")
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
Loading…
Reference in New Issue