Skip to content
Merged

V5 #140

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/echo-contrib.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ jobs:
# Echo CORE tests with last four major releases (unless there are pressing vulnerabilities)
# As we depend on MANY DIFFERENT libraries which of SOME support last 2 Go releases we could have situations when
# we derive from last four major releases promise.
go: ["1.24", "1.25"]
go: ["1.25"]
name: ${{ matrix.os }} @ Go ${{ matrix.go }}
runs-on: ${{ matrix.os }}
steps:
Expand Down
29 changes: 14 additions & 15 deletions casbin/casbin.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Simple example:

import (
"github.com/casbin/casbin/v2"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v5"
casbin_mw "github.com/labstack/echo-contrib/casbin"
)

Expand All @@ -28,7 +28,7 @@ Advanced example:

import (
"github.com/casbin/casbin/v2"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v5"
casbin_mw "github.com/labstack/echo-contrib/casbin"
)

Expand All @@ -49,10 +49,11 @@ package casbin

import (
"errors"
"github.com/casbin/casbin/v2"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
"net/http"

"github.com/casbin/casbin/v2"
"github.com/labstack/echo/v5"
"github.com/labstack/echo/v5/middleware"
)

type (
Expand All @@ -67,28 +68,26 @@ type (

// EnforceHandler is custom callback to handle enforcing.
// One of Enforcer or EnforceHandler fields is required.
EnforceHandler func(c echo.Context, user string) (bool, error)
EnforceHandler func(c *echo.Context, user string) (bool, error)

// Method to get the username - defaults to using basic auth
UserGetter func(c echo.Context) (string, error)
UserGetter func(c *echo.Context) (string, error)

// Method to handle errors
ErrorHandler func(c echo.Context, internal error, proposedStatus int) error
ErrorHandler func(c *echo.Context, internal error, proposedStatus int) error
}
)

var (
// DefaultConfig is the default CasbinAuth middleware config.
DefaultConfig = Config{
Skipper: middleware.DefaultSkipper,
UserGetter: func(c echo.Context) (string, error) {
UserGetter: func(c *echo.Context) (string, error) {
username, _, _ := c.Request().BasicAuth()
return username, nil
},
ErrorHandler: func(c echo.Context, internal error, proposedStatus int) error {
err := echo.NewHTTPError(proposedStatus, internal.Error())
err.Internal = internal
return err
ErrorHandler: func(c *echo.Context, internal error, proposedStatus int) error {
return echo.NewHTTPError(proposedStatus, internal.Error()).Wrap(internal)
},
}
)
Expand Down Expand Up @@ -119,13 +118,13 @@ func MiddlewareWithConfig(config Config) echo.MiddlewareFunc {
config.ErrorHandler = DefaultConfig.ErrorHandler
}
if config.EnforceHandler == nil {
config.EnforceHandler = func(c echo.Context, user string) (bool, error) {
config.EnforceHandler = func(c *echo.Context, user string) (bool, error) {
return config.Enforcer.Enforce(user, c.Request().URL.Path, c.Request().Method)
}
}

return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
return func(c *echo.Context) error {
if config.Skipper(c) {
return next(c)
}
Expand Down
113 changes: 59 additions & 54 deletions casbin/casbin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,16 @@ package casbin

import (
"errors"
"github.com/stretchr/testify/assert"
"net/http"
"net/http/httptest"
"strings"
"testing"

"github.com/stretchr/testify/assert"

"github.com/casbin/casbin/v2"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
"github.com/labstack/echo/v5"
"github.com/labstack/echo/v5/middleware"
)

func testRequest(t *testing.T, h echo.HandlerFunc, user string, path string, method string, code int) {
Expand All @@ -33,107 +34,111 @@ func testRequest(t *testing.T, h echo.HandlerFunc, user string, path string, met
}
}
} else {
if c.Response().Status != code {
t.Errorf("%s, %s, %s: %d, supposed to be %d", user, path, method, c.Response().Status, code)
status := 0
if eResp, uErr := echo.UnwrapResponse(c.Response()); uErr == nil {
status = eResp.Status
}
if status != code {
t.Errorf("%s, %s, %s: %d, supposed to be %d", user, path, method, status, code)
}
}
}

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

testRequest(t, h, "alice", "/dataset1/resource1", echo.GET, http.StatusOK)
testRequest(t, h, "alice", "/dataset1/resource1", echo.POST, http.StatusOK)
testRequest(t, h, "alice", "/dataset1/resource2", echo.GET, http.StatusOK)
testRequest(t, h, "alice", "/dataset1/resource2", echo.POST, http.StatusForbidden)
testRequest(t, h, "alice", "/dataset1/resource1", http.MethodGet, http.StatusOK)
testRequest(t, h, "alice", "/dataset1/resource1", http.MethodPost, http.StatusOK)
testRequest(t, h, "alice", "/dataset1/resource2", http.MethodGet, http.StatusOK)
testRequest(t, h, "alice", "/dataset1/resource2", http.MethodPost, http.StatusForbidden)
}

func TestPathWildcard(t *testing.T) {
ce, _ := casbin.NewEnforcer("auth_model.conf", "auth_policy.csv")
h := Middleware(ce)(func(c echo.Context) error {
h := Middleware(ce)(func(c *echo.Context) error {
return c.String(http.StatusOK, "test")
})

testRequest(t, h, "bob", "/dataset2/resource1", echo.GET, http.StatusOK)
testRequest(t, h, "bob", "/dataset2/resource1", echo.POST, http.StatusOK)
testRequest(t, h, "bob", "/dataset2/resource1", echo.DELETE, http.StatusOK)
testRequest(t, h, "bob", "/dataset2/resource2", echo.GET, http.StatusOK)
testRequest(t, h, "bob", "/dataset2/resource2", echo.POST, http.StatusForbidden)
testRequest(t, h, "bob", "/dataset2/resource2", echo.DELETE, http.StatusForbidden)

testRequest(t, h, "bob", "/dataset2/folder1/item1", echo.GET, http.StatusForbidden)
testRequest(t, h, "bob", "/dataset2/folder1/item1", echo.POST, http.StatusOK)
testRequest(t, h, "bob", "/dataset2/folder1/item1", echo.DELETE, http.StatusForbidden)
testRequest(t, h, "bob", "/dataset2/folder1/item2", echo.GET, http.StatusForbidden)
testRequest(t, h, "bob", "/dataset2/folder1/item2", echo.POST, http.StatusOK)
testRequest(t, h, "bob", "/dataset2/folder1/item2", echo.DELETE, http.StatusForbidden)
testRequest(t, h, "bob", "/dataset2/resource1", http.MethodGet, http.StatusOK)
testRequest(t, h, "bob", "/dataset2/resource1", http.MethodPost, http.StatusOK)
testRequest(t, h, "bob", "/dataset2/resource1", http.MethodDelete, http.StatusOK)
testRequest(t, h, "bob", "/dataset2/resource2", http.MethodGet, http.StatusOK)
testRequest(t, h, "bob", "/dataset2/resource2", http.MethodPost, http.StatusForbidden)
testRequest(t, h, "bob", "/dataset2/resource2", http.MethodDelete, http.StatusForbidden)

testRequest(t, h, "bob", "/dataset2/folder1/item1", http.MethodGet, http.StatusForbidden)
testRequest(t, h, "bob", "/dataset2/folder1/item1", http.MethodPost, http.StatusOK)
testRequest(t, h, "bob", "/dataset2/folder1/item1", http.MethodDelete, http.StatusForbidden)
testRequest(t, h, "bob", "/dataset2/folder1/item2", http.MethodGet, http.StatusForbidden)
testRequest(t, h, "bob", "/dataset2/folder1/item2", http.MethodPost, http.StatusOK)
testRequest(t, h, "bob", "/dataset2/folder1/item2", http.MethodDelete, http.StatusForbidden)
}

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

// cathy can access all /dataset1/* resources via all methods because it has the dataset1_admin role.
testRequest(t, h, "cathy", "/dataset1/item", echo.GET, http.StatusOK)
testRequest(t, h, "cathy", "/dataset1/item", echo.POST, http.StatusOK)
testRequest(t, h, "cathy", "/dataset1/item", echo.DELETE, http.StatusOK)
testRequest(t, h, "cathy", "/dataset2/item", echo.GET, http.StatusForbidden)
testRequest(t, h, "cathy", "/dataset2/item", echo.POST, http.StatusForbidden)
testRequest(t, h, "cathy", "/dataset2/item", echo.DELETE, http.StatusForbidden)
testRequest(t, h, "cathy", "/dataset1/item", http.MethodGet, http.StatusOK)
testRequest(t, h, "cathy", "/dataset1/item", http.MethodPost, http.StatusOK)
testRequest(t, h, "cathy", "/dataset1/item", http.MethodDelete, http.StatusOK)
testRequest(t, h, "cathy", "/dataset2/item", http.MethodGet, http.StatusForbidden)
testRequest(t, h, "cathy", "/dataset2/item", http.MethodPost, http.StatusForbidden)
testRequest(t, h, "cathy", "/dataset2/item", http.MethodDelete, http.StatusForbidden)

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

testRequest(t, h, "cathy", "/dataset1/item", echo.GET, http.StatusForbidden)
testRequest(t, h, "cathy", "/dataset1/item", echo.POST, http.StatusForbidden)
testRequest(t, h, "cathy", "/dataset1/item", echo.DELETE, http.StatusForbidden)
testRequest(t, h, "cathy", "/dataset2/item", echo.GET, http.StatusForbidden)
testRequest(t, h, "cathy", "/dataset2/item", echo.POST, http.StatusForbidden)
testRequest(t, h, "cathy", "/dataset2/item", echo.DELETE, http.StatusForbidden)
testRequest(t, h, "cathy", "/dataset1/item", http.MethodGet, http.StatusForbidden)
testRequest(t, h, "cathy", "/dataset1/item", http.MethodPost, http.StatusForbidden)
testRequest(t, h, "cathy", "/dataset1/item", http.MethodDelete, http.StatusForbidden)
testRequest(t, h, "cathy", "/dataset2/item", http.MethodGet, http.StatusForbidden)
testRequest(t, h, "cathy", "/dataset2/item", http.MethodPost, http.StatusForbidden)
testRequest(t, h, "cathy", "/dataset2/item", http.MethodDelete, http.StatusForbidden)
}

func TestEnforceError(t *testing.T) {
ce, _ := casbin.NewEnforcer("broken_auth_model.conf", "auth_policy.csv")
h := Middleware(ce)(func(c echo.Context) error {
h := Middleware(ce)(func(c *echo.Context) error {
return c.String(http.StatusOK, "test")
})

testRequest(t, h, "cathy", "/dataset1/item", echo.GET, http.StatusInternalServerError)
testRequest(t, h, "cathy", "/dataset1/item", http.MethodGet, http.StatusInternalServerError)
}

func TestCustomUserGetter(t *testing.T) {
ce, _ := casbin.NewEnforcer("auth_model.conf", "auth_policy.csv")
cnf := Config{
Skipper: middleware.DefaultSkipper,
Enforcer: ce,
UserGetter: func(c echo.Context) (string, error) {
UserGetter: func(c *echo.Context) (string, error) {
return "not_cathy_at_all", nil
},
}
h := MiddlewareWithConfig(cnf)(func(c echo.Context) error {
h := MiddlewareWithConfig(cnf)(func(c *echo.Context) error {
return c.String(http.StatusOK, "test")
})
testRequest(t, h, "cathy", "/dataset1/item", echo.GET, http.StatusForbidden)
testRequest(t, h, "cathy", "/dataset1/item", http.MethodGet, http.StatusForbidden)
}

func TestUserGetterError(t *testing.T) {
ce, _ := casbin.NewEnforcer("auth_model.conf", "auth_policy.csv")
cnf := Config{
Skipper: middleware.DefaultSkipper,
Enforcer: ce,
UserGetter: func(c echo.Context) (string, error) {
UserGetter: func(c *echo.Context) (string, error) {
return "", errors.New("no idea who you are")
},
}
h := MiddlewareWithConfig(cnf)(func(c echo.Context) error {
h := MiddlewareWithConfig(cnf)(func(c *echo.Context) error {
return c.String(http.StatusOK, "test")
})
testRequest(t, h, "cathy", "/dataset1/item", echo.GET, http.StatusForbidden)
testRequest(t, h, "cathy", "/dataset1/item", http.MethodGet, http.StatusForbidden)
}

func TestCustomEnforceHandler(t *testing.T) {
Expand All @@ -144,33 +149,33 @@ func TestCustomEnforceHandler(t *testing.T) {
assert.NoError(t, err)

cnf := Config{
EnforceHandler: func(c echo.Context, user string) (bool, error) {
EnforceHandler: func(c *echo.Context, user string) (bool, error) {
method := c.Request().Method
if strings.HasPrefix(c.Request().URL.Path, "/user/bob") {
method += "_SELF"
}
return ce.Enforce(user, c.Request().URL.Path, method)
},
}
h := MiddlewareWithConfig(cnf)(func(c echo.Context) error {
h := MiddlewareWithConfig(cnf)(func(c *echo.Context) error {
return c.String(http.StatusOK, "test")
})
testRequest(t, h, "bob", "/dataset2/resource1", echo.GET, http.StatusOK)
testRequest(t, h, "bob", "/user/alice", echo.PATCH, http.StatusForbidden)
testRequest(t, h, "bob", "/user/bob", echo.PATCH, http.StatusOK)
testRequest(t, h, "bob", "/dataset2/resource1", http.MethodGet, http.StatusOK)
testRequest(t, h, "bob", "/user/alice", http.MethodPatch, http.StatusForbidden)
testRequest(t, h, "bob", "/user/bob", http.MethodPatch, http.StatusOK)
}

func TestCustomSkipper(t *testing.T) {
ce, _ := casbin.NewEnforcer("auth_model.conf", "auth_policy.csv")
cnf := Config{
Skipper: func(c echo.Context) bool {
Skipper: func(c *echo.Context) bool {
return c.Request().URL.Path == "/dataset1/resource1"
},
Enforcer: ce,
}
h := MiddlewareWithConfig(cnf)(func(c echo.Context) error {
h := MiddlewareWithConfig(cnf)(func(c *echo.Context) error {
return c.String(http.StatusOK, "test")
})
testRequest(t, h, "alice", "/dataset1/resource1", echo.GET, http.StatusOK)
testRequest(t, h, "alice", "/dataset1/resource2", echo.POST, http.StatusForbidden)
testRequest(t, h, "alice", "/dataset1/resource1", http.MethodGet, http.StatusOK)
testRequest(t, h, "alice", "/dataset1/resource2", http.MethodPost, http.StatusForbidden)
}
Loading
Loading