Casbin
Casbin is a powerful and efficient open-source access control library that supports various access control models ( such as ACL/RBAC/ABAC
) for enforcing authorization across the board.
According to the user’s use scenario, we provide Casbin Middleware that adapted to Hertz.
Install
go get github.com/hertz-contrib/casbin
Import
import "github.com/hertz-contrib/casbin"
Example
package main
import (
"context"
"log"
"github.com/cloudwego/hertz/pkg/app"
"github.com/cloudwego/hertz/pkg/app/server"
"github.com/hertz-contrib/casbin"
"github.com/hertz-contrib/sessions"
"github.com/hertz-contrib/sessions/cookie"
)
func main() {
h := server.Default()
// using sessions to store user info.
store := cookie.NewStore([]byte("secret"))
h.Use(sessions.New("session", store))
auth, err := casbin.NewCasbinMiddleware("example/config/model.conf", "example/config/policy.csv", subjectFromSession)
if err != nil {
log.Fatal(err)
}
h.POST("/login", func(ctx context.Context, c *app.RequestContext) {
// verify username and password.
// ...
// store username (casbin subject) in session
session := sessions.Default(c)
session.Set("name", "alice")
err := session.Save()
if err != nil {
log.Fatal(err)
}
c.String(200, "you login successfully")
})
h.GET("/book", auth.RequiresPermissions("book:read", casbin.WithLogic(casbin.AND)), func(ctx context.Context, c *app.RequestContext) {
c.String(200, "you read the book successfully")
})
h.POST("/book", auth.RequiresRoles("user", casbin.WithLogic(casbin.AND)), func(ctx context.Context, c *app.RequestContext) {
c.String(200, "you posted a book successfully")
})
h.Spin()
}
// subjectFromSession get subject from session.
func subjectFromSession(ctx context.Context, c *app.RequestContext) string {
// get subject from session.
session := sessions.Default(c)
if subject, ok := session.Get("name").(string); !ok {
return ""
} else {
return subject
}
}
Config
By Casbin Middleware, hertz is capable of controlling user access permissions.
use this extension, you need to initialize middleware and use method that provided by this middleware to authorize.
Initialize middleware
NewCasbinMiddleware
You need to provide Model, Policy and LookupHandler
(handler that get subject) to initialize middleware.
This function will initialize *casbin.Enforcer
by provided configs to perform authentication operation.
The function signature is as follows:
func NewCasbinMiddleware(modelFile string, adapter interface{}, lookup LookupHandler) (*Middleware, error)
Sample code:
func exampleLookupHandler(ctx context.Context, c *app.RequestContext) string {
// get subject from session
session := sessions.Default(c)
if subject, ok := session.Get("name").(string); !ok {
return ""
} else {
return subject
}
}
func main() {
...
casbinMiddleware, err := casbin.NewCasbinMiddleware("example/config/model.conf", "example/config/policy.csv", exampleLookupHandler)
if err != nil {
log.Fatal(err)
}
...
}
NewCasbinMiddlewareFromEnforcer
You need to provide enforcer and LookupHandler
(handler that get subject) to initialize middleware.
The function signature is as follows:
func NewCasbinMiddlewareFromEnforcer(e casbin.IEnforcer, lookup LookupHandler) (*Middleware, error)
Sample code:
func exampleLookupHandler(ctx context.Context, c *app.RequestContext) string {
// get subject from session
session := sessions.Default(c)
if subject, ok := session.Get("name").(string); !ok {
return ""
} else {
return subject
}
}
func main() {
...
enforcer, err := casbinsdk.NewEnforcer("example/config/model.conf", "example/config/policy.csv")
if err != nil{
log.Fatal(err)
}
casbinMiddleware, err := casbin.NewCasbinMiddlewareFromEnforcer(enforcer, exampleLookupHandler)
if err != nil {
log.Fatal(err)
}
...
}
Middleware method
middleware provide methods that perform authentication operation.
method parameter format is as follows:
func (m *Middleware) exampleMiddlwareMethod(expression string, opts ...Option) app.HandlerFunc
it contains expression and opts two params.
parameters descriptions is as follows:
expression
expression has one or more params, each param is divided by space, the expression format is related to
Logic
(see option description),the calculated final value of the expression is either True or False, True represents for has passed casbin middleware,
False represents for has not passed casbin middleware.
If
Logic
is AND or OR, the format is:"var 1 var2 var3 var4"
, like"book:read book:write"
If
Logic
is CUSTOM, the format is :"var1 opr1 var2 opr2 var3"
like"book:read && book:write || book:all"
opts
Option Description Default WithLogic
Logic
is the logical operation (AND/OR/CUSTOM) used in permission checks that multiple permissions or roles are specifiedAND
WithPermissionParser
PermissionParserFunc
is used for parsing the permission to extract object and action usuallyPermissionParserWithSeparator(":")
WithPermissionParserSeparator
PermissionParserSeparator
is used to set separator that divide permission to object and action usually:
WithUnauthorized
Unauthorized
defined the response body for unauthorized responsesfunc(ctx context.Context, c *app.RequestContext) { c.AbortWithStatus(consts.StatusUnauthorized) }
WithForbidden
Forbidden
defines the response body for forbidden responsesfunc(ctx context.Context, c *app.RequestContext) { c.AbortWithStatus(consts.StatusForbidden) }
RequiresPermission
Use Subject (provided by LookupHandler
) and expression (see the following text) to check subject is whether satisfies the permission list relationship.
vars inside expression is param list behind sub in Model :
[request_definition]
r = sub, xxx, xxx
like:
[request_definition]
r = sub, dom, obj, act
when use default PermissionParser
, expression format should be like "book:read"
.
like:
[request_definition]
r = sub, dom, obj, act
when use default PermissionParser
, expression format should be like ""book1.com:book:read
.
The function signature is as follows:
func (m *Middleware) RequiresPermissions(expression string, opts ...Option) app.HandlerFunc
Sample code:
when user has book:read
permission,
func main(){
...
h := server.Default()
m, err := casbin.NewCasbinMiddleware("example/config/model.conf", "example/config/policy.csv", subjectFromSession)
if err != nil {
log.Fatal(err)
}
h.GET("/book",
m.RequiresPermissions("book:read"), // passed
func(ctx context.Context, c *app.RequestContext) {
c.String(200, "you read the book successfully")
},
)
h.GET("/book",
m.RequiresPermissions("book:read book:write"), // not passed
func(ctx context.Context, c *app.RequestContext) {
c.String(200, "you read the book successfully")
},
)
...
}
RequiresRoles
Use Subject (provided by LookupHandler
) and expression (see the following text) to check roles to which the user belongs is whether satisfies the role list relationship.
The function signature is as follows:
func (m *Middleware) RequiresRoles(expression string, opts ...Option) app.HandlerFunc
Sample code:
when user has role of user and reader,
func main(){
...
h := server.Default()
m, err := casbin.NewCasbinMiddleware("example/config/model.conf", "example/config/policy.csv", subjectFromSession)
if err != nil {
log.Fatal(err)
}
h.POST("/book",
auth.RequiresRoles("user"), // passed
func(ctx context.Context, c *app.RequestContext) {
c.String(200, "you posted a book successfully")
},
)
h.POST("/book",
auth.RequiresRoles("user reader"), // passed
func(ctx context.Context, c *app.RequestContext) {
c.String(200, "you posted a book successfully")
},
)
h.POST("/book",
auth.RequiresRoles("user reader admin"), // not passed
func(ctx context.Context, c *app.RequestContext) {
c.String(200, "you posted a book successfully")
},
)
...
}
Attention: This method is only valid when use RBAC.
Options description
WithLogic
Logic
is the logical operation (AND/OR/CUSTOM) used in permission checks that multiple permissions or roles are specified.
The function signature is as follows:
func WithLogic(logic Logic) Option
option:
const (
AND Logic = iota
OR
CUSTOM
)
AND
all variables in expression
will perform Logic AND operation.
Sample code:
when user has book:read
permission,
func main(){
...
h := server.Default()
m, err := casbin.NewCasbinMiddleware("example/config/model.conf", "example/config/policy.csv", subjectFromSession)
if err != nil {
log.Fatal(err)
}
h.GET("/book",
m.RequiresPermissions("book:read", casbin.WithLogic(casbin.AND)), // passed
func(ctx context.Context, c *app.RequestContext) {
c.String(200, "you read the book successfully")
},
)
h.GET("/book",
m.RequiresPermissions("book:read book:write", casbin.WithLogic(casbin.AND)), // not passed
func(ctx context.Context, c *app.RequestContext) {
c.String(200, "you read the book successfully")
},
)
...
}
OR
all variables in expression
will perform Logical OR operation.
Sample code:
when user has book:read
permission,
func main(){
...
h := server.Default()
m, err := casbin.NewCasbinMiddleware("example/config/model.conf", "example/config/policy.csv", subjectFromSession)
if err != nil {
log.Fatal(err)
}
h.GET("/book",
m.RequiresPermissions("book:read", casbin.WithLogic(casbin.OR)), // passed
func(ctx context.Context, c *app.RequestContext) {
c.String(200, "you read the book successfully")
},
)
h.GET("/book",
m.RequiresPermissions("book:read book:and", casbin.WithLogic(casbin.OR)), // not passed
func(ctx context.Context, c *app.RequestContext) {
c.String(200, "you read the book successfully")
},
)
...
}
CUSTOM
expression
will be parsed like C-like artithmetic/string expression。
Attention:
when using CUSTOM
, use WithPermissionParser
Option is forbidden, it is suggested using WithPermissionParserSeparator
Option instead.
Sample code:
when user has book:read
permission,
func main(){
...
h := server.Default()
m, err := casbin.NewCasbinMiddleware("example/config/model.conf", "example/config/policy.csv", subjectFromSession)
if err != nil {
log.Fatal(err)
}
h.GET("/book",
m.RequiresPermissions("book:read", casbin.WithLogic(casbin.CUSTOM)), // passed
func(ctx context.Context, c *app.RequestContext) {
c.String(200, "you read the book successfully")
},
)
h.GET("/book",
m.RequiresPermissions("book:read && book:write", casbin.WithLogic(casbin.CUSTOM)), // not passed
func(ctx context.Context, c *app.RequestContext) {
c.String(200, "you read the book successfully")
},
)
h.GET("/book",
m.RequiresPermissions("book:read || book:write", casbin.WithLogic(casbin.CUSTOM)), // passed
func(ctx context.Context, c *app.RequestContext) {
c.String(200, "you read the book successfully")
},
)
h.GET("/book",
m.RequiresPermissions("!book:read", casbin.WithLogic(casbin.CUSTOM)), // not passed
func(ctx context.Context, c *app.RequestContext) {
c.String(200, "you read the book successfully")
},
)
...
}
WithPermissionParser
PermissionParserFunc
is used for parsing the permission to extract object and action usually.
The function signature is as follows:
func WithPermissionParser(pp PermissionParserFunc) Option
Sample code:
func main(){
...
h := server.Default()
m, err := casbin.NewCasbinMiddleware("example/config/model.conf", "example/config/policy.csv", subjectFromSession)
if err != nil {
log.Fatal(err)
}
h.GET("/book",
m.RequiresPermissions("book-read",
casbin.WithPermissionParser(func(str string) []string {
return strings.Split(str, "-")
}),
),
func(ctx context.Context, c *app.RequestContext) {
c.String(200, "you read the book successfully")
},
)
...
}
WithPermissionParserSeparator
PermissionParserSeparator
is used to set separator that divide permission to object and action usually.
The function signature is as follows:
func WithPermissionParserSeparator(sep string) Option
Sample code:
func main(){
...
h := server.Default()
m, err := casbin.NewCasbinMiddleware("example/config/model.conf", "example/config/policy.csv", subjectFromSession)
if err != nil {
log.Fatal(err)
}
h.GET("/book",
m.RequiresPermissions("book-read",
casbin.WithPermissionParserSeparator("-"),
),
func(ctx context.Context, c *app.RequestContext) {
c.String(200, "you read the book successfully")
},
)
...
}
WithUnauthorized
Unauthorized
defined the response body for unauthorized responses.
The function signature is as follows:
func WithUnauthorized(u app.HandlerFunc) Option
Sample code:
func main(){
...
h := server.Default()
m, err := casbin.NewCasbinMiddleware("example/config/model.conf", "example/config/policy.csv", subjectFromSession)
if err != nil {
log.Fatal(err)
}
h.GET("/book",
m.RequiresPermissions("book:read",
casbin.WithUnauthorized(func(ctx context.Context, c *app.RequestContext) {
c.AbortWithStatus(consts.StatusUnauthorized)
}),
),
func(ctx context.Context, c *app.RequestContext) {
c.String(200, "you read the book successfully")
},
)
...
}
WithForbidden
Forbidden
defines the response body for forbidden responses.
The function signature is as follows:
func WithForbidden(f app.HandlerFunc) Option
Sample code:
func main(){
...
h := server.Default()
m, err := casbin.NewCasbinMiddleware("example/config/model.conf", "example/config/policy.csv", subjectFromSession)
if err != nil {
log.Fatal(err)
}
h.GET("/book",
m.RequiresPermissions("book:read",
casbin.WithForbidden(func(ctx context.Context, c *app.RequestContext) {
c.AbortWithStatus(consts.StatusForbidden)
}),
),
func(ctx context.Context, c *app.RequestContext) {
c.String(200, "you read the book successfully")
},
)
...
}