package iam import ( "encoding/json" "fmt" "os" "strings" "sync" "github.com/datarhei/core/v16/io/fs" "github.com/datarhei/core/v16/log" "github.com/casbin/casbin/v2/model" ) // Adapter is the file adapter for Casbin. // It can load policy from file or save policy to file. type adapter struct { fs fs.Filesystem filePath string logger log.Logger groups []Group lock sync.Mutex } func newAdapter(fs fs.Filesystem, filePath string, logger log.Logger) *adapter { return &adapter{ fs: fs, filePath: filePath, logger: logger, } } // Adapter func (a *adapter) LoadPolicy(model model.Model) error { a.lock.Lock() defer a.lock.Unlock() if a.filePath == "" { return fmt.Errorf("invalid file path, file path cannot be empty") } return a.loadPolicyFile(model) } func (a *adapter) loadPolicyFile(model model.Model) error { if _, err := a.fs.Stat(a.filePath); os.IsNotExist(err) { a.groups = []Group{} return nil } data, err := a.fs.ReadFile(a.filePath) if err != nil { return err } groups := []Group{} err = json.Unmarshal(data, &groups) if err != nil { return err } rule := [5]string{} for _, group := range groups { rule[0] = "p" rule[2] = group.Name for name, roles := range group.Roles { rule[1] = "role:" + name for _, role := range roles { rule[3] = role.Resource rule[4] = role.Actions if err := a.importPolicy(model, rule[0:5]); err != nil { return err } } } for _, policy := range group.Policies { rule[1] = policy.Username rule[3] = policy.Resource rule[4] = policy.Actions if err := a.importPolicy(model, rule[0:5]); err != nil { return err } } rule[0] = "g" rule[3] = group.Name for _, ug := range group.UserRoles { rule[1] = ug.Username rule[2] = "role:" + ug.Role if err := a.importPolicy(model, rule[0:4]); err != nil { return err } } } a.groups = groups return nil } func (a *adapter) importPolicy(model model.Model, rule []string) error { copiedRule := make([]string, len(rule)) copy(copiedRule, rule) a.logger.Debug().WithFields(log.Fields{ "username": copiedRule[1], "domain": copiedRule[2], "resource": copiedRule[3], "actions": copiedRule[4], }).Log("imported policy") ok, err := model.HasPolicyEx(copiedRule[0], copiedRule[0], copiedRule[1:]) if err != nil { return err } if ok { return nil // skip duplicated policy } model.AddPolicy(copiedRule[0], copiedRule[0], copiedRule[1:]) return nil } // Adapter func (a *adapter) SavePolicy(model model.Model) error { a.lock.Lock() defer a.lock.Unlock() return a.savePolicyFile() } func (a *adapter) savePolicyFile() error { if a.filePath == "" { return fmt.Errorf("invalid file path, file path cannot be empty") } jsondata, err := json.MarshalIndent(a.groups, "", " ") if err != nil { return err } _, _, err = a.fs.WriteFileSafe(a.filePath, jsondata) return err } // Adapter (auto-save) func (a *adapter) AddPolicy(sec, ptype string, rule []string) error { a.lock.Lock() defer a.lock.Unlock() err := a.addPolicy(ptype, rule) if err != nil { return err } return a.savePolicyFile() } // BatchAdapter (auto-save) func (a *adapter) AddPolicies(sec string, ptype string, rules [][]string) error { a.lock.Lock() defer a.lock.Unlock() for _, rule := range rules { err := a.addPolicy(ptype, rule) if err != nil { return err } } return a.savePolicyFile() } func (a *adapter) addPolicy(ptype string, rule []string) error { ok, err := a.hasPolicy(ptype, rule) if err != nil { return err } if ok { // the policy is already there, nothing to add return nil } username := "" role := "" domain := "" resource := "" actions := "" if ptype == "p" { username = rule[0] domain = rule[1] resource = rule[2] actions = rule[3] a.logger.Debug().WithFields(log.Fields{ "username": username, "domain": domain, "resource": resource, "actions": actions, }).Log("adding policy") } else if ptype == "g" { username = rule[0] role = rule[1] domain = rule[2] a.logger.Debug().WithFields(log.Fields{ "username": username, "role": role, "domain": domain, }).Log("adding role mapping") } else { return fmt.Errorf("unknown ptype: %s", ptype) } var group *Group = nil for i := range a.groups { if a.groups[i].Name == domain { group = &a.groups[i] } } if group == nil { g := Group{ Name: domain, } a.groups = append(a.groups, g) group = &g } if ptype == "p" { if strings.HasPrefix(username, "role:") { if group.Roles == nil { group.Roles = make(map[string][]Role) } role := strings.TrimPrefix(username, "role:") group.Roles[role] = append(group.Roles[role], Role{ Resource: resource, Actions: actions, }) } else { group.Policies = append(group.Policies, Policy{ Username: rule[0], Role: Role{ Resource: resource, Actions: actions, }, }) } } else { group.UserRoles = append(group.UserRoles, MapUserRole{ Username: username, Role: strings.TrimPrefix(role, "role:"), }) } return nil } func (a *adapter) hasPolicy(ptype string, rule []string) (bool, error) { var username string var role string var domain string var resource string var actions string if ptype == "p" { if len(rule) != 4 { return false, fmt.Errorf("invalid rule length. must be 'user/role, domain, resource, actions'") } username = rule[0] domain = rule[1] resource = rule[2] actions = rule[3] } else if ptype == "g" { username = rule[0] role = rule[1] domain = rule[2] } else { return false, fmt.Errorf("unknown ptype: %s", ptype) } var group *Group = nil for _, g := range a.groups { if g.Name == domain { group = &g break } } if group == nil { // if we can't find any group with that name, then the policy doesn't exist return false, nil } if ptype == "p" { isRole := false if strings.HasPrefix(username, "role:") { isRole = true username = strings.TrimPrefix(username, "role:") } if isRole { roles, ok := group.Roles[username] if !ok { // unknown role, policy doesn't exist return false, nil } for _, role := range roles { if role.Resource == resource && role.Actions == actions { return true, nil } } } else { for _, p := range group.Policies { if p.Username == username && p.Resource == resource && p.Actions == actions { return true, nil } } } } else { role = strings.TrimPrefix(role, "role:") for _, user := range group.UserRoles { if user.Username == username && user.Role == role { return true, nil } } } return false, nil } // Adapter (auto-save) func (a *adapter) RemovePolicy(sec string, ptype string, rule []string) error { a.lock.Lock() defer a.lock.Unlock() err := a.removePolicy(ptype, rule) if err != nil { return err } return a.savePolicyFile() } // BatchAdapter (auto-save) func (a *adapter) RemovePolicies(sec string, ptype string, rules [][]string) error { a.lock.Lock() defer a.lock.Unlock() for _, rule := range rules { err := a.removePolicy(ptype, rule) if err != nil { return err } } return a.savePolicyFile() } func (a *adapter) removePolicy(ptype string, rule []string) error { ok, err := a.hasPolicy(ptype, rule) if err != nil { return err } if !ok { // the policy is not there, nothing to remove return nil } username := "" role := "" domain := "" resource := "" actions := "" if ptype == "p" { username = rule[0] domain = rule[1] resource = rule[2] actions = rule[3] a.logger.Debug().WithFields(log.Fields{ "username": username, "domain": domain, "resource": resource, "actions": actions, }).Log("removing policy") } else if ptype == "g" { username = rule[0] role = rule[1] domain = rule[2] a.logger.Debug().WithFields(log.Fields{ "username": username, "role": role, "domain": domain, }).Log("adding role mapping") } else { return fmt.Errorf("unknown ptype: %s", ptype) } var group *Group = nil for i := range a.groups { if a.groups[i].Name == domain { group = &a.groups[i] } } if ptype == "p" { isRole := false if strings.HasPrefix(username, "role:") { isRole = true username = strings.TrimPrefix(username, "role:") } if isRole { roles := group.Roles[username] newRoles := []Role{} for _, role := range roles { if role.Resource == resource && role.Actions == actions { continue } newRoles = append(newRoles, role) } group.Roles[username] = newRoles } else { policies := []Policy{} for _, p := range group.Policies { if p.Username == username && p.Resource == resource && p.Actions == actions { continue } policies = append(policies, p) } group.Policies = policies } } else { role = strings.TrimPrefix(role, "role:") users := []MapUserRole{} for _, user := range group.UserRoles { if user.Username == username && user.Role == role { continue } users = append(users, user) } group.UserRoles = users } return nil } // Adapter func (a *adapter) RemoveFilteredPolicy(sec string, ptype string, fieldIndex int, fieldValues ...string) error { return fmt.Errorf("not implemented") } type Group struct { Name string `json:"name"` Roles map[string][]Role `json:"roles"` UserRoles []MapUserRole `json:"userroles"` Policies []Policy `json:"policies"` } type Role struct { Resource string `json:"resource"` Actions string `json:"actions"` } type MapUserRole struct { Username string `json:"username"` Role string `json:"role"` } type Policy struct { Username string `json:"username"` Role }