Skip to content

Commit 7944b61

Browse files
authored
casbin: let the caller decide the method of user identification (#41)
* feat(casbin): let the caller decide the method of user identification * feat(casbin): do not ignore user identification errors * test(casbin): add unit test to cover UserGetter
1 parent fd04525 commit 7944b61

File tree

2 files changed

+92
-39
lines changed

2 files changed

+92
-39
lines changed

casbin/casbin.go

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -61,13 +61,20 @@ type (
6161
// Enforcer CasbinAuth main rule.
6262
// Required.
6363
Enforcer *casbin.Enforcer
64+
65+
// Method to get the username - defaults to using basic auth
66+
UserGetter func(c echo.Context) (string, error)
6467
}
6568
)
6669

6770
var (
6871
// DefaultConfig is the default CasbinAuth middleware config.
6972
DefaultConfig = Config{
7073
Skipper: middleware.DefaultSkipper,
74+
UserGetter: func(c echo.Context) (string, error) {
75+
username, _, _ := c.Request().BasicAuth()
76+
return username, nil
77+
},
7178
}
7279
)
7380

@@ -107,16 +114,20 @@ func MiddlewareWithConfig(config Config) echo.MiddlewareFunc {
107114
}
108115

109116
// GetUserName gets the user name from the request.
110-
// Currently, only HTTP basic authentication is supported
111-
func (a *Config) GetUserName(c echo.Context) string {
112-
username, _, _ := c.Request().BasicAuth()
113-
return username
117+
// It calls the UserGetter field of the Config struct that allows the caller to customize user identification.
118+
func (a *Config) GetUserName(c echo.Context) (string, error) {
119+
username, err := a.UserGetter(c)
120+
return username, err
114121
}
115122

116123
// CheckPermission checks the user/method/path combination from the request.
117124
// Returns true (permission granted) or false (permission forbidden)
118125
func (a *Config) CheckPermission(c echo.Context) (bool, error) {
119-
user := a.GetUserName(c)
126+
user, err := a.GetUserName(c)
127+
if err != nil {
128+
// Fail safe and do not propagate
129+
return false, nil
130+
}
120131
method := c.Request().Method
121132
path := c.Request().URL.Path
122133
return a.Enforcer.Enforce(user, path, method)

casbin/casbin_test.go

Lines changed: 76 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,22 @@
11
package casbin
22

33
import (
4+
"errors"
45
"net/http"
56
"net/http/httptest"
67
"testing"
78

89
"github.com/casbin/casbin/v2"
910
"github.com/labstack/echo/v4"
11+
"github.com/labstack/echo/v4/middleware"
1012
)
1113

12-
func testRequest(t *testing.T, ce *casbin.Enforcer, user string, path string, method string, code int) {
14+
func testRequest(t *testing.T, h echo.HandlerFunc, user string, path string, method string, code int) {
1315
e := echo.New()
1416
req := httptest.NewRequest(method, path, nil)
1517
req.SetBasicAuth(user, "secret")
1618
res := httptest.NewRecorder()
1719
c := e.NewContext(req, res)
18-
h := Middleware(ce)(func(c echo.Context) error {
19-
return c.String(http.StatusOK, "test")
20-
})
2120

2221
err := h(c)
2322

@@ -38,54 +37,97 @@ func testRequest(t *testing.T, ce *casbin.Enforcer, user string, path string, me
3837

3938
func TestAuth(t *testing.T) {
4039
ce, _ := casbin.NewEnforcer("auth_model.conf", "auth_policy.csv")
40+
h := Middleware(ce)(func(c echo.Context) error {
41+
return c.String(http.StatusOK, "test")
42+
})
4143

42-
testRequest(t, ce, "alice", "/dataset1/resource1", echo.GET, 200)
43-
testRequest(t, ce, "alice", "/dataset1/resource1", echo.POST, 200)
44-
testRequest(t, ce, "alice", "/dataset1/resource2", echo.GET, 200)
45-
testRequest(t, ce, "alice", "/dataset1/resource2", echo.POST, 403)
44+
testRequest(t, h, "alice", "/dataset1/resource1", echo.GET, 200)
45+
testRequest(t, h, "alice", "/dataset1/resource1", echo.POST, 200)
46+
testRequest(t, h, "alice", "/dataset1/resource2", echo.GET, 200)
47+
testRequest(t, h, "alice", "/dataset1/resource2", echo.POST, 403)
4648
}
4749

4850
func TestPathWildcard(t *testing.T) {
4951
ce, _ := casbin.NewEnforcer("auth_model.conf", "auth_policy.csv")
52+
h := Middleware(ce)(func(c echo.Context) error {
53+
return c.String(http.StatusOK, "test")
54+
})
55+
56+
testRequest(t, h, "bob", "/dataset2/resource1", "GET", 200)
57+
testRequest(t, h, "bob", "/dataset2/resource1", "POST", 200)
58+
testRequest(t, h, "bob", "/dataset2/resource1", "DELETE", 200)
59+
testRequest(t, h, "bob", "/dataset2/resource2", "GET", 200)
60+
testRequest(t, h, "bob", "/dataset2/resource2", "POST", 403)
61+
testRequest(t, h, "bob", "/dataset2/resource2", "DELETE", 403)
5062

51-
testRequest(t, ce, "bob", "/dataset2/resource1", "GET", 200)
52-
testRequest(t, ce, "bob", "/dataset2/resource1", "POST", 200)
53-
testRequest(t, ce, "bob", "/dataset2/resource1", "DELETE", 200)
54-
testRequest(t, ce, "bob", "/dataset2/resource2", "GET", 200)
55-
testRequest(t, ce, "bob", "/dataset2/resource2", "POST", 403)
56-
testRequest(t, ce, "bob", "/dataset2/resource2", "DELETE", 403)
57-
58-
testRequest(t, ce, "bob", "/dataset2/folder1/item1", "GET", 403)
59-
testRequest(t, ce, "bob", "/dataset2/folder1/item1", "POST", 200)
60-
testRequest(t, ce, "bob", "/dataset2/folder1/item1", "DELETE", 403)
61-
testRequest(t, ce, "bob", "/dataset2/folder1/item2", "GET", 403)
62-
testRequest(t, ce, "bob", "/dataset2/folder1/item2", "POST", 200)
63-
testRequest(t, ce, "bob", "/dataset2/folder1/item2", "DELETE", 403)
63+
testRequest(t, h, "bob", "/dataset2/folder1/item1", "GET", 403)
64+
testRequest(t, h, "bob", "/dataset2/folder1/item1", "POST", 200)
65+
testRequest(t, h, "bob", "/dataset2/folder1/item1", "DELETE", 403)
66+
testRequest(t, h, "bob", "/dataset2/folder1/item2", "GET", 403)
67+
testRequest(t, h, "bob", "/dataset2/folder1/item2", "POST", 200)
68+
testRequest(t, h, "bob", "/dataset2/folder1/item2", "DELETE", 403)
6469
}
6570

6671
func TestRBAC(t *testing.T) {
6772
ce, _ := casbin.NewEnforcer("auth_model.conf", "auth_policy.csv")
73+
h := Middleware(ce)(func(c echo.Context) error {
74+
return c.String(http.StatusOK, "test")
75+
})
6876

6977
// cathy can access all /dataset1/* resources via all methods because it has the dataset1_admin role.
70-
testRequest(t, ce, "cathy", "/dataset1/item", "GET", 200)
71-
testRequest(t, ce, "cathy", "/dataset1/item", "POST", 200)
72-
testRequest(t, ce, "cathy", "/dataset1/item", "DELETE", 200)
73-
testRequest(t, ce, "cathy", "/dataset2/item", "GET", 403)
74-
testRequest(t, ce, "cathy", "/dataset2/item", "POST", 403)
75-
testRequest(t, ce, "cathy", "/dataset2/item", "DELETE", 403)
78+
testRequest(t, h, "cathy", "/dataset1/item", "GET", 200)
79+
testRequest(t, h, "cathy", "/dataset1/item", "POST", 200)
80+
testRequest(t, h, "cathy", "/dataset1/item", "DELETE", 200)
81+
testRequest(t, h, "cathy", "/dataset2/item", "GET", 403)
82+
testRequest(t, h, "cathy", "/dataset2/item", "POST", 403)
83+
testRequest(t, h, "cathy", "/dataset2/item", "DELETE", 403)
7684

7785
// delete all roles on user cathy, so cathy cannot access any resources now.
7886
ce.DeleteRolesForUser("cathy")
7987

80-
testRequest(t, ce, "cathy", "/dataset1/item", "GET", 403)
81-
testRequest(t, ce, "cathy", "/dataset1/item", "POST", 403)
82-
testRequest(t, ce, "cathy", "/dataset1/item", "DELETE", 403)
83-
testRequest(t, ce, "cathy", "/dataset2/item", "GET", 403)
84-
testRequest(t, ce, "cathy", "/dataset2/item", "POST", 403)
85-
testRequest(t, ce, "cathy", "/dataset2/item", "DELETE", 403)
88+
testRequest(t, h, "cathy", "/dataset1/item", "GET", 403)
89+
testRequest(t, h, "cathy", "/dataset1/item", "POST", 403)
90+
testRequest(t, h, "cathy", "/dataset1/item", "DELETE", 403)
91+
testRequest(t, h, "cathy", "/dataset2/item", "GET", 403)
92+
testRequest(t, h, "cathy", "/dataset2/item", "POST", 403)
93+
testRequest(t, h, "cathy", "/dataset2/item", "DELETE", 403)
8694
}
8795

8896
func TestEnforceError(t *testing.T) {
8997
ce, _ := casbin.NewEnforcer("broken_auth_model.conf", "auth_policy.csv")
90-
testRequest(t, ce, "cathy", "/dataset1/item", "GET", 500)
98+
h := Middleware(ce)(func(c echo.Context) error {
99+
return c.String(http.StatusOK, "test")
100+
})
101+
102+
testRequest(t, h, "cathy", "/dataset1/item", "GET", 500)
103+
}
104+
105+
func TestCustomUserGetter(t *testing.T) {
106+
ce, _ := casbin.NewEnforcer("auth_model.conf", "auth_policy.csv")
107+
cnf := Config{
108+
Skipper: middleware.DefaultSkipper,
109+
Enforcer: ce,
110+
UserGetter: func(c echo.Context) (string, error) {
111+
return "not_cathy_at_all", nil
112+
},
113+
}
114+
h := MiddlewareWithConfig(cnf)(func(c echo.Context) error {
115+
return c.String(http.StatusOK, "test")
116+
})
117+
testRequest(t, h, "cathy", "/dataset1/item", "GET", 403)
118+
}
119+
120+
func TestUserGetterError(t *testing.T) {
121+
ce, _ := casbin.NewEnforcer("auth_model.conf", "auth_policy.csv")
122+
cnf := Config{
123+
Skipper: middleware.DefaultSkipper,
124+
Enforcer: ce,
125+
UserGetter: func(c echo.Context) (string, error) {
126+
return "", errors.New("no idea who you are")
127+
},
128+
}
129+
h := MiddlewareWithConfig(cnf)(func(c echo.Context) error {
130+
return c.String(http.StatusOK, "test")
131+
})
132+
testRequest(t, h, "cathy", "/dataset1/item", "GET", 403)
91133
}

0 commit comments

Comments
 (0)