// 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 }