Skip to content

Commit d47d4df

Browse files
committed
Add redis adapter support
1 parent e2ed23a commit d47d4df

6 files changed

Lines changed: 148 additions & 5 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ Alternatively, you can also [run it from an IDE](https://github.com/casbin/casbi
7676

7777
Similar to Casbin, Casbin-Server also uses adapters to provide policy storage. However, because Casbin-Server is a service instead of a library, the adapters have to be implemented inside Casbin-Server. As Golang is a static language, each adapter requires to import 3rd-party library for that database. We cannot import all those 3rd-party libraries inside Casbin-Server's code, as it causes dependency overhead.
7878

79-
For now, only [Gorm Adapter](https://github.com/casbin/casbin-server/blob/master/server/adapter.go) is built-in with ``mssql``, ``mysql``, ``postgres`` imports all commented. If you want to use ``Gorm Adapter`` with one of those databases, you should uncomment that import line, or add your own import, or even use another adapter by modifying Casbin-Server's source code.
79+
For now, [Gorm Adapter](https://github.com/casbin/casbin-server/blob/master/server/adapter.go) (with ``mssql``, ``mysql``, ``postgres``), MongoDB und Redis Adapter are built-in imports all commented. If you want to use ``Gorm Adapter`` with one of those databases, you should uncomment that import line, or add your own import, or even use another adapter by modifying Casbin-Server's source code.
8080

8181
To allow Casbin-Server to be production-ready, the adapter configuration supports environment variables. For example, assume we created a ``postgres`` database for our RBAC model and want Casbin-Server to use it. Assuming that the environment in which the Casbin-Server runs contains the necessary variables, we can simply use the ``$ENV_VAR`` notation to provide these to the adapter.
8282

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"driver": "redis",
3+
"connection": "localhost:6379",
4+
"enforcer": "examples/rbac_model.conf"
5+
}

go.mod

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,17 @@ module github.com/casbin/casbin-server
33
go 1.19
44

55
require (
6+
github.com/alicebob/miniredis/v2 v2.35.0
67
github.com/casbin/casbin/v2 v2.100.0
78
github.com/casbin/gorm-adapter/v3 v3.14.0
89
github.com/casbin/mongodb-adapter/v3 v3.7.0
10+
github.com/casbin/redis-adapter/v3 v3.6.0
911
github.com/stretchr/testify v1.8.0
1012
google.golang.org/grpc v1.42.0
1113
google.golang.org/protobuf v1.27.1
1214
)
1315

1416
require (
15-
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible // indirect
1617
github.com/bmatcuk/doublestar/v4 v4.6.1 // indirect
1718
github.com/casbin/govaluate v1.2.0 // indirect
1819
github.com/davecgh/go-spew v1.1.1 // indirect
@@ -23,6 +24,7 @@ require (
2324
github.com/golang-sql/sqlexp v0.1.0 // indirect
2425
github.com/golang/protobuf v1.5.0 // indirect
2526
github.com/golang/snappy v0.0.1 // indirect
27+
github.com/gomodule/redigo v1.8.9 // indirect
2628
github.com/google/uuid v1.3.0 // indirect
2729
github.com/jackc/chunkreader/v2 v2.0.1 // indirect
2830
github.com/jackc/pgconn v1.13.0 // indirect
@@ -45,6 +47,7 @@ require (
4547
github.com/xdg-go/scram v1.1.2 // indirect
4648
github.com/xdg-go/stringprep v1.0.4 // indirect
4749
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect
50+
github.com/yuin/gopher-lua v1.1.1 // indirect
4851
go.mongodb.org/mongo-driver v1.12.0 // indirect
4952
golang.org/x/crypto v0.0.0-20221005025214-4161e89ecf1b // indirect
5053
golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect

go.sum

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,16 @@ github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.0.0/go.mod h1:+6sju8gk8FRmSa
55
github.com/Azure/azure-sdk-for-go/sdk/internal v1.0.0/go.mod h1:eWRD7oawr1Mu1sLCawqVc0CUiF43ia3qQMxLscsKQ9w=
66
github.com/AzureAD/microsoft-authentication-library-for-go v0.4.0/go.mod h1:Vt9sXTKwMyGcOxSmLDMnGPgqsUg7m8pe215qMLrDXw4=
77
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
8-
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible h1:1G1pk05UrOh0NlF1oeaaix1x8XzrfjIDK47TY0Zehcw=
98
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
109
github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc=
1110
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
11+
github.com/alicebob/miniredis/v2 v2.35.0 h1:QwLphYqCEAo1eu1TqPRN2jgVMPBweeQcR21jeqDCONI=
12+
github.com/alicebob/miniredis/v2 v2.35.0/go.mod h1:TcL7YfarKPGDAthEtl5NBeHZfeUQj6OXMm/+iu5cLMM=
1213
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
1314
github.com/bmatcuk/doublestar/v4 v4.6.1 h1:FH9SifrbvJhnlQpztAx++wlkk70QBf0iBWDwNy7PA4I=
1415
github.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
1516
github.com/casbin/casbin/v2 v2.55.1/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRtgrQfcJqHg=
16-
github.com/casbin/casbin/v2 v2.71.1 h1:LRHyqM0S1LzM/K59PmfUIN0ZJfLgcOjL4OhOQI/FNXU=
17-
github.com/casbin/casbin/v2 v2.71.1/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRtgrQfcJqHg=
17+
github.com/casbin/casbin/v2 v2.60.0/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRtgrQfcJqHg=
1818
github.com/casbin/casbin/v2 v2.100.0 h1:aeugSNjjHfCrgA22nHkVvw2xsscboHv5r0a13ljQKGQ=
1919
github.com/casbin/casbin/v2 v2.100.0/go.mod h1:LO7YPez4dX3LgoTCqSQAleQDo0S0BeZBDxYnPUl95Ng=
2020
github.com/casbin/gorm-adapter/v3 v3.14.0 h1:zZ6AIiNHJZ3ntdf5RBrqD+0Cb4UO+uKFk79R9yJ7mpw=
@@ -23,6 +23,8 @@ github.com/casbin/govaluate v1.2.0 h1:wXCXFmqyY+1RwiKfYo3jMKyrtZmOL3kHwaqDyCPOYa
2323
github.com/casbin/govaluate v1.2.0/go.mod h1:G/UnbIjZk/0uMNaLwZZmFQrR72tYRZWQkO70si/iR7A=
2424
github.com/casbin/mongodb-adapter/v3 v3.7.0 h1:w9c3bea1BGK4eZTAmk17JkY52yv/xSZDSHKji8q+z6E=
2525
github.com/casbin/mongodb-adapter/v3 v3.7.0/go.mod h1:F1mu4ojoJVE/8VhIMxMedhjfwRDdIXgANYs6Sd0MgVA=
26+
github.com/casbin/redis-adapter/v3 v3.6.0 h1:JY9eUJeF428e87s1HvNZxrgUHLGFcJqNiDg5JUftkas=
27+
github.com/casbin/redis-adapter/v3 v3.6.0/go.mod h1:SGL+D0Gx7dQIR8frcnZeq8E0pT2WYuJ05gcEH4c2elY=
2628
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
2729
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
2830
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
@@ -89,6 +91,8 @@ github.com/golang/protobuf v1.5.0 h1:LUVKkCeviFUMKqHa4tXIIij/lbhnMbP7Fn5wKdKkRh4
8991
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
9092
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
9193
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
94+
github.com/gomodule/redigo v1.8.9 h1:Sl3u+2BI/kk+VEatbj0scLdrFhjPmbxOc1myhDP41ws=
95+
github.com/gomodule/redigo v1.8.9/go.mod h1:7ArFNvsTjH8GMMzB4uy1snslv2BwmginuMs06a1uzZE=
9296
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
9397
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
9498
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
@@ -234,6 +238,8 @@ github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d h1:splanxYIlg+5LfHAM
234238
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
235239
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
236240
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
241+
github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M=
242+
github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw=
237243
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
238244
go.mongodb.org/mongo-driver v1.12.0 h1:aPx33jmn/rQuJXPQLZQ8NtfPQG8CaqgLThFtqRb0PiE=
239245
go.mongodb.org/mongo-driver v1.12.0/go.mod h1:AZkxhPnFJUoH7kZlFkVKucV20K387miPfm7oimrSmK0=

server/adapter.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"encoding/json"
1919
"errors"
2020
"fmt"
21+
"net/url"
2122
"os"
2223
"regexp"
2324
"strings"
@@ -27,10 +28,31 @@ import (
2728
fileadapter "github.com/casbin/casbin/v2/persist/file-adapter"
2829
gormadapter "github.com/casbin/gorm-adapter/v3"
2930
mongodbadapter "github.com/casbin/mongodb-adapter/v3"
31+
redisadapter "github.com/casbin/redis-adapter/v3"
3032
)
3133

3234
var errDriverName = errors.New("currently supported DriverName: file | mysql | postgres | mssql")
3335

36+
func parseRedisUrl(redisURL string) (host, port, username, password string, err error) {
37+
if redisURL == "" {
38+
return "", "", "", "", errors.New("redis URL cannot be empty")
39+
}
40+
if !strings.Contains(redisURL, "://") {
41+
redisURL = "redis://" + redisURL
42+
}
43+
u, err := url.Parse(redisURL)
44+
if err != nil {
45+
return "", "", "", "", err
46+
}
47+
host = u.Hostname()
48+
port = u.Port()
49+
if u.User != nil {
50+
username = u.User.Username()
51+
password, _ = u.User.Password()
52+
}
53+
return host, port, username, password, nil
54+
}
55+
3456
func newAdapter(in *pb.NewAdapterRequest) (persist.Adapter, error) {
3557
var a persist.Adapter
3658
in = checkLocalConfig(in)
@@ -45,6 +67,24 @@ func newAdapter(in *pb.NewAdapterRequest) (persist.Adapter, error) {
4567
if err != nil {
4668
return nil, err
4769
}
70+
case "redis":
71+
var err error
72+
host, port, username, password, err := parseRedisUrl(in.ConnectString)
73+
if err != nil {
74+
return nil, err
75+
}
76+
hostWithPort := fmt.Sprintf("%s:%s", host, port)
77+
78+
config := &redisadapter.Config{
79+
Network: "tcp",
80+
Address: hostWithPort,
81+
Username: username,
82+
Password: password,
83+
}
84+
a, err = redisadapter.NewAdapter(config)
85+
if err != nil {
86+
return nil, err
87+
}
4888
default:
4989
var support = false
5090
for _, driverName := range supportDriverNames {

server/adapter_test.go

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ import (
44
"os"
55
"testing"
66

7+
miniredis "github.com/alicebob/miniredis/v2"
8+
9+
pb "github.com/casbin/casbin-server/proto"
710
"github.com/stretchr/testify/assert"
811
)
912

@@ -13,3 +16,89 @@ func TestGetLocalConfig(t *testing.T) {
1316
os.Setenv(configFilePathEnvironmentVariable, "dir/custom_path.json")
1417
assert.Equal(t, "dir/custom_path.json", getLocalConfigPath())
1518
}
19+
20+
func runFakeRedis(username string, password string) (host string, port string, err error) {
21+
s, err := miniredis.Run()
22+
if err != nil {
23+
return "", "", err
24+
}
25+
if username != "" && password != "" {
26+
s.RequireUserAuth(username, password)
27+
}
28+
return s.Host(), s.Port(), err
29+
}
30+
31+
func TestRedisAdapterConfig(t *testing.T) {
32+
os.Setenv(configFilePathEnvironmentVariable, "../config/connection_config.json")
33+
34+
host, port, err := runFakeRedis("", "")
35+
36+
in := &pb.NewAdapterRequest{
37+
DriverName: "redis",
38+
ConnectString: "redis://" + host + ":" + port,
39+
}
40+
41+
a, err := newAdapter(in)
42+
assert.NoError(t, err, "should create redis adapter without error")
43+
assert.NotNil(t, a, "adapter should not be nil")
44+
}
45+
46+
func TestRedisAdapterConfigWithUsernameAndPassword(t *testing.T) {
47+
os.Setenv(configFilePathEnvironmentVariable, "../config/connection_config.json")
48+
49+
username, password := "foo", "bar"
50+
host, port, err := runFakeRedis(username, password)
51+
52+
in := &pb.NewAdapterRequest{
53+
DriverName: "redis",
54+
ConnectString: "redis://" + username + ":" + password + "@" + host + ":" + port,
55+
}
56+
57+
a, err := newAdapter(in)
58+
assert.NoError(t, err, "should create redis adapter without error")
59+
assert.NotNil(t, a, "adapter should not be nil")
60+
}
61+
62+
func TestRedisAdapterConfigWithoutPrefix(t *testing.T) {
63+
os.Setenv(configFilePathEnvironmentVariable, "../config/connection_config.json")
64+
65+
host, port, err := runFakeRedis("", "")
66+
67+
in := &pb.NewAdapterRequest{
68+
DriverName: "redis",
69+
ConnectString: host + ":" + port,
70+
}
71+
72+
a, err := newAdapter(in)
73+
assert.NoError(t, err, "should create redis adapter without error")
74+
assert.NotNil(t, a, "adapter should not be nil")
75+
}
76+
77+
func TestInvalidRedisAdapterConfig(t *testing.T) {
78+
os.Setenv(configFilePathEnvironmentVariable, "../config/connection_config.json")
79+
80+
_, _, err := runFakeRedis("", "")
81+
82+
in := &pb.NewAdapterRequest{
83+
DriverName: "redis",
84+
ConnectString: "invalid-address",
85+
}
86+
87+
a, err := newAdapter(in)
88+
assert.Error(t, err, "should cause an redis adapter without error")
89+
assert.ErrorContains(t, err, "dial tcp: lookup invalid-address")
90+
assert.Nil(t, a, "adapter should be nil")
91+
}
92+
93+
func TestRedisAdapterConfigReturnDefaultFallback(t *testing.T) {
94+
os.Setenv(configFilePathEnvironmentVariable, "../config/connection_config.json")
95+
96+
in := &pb.NewAdapterRequest{
97+
DriverName: "redis",
98+
ConnectString: "",
99+
}
100+
101+
a, err := newAdapter(in)
102+
assert.NoError(t, err, "should create file default adapter without error")
103+
assert.NotNil(t, a, "adapter should not be nil")
104+
}

0 commit comments

Comments
 (0)