1
0
mirror of https://github.com/labstack/echo.git synced 2024-11-28 08:38:39 +02:00

Add an authorization middleware that supports ACL, RBAC based on casbin. (#939)

* Add an authorization middleware that supports ACL, RBAC based on casbin.
fix some coveralls check.
change test file with new rule.
renaming the prefix Authz to CasbinAuth
use built-in SetBasicAuth method in test file.
export all casbin auth configs.

* fix example code
This commit is contained in:
卜木 2017-06-02 23:20:35 +08:00 committed by Vishal Rana
parent 466d509e34
commit c73dd1f54c
4 changed files with 208 additions and 0 deletions

101
middleware/casbin_auth.go Normal file
View File

@ -0,0 +1,101 @@
// Package middleware CasbinAuth provides handlers to enable ACL, RBAC, ABAC authorization support.
// Simple Usage:
// package main
//
// import (
// "github.com/casbin/casbin"
// "github.com/labstack/echo"
// "github.com/labstack/echo/middleware"
// )
//
// func main() {
// e := echo.New()
//
// // mediate the access for every request
// e.Use(middleware.CasbinAuth(casbin.NewEnforcer("casbin_auth_model.conf", "casbin_auth_policy.csv")))
//
// e.Logger.Fatal(e.Start(":1323"))
// }
//
// Advanced Usage:
//
// func main(){
// ce := casbin.NewEnforcer("casbin_auth_model.conf", "")
// ce.AddRoleForUser("alice", "admin")
// ce.AddPolicy(...)
//
// e := echo.New()
//
// echo.Use(middleware.CasbinAuth(ce))
//
// e.Logger.Fatal(e.Start(":1323"))
// }
package middleware
import (
"github.com/casbin/casbin"
"github.com/labstack/echo"
)
type (
// CasbinAuthConfig defines the config for CasbinAuth middleware.
CasbinAuthConfig struct {
// Skipper defines a function to skip middleware.
Skipper Skipper
// Enforcer CasbinAuth main rule.
// Required.
Enforcer *casbin.Enforcer
}
)
var (
// DefaultCasbinAuthConfig is the default CasbinAuth middleware config.
DefaultCasbinAuthConfig = CasbinAuthConfig{
Skipper: DefaultSkipper,
}
)
// CasbinAuth returns an CasbinAuth middleware.
//
// For valid credentials it calls the next handler.
// For missing or invalid credentials, it sends "401 - Unauthorized" response.
func CasbinAuth(ce *casbin.Enforcer) echo.MiddlewareFunc {
c := DefaultCasbinAuthConfig
c.Enforcer = ce
return CasbinAuthWithConfig(c)
}
// CasbinAuthWithConfig returns an CasbinAuth middleware with config.
// See `CasbinAuth()`.
func CasbinAuthWithConfig(config CasbinAuthConfig) echo.MiddlewareFunc {
// Defaults
if config.Skipper == nil {
config.Skipper = DefaultCasbinAuthConfig.Skipper
}
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
if config.Skipper(c) || config.CheckPermission(c) {
return next(c)
}
return echo.ErrForbidden
}
}
}
// GetUserName gets the user name from the request.
// Currently, only HTTP basic authentication is supported
func (a *CasbinAuthConfig) GetUserName(c echo.Context) string {
username, _, _ := c.Request().BasicAuth()
return username
}
// CheckPermission checks the user/method/path combination from the request.
// Returns true (permission granted) or false (permission forbidden)
func (a *CasbinAuthConfig) CheckPermission(c echo.Context) bool {
user := a.GetUserName(c)
method := c.Request().Method
path := c.Request().URL.Path
return a.Enforcer.Enforce(user, path, method)
}

View File

@ -0,0 +1,14 @@
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act
[role_definition]
g = _, _
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = g(r.sub, p.sub) && keyMatch(r.obj, p.obj) && (r.act == p.act || p.act == "*")

View File

@ -0,0 +1,7 @@
p, alice, /dataset1/*, GET
p, alice, /dataset1/resource1, POST
p, bob, /dataset2/resource1, *
p, bob, /dataset2/resource2, GET
p, bob, /dataset2/folder1/*, POST
p, dataset1_admin, /dataset1/*, *
g, cathy, dataset1_admin
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

View File

@ -0,0 +1,86 @@
package middleware
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/casbin/casbin"
"github.com/labstack/echo"
)
func testRequest(t *testing.T, ce *casbin.Enforcer, user string, path string, method string, code int) {
e := echo.New()
req := httptest.NewRequest(method, path, nil)
req.SetBasicAuth(user, "secret")
res := httptest.NewRecorder()
c := e.NewContext(req, res)
h := CasbinAuth(ce)(func(c echo.Context) error {
return c.String(http.StatusOK, "test")
})
err := h(c)
if err != nil {
if errObj, ok := err.(*echo.HTTPError); ok {
if errObj.Code != code {
t.Errorf("%s, %s, %s: %d, supposed to be %d", user, path, method, errObj.Code, code)
}
} else {
t.Error(err)
}
} else {
if c.Response().Status != code {
t.Errorf("%s, %s, %s: %d, supposed to be %d", user, path, method, c.Response().Status, code)
}
}
}
func TestCasbinAuth(t *testing.T) {
ce := casbin.NewEnforcer("casbin_auth_model.conf", "casbin_auth_policy.csv")
testRequest(t, ce, "alice", "/dataset1/resource1", echo.GET, 200)
testRequest(t, ce, "alice", "/dataset1/resource1", echo.POST, 200)
testRequest(t, ce, "alice", "/dataset1/resource2", echo.GET, 200)
testRequest(t, ce, "alice", "/dataset1/resource2", echo.POST, 403)
}
func TestPathWildcard(t *testing.T) {
ce := casbin.NewEnforcer("casbin_auth_model.conf", "casbin_auth_policy.csv")
testRequest(t, ce, "bob", "/dataset2/resource1", "GET", 200)
testRequest(t, ce, "bob", "/dataset2/resource1", "POST", 200)
testRequest(t, ce, "bob", "/dataset2/resource1", "DELETE", 200)
testRequest(t, ce, "bob", "/dataset2/resource2", "GET", 200)
testRequest(t, ce, "bob", "/dataset2/resource2", "POST", 403)
testRequest(t, ce, "bob", "/dataset2/resource2", "DELETE", 403)
testRequest(t, ce, "bob", "/dataset2/folder1/item1", "GET", 403)
testRequest(t, ce, "bob", "/dataset2/folder1/item1", "POST", 200)
testRequest(t, ce, "bob", "/dataset2/folder1/item1", "DELETE", 403)
testRequest(t, ce, "bob", "/dataset2/folder1/item2", "GET", 403)
testRequest(t, ce, "bob", "/dataset2/folder1/item2", "POST", 200)
testRequest(t, ce, "bob", "/dataset2/folder1/item2", "DELETE", 403)
}
func TestRBAC(t *testing.T) {
ce := casbin.NewEnforcer("casbin_auth_model.conf", "casbin_auth_policy.csv")
// cathy can access all /dataset1/* resources via all methods because it has the dataset1_admin role.
testRequest(t, ce, "cathy", "/dataset1/item", "GET", 200)
testRequest(t, ce, "cathy", "/dataset1/item", "POST", 200)
testRequest(t, ce, "cathy", "/dataset1/item", "DELETE", 200)
testRequest(t, ce, "cathy", "/dataset2/item", "GET", 403)
testRequest(t, ce, "cathy", "/dataset2/item", "POST", 403)
testRequest(t, ce, "cathy", "/dataset2/item", "DELETE", 403)
// delete all roles on user cathy, so cathy cannot access any resources now.
ce.DeleteRolesForUser("cathy")
testRequest(t, ce, "cathy", "/dataset1/item", "GET", 403)
testRequest(t, ce, "cathy", "/dataset1/item", "POST", 403)
testRequest(t, ce, "cathy", "/dataset1/item", "DELETE", 403)
testRequest(t, ce, "cathy", "/dataset2/item", "GET", 403)
testRequest(t, ce, "cathy", "/dataset2/item", "POST", 403)
testRequest(t, ce, "cathy", "/dataset2/item", "DELETE", 403)
}