Skip to content

Commit f4cc73d

Browse files
committed
Add the plugin code.
1 parent 0c78072 commit f4cc73d

File tree

4 files changed

+177
-0
lines changed

4 files changed

+177
-0
lines changed

authz.go

+66
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package authz
2+
3+
import (
4+
"github.com/mholt/caddy/caddyhttp/httpserver"
5+
"github.com/mholt/caddy"
6+
"net/http"
7+
"github.com/casbin/casbin"
8+
)
9+
10+
// Authorizer is a middleware for filtering clients based on their ip or country's ISO code.
11+
type Authorizer struct {
12+
Next httpserver.Handler
13+
Enforcer *casbin.Enforcer
14+
}
15+
16+
// Init initializes the plugin
17+
func init() {
18+
caddy.RegisterPlugin("authz", caddy.Plugin{
19+
ServerType: "http",
20+
Action: Setup,
21+
})
22+
}
23+
24+
// Setup parses the ipfilter configuration and returns the middleware handler.
25+
func Setup(c *caddy.Controller) error {
26+
e := casbin.NewEnforcer("authz_model.conf", "authz_policy.csv")
27+
28+
// Create new middleware
29+
newMiddleWare := func(next httpserver.Handler) httpserver.Handler {
30+
return &Authorizer{
31+
Next: next,
32+
Enforcer: e,
33+
}
34+
}
35+
// Add middleware
36+
cfg := httpserver.GetConfig(c)
37+
cfg.AddMiddleware(newMiddleWare)
38+
39+
return nil
40+
}
41+
42+
// ServeHTTP serves the request.
43+
func (a Authorizer) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
44+
if !a.CheckPermission(r) {
45+
w.WriteHeader(403)
46+
return http.StatusForbidden, nil
47+
} else {
48+
return a.Next.ServeHTTP(w, r)
49+
}
50+
}
51+
52+
// GetUserName gets the user name from the request.
53+
// Currently, only HTTP basic authentication is supported
54+
func (a *Authorizer) GetUserName(r *http.Request) string {
55+
username, _, _ := r.BasicAuth()
56+
return username
57+
}
58+
59+
// CheckPermission checks the user/method/path combination from the request.
60+
// Returns true (permission granted) or false (permission forbidden)
61+
func (a *Authorizer) CheckPermission(r *http.Request) bool {
62+
user := a.GetUserName(r)
63+
method := r.Method
64+
path := r.URL.Path
65+
return a.Enforcer.Enforce(user, path, method)
66+
}

authz_model.conf

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
[request_definition]
2+
r = sub, obj, act
3+
4+
[policy_definition]
5+
p = sub, obj, act
6+
7+
[role_definition]
8+
g = _, _
9+
10+
[policy_effect]
11+
e = some(where (p.eft == allow))
12+
13+
[matchers]
14+
m = g(r.sub, p.sub) && keyMatch(r.obj, p.obj) && (r.act == p.act || p.act == "*")

authz_policy.csv

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
p, alice, /dataset1/*, GET
2+
p, alice, /dataset1/resource1, POST
3+
p, bob, /dataset2/resource1, *
4+
p, bob, /dataset2/resource2, GET
5+
p, bob, /dataset2/folder1/*, POST
6+
p, dataset1_admin, /dataset1/*, *
7+
g, cathy, dataset1_admin

authz_test.go

+90
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
package authz
2+
3+
import (
4+
"testing"
5+
"github.com/mholt/caddy/caddyhttp/httpserver"
6+
"net/http/httptest"
7+
"net/http"
8+
"github.com/casbin/casbin"
9+
)
10+
11+
func testRequest(t *testing.T, handler Authorizer, user string, path string, method string, code int) {
12+
r, _ := http.NewRequest(method, path, nil)
13+
r.SetBasicAuth(user, "123")
14+
w := httptest.NewRecorder()
15+
handler.ServeHTTP(w, r)
16+
17+
if w.Code != code {
18+
t.Errorf("%s, %s, %s: %d, supposed to be %d", user, path, method, w.Code, code)
19+
}
20+
}
21+
22+
func TestBasic(t *testing.T) {
23+
e := casbin.NewEnforcer("authz_model.conf", "authz_policy.csv")
24+
25+
handler := Authorizer{
26+
Next: httpserver.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) {
27+
return http.StatusOK, nil
28+
}),
29+
Enforcer: e,
30+
}
31+
32+
testRequest(t, handler, "alice", "/dataset1/resource1", "GET", 200)
33+
testRequest(t, handler, "alice", "/dataset1/resource1", "POST", 200)
34+
testRequest(t, handler, "alice", "/dataset1/resource2", "GET", 200)
35+
testRequest(t, handler, "alice", "/dataset1/resource2", "POST", 403)
36+
}
37+
38+
func TestPathWildcard(t *testing.T) {
39+
e := casbin.NewEnforcer("authz_model.conf", "authz_policy.csv")
40+
41+
handler := Authorizer{
42+
Next: httpserver.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) {
43+
return http.StatusOK, nil
44+
}),
45+
Enforcer: e,
46+
}
47+
48+
testRequest(t, handler, "bob", "/dataset2/resource1", "GET", 200)
49+
testRequest(t, handler, "bob", "/dataset2/resource1", "POST", 200)
50+
testRequest(t, handler, "bob", "/dataset2/resource1", "DELETE", 200)
51+
testRequest(t, handler, "bob", "/dataset2/resource2", "GET", 200)
52+
testRequest(t, handler, "bob", "/dataset2/resource2", "POST", 403)
53+
testRequest(t, handler, "bob", "/dataset2/resource2", "DELETE", 403)
54+
55+
testRequest(t, handler, "bob", "/dataset2/folder1/item1", "GET", 403)
56+
testRequest(t, handler, "bob", "/dataset2/folder1/item1", "POST", 200)
57+
testRequest(t, handler, "bob", "/dataset2/folder1/item1", "DELETE", 403)
58+
testRequest(t, handler, "bob", "/dataset2/folder1/item2", "GET", 403)
59+
testRequest(t, handler, "bob", "/dataset2/folder1/item2", "POST", 200)
60+
testRequest(t, handler, "bob", "/dataset2/folder1/item2", "DELETE", 403)
61+
}
62+
63+
func TestRBAC(t *testing.T) {
64+
e := casbin.NewEnforcer("authz_model.conf", "authz_policy.csv")
65+
66+
handler := Authorizer{
67+
Next: httpserver.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) {
68+
return http.StatusOK, nil
69+
}),
70+
Enforcer: e,
71+
}
72+
73+
// cathy can access all /dataset1/* resources via all methods because it has the dataset1_admin role.
74+
testRequest(t, handler, "cathy", "/dataset1/item", "GET", 200)
75+
testRequest(t, handler, "cathy", "/dataset1/item", "POST", 200)
76+
testRequest(t, handler, "cathy", "/dataset1/item", "DELETE", 200)
77+
testRequest(t, handler, "cathy", "/dataset2/item", "GET", 403)
78+
testRequest(t, handler, "cathy", "/dataset2/item", "POST", 403)
79+
testRequest(t, handler, "cathy", "/dataset2/item", "DELETE", 403)
80+
81+
// delete all roles on user cathy, so cathy cannot access any resources now.
82+
e.DeleteRolesForUser("cathy")
83+
84+
testRequest(t, handler, "cathy", "/dataset1/item", "GET", 403)
85+
testRequest(t, handler, "cathy", "/dataset1/item", "POST", 403)
86+
testRequest(t, handler, "cathy", "/dataset1/item", "DELETE", 403)
87+
testRequest(t, handler, "cathy", "/dataset2/item", "GET", 403)
88+
testRequest(t, handler, "cathy", "/dataset2/item", "POST", 403)
89+
testRequest(t, handler, "cathy", "/dataset2/item", "DELETE", 403)
90+
}

0 commit comments

Comments
 (0)