首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >casbin源码分析

casbin源码分析

作者头像
golangLeetcode
发布2022-08-02 19:15:56
发布2022-08-02 19:15:56
73500
代码可运行
举报
运行总次数:0
代码可运行

https://casbin.org/editor/

Casbin的工作原理

在 Casbin 中, 访问控制模型被抽象为基于 **PERM **(Policy, Effect, Request, Matcher) [策略,效果,请求,匹配器]的一个文件。

Policy:定义权限的规则

Effect:定义组合了多个Policy之后的结果

Request:访问请求

Matcher:判断Request是否满足Policy

首先会定义一堆Policy,然后通过Matcher来判断Request和Policy是否匹配,然后通过Effect来判断匹配结果是Allow还是Deny。

Model CONF 至少应包含四个部分: [request_definition], [policy_definition], [policy_effect], [matchers]。

如果 model 使用 RBAC, 还需要添加[role_definition]部分。

Model CONF 文件可以包含注释。注释以 # 开头, # 会注释该行剩余部分。

request_definition:用于request的定义,它明确了e.Enforce(...)函数中参数的定义,sub, obj, act 表示经典三元组: 访问实体 (Subject),访问资源 (Object) 和访问方法 (Action)。

policy_definition:用于policy的定义,每条规则通常以形如p的policy type开头,比如p,joker,data1,read就是一条joker具有data1读权限的规则。

role_definition:是RBAC角色继承关系的定义。g 是一个 RBAC系统,_, _表示角色继承关系的前项和后项,即前项继承后项角色的权限。

policy_effect:是对policy生效范围的定义,它对request的决策结果进行统一的决策,比如e = some(where (p.eft == allow))就表示如果存在任意一个决策结果为allow的匹配规则,则最终决策结果为allow。p.eft 表示策略规则的决策结果,可以为allow 或者deny,当不指定规则的决策结果时,取默认值allow 。

matchers:定义了策略匹配者。匹配者是一组表达式,它定义了如何根据请求来匹配策略规则

实例1:

代码语言:javascript
代码运行次数:0
运行
复制
package main

import (
  "fmt"
  "log"

  "github.com/casbin/casbin"
  xormadapter "github.com/casbin/xorm-adapter"
  "github.com/gin-gonic/gin"
  _ "github.com/go-sql-driver/mysql"
)

func main() { // 要使用自己定义的数据库rbac_db,最后的true很重要.默认为false,使用缺省的数据库名casbin,不存在则创建
  a := xormadapter.NewAdapter("mysql", "root:@tcp(127.0.0.1:3306)/goblog?charset=utf8", true)
  if a == nil {
    log.Printf("连接数据库错误: %v", a)
    return
  }
  e := casbin.NewEnforcer("./exp1/rbac_models.conf", a)
  if e == nil {
    log.Printf("初始化casbin错误: %v", e)
    return
  } //从DB加载策略 e.LoadPolicy()

  //获取router路由对象
  r := gin.New()

  r.POST("/api/v1/add", func(c *gin.Context) {
    fmt.Println("增加Policy")
    if ok := e.AddPolicy("admin", "/api/v1/hello", "GET"); !ok {
      fmt.Println("Policy已经存在")
    } else {
      fmt.Println("增加成功")
    }
  }) //删除policy
  r.DELETE("/api/v1/delete", func(c *gin.Context) {
    fmt.Println("删除Policy")
    if ok := e.RemovePolicy("admin", "/api/v1/hello", "GET"); !ok {
      fmt.Println("Policy不存在")
    } else {
      fmt.Println("删除成功")
    }
  })
  //获取policy
  r.GET("/api/v1/get", func(c *gin.Context) {
    fmt.Println("查看policy")
    list := e.GetPolicy()
    for _, vlist := range list {
      for _, v := range vlist {
        fmt.Printf("value: %s, ", v)
      }
    }
  })
  //使用自定义拦截器中间件
  r.Use(Authorize(e)) //创建请求
  r.GET("/api/v1/hello", func(c *gin.Context) {
    fmt.Println("Hello 接收到GET请求..")
  })

  r.Run(":9000") //参数为空 默认监听8080端口
}

//拦截器
func Authorize(e *casbin.Enforcer) gin.HandlerFunc {
  return func(c *gin.Context) {
    //获取请求的URI
    obj := c.Request.URL.RequestURI()
    //获取请求方法
    act := c.Request.Method
    //获取用户的角色
    sub := "admin"
    //判断策略中是否存在
    if ok := e.Enforce(sub, obj, act); ok {
      fmt.Println("恭喜您,权限验证通过")
      c.Next()
    } else {
      fmt.Println("很遗憾,权限验证没有通过")
      c.Abort()
    }
  }
}
代码语言:javascript
代码运行次数:0
运行
复制
[request_definition]
r = sub, obj, act

[policy_definition]
p = sub, obj, act

[role_definition]
g = _, _

[policy_effect]
e = some(where (p.eft == allow))

[matchers]
m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act

实例2

代码语言:javascript
代码运行次数:0
运行
复制
package main

import (
  "fmt"
  "time"

  "github.com/casbin/casbin"
  "github.com/casbin/casbin/model"
)

type Person struct {
  Role string
  Name string
}
type Gate struct {
  Name string
}
type Env struct {
  Time     time.Time
  Location string
}

func (env *Env) IsSchooltime() bool {
  return env.Time.Hour() >= 8 && env.Time.Hour() <= 18
}

func TestTeacherEnterSchoolGate() {
  p1 := Person{Role: "Student", Name: "Yun"}
  p2 := Person{Role: "Teacher", Name: "Devin"}
  persons := []Person{p1, p2}
  g1 := Gate{Name: "School Gate"}
  g2 := Gate{Name: "Factory Gate"}
  gates := []Gate{g1, g2}
  const modelText = `
 [request_definition]
 r = sub, obj, act, env
 
 [policy_definition]
 p = sub, obj,act
 
 [policy_effect]
 e = some(where (p.eft == allow))
 
 [matchers]
 m = r.sub.Role=='Teacher' && r.obj.Name=='School Gate' && r.act in('In','Out') && r.env.Time.Hour >7 && r.env.Time.Hour <= 18
 `
  //m = r.sub.Role=='Teacher' && r.obj.Name=='School Gate' && r.act in('In','Out') && r.env.IsSchooltime()
  m := model.Model{}

  m.LoadModelFromText(modelText)
  e := casbin.NewEnforcer(m)
  envs := []*Env{InitEnv(9), InitEnv(23)}
  for _, env := range envs {
    fmt.Println("\r\nTime:", env.Time.Local())
    for _, p := range persons {
      for _, g := range gates {
        pass := e.Enforce(p, g, "In", env)
        fmt.Println(p.Role, p.Name, "In", g.Name, pass)
        pass = e.Enforce(p, g, "Control", env)
        fmt.Println(p.Role, p.Name, "Control", g.Name, pass)
      }
    }
  }
}

func InitEnv(hour int) *Env {
  env := &Env{}
  env.Time = time.Date(2019, 8, 20, hour, 0, 0, 0, time.Local)
  return env
}

func main() {
  TestTeacherEnterSchoolGate()
}

通过上述两个实例分析我们发现casbin的核心逻辑有下面几步

1,加载模型

2,加载配置文件

3,初始化enforcer

4,enforce

代码语言:javascript
代码运行次数:0
运行
复制
exp1
  a := xormadapter.NewAdapter("mysql", "root:@tcp(127.0.0.1:3306)/goblog?charset=utf8", true)
    e := casbin.NewEnforcer("./exp1/rbac_models.conf", a)
    if ok := e.AddPolicy("admin", "/api/v1/hello", "GET"); !ok {
    if ok := e.Enforce(sub, obj, act); ok {

exp2
  m := model.Model{}
    m.LoadModelFromText(modelText)
    e := casbin.NewEnforcer(m)
    pass := e.Enforce(p, g, "In", env)

1,看下模型的定义

代码语言:javascript
代码运行次数:0
运行
复制
type Model map[string]AssertionMap
代码语言:javascript
代码运行次数:0
运行
复制
type AssertionMap map[string]*Assertion
代码语言:javascript
代码运行次数:0
运行
复制
// Assertion represents an expression in a section of the model.
// For example: r = sub, obj, act
type Assertion struct {
  Key    string
  Value  string
  Tokens []string
  Policy [][]string
  RM     rbac.RoleManager
}
代码语言:javascript
代码运行次数:0
运行
复制
// RoleManager provides interface to define the operations for managing roles.
type RoleManager interface {
  // Clear clears all stored data and resets the role manager to the initial state.
  Clear() error
  // AddLink adds the inheritance link between two roles. role: name1 and role: name2.
  // domain is a prefix to the roles (can be used for other purposes).
  AddLink(name1 string, name2 string, domain ...string) error
  // DeleteLink deletes the inheritance link between two roles. role: name1 and role: name2.
  // domain is a prefix to the roles (can be used for other purposes).
  DeleteLink(name1 string, name2 string, domain ...string) error
  // HasLink determines whether a link exists between two roles. role: name1 inherits role: name2.
  // domain is a prefix to the roles (can be used for other purposes).
  HasLink(name1 string, name2 string, domain ...string) (bool, error)
  // GetRoles gets the roles that a user inherits.
  // domain is a prefix to the roles (can be used for other purposes).
  GetRoles(name string, domain ...string) ([]string, error)
  // GetUsers gets the users that inherits a role.
  // domain is a prefix to the users (can be used for other purposes).
  GetUsers(name string, domain ...string) ([]string, error)
  // PrintRoles prints all the roles to log.
  PrintRoles() error
}

2,加载配置文件

代码语言:javascript
代码运行次数:0
运行
复制
func (model Model) LoadModelFromText(text string) {
  cfg, err := config.NewConfigFromText(text)
  if err != nil {
    panic(err)
  }

  loadSection(model, cfg, "r")
  loadSection(model, cfg, "p")
  loadSection(model, cfg, "e")
  loadSection(model, cfg, "m")

  loadSection(model, cfg, "g")
}
代码语言:javascript
代码运行次数:0
运行
复制
// Config represents an implementation of the ConfigInterface
type Config struct {
  // map is not safe.
  sync.RWMutex
  // Section:key=value
  data map[string]map[string]string
}
代码语言:javascript
代码运行次数:0
运行
复制
// NewConfig create an empty configuration representation from file.
func NewConfig(confName string) (ConfigInterface, error) {
  c := &Config{
    data: make(map[string]map[string]string),
  }
  err := c.parse(confName)
  return c, err
}

func (c *Config) parse(fname string) (err error) {
  c.Lock()
  f, err := os.Open(fname)
  if err != nil {
    return err
  }
  defer c.Unlock()
  defer f.Close()

  buf := bufio.NewReader(f)
  return c.parseBuffer(buf)
}

func loadAssertion(model Model, cfg config.ConfigInterface, sec string, key string) bool {
  value := cfg.String(sectionNameMap[sec] + "::" + key)
  return model.AddDef(sec, key, value)
}

var sectionNameMap = map[string]string{
  "r": "request_definition",
  "p": "policy_definition",
  "g": "role_definition",
  "e": "policy_effect",
  "m": "matchers",
}
代码语言:javascript
代码运行次数:0
运行
复制
// AddDef adds an assertion to the model.
func (model Model) AddDef(sec string, key string, value string) bool {
  ast := Assertion{}
  ast.Key = key
  ast.Value = value

  if ast.Value == "" {
    return false
  }

  if sec == "r" || sec == "p" {
    ast.Tokens = strings.Split(ast.Value, ", ")
    for i := range ast.Tokens {
      ast.Tokens[i] = key + "_" + ast.Tokens[i]
    }
  } else {
    ast.Value = util.RemoveComments(util.EscapeAssertion(ast.Value))
  }

  _, ok := model[sec]
  if !ok {
    model[sec] = make(AssertionMap)
  }

  model[sec][key] = &ast
  return true
}

3,初始化enforcer

代码语言:javascript
代码运行次数:0
运行
复制
// File:
// e := casbin.NewEnforcer("path/to/basic_model.conf", "path/to/basic_policy.csv")
// MySQL DB:
// a := mysqladapter.NewDBAdapter("mysql", "mysql_username:mysql_password@tcp(127.0.0.1:3306)/")
// e := casbin.NewEnforcer("path/to/basic_model.conf", a)
func NewEnforcer(params ...interface{}) *Enforcer {
  e := &Enforcer{}
    switch p1 := params[1].(type) {
    case string:
        e.InitWithFile(p0, p1)
    default:
        e.InitWithAdapter(p0, p1.(persist.Adapter))
    }
}
代码语言:javascript
代码运行次数:0
运行
复制
// Enforcer is the main interface for authorization enforcement and policy management.
type Enforcer struct {
  modelPath string
  model     model.Model
  fm        model.FunctionMap
  eft       effect.Effector

  adapter persist.Adapter
  watcher persist.Watcher
  rm      rbac.RoleManager

  enabled            bool
  autoSave           bool
  autoBuildRoleLinks bool
}

type FunctionMap map[string]func(args ...interface{}) (interface{}, error)

// Effector is the interface for Casbin effectors.
type Effector interface {
  // MergeEffects merges all matching results collected by the enforcer into a single decision.
  MergeEffects(expr string, effects []Effect, results []float64) (bool, error)
}
代码语言:javascript
代码运行次数:0
运行
复制
// Adapter is the interface for Casbin adapters.
type Adapter interface {
  // LoadPolicy loads all policy rules from the storage.
  LoadPolicy(model model.Model) error
  // SavePolicy saves all policy rules to the storage.
  SavePolicy(model model.Model) error

  // AddPolicy adds a policy rule to the storage.
  // This is part of the Auto-Save feature.
  AddPolicy(sec string, ptype string, rule []string) error
  // RemovePolicy removes a policy rule from the storage.
  // This is part of the Auto-Save feature.
  RemovePolicy(sec string, ptype string, rule []string) error
  // RemoveFilteredPolicy removes policy rules that match the filter from the storage.
  // This is part of the Auto-Save feature.
  RemoveFilteredPolicy(sec string, ptype string, fieldIndex int, fieldValues ...string) error
}

// Watcher is the interface for Casbin watchers.
type Watcher interface {
  // SetUpdateCallback sets the callback function that the watcher will call
  // when the policy in DB has been changed by other instances.
  // A classic callback is Enforcer.LoadPolicy().
  SetUpdateCallback(func(string)) error
  // Update calls the update callback of other instances to synchronize their policy.
  // It is usually called after changing the policy in DB, like Enforcer.SavePolicy(),
  // Enforcer.AddPolicy(), Enforcer.RemovePolicy(), etc.
  Update() error
  // Close stops and releases the watcher, the callback function will not be called any more.
  Close()
}

// RoleManager provides interface to define the operations for managing roles.
type RoleManager interface {
  // Clear clears all stored data and resets the role manager to the initial state.
  Clear() error
  // AddLink adds the inheritance link between two roles. role: name1 and role: name2.
  // domain is a prefix to the roles (can be used for other purposes).
  AddLink(name1 string, name2 string, domain ...string) error
  // DeleteLink deletes the inheritance link between two roles. role: name1 and role: name2.
  // domain is a prefix to the roles (can be used for other purposes).
  DeleteLink(name1 string, name2 string, domain ...string) error
  // HasLink determines whether a link exists between two roles. role: name1 inherits role: name2.
  // domain is a prefix to the roles (can be used for other purposes).
  HasLink(name1 string, name2 string, domain ...string) (bool, error)
  // GetRoles gets the roles that a user inherits.
  // domain is a prefix to the roles (can be used for other purposes).
  GetRoles(name string, domain ...string) ([]string, error)
  // GetUsers gets the users that inherits a role.
  // domain is a prefix to the users (can be used for other purposes).
  GetUsers(name string, domain ...string) ([]string, error)
  // PrintRoles prints all the roles to log.
  PrintRoles() error
}

4,enforce

代码语言:javascript
代码运行次数:0
运行
复制
// Enforce decides whether a "subject" can access a "object" with the operation "action", input parameters are usually: (sub, obj, act).
func (e *Enforcer) Enforce(rvals ...interface{}) bool {
  if !e.enabled {
    return true
  }

  functions := make(map[string]govaluate.ExpressionFunction)
  for key, function := range e.fm {
    functions[key] = function
  }
  if _, ok := e.model["g"]; ok {
    for key, ast := range e.model["g"] {
      rm := ast.RM
      functions[key] = util.GenerateGFunction(rm)
    }
  }

  expString := e.model["m"]["m"].Value
  expression, err := govaluate.NewEvaluableExpressionWithFunctions(expString, functions)
  if err != nil {
    panic(err)
  }

  rTokens := make(map[string]int, len(e.model["r"]["r"].Tokens))
  for i, token := range e.model["r"]["r"].Tokens {
    rTokens[token] = i
  }
  pTokens := make(map[string]int, len(e.model["p"]["p"].Tokens))
  for i, token := range e.model["p"]["p"].Tokens {
    pTokens[token] = i
  }

  parameters := enforceParameters{
    rTokens: rTokens,
    rVals:   rvals,

    pTokens: pTokens,
  }

  var policyEffects []effect.Effect
  var matcherResults []float64
  if policyLen := len(e.model["p"]["p"].Policy); policyLen != 0 {
    policyEffects = make([]effect.Effect, policyLen)
    matcherResults = make([]float64, policyLen)
    if len(e.model["r"]["r"].Tokens) != len(rvals) {
      panic(
        fmt.Sprintf(
          "Invalid Request Definition size: expected %d got %d rvals: %v",
          len(e.model["r"]["r"].Tokens),
          len(rvals),
          rvals))
    }
    for i, pvals := range e.model["p"]["p"].Policy {
      // log.LogPrint("Policy Rule: ", pvals)
      if len(e.model["p"]["p"].Tokens) != len(pvals) {
        panic(
          fmt.Sprintf(
            "Invalid Policy Rule size: expected %d got %d pvals: %v",
            len(e.model["p"]["p"].Tokens),
            len(pvals),
            pvals))
      }

      parameters.pVals = pvals

      result, err := expression.Eval(parameters)
      // log.LogPrint("Result: ", result)

      if err != nil {
        policyEffects[i] = effect.Indeterminate
        panic(err)
      }

      switch result := result.(type) {
      case bool:
        if !result {
          policyEffects[i] = effect.Indeterminate
          continue
        }
      case float64:
        if result == 0 {
          policyEffects[i] = effect.Indeterminate
          continue
        } else {
          matcherResults[i] = result
        }
      default:
        panic(errors.New("matcher result should be bool, int or float"))
      }

      if j, ok := parameters.pTokens["p_eft"]; ok {
        eft := parameters.pVals[j]
        if eft == "allow" {
          policyEffects[i] = effect.Allow
        } else if eft == "deny" {
          policyEffects[i] = effect.Deny
        } else {
          policyEffects[i] = effect.Indeterminate
        }
      } else {
        policyEffects[i] = effect.Allow
      }

      if e.model["e"]["e"].Value == "priority(p_eft) || deny" {
        break
      }

    }
  } else {
    policyEffects = make([]effect.Effect, 1)
    matcherResults = make([]float64, 1)

    parameters.pVals = make([]string, len(parameters.pTokens))

    result, err := expression.Eval(parameters)
    // log.LogPrint("Result: ", result)

    if err != nil {
      policyEffects[0] = effect.Indeterminate
      panic(err)
    }

    if result.(bool) {
      policyEffects[0] = effect.Allow
    } else {
      policyEffects[0] = effect.Indeterminate
    }
  }

  // log.LogPrint("Rule Results: ", policyEffects)

  result, err := e.eft.MergeEffects(e.model["e"]["e"].Value, policyEffects, matcherResults)
  if err != nil {
    panic(err)
  }

  // Log request.
  if log.GetLogger().IsEnabled() {
    reqStr := "Request: "
    for i, rval := range rvals {
      if i != len(rvals)-1 {
        reqStr += fmt.Sprintf("%v, ", rval)
      } else {
        reqStr += fmt.Sprintf("%v", rval)
      }
    }
    reqStr += fmt.Sprintf(" ---> %t", result)
    log.LogPrint(reqStr)
  }

  return result
}


// GenerateGFunction is the factory method of the g(_, _) function.
func GenerateGFunction(rm rbac.RoleManager) func(args ...interface{}) (interface{}, error) {
  return func(args ...interface{}) (interface{}, error) {
    name1 := args[0].(string)
    name2 := args[1].(string)

    if rm == nil {
      return name1 == name2, nil
    } else if len(args) == 2 {
      res, _ := rm.HasLink(name1, name2)
      return res, nil
    } else {
      domain := args[2].(string)
      res, _ := rm.HasLink(name1, name2, domain)
      return res, nil
    }
  }
}

里面引用了规则计算库

代码语言:javascript
代码运行次数:0
运行
复制
"github.com/Knetic/govaluate"
代码语言:javascript
代码运行次数:0
运行
复制
expression, err := govaluate.NewEvaluableExpressionWithFunctions(expString, functions)
代码语言:javascript
代码运行次数:0
运行
复制
type EvaluableExpression struct {

  /*
    Represents the query format used to output dates. Typically only used when creating SQL or Mongo queries from an expression.
    Defaults to the complete ISO8601 format, including nanoseconds.
  */
  QueryDateFormat string

  /*
    Whether or not to safely check types when evaluating.
    If true, this library will return error messages when invalid types are used.
    If false, the library will panic when operators encounter types they can't use.

    This is exclusively for users who need to squeeze every ounce of speed out of the library as they can,
    and you should only set this to false if you know exactly what you're doing.
  */
  ChecksTypes bool

  tokens           []ExpressionToken
  evaluationStages *evaluationStage
  inputExpression  string
}

func parseTokens(expression string, functions map[string]ExpressionFunction) ([]ExpressionToken, error) {

  var ret []ExpressionToken
  var token ExpressionToken
  var stream *lexerStream
  var state lexerState
  var err error
  var found bool

  stream = newLexerStream(expression)
  state = validLexerStates[0]

  for stream.canRead() {

    token, err, found = readToken(stream, state, functions)

    if err != nil {
      return ret, err
    }

    if !found {
      break
    }

    state, err = getLexerStateForToken(token.Kind)
    if err != nil {
      return ret, err
    }

    // append this valid token
    ret = append(ret, token)
  }

  err = checkBalance(ret)
  if err != nil {
    return nil, err
  }

  return ret, nil
}

整体源码目录如下

代码语言:javascript
代码运行次数:0
运行
复制
tree
.
|____model_b_test.go
|____rbac_api_with_domains_test.go
|____rbac_api_with_domains_synced.go
|____rbac
| |____role_manager.go
| |____default-role-manager
| | |____role_manager.go
| | |____role_manager_test.go
|____go.mod
|____filter_test.go
|____enforcer_test.go
|____internal_api.go
|____model_test.go
|____management_api_test.go
|____LICENSE
|____casbin-logo.png
|____util
| |____builtin_operators.go
| |____builtin_operators_test.go
| |____util_test.go
| |____util.go
|____config
| |____testdata
| | |____testini.ini
| |____config.go
| |____config_test.go
|____watcher_test.go
|____enforcer_cached.go
|____enforcer_synced.go
|____enforcer.go
|____effect
| |____effector.go
| |____default_effector.go
|____go.sum
|____rbac_api_test.go
|____enforcer_safe.go
|____rbac_api.go
|____README.md
|____rbac_api_with_domains.go
|____.gitignore
|____enforcer_cached_b_test.go
|____examples
| |____rbac_policy.csv
| |____rbac_with_hierarchy_policy.csv
| |____keymatch2_policy.csv
| |____basic_without_resources_policy.csv
| |____basic_with_root_model.conf
| |____ipmatch_model.conf
| |____basic_inverse_policy.csv
| |____rbac_with_domains_model.conf
| |____priority_indeterminate_policy.csv
| |____keymatch_custom_model.conf
| |____rbac_with_resource_roles_policy.csv
| |____rbac_with_deny_policy.csv
| |____abac_model.conf
| |____basic_without_users_model.conf
| |____rbac_with_hierarchy_with_domains_policy.csv
| |____keymatch_policy.csv
| |____priority_model.conf
| |____rbac_with_pattern_model.conf
| |____rbac_with_not_deny_model.conf
| |____basic_model.conf
| |____keymatch2_model.conf
| |____basic_without_resources_model.conf
| |____rbac_model_in_multi_line.conf
| |____rbac_model.conf
| |____ipmatch_policy.csv
| |____rbac_with_resource_roles_model.conf
| |____rbac_with_deny_model.conf
| |____rbac_with_domains_policy.csv
| |____error
| | |____error_policy.csv
| | |____error_model.conf
| |____rbac_with_pattern_policy.csv
| |____priority_policy.csv
| |____basic_policy.csv
| |____basic_without_users_policy.csv
| |____rbac_model_matcher_using_in_op.conf
| |____keymatch_model.conf
|____management_api.go
|____.github
| |____FUNDING.yml
|____model
| |____model_test.go
| |____assertion.go
| |____policy.go
| |____model.go
| |____function.go
|____rbac_api_synced.go
|____enforcer_synced_test.go
|____log
| |____logger.go
| |____log_util.go
| |____default_logger.go
| |____log_util_test.go
|____error_test.go
|____errors
| |____rbac_errors.go
|____enforcer_cached_test.go
|____enforcer_synced_safe.go
|____persist
| |____file-adapter
| | |____adapter.go
| | |____adapter_mock.go
| | |____adapter_filtered.go
| |____adapter.go
| |____persist_test.go
| |____adapter_filtered.go
| |____watcher.go
|____.travis.yml
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-04-12,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 golang算法架构leetcode技术php 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档