enforcer.go 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. // Package rbac RBAC 接口强制层(第②层)—— Casbin v3 策略引擎 + 自写 GORM 存储。
  2. //
  3. // 定位(RBAC 三层模型的"接口强制层",2026-11-08 拍板"引入 Casbin+GORM"):
  4. // - 本包持有进程内 *casbin.Enforcer(RBAC with Domains,模型见 casbin_model.conf);
  5. // - 策略存 MySQL `casbin_rule` 表(ptype/v0/v1/v2/v3),经自写 GORM Adapter 读写
  6. // (不用官方 gorm-adapter——其依赖拉入 pgx/sqlserver/postgres driver + modernc.org/sqlite 大跳,
  7. // 风险大;自写 Adapter 仅 6 方法,零额外 driver 依赖,只需 casbin/v3);
  8. // - 接口强制中间件见 middleware.go(CasbinAuth);策略改配写库后 Reload 即时生效(跨实例 30s 轮询,设计 §9)。
  9. //
  10. // 设计依据:unified/01-architecture/casbin-rbac-design.md v1.1。
  11. // - sub=角色码(sc.Role,非 ULID;保留 g 关系口子供未来人级授权);
  12. // - 两表并存:casbin_rule(接口路径)与 t_role_permission(菜单权限点)不合并;
  13. // - 超管代码层恒放行(middleware 判 sc.SuperAdmin,不查策略)。
  14. //
  15. // 依赖方向:本包属 infra 横切(鉴权执行),依赖 infra/db + gorm + casbin/v3;
  16. // 不依赖业务域(identity/org)。装配层(cmd/api/main.go)在 DB 就绪后 NewEnforcer。
  17. package rbac
  18. import (
  19. "embed"
  20. "fmt"
  21. "strings"
  22. "github.com/casbin/casbin/v3"
  23. "github.com/casbin/casbin/v3/model"
  24. "github.com/casbin/casbin/v3/persist"
  25. "gorm.io/gorm"
  26. )
  27. //go:embed casbin_model.conf
  28. var modelConfFS embed.FS
  29. // gormAdapter 自写 GORM 存储适配器(实现 casbin persist.Adapter 6 方法)。
  30. //
  31. // 表结构 `casbin_rule`(ptype/v0/v1/v2/v3)——目标态 DDL 见 core-model-ddl.sql v1.3,
  32. // 本包只读写、不建表(建表走 DDL 先行纪律 + main 的 AutoMigrate 守卫,见 S3)。
  33. type gormAdapter struct {
  34. db *gorm.DB
  35. }
  36. // ruleRow casbin_rule 表行映射(显式列名,对齐 §1.2 命名纪律)。
  37. type ruleRow struct {
  38. Ptype string `gorm:"column:ptype"`
  39. V0 string `gorm:"column:v0"`
  40. V1 string `gorm:"column:v1"`
  41. V2 string `gorm:"column:v2"`
  42. V3 string `gorm:"column:v3"`
  43. }
  44. // 编译期断言:gormAdapter 须满足 casbin persist.Adapter(签名漂移即编译失败)。
  45. var _ persist.Adapter = (*gormAdapter)(nil)
  46. // LoadPolicy 全量加载策略入模型(LoadPolicy 语义:清空现有 + 逐行载入)。
  47. func (a *gormAdapter) LoadPolicy(m model.Model) error {
  48. var rows []ruleRow
  49. if err := a.db.Model(&ruleRow{}).Find(&rows).Error; err != nil {
  50. return err
  51. }
  52. m.ClearPolicy()
  53. for _, r := range rows {
  54. line := strings.Join([]string{r.Ptype, r.V0, r.V1, r.V2, r.V3}, ",")
  55. if err := persist.LoadPolicyLine(line, m); err != nil {
  56. return err
  57. }
  58. }
  59. return nil
  60. }
  61. // SavePolicy 全量保存策略(清空表 + 逐条写入;本包仅在显式全量替换时调用,默认改配走 RemoveFilteredPolicy+AddPolicy)。
  62. func (a *gormAdapter) SavePolicy(m model.Model) error {
  63. // 清空
  64. if err := a.db.Exec("DELETE FROM casbin_rule").Error; err != nil {
  65. return err
  66. }
  67. // 逐条写入(p 全量;g 一般为空)。
  68. // v3 model.GetPolicy(sec, ptype) 双参、双返回值;本模型 p 段 ptype="p"、g 段 ptype="g"。
  69. for _, sec := range []string{"p", "g"} {
  70. pol, err := m.GetPolicy(sec, sec)
  71. if err != nil {
  72. return err
  73. }
  74. for _, rule := range pol {
  75. if err := a.AddPolicy(sec, sec, rule); err != nil {
  76. return err
  77. }
  78. }
  79. }
  80. return nil
  81. }
  82. // AddPolicy 追加一条策略(ptype + 字段;v3 列不足时补空串,保持 5 列)。
  83. func (a *gormAdapter) AddPolicy(sec string, ptype string, rule []string) error {
  84. fields := rule
  85. for len(fields) < 4 {
  86. fields = append(fields, "")
  87. }
  88. row := ruleRow{
  89. Ptype: ptype,
  90. V0: fields[0],
  91. V1: fields[1],
  92. V2: fields[2],
  93. V3: fields[3],
  94. }
  95. return a.db.Create(&row).Error
  96. }
  97. // RemovePolicy 删除精确匹配的某条策略。
  98. func (a *gormAdapter) RemovePolicy(sec string, ptype string, rule []string) error {
  99. q := a.db.Where("ptype = ?", ptype)
  100. if len(rule) > 0 {
  101. q = q.Where("v0 = ?", rule[0])
  102. }
  103. if len(rule) > 1 {
  104. q = q.Where("v1 = ?", rule[1])
  105. }
  106. if len(rule) > 2 {
  107. q = q.Where("v2 = ?", rule[2])
  108. }
  109. if len(rule) > 3 {
  110. q = q.Where("v3 = ?", rule[3])
  111. }
  112. return q.Delete(&ruleRow{}).Error
  113. }
  114. // RemoveFilteredPolicy 按字段位删策略(fieldIndex 从 0 计,对应 v0..v3)。
  115. // fieldValues 为空且 fieldIndex>=0 时,删除该 ptype 下所有行(全量替换语义用)。
  116. func (a *gormAdapter) RemoveFilteredPolicy(sec string, ptype string, fieldIndex int, fieldValues ...string) error {
  117. q := a.db.Where("ptype = ?", ptype)
  118. if len(fieldValues) > 0 {
  119. if fieldIndex < 0 || fieldIndex > 3 {
  120. return fmt.Errorf("rbac: RemoveFilteredPolicy 非法字段位 %d", fieldIndex)
  121. }
  122. col := []string{"v0", "v1", "v2", "v3"}[fieldIndex]
  123. q = q.Where(fmt.Sprintf("%s = ?", col), fieldValues[0])
  124. }
  125. return q.Delete(&ruleRow{}).Error
  126. }
  127. // Enforcer 进程内 Casbin 鉴权执行器(启动装配一次,全进程共享;含自写 GORM Adapter)。
  128. //
  129. // 并发说明:*casbin.Enforcer 内部自带读写锁,Enforce/LoadPolicy 并发安全;
  130. // 本结构仅持有只读引用 + 暴露 Reload,不额外加锁。
  131. type Enforcer struct {
  132. e *casbin.Enforcer
  133. }
  134. // NewEnforcer 构造 Enforcer(model.conf 内嵌 + GORM Adapter;启动即 LoadPolicy)。
  135. //
  136. // 入参:
  137. // - db *gorm.DB:数据库句柄(必填;casbin_rule 表须已建,见 core-model-ddl.sql v1.3)。
  138. //
  139. // 出参:
  140. // - *Enforcer:就绪执行器(LoadPolicy 成功后返回;表为空时策略为空集——非超管全 1003,
  141. // 属 fail-closed;装配层应告警提示种子缺失,见设计 §9 启动)。
  142. // - error:DB 加载策略失败。
  143. func NewEnforcer(db *gorm.DB) (*Enforcer, error) {
  144. if db == nil {
  145. return nil, fmt.Errorf("rbac: DB 未注入")
  146. }
  147. raw, err := modelConfFS.ReadFile("casbin_model.conf")
  148. if err != nil {
  149. return nil, fmt.Errorf("rbac: 读取内嵌 model.conf 失败: %w", err)
  150. }
  151. m, err := model.NewModelFromString(string(raw))
  152. if err != nil {
  153. return nil, fmt.Errorf("rbac: 解析 model.conf 失败: %w", err)
  154. }
  155. adapter := &gormAdapter{db: db}
  156. e, err := casbin.NewEnforcer(m, adapter)
  157. if err != nil {
  158. return nil, fmt.Errorf("rbac: 构造 Enforcer 失败: %w", err)
  159. }
  160. return &Enforcer{e: e}, nil
  161. }
  162. // Reload 重新加载策略(改配端点写库后调用,本实例即时生效;跨实例靠 30s 轮询,设计 §9)。
  163. func (en *Enforcer) Reload() error {
  164. return en.e.LoadPolicy()
  165. }
  166. // Get 暴露底层 *casbin.Enforcer(供 middleware 调 Enforce;不暴露写接口,写走改配端点)。
  167. func (en *Enforcer) Get() *casbin.Enforcer {
  168. return en.e
  169. }