From fcc00071eef01a9597853ee403af24ea90bef0ef Mon Sep 17 00:00:00 2001 From: Nedyalko Dyakov Date: Tue, 4 Feb 2025 14:42:07 +0200 Subject: [PATCH 1/8] add ACL{SetUser,DelUser,List} commands --- acl_commands.go | 32 +++++++++++++++++++++++ acl_commands_test.go | 60 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+) create mode 100644 acl_commands_test.go diff --git a/acl_commands.go b/acl_commands.go index 06847be2e..6fb75c782 100644 --- a/acl_commands.go +++ b/acl_commands.go @@ -6,6 +6,9 @@ type ACLCmdable interface { ACLDryRun(ctx context.Context, username string, command ...interface{}) *StringCmd ACLLog(ctx context.Context, count int64) *ACLLogCmd ACLLogReset(ctx context.Context) *StatusCmd + ACLSetUser(ctx context.Context, username string, rules ...string) *StatusCmd + ACLDelUser(ctx context.Context, username string) *IntCmd + ACLList(ctx context.Context) *StringSliceCmd } func (c cmdable) ACLDryRun(ctx context.Context, username string, command ...interface{}) *StringCmd { @@ -33,3 +36,32 @@ func (c cmdable) ACLLogReset(ctx context.Context) *StatusCmd { _ = c(ctx, cmd) return cmd } + +func (c cmdable) ACLDelUser(ctx context.Context, username string) *IntCmd { + args := make([]interface{}, 3, 3) + args[0] = "acl" + args[1] = "deluser" + args[2] = username + cmd := NewIntCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) ACLSetUser(ctx context.Context, username string, rules ...string) *StatusCmd { + args := make([]interface{}, 3+len(rules), 3+len(rules)) + args[0] = "acl" + args[1] = "setuser" + args[2] = username + for i, rule := range rules { + args[i+3] = rule + } + cmd := NewStatusCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) ACLList(ctx context.Context) *StringSliceCmd { + cmd := NewStringSliceCmd(ctx, "acl", "list") + _ = c(ctx, cmd) + return cmd +} diff --git a/acl_commands_test.go b/acl_commands_test.go new file mode 100644 index 000000000..0c6266fe7 --- /dev/null +++ b/acl_commands_test.go @@ -0,0 +1,60 @@ +package redis_test + +import ( + "context" + + "github.com/redis/go-redis/v9" + + . "github.com/bsm/ginkgo/v2" + . "github.com/bsm/gomega" +) + +var TestUserName string = "goredis" + +var _ = Describe("ACL Commands", func() { + var client *redis.Client + var ctx context.Context + + BeforeEach(func() { + ctx = context.Background() + client = redis.NewClient(redisOptions()) + }) + + AfterEach(func() { + _, err := client.ACLDelUser(context.Background(), TestUserName).Result() + Expect(client.Close()).NotTo(HaveOccurred()) + Expect(err).NotTo(HaveOccurred()) + }) + + It("list only default user", func() { + res, err := client.ACLList(ctx).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(res).To(HaveLen(1)) + Expect(res[0]).To(ContainSubstring("default")) + }) + + It("setuser and deluser", func() { + res, err := client.ACLList(ctx).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(res).To(HaveLen(1)) + Expect(res[0]).To(ContainSubstring("default")) + + add, err := client.ACLSetUser(ctx, TestUserName, "nopass", "on", "allkeys", "+set", "+get").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(add).To(Equal("OK")) + + resAfter, err := client.ACLList(ctx).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resAfter).To(HaveLen(2)) + Expect(resAfter[1]).To(ContainSubstring(TestUserName)) + + deletedN, err := client.ACLDelUser(ctx, TestUserName).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(deletedN).To(BeNumerically("==", 1)) + + resAfterDeletion, err := client.ACLList(ctx).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resAfterDeletion).To(HaveLen(1)) + Expect(resAfterDeletion[0]).To(BeEquivalentTo(res[0])) + }) +}) From e94b310aec2e9f72edd2050f234a36ea0e4e5aad Mon Sep 17 00:00:00 2001 From: Nedyalko Dyakov Date: Tue, 4 Feb 2025 15:43:03 +0200 Subject: [PATCH 2/8] test presence of categories in acl cat --- acl_commands.go | 27 ++++++++++++++++++++++++ acl_commands_test.go | 50 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+) diff --git a/acl_commands.go b/acl_commands.go index 6fb75c782..ff0b8530a 100644 --- a/acl_commands.go +++ b/acl_commands.go @@ -4,11 +4,20 @@ import "context" type ACLCmdable interface { ACLDryRun(ctx context.Context, username string, command ...interface{}) *StringCmd + ACLLog(ctx context.Context, count int64) *ACLLogCmd ACLLogReset(ctx context.Context) *StatusCmd + ACLSetUser(ctx context.Context, username string, rules ...string) *StatusCmd ACLDelUser(ctx context.Context, username string) *IntCmd ACLList(ctx context.Context) *StringSliceCmd + + ACLCat(ctx context.Context) *StringSliceCmd + ACLCatArgs(ctx context.Context, options *ACLCatArgs) *StringSliceCmd +} + +type ACLCatArgs struct { + Category string } func (c cmdable) ACLDryRun(ctx context.Context, username string, command ...interface{}) *StringCmd { @@ -65,3 +74,21 @@ func (c cmdable) ACLList(ctx context.Context) *StringSliceCmd { _ = c(ctx, cmd) return cmd } + +func (c cmdable) ACLCat(ctx context.Context) *StringSliceCmd { + cmd := NewStringSliceCmd(ctx, "acl", "cat") + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) ACLCatArgs(ctx context.Context, options *ACLCatArgs) *StringSliceCmd { + // if there is a category passed, build new cmd, if there isn't - use the ACLCat method + if options != nil && options.Category != "" { + args := []interface{}{"acl", "cat", options.Category} + cmd := NewStringSliceCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd + } + + return c.ACLCat(ctx) +} diff --git a/acl_commands_test.go b/acl_commands_test.go index 0c6266fe7..2c0b5d9e6 100644 --- a/acl_commands_test.go +++ b/acl_commands_test.go @@ -57,4 +57,54 @@ var _ = Describe("ACL Commands", func() { Expect(resAfterDeletion).To(HaveLen(1)) Expect(resAfterDeletion[0]).To(BeEquivalentTo(res[0])) }) + + It("lists acl categories and subcategories", func() { + res, err := client.ACLCat(ctx).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(len(res)).To(BeNumerically(">", 20)) + Expect(res).To(ContainElements( + "read", + "write", + "keyspace", + "dangerous", + "slow", + "set", + "sortedset", + "list", + "hash", + )) + + res, err = client.ACLCatArgs(ctx, &redis.ACLCatArgs{Category: "read"}).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(res).To(ContainElement("get")) + }) + + It("lists acl categories and subcategories with Modules", func() { + SkipBeforeRedisMajor(8, "modules are included in acl for redis version >= 8") + aclTestCase := map[string]string{ + "search": "FT.CREATE", + "bloom": "bf.add", + "json": "json.get", + "cuckoo": "cf.insert", + "cms": "cms.query", + "topk": "topk.list", + "tdigest": "tdigest.rank", + "timeseries": "ts.range", + } + var cats []interface{} + + for cat, subitem := range aclTestCase { + cats = append(cats, cat) + + res, err := client.ACLCatArgs(ctx, &redis.ACLCatArgs{ + Category: cat, + }).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(res).To(ContainElement(subitem)) + } + + res, err := client.ACLCat(ctx).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(res).To(ContainElements(cats...)) + }) }) From c609df4e6b44411fb182bb50e3b495545aadb94e Mon Sep 17 00:00:00 2001 From: Nedyalko Dyakov Date: Tue, 4 Feb 2025 16:01:53 +0200 Subject: [PATCH 3/8] code cleanup --- acl_commands.go | 11 +++----- acl_commands_test.go | 63 +++++++++++++++++++++++++++++++++++++++++++- commands_test.go | 4 +++ 3 files changed, 69 insertions(+), 9 deletions(-) diff --git a/acl_commands.go b/acl_commands.go index ff0b8530a..9cb800bb3 100644 --- a/acl_commands.go +++ b/acl_commands.go @@ -47,17 +47,13 @@ func (c cmdable) ACLLogReset(ctx context.Context) *StatusCmd { } func (c cmdable) ACLDelUser(ctx context.Context, username string) *IntCmd { - args := make([]interface{}, 3, 3) - args[0] = "acl" - args[1] = "deluser" - args[2] = username - cmd := NewIntCmd(ctx, args...) + cmd := NewIntCmd(ctx, "acl", "deluser", username) _ = c(ctx, cmd) return cmd } func (c cmdable) ACLSetUser(ctx context.Context, username string, rules ...string) *StatusCmd { - args := make([]interface{}, 3+len(rules), 3+len(rules)) + args := make([]interface{}, 3+len(rules)) args[0] = "acl" args[1] = "setuser" args[2] = username @@ -84,8 +80,7 @@ func (c cmdable) ACLCat(ctx context.Context) *StringSliceCmd { func (c cmdable) ACLCatArgs(ctx context.Context, options *ACLCatArgs) *StringSliceCmd { // if there is a category passed, build new cmd, if there isn't - use the ACLCat method if options != nil && options.Category != "" { - args := []interface{}{"acl", "cat", options.Category} - cmd := NewStringSliceCmd(ctx, args...) + cmd := NewStringSliceCmd(ctx, "acl", "cat", options.Category) _ = c(ctx, cmd) return cmd } diff --git a/acl_commands_test.go b/acl_commands_test.go index 2c0b5d9e6..271980da8 100644 --- a/acl_commands_test.go +++ b/acl_commands_test.go @@ -10,8 +10,55 @@ import ( ) var TestUserName string = "goredis" +var _ = Describe("ACL Users Commands", Label("NonRedisEnterprise"), func() { + var client *redis.Client + var ctx context.Context + + BeforeEach(func() { + ctx = context.Background() + client = redis.NewClient(redisOptions()) + }) + + AfterEach(func() { + _, err := client.ACLDelUser(context.Background(), TestUserName).Result() + Expect(client.Close()).NotTo(HaveOccurred()) + Expect(err).NotTo(HaveOccurred()) + }) + + It("list only default user", func() { + res, err := client.ACLList(ctx).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(res).To(HaveLen(1)) + Expect(res[0]).To(ContainSubstring("default")) + }) + + It("setuser and deluser", func() { + res, err := client.ACLList(ctx).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(res).To(HaveLen(1)) + Expect(res[0]).To(ContainSubstring("default")) + + add, err := client.ACLSetUser(ctx, TestUserName, "nopass", "on", "allkeys", "+set", "+get").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(add).To(Equal("OK")) -var _ = Describe("ACL Commands", func() { + resAfter, err := client.ACLList(ctx).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resAfter).To(HaveLen(2)) + Expect(resAfter[1]).To(ContainSubstring(TestUserName)) + + deletedN, err := client.ACLDelUser(ctx, TestUserName).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(deletedN).To(BeNumerically("==", 1)) + + resAfterDeletion, err := client.ACLList(ctx).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resAfterDeletion).To(HaveLen(1)) + Expect(resAfterDeletion[0]).To(BeEquivalentTo(res[0])) + }) +}) + +var _ = Describe("ACL Permissions", Label("NonRedisEnterprise"), func() { var client *redis.Client var ctx context.Context @@ -57,6 +104,20 @@ var _ = Describe("ACL Commands", func() { Expect(resAfterDeletion).To(HaveLen(1)) Expect(resAfterDeletion[0]).To(BeEquivalentTo(res[0])) }) +}) + +var _ = Describe("ACL Categories", func() { + var client *redis.Client + var ctx context.Context + + BeforeEach(func() { + ctx = context.Background() + client = redis.NewClient(redisOptions()) + }) + + AfterEach(func() { + Expect(client.Close()).NotTo(HaveOccurred()) + }) It("lists acl categories and subcategories", func() { res, err := client.ACLCat(ctx).Result() diff --git a/commands_test.go b/commands_test.go index dacc7f3d5..fdc240c74 100644 --- a/commands_test.go +++ b/commands_test.go @@ -2313,6 +2313,10 @@ var _ = Describe("Commands", func() { limitedLogEntries, err := client.ACLLog(ctx, 2).Result() Expect(err).NotTo(HaveOccurred()) Expect(len(limitedLogEntries)).To(Equal(2)) + + // cleanup after creating the user + err = client.Do(ctx, "acl", "deluser", "test").Err() + Expect(err).NotTo(HaveOccurred()) }) It("should ACL LOG RESET", Label("NonRedisEnterprise"), func() { From 22004372af8ee4f7607eea215aa47f2066ef7181 Mon Sep 17 00:00:00 2001 From: Nedyalko Dyakov Date: Tue, 4 Feb 2025 18:32:01 +0200 Subject: [PATCH 4/8] add basic acl tests --- acl_commands_test.go | 104 +++++++++++++++++++++++++++++++++++-------- 1 file changed, 85 insertions(+), 19 deletions(-) diff --git a/acl_commands_test.go b/acl_commands_test.go index 271980da8..56882bfd1 100644 --- a/acl_commands_test.go +++ b/acl_commands_test.go @@ -21,8 +21,8 @@ var _ = Describe("ACL Users Commands", Label("NonRedisEnterprise"), func() { AfterEach(func() { _, err := client.ACLDelUser(context.Background(), TestUserName).Result() - Expect(client.Close()).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred()) + Expect(client.Close()).NotTo(HaveOccurred()) }) It("list only default user", func() { @@ -69,40 +69,106 @@ var _ = Describe("ACL Permissions", Label("NonRedisEnterprise"), func() { AfterEach(func() { _, err := client.ACLDelUser(context.Background(), TestUserName).Result() - Expect(client.Close()).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred()) + Expect(client.Close()).NotTo(HaveOccurred()) }) - It("list only default user", func() { - res, err := client.ACLList(ctx).Result() + It("reset permissions", func() { + add, err := client.ACLSetUser(ctx, + TestUserName, + "reset", + "nopass", + "on", + ).Result() Expect(err).NotTo(HaveOccurred()) - Expect(res).To(HaveLen(1)) - Expect(res[0]).To(ContainSubstring("default")) + Expect(add).To(Equal("OK")) + + connection := client.Conn() + authed, err := connection.AuthACL(ctx, TestUserName, "").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(authed).To(Equal("OK")) + + _, err = connection.Get(ctx, "anykey").Result() + Expect(err).To(HaveOccurred()) }) - It("setuser and deluser", func() { - res, err := client.ACLList(ctx).Result() + It("add write permissions", func() { + add, err := client.ACLSetUser(ctx, + TestUserName, + "reset", + "nopass", + "on", + "~*", + "+SET", + ).Result() Expect(err).NotTo(HaveOccurred()) - Expect(res).To(HaveLen(1)) - Expect(res[0]).To(ContainSubstring("default")) + Expect(add).To(Equal("OK")) - add, err := client.ACLSetUser(ctx, TestUserName, "nopass", "on", "allkeys", "+set", "+get").Result() + connection := client.Conn() + authed, err := connection.AuthACL(ctx, TestUserName, "").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(authed).To(Equal("OK")) + + // can write + v, err := connection.Set(ctx, "anykey", "anyvalue", 0).Result() + Expect(err).ToNot(HaveOccurred()) + Expect(v).To(Equal("OK")) + + // but can't read + value, err := connection.Get(ctx, "anykey").Result() + Expect(err).To(HaveOccurred()) + Expect(value).To(BeEmpty()) + }) + + It("add read permissions", func() { + add, err := client.ACLSetUser(ctx, + TestUserName, + "reset", + "nopass", + "on", + "~*", + "+GET", + ).Result() Expect(err).NotTo(HaveOccurred()) Expect(add).To(Equal("OK")) - resAfter, err := client.ACLList(ctx).Result() + connection := client.Conn() + authed, err := connection.AuthACL(ctx, TestUserName, "").Result() Expect(err).NotTo(HaveOccurred()) - Expect(resAfter).To(HaveLen(2)) - Expect(resAfter[1]).To(ContainSubstring(TestUserName)) + Expect(authed).To(Equal("OK")) - deletedN, err := client.ACLDelUser(ctx, TestUserName).Result() + // can read + value, err := connection.Get(ctx, "anykey").Result() + Expect(err).ToNot(HaveOccurred()) + Expect(value).To(Equal("anyvalue")) + + // but can't delete + del, err := connection.Del(ctx, "anykey").Result() + Expect(err).To(HaveOccurred()) + Expect(del).ToNot(Equal(1)) + }) + + It("add del permissions", func() { + add, err := client.ACLSetUser(ctx, + TestUserName, + "reset", + "nopass", + "on", + "~*", + "+DEL", + ).Result() Expect(err).NotTo(HaveOccurred()) - Expect(deletedN).To(BeNumerically("==", 1)) + Expect(add).To(Equal("OK")) - resAfterDeletion, err := client.ACLList(ctx).Result() + connection := client.Conn() + authed, err := connection.AuthACL(ctx, TestUserName, "").Result() Expect(err).NotTo(HaveOccurred()) - Expect(resAfterDeletion).To(HaveLen(1)) - Expect(resAfterDeletion[0]).To(BeEquivalentTo(res[0])) + Expect(authed).To(Equal("OK")) + + // can read + del, err := connection.Del(ctx, "anykey").Result() + Expect(err).ToNot(HaveOccurred()) + Expect(del).To(BeEquivalentTo(1)) }) }) From 5f5cc52bf627c40dba0a5bda6b8abec9f3bf45ee Mon Sep 17 00:00:00 2001 From: Nedyalko Dyakov Date: Tue, 4 Feb 2025 20:31:10 +0200 Subject: [PATCH 5/8] add acl modules tests --- acl_commands_test.go | 157 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 152 insertions(+), 5 deletions(-) diff --git a/acl_commands_test.go b/acl_commands_test.go index 56882bfd1..360562173 100644 --- a/acl_commands_test.go +++ b/acl_commands_test.go @@ -10,13 +10,15 @@ import ( ) var TestUserName string = "goredis" -var _ = Describe("ACL Users Commands", Label("NonRedisEnterprise"), func() { +var _ = Describe("ACL user commands", Label("NonRedisEnterprise"), func() { var client *redis.Client var ctx context.Context BeforeEach(func() { ctx = context.Background() - client = redis.NewClient(redisOptions()) + opt := redisOptions() + opt.UnstableResp3 = true + client = redis.NewClient(opt) }) AfterEach(func() { @@ -58,13 +60,15 @@ var _ = Describe("ACL Users Commands", Label("NonRedisEnterprise"), func() { }) }) -var _ = Describe("ACL Permissions", Label("NonRedisEnterprise"), func() { +var _ = Describe("ACL permissions", Label("NonRedisEnterprise"), func() { var client *redis.Client var ctx context.Context BeforeEach(func() { ctx = context.Background() - client = redis.NewClient(redisOptions()) + opt := redisOptions() + opt.UnstableResp3 = true + client = redis.NewClient(opt) }) AfterEach(func() { @@ -170,6 +174,147 @@ var _ = Describe("ACL Permissions", Label("NonRedisEnterprise"), func() { Expect(err).ToNot(HaveOccurred()) Expect(del).To(BeEquivalentTo(1)) }) + + It("set permissions for module commands", func() { + SkipBeforeRedisMajor(8, "permissions for modules are supported for Redis Version >=8") + Expect(client.FlushDB(ctx).Err()).NotTo(HaveOccurred()) + val, err := client.FTCreate(ctx, "txt", &redis.FTCreateOptions{}, &redis.FieldSchema{FieldName: "txt", FieldType: redis.SearchFieldTypeText}).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(val).To(BeEquivalentTo("OK")) + WaitForIndexing(client, "txt") + client.HSet(ctx, "doc1", "txt", "foo baz") + client.HSet(ctx, "doc2", "txt", "foo bar") + add, err := client.ACLSetUser(ctx, + TestUserName, + "reset", + "nopass", + "on", + "~*", + "+FT.SEARCH", + "-FT.DROPINDEX", + "+json.set", + "+json.get", + "-json.clear", + "+bf.reserve", + "-bf.info", + "+cf.reserve", + "+cms.initbydim", + "+topk.reserve", + "+tdigest.create", + "+ts.create", + "-ts.info", + ).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(add).To(Equal("OK")) + + c := client.Conn() + authed, err := c.AuthACL(ctx, TestUserName, "").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(authed).To(Equal("OK")) + + // has perm for search + Expect(c.FTSearch(ctx, "txt", "foo ~bar").Err()).NotTo(HaveOccurred()) + + // no perm for dropindex + err = c.FTDropIndex(ctx, "txt").Err() + Expect(err).ToNot(BeEmpty()) + Expect(err.Error()).To(ContainSubstring("NOPERM")) + + // json set and get have perm + Expect(c.JSONSet(ctx, "foo", "$", "\"bar\"").Err()).NotTo(HaveOccurred()) + Expect(c.JSONGet(ctx, "foo", "$").Val()).To(BeEquivalentTo("[\"bar\"]")) + + // no perm for json clear + err = c.JSONClear(ctx, "foo", "$").Err() + Expect(err).ToNot(BeEmpty()) + Expect(err.Error()).To(ContainSubstring("NOPERM")) + + // perm for reserve + Expect(c.BFReserve(ctx, "bloom", 0.01, 100).Err()).NotTo(HaveOccurred()) + + // no perm for info + err = c.BFInfo(ctx, "bloom").Err() + Expect(err).ToNot(BeEmpty()) + Expect(err.Error()).To(ContainSubstring("NOPERM")) + + // perm for cf.reserve + Expect(c.CFReserve(ctx, "cfres", 100).Err()).NotTo(HaveOccurred()) + // perm for cms.initbydim + Expect(c.CMSInitByDim(ctx, "cmsdim", 100, 5).Err()).NotTo(HaveOccurred()) + // perm for topk.reserve + Expect(c.TopKReserve(ctx, "topk", 10).Err()).NotTo(HaveOccurred()) + // perm for tdigest.create + Expect(c.TDigestCreate(ctx, "tdc").Err()).NotTo(HaveOccurred()) + // perm for ts.create + Expect(c.TSCreate(ctx, "tsts").Err()).NotTo(HaveOccurred()) + // noperm for ts.info + err = c.TSInfo(ctx, "tsts").Err() + Expect(err).ToNot(BeEmpty()) + Expect(err.Error()).To(ContainSubstring("NOPERM")) + + Expect(client.FlushDB(ctx).Err()).NotTo(HaveOccurred()) + }) + + It("set permissions for module categories", func() { + SkipBeforeRedisMajor(8, "permissions for modules are supported for Redis Version >=8") + Expect(client.FlushDB(ctx).Err()).NotTo(HaveOccurred()) + val, err := client.FTCreate(ctx, "txt", &redis.FTCreateOptions{}, &redis.FieldSchema{FieldName: "txt", FieldType: redis.SearchFieldTypeText}).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(val).To(BeEquivalentTo("OK")) + WaitForIndexing(client, "txt") + client.HSet(ctx, "doc1", "txt", "foo baz") + client.HSet(ctx, "doc2", "txt", "foo bar") + add, err := client.ACLSetUser(ctx, + TestUserName, + "reset", + "nopass", + "on", + "~*", + "+@search", + "+@json", + "+@bloom", + "+@cuckoo", + "+@topk", + "+@cms", + "+@timeseries", + "+@tdigest", + ).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(add).To(Equal("OK")) + + c := client.Conn() + authed, err := c.AuthACL(ctx, TestUserName, "").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(authed).To(Equal("OK")) + + // has perm for search + Expect(c.FTSearch(ctx, "txt", "foo ~bar").Err()).NotTo(HaveOccurred()) + // perm for dropindex + Expect(c.FTDropIndex(ctx, "txt").Err()).NotTo(HaveOccurred()) + // json set and get have perm + Expect(c.JSONSet(ctx, "foo", "$", "\"bar\"").Err()).NotTo(HaveOccurred()) + Expect(c.JSONGet(ctx, "foo", "$").Val()).To(BeEquivalentTo("[\"bar\"]")) + // perm for json clear + Expect(c.JSONClear(ctx, "foo", "$").Err()).NotTo(HaveOccurred()) + // perm for reserve + Expect(c.BFReserve(ctx, "bloom", 0.01, 100).Err()).NotTo(HaveOccurred()) + // perm for info + Expect(c.BFInfo(ctx, "bloom").Err()).NotTo(HaveOccurred()) + // perm for cf.reserve + Expect(c.CFReserve(ctx, "cfres", 100).Err()).NotTo(HaveOccurred()) + // perm for cms.initbydim + Expect(c.CMSInitByDim(ctx, "cmsdim", 100, 5).Err()).NotTo(HaveOccurred()) + // perm for topk.reserve + Expect(c.TopKReserve(ctx, "topk", 10).Err()).NotTo(HaveOccurred()) + // perm for tdigest.create + Expect(c.TDigestCreate(ctx, "tdc").Err()).NotTo(HaveOccurred()) + // perm for ts.create + Expect(c.TSCreate(ctx, "tsts").Err()).NotTo(HaveOccurred()) + // perm for ts.info + Expect(c.TSInfo(ctx, "tsts").Err()).NotTo(HaveOccurred()) + + Expect(client.FlushDB(ctx).Err()).NotTo(HaveOccurred()) + }) }) var _ = Describe("ACL Categories", func() { @@ -178,7 +323,9 @@ var _ = Describe("ACL Categories", func() { BeforeEach(func() { ctx = context.Background() - client = redis.NewClient(redisOptions()) + opt := redisOptions() + opt.UnstableResp3 = true + client = redis.NewClient(opt) }) AfterEach(func() { From 1bdb7a792315f566cec40eecea7e2a61c8c00316 Mon Sep 17 00:00:00 2001 From: Nedyalko Dyakov Date: Tue, 4 Feb 2025 20:47:43 +0200 Subject: [PATCH 6/8] reset acl log before test --- commands_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/commands_test.go b/commands_test.go index fdc240c74..89fbd770c 100644 --- a/commands_test.go +++ b/commands_test.go @@ -2283,6 +2283,7 @@ var _ = Describe("Commands", func() { }) It("should ACL LOG", Label("NonRedisEnterprise"), func() { + Expect(client.ACLLogReset(ctx).Err()).NotTo(HaveOccurred()) err := client.Do(ctx, "acl", "setuser", "test", ">test", "on", "allkeys", "+get").Err() Expect(err).NotTo(HaveOccurred()) From 8cfff6106d75d99fedd9d4967cdfd07e6ad61113 Mon Sep 17 00:00:00 2001 From: Nedyalko Dyakov Date: Wed, 5 Feb 2025 11:39:41 +0200 Subject: [PATCH 7/8] refactor acl tests --- acl_commands_test.go | 69 ++++++++++++++++++++++++++++++++++++++++++-- commands_test.go | 56 ----------------------------------- 2 files changed, 67 insertions(+), 58 deletions(-) diff --git a/acl_commands_test.go b/acl_commands_test.go index 360562173..846455831 100644 --- a/acl_commands_test.go +++ b/acl_commands_test.go @@ -10,6 +10,67 @@ import ( ) var TestUserName string = "goredis" +var _ = Describe("ACL", func() { + var client *redis.Client + var ctx context.Context + + BeforeEach(func() { + ctx = context.Background() + opt := redisOptions() + client = redis.NewClient(opt) + }) + + It("should ACL LOG", Label("NonRedisEnterprise"), func() { + Expect(client.ACLLogReset(ctx).Err()).NotTo(HaveOccurred()) + err := client.Do(ctx, "acl", "setuser", "test", ">test", "on", "allkeys", "+get").Err() + Expect(err).NotTo(HaveOccurred()) + + clientAcl := redis.NewClient(redisOptions()) + clientAcl.Options().Username = "test" + clientAcl.Options().Password = "test" + clientAcl.Options().DB = 0 + _ = clientAcl.Set(ctx, "mystring", "foo", 0).Err() + _ = clientAcl.HSet(ctx, "myhash", "foo", "bar").Err() + _ = clientAcl.SAdd(ctx, "myset", "foo", "bar").Err() + + logEntries, err := client.ACLLog(ctx, 10).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(len(logEntries)).To(Equal(4)) + + for _, entry := range logEntries { + Expect(entry.Reason).To(Equal("command")) + Expect(entry.Context).To(Equal("toplevel")) + Expect(entry.Object).NotTo(BeEmpty()) + Expect(entry.Username).To(Equal("test")) + Expect(entry.AgeSeconds).To(BeNumerically(">=", 0)) + Expect(entry.ClientInfo).NotTo(BeNil()) + Expect(entry.EntryID).To(BeNumerically(">=", 0)) + Expect(entry.TimestampCreated).To(BeNumerically(">=", 0)) + Expect(entry.TimestampLastUpdated).To(BeNumerically(">=", 0)) + } + + limitedLogEntries, err := client.ACLLog(ctx, 2).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(len(limitedLogEntries)).To(Equal(2)) + + // cleanup after creating the user + err = client.Do(ctx, "acl", "deluser", "test").Err() + Expect(err).NotTo(HaveOccurred()) + }) + + It("should ACL LOG RESET", Label("NonRedisEnterprise"), func() { + // Call ACL LOG RESET + resetCmd := client.ACLLogReset(ctx) + Expect(resetCmd.Err()).NotTo(HaveOccurred()) + Expect(resetCmd.Val()).To(Equal("OK")) + + // Verify that the log is empty after the reset + logEntries, err := client.ACLLog(ctx, 10).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(len(logEntries)).To(Equal(0)) + }) + +}) var _ = Describe("ACL user commands", Label("NonRedisEnterprise"), func() { var client *redis.Client var ctx context.Context @@ -17,7 +78,6 @@ var _ = Describe("ACL user commands", Label("NonRedisEnterprise"), func() { BeforeEach(func() { ctx = context.Background() opt := redisOptions() - opt.UnstableResp3 = true client = redis.NewClient(opt) }) @@ -58,6 +118,12 @@ var _ = Describe("ACL user commands", Label("NonRedisEnterprise"), func() { Expect(resAfterDeletion).To(HaveLen(1)) Expect(resAfterDeletion[0]).To(BeEquivalentTo(res[0])) }) + + It("should acl dryrun", func() { + dryRun := client.ACLDryRun(ctx, "default", "get", "randomKey") + Expect(dryRun.Err()).NotTo(HaveOccurred()) + Expect(dryRun.Val()).To(Equal("OK")) + }) }) var _ = Describe("ACL permissions", Label("NonRedisEnterprise"), func() { @@ -324,7 +390,6 @@ var _ = Describe("ACL Categories", func() { BeforeEach(func() { ctx = context.Background() opt := redisOptions() - opt.UnstableResp3 = true client = redis.NewClient(opt) }) diff --git a/commands_test.go b/commands_test.go index 89fbd770c..1fede32ff 100644 --- a/commands_test.go +++ b/commands_test.go @@ -2228,12 +2228,6 @@ var _ = Describe("Commands", func() { Expect(replace.Val()).To(Equal(int64(1))) }) - It("should acl dryrun", func() { - dryRun := client.ACLDryRun(ctx, "default", "get", "randomKey") - Expect(dryRun.Err()).NotTo(HaveOccurred()) - Expect(dryRun.Val()).To(Equal("OK")) - }) - It("should fail module loadex", Label("NonRedisEnterprise"), func() { dryRun := client.ModuleLoadex(ctx, &redis.ModuleLoadexConfig{ Path: "/path/to/non-existent-library.so", @@ -2281,56 +2275,6 @@ var _ = Describe("Commands", func() { Expect(args).To(Equal(expectedArgs)) }) - - It("should ACL LOG", Label("NonRedisEnterprise"), func() { - Expect(client.ACLLogReset(ctx).Err()).NotTo(HaveOccurred()) - err := client.Do(ctx, "acl", "setuser", "test", ">test", "on", "allkeys", "+get").Err() - Expect(err).NotTo(HaveOccurred()) - - clientAcl := redis.NewClient(redisOptions()) - clientAcl.Options().Username = "test" - clientAcl.Options().Password = "test" - clientAcl.Options().DB = 0 - _ = clientAcl.Set(ctx, "mystring", "foo", 0).Err() - _ = clientAcl.HSet(ctx, "myhash", "foo", "bar").Err() - _ = clientAcl.SAdd(ctx, "myset", "foo", "bar").Err() - - logEntries, err := client.ACLLog(ctx, 10).Result() - Expect(err).NotTo(HaveOccurred()) - Expect(len(logEntries)).To(Equal(4)) - - for _, entry := range logEntries { - Expect(entry.Reason).To(Equal("command")) - Expect(entry.Context).To(Equal("toplevel")) - Expect(entry.Object).NotTo(BeEmpty()) - Expect(entry.Username).To(Equal("test")) - Expect(entry.AgeSeconds).To(BeNumerically(">=", 0)) - Expect(entry.ClientInfo).NotTo(BeNil()) - Expect(entry.EntryID).To(BeNumerically(">=", 0)) - Expect(entry.TimestampCreated).To(BeNumerically(">=", 0)) - Expect(entry.TimestampLastUpdated).To(BeNumerically(">=", 0)) - } - - limitedLogEntries, err := client.ACLLog(ctx, 2).Result() - Expect(err).NotTo(HaveOccurred()) - Expect(len(limitedLogEntries)).To(Equal(2)) - - // cleanup after creating the user - err = client.Do(ctx, "acl", "deluser", "test").Err() - Expect(err).NotTo(HaveOccurred()) - }) - - It("should ACL LOG RESET", Label("NonRedisEnterprise"), func() { - // Call ACL LOG RESET - resetCmd := client.ACLLogReset(ctx) - Expect(resetCmd.Err()).NotTo(HaveOccurred()) - Expect(resetCmd.Val()).To(Equal("OK")) - - // Verify that the log is empty after the reset - logEntries, err := client.ACLLog(ctx, 10).Result() - Expect(err).NotTo(HaveOccurred()) - Expect(len(logEntries)).To(Equal(0)) - }) }) Describe("hashes", func() { From 555f384e112ca37e573bbf90fd51f2615c159c90 Mon Sep 17 00:00:00 2001 From: Nedyalko Dyakov Date: Wed, 5 Feb 2025 11:47:43 +0200 Subject: [PATCH 8/8] fix clientkillbyage test --- commands_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/commands_test.go b/commands_test.go index 1fede32ff..404ffd02b 100644 --- a/commands_test.go +++ b/commands_test.go @@ -211,13 +211,13 @@ var _ = Describe("Commands", func() { select { case <-done: Fail("BLPOP is not blocked.") - case <-time.After(2 * time.Second): + case <-time.After(1 * time.Second): // ok } killed := client.ClientKillByFilter(ctx, "MAXAGE", "1") Expect(killed.Err()).NotTo(HaveOccurred()) - Expect(killed.Val()).To(SatisfyAny(Equal(int64(2)), Equal(int64(3)))) + Expect(killed.Val()).To(SatisfyAny(Equal(int64(2)), Equal(int64(3)), Equal(int64(4)))) select { case <-done: