259 lines
6.7 KiB
Go
259 lines
6.7 KiB
Go
// Package casbin
|
||
// @Link https://github.com/bufanyun/hotgo
|
||
// @Copyright Copyright (c) 2023 HotGo CLI
|
||
// @Author Ms <133814250@qq.com>
|
||
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
|
||
package casbin
|
||
|
||
import (
|
||
"context"
|
||
"fmt"
|
||
"hotgo/internal/consts"
|
||
"hotgo/internal/dao"
|
||
"hotgo/internal/library/cache"
|
||
"net/http"
|
||
"regexp"
|
||
"strings"
|
||
"time"
|
||
|
||
"github.com/casbin/casbin/v2"
|
||
"github.com/casbin/casbin/v2/model"
|
||
casbinutil "github.com/casbin/casbin/v2/util"
|
||
_ "github.com/gogf/gf/contrib/drivers/mysql/v2"
|
||
"github.com/gogf/gf/v2/container/gvar"
|
||
"github.com/gogf/gf/v2/encoding/gjson"
|
||
"github.com/gogf/gf/v2/frame/g"
|
||
"github.com/gogf/gf/v2/os/gfile"
|
||
"github.com/gogf/gf/v2/os/gres"
|
||
"github.com/gogf/gf/v2/text/gstr"
|
||
)
|
||
|
||
const (
|
||
ActionGet = http.MethodGet
|
||
ActionPost = http.MethodPost
|
||
ActionPut = http.MethodPut
|
||
ActionDelete = http.MethodDelete
|
||
ActionAll = "GET|POST|PUT|DELETE|PATCH|OPTIONS|HEAD"
|
||
)
|
||
|
||
var Enforcer *casbin.Enforcer
|
||
|
||
const permissionCacheKey = "admin:role:permissions"
|
||
|
||
type PermissionPolicy struct {
|
||
Key string `json:"key"`
|
||
Perm string `json:"perm"`
|
||
ActRegexp string `json:"actRegexp"`
|
||
}
|
||
|
||
// InitEnforcer 初始化
|
||
func InitEnforcer(ctx context.Context) {
|
||
var (
|
||
link = getDbLink(ctx)
|
||
a, err = NewAdapter(link.String())
|
||
)
|
||
|
||
if err != nil {
|
||
g.Log().Panicf(ctx, "casbin.NewAdapter err . %v", err)
|
||
return
|
||
}
|
||
|
||
path := "manifest/config/casbin.conf"
|
||
|
||
// 优先从本地加载casbin.conf,如果不存在就从资源文件中找
|
||
modelContent := gfile.GetContents(path)
|
||
if len(modelContent) == 0 {
|
||
if !gres.IsEmpty() && gres.Contains(path) {
|
||
modelContent = string(gres.GetContent(path))
|
||
}
|
||
}
|
||
|
||
if len(modelContent) == 0 {
|
||
g.Log().Panicf(ctx, "casbin model file does not exist:%v", path)
|
||
}
|
||
|
||
m, err := model.NewModelFromString(modelContent)
|
||
if err != nil {
|
||
g.Log().Panicf(ctx, "casbin NewModelFromString err:%v", err)
|
||
}
|
||
|
||
Enforcer, err = casbin.NewEnforcer(m, a)
|
||
if err != nil {
|
||
g.Log().Panicf(ctx, "casbin NewEnforcer err:%v", err)
|
||
}
|
||
loadPermissions(ctx)
|
||
if err = RefreshPermissionCache(ctx); err != nil {
|
||
g.Log().Panicf(ctx, "casbin RefreshPermissionCache err:%v", err)
|
||
}
|
||
}
|
||
|
||
// GetDbLink 获取数据库链接
|
||
func getDbLink(ctx context.Context) *gvar.Var {
|
||
|
||
link := g.Cfg().MustGet(ctx, "database.default")
|
||
//读写分离
|
||
if !link.IsSlice() {
|
||
return g.Cfg().MustGet(ctx, "database.default.link")
|
||
}
|
||
|
||
for _, v := range link.Array() {
|
||
// 只获取主库
|
||
val := v.(map[string]interface{})
|
||
if val["role"] == "master" {
|
||
return gvar.New(val["link"])
|
||
}
|
||
}
|
||
return gvar.New("database.default.0.link")
|
||
|
||
}
|
||
|
||
func queryPermissionPolicies(ctx context.Context) (policies []*PermissionPolicy, err error) {
|
||
type RolePermission struct {
|
||
Key string `json:"key"`
|
||
Permissions string `json:"permissions"`
|
||
}
|
||
var (
|
||
records []*RolePermission
|
||
)
|
||
//别名拼接 r.key m.permissions
|
||
q := func(alias string, column string) string {
|
||
return fmt.Sprintf("%s.%s", alias, column)
|
||
}
|
||
err = g.Model(gstr.Join([]string{dao.AdminRole.Table(), "r"}, " ")).
|
||
LeftJoin(gstr.Join([]string{dao.AdminRoleMenu.Table(), "rm"}, " "), "r.id=rm.role_id").
|
||
LeftJoin(gstr.Join([]string{dao.AdminMenu.Table(), "m"}, " "), "rm.menu_id=m.id").
|
||
Fields(q("r", dao.AdminRole.Columns().Key), q("m", dao.AdminMenu.Columns().Permissions)).
|
||
Where(q("r", dao.AdminRole.Columns().Status), consts.StatusEnabled).
|
||
Where(q("m", dao.AdminMenu.Columns().Status), consts.StatusEnabled).
|
||
WhereNot(q("m", dao.AdminMenu.Columns().Permissions), "").
|
||
WhereNot(q("r", dao.AdminRole.Columns().Key), consts.SuperRoleKey).
|
||
Scan(&records)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
for _, record := range records {
|
||
if strings.Contains(record.Permissions, ",") {
|
||
lst := strings.Split(record.Permissions, ",")
|
||
for _, permission := range lst {
|
||
permission = strings.TrimSpace(permission)
|
||
if permission == "" {
|
||
continue
|
||
}
|
||
policies = append(policies, &PermissionPolicy{Key: record.Key, Perm: permission, ActRegexp: ActionAll})
|
||
}
|
||
} else {
|
||
permission := strings.TrimSpace(record.Permissions)
|
||
if permission == "" {
|
||
continue
|
||
}
|
||
policies = append(policies, &PermissionPolicy{Key: record.Key, Perm: permission, ActRegexp: ActionAll})
|
||
}
|
||
}
|
||
return
|
||
}
|
||
|
||
func loadPermissions(ctx context.Context) {
|
||
policies, err := queryPermissionPolicies(ctx)
|
||
if err != nil {
|
||
g.Log().Fatalf(ctx, "loadPermissions Scan err:%v", err)
|
||
return
|
||
}
|
||
|
||
rules := make([][]string, 0, len(policies))
|
||
for _, policy := range policies {
|
||
rules = append(rules, []string{policy.Key, policy.Perm, policy.ActRegexp})
|
||
}
|
||
if _, err = Enforcer.AddPolicies(rules); err != nil {
|
||
g.Log().Fatalf(ctx, "loadPermissions AddPolicies err:%v", err)
|
||
return
|
||
}
|
||
}
|
||
|
||
func Clear(ctx context.Context) (err error) {
|
||
policy, err := Enforcer.GetPolicy()
|
||
if err != nil {
|
||
g.Log().Warningf(ctx, "Enforcer RemovePolicies err:%+v", err)
|
||
return
|
||
}
|
||
_, err = Enforcer.RemovePolicies(policy)
|
||
if err != nil {
|
||
g.Log().Warningf(ctx, "Enforcer RemovePolicies err:%+v", err)
|
||
return
|
||
}
|
||
|
||
// 检查是否清理干净
|
||
if len(policy) > 0 {
|
||
return Clear(ctx)
|
||
}
|
||
return
|
||
}
|
||
|
||
func Refresh(ctx context.Context) (err error) {
|
||
if err = Clear(ctx); err != nil {
|
||
return err
|
||
}
|
||
loadPermissions(ctx)
|
||
return RefreshPermissionCache(ctx)
|
||
}
|
||
|
||
func RefreshPermissionCache(ctx context.Context) (err error) {
|
||
policies, err := queryPermissionPolicies(ctx)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
rolePolicies := make(map[string][]*PermissionPolicy)
|
||
for _, policy := range policies {
|
||
rolePolicies[policy.Key] = append(rolePolicies[policy.Key], policy)
|
||
}
|
||
data, err := gjson.Encode(rolePolicies)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
return cache.Instance().Set(ctx, permissionCacheKey, string(data), 30*24*time.Hour)
|
||
}
|
||
|
||
func cachedPermissionPolicies(ctx context.Context) (rolePolicies map[string][]*PermissionPolicy, err error) {
|
||
value, err := cache.Instance().Get(ctx, permissionCacheKey)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
if value.IsNil() {
|
||
if err = RefreshPermissionCache(ctx); err != nil {
|
||
return nil, err
|
||
}
|
||
value, err = cache.Instance().Get(ctx, permissionCacheKey)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
}
|
||
rolePolicies = make(map[string][]*PermissionPolicy)
|
||
if value.IsNil() {
|
||
return rolePolicies, nil
|
||
}
|
||
if err = gjson.DecodeTo([]byte(value.String()), &rolePolicies); err != nil {
|
||
return nil, err
|
||
}
|
||
return
|
||
}
|
||
|
||
func Verify(ctx context.Context, roleKey, path, method string) (bool, error) {
|
||
rolePolicies, err := cachedPermissionPolicies(ctx)
|
||
if err != nil {
|
||
return false, err
|
||
}
|
||
for _, policy := range rolePolicies[roleKey] {
|
||
if !casbinutil.KeyMatch(path, policy.Perm) {
|
||
continue
|
||
}
|
||
ok, err := regexp.MatchString(policy.ActRegexp, method)
|
||
if err != nil {
|
||
return false, err
|
||
}
|
||
if ok {
|
||
return true, nil
|
||
}
|
||
}
|
||
return false, nil
|
||
}
|