mirror of
https://github.com/datarhei/core.git
synced 2025-10-05 07:57:13 +08:00
585 lines
11 KiB
Go
585 lines
11 KiB
Go
package access
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"sort"
|
|
"strings"
|
|
"sync"
|
|
|
|
"github.com/datarhei/core/v16/io/fs"
|
|
"github.com/datarhei/core/v16/log"
|
|
|
|
"github.com/casbin/casbin/v2/model"
|
|
"github.com/casbin/casbin/v2/persist"
|
|
)
|
|
|
|
// 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
|
|
domains []Domain
|
|
lock sync.Mutex
|
|
}
|
|
|
|
type Adapter interface {
|
|
persist.BatchAdapter
|
|
|
|
AllDomains() []string
|
|
HasDomain(string) bool
|
|
}
|
|
|
|
func NewJSONAdapter(fs fs.Filesystem, filePath string, logger log.Logger) (Adapter, error) {
|
|
a := &adapter{
|
|
fs: fs,
|
|
filePath: filePath,
|
|
logger: logger,
|
|
}
|
|
|
|
if a.fs == nil {
|
|
return nil, fmt.Errorf("a filesystem has to be provided")
|
|
}
|
|
|
|
if len(a.filePath) == 0 {
|
|
return nil, fmt.Errorf("invalid file path, file path cannot be empty")
|
|
}
|
|
|
|
if a.logger == nil {
|
|
a.logger = log.New("")
|
|
}
|
|
|
|
return a, nil
|
|
}
|
|
|
|
// Adapter
|
|
func (a *adapter) LoadPolicy(model model.Model) error {
|
|
a.lock.Lock()
|
|
defer a.lock.Unlock()
|
|
|
|
return a.loadPolicyFile(model)
|
|
}
|
|
|
|
func (a *adapter) loadPolicyFile(model model.Model) error {
|
|
if _, err := a.fs.Stat(a.filePath); os.IsNotExist(err) {
|
|
a.domains = []Domain{}
|
|
return nil
|
|
}
|
|
|
|
data, err := a.fs.ReadFile(a.filePath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
domains := []Domain{}
|
|
|
|
err = json.Unmarshal(data, &domains)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
rule := [5]string{}
|
|
for _, domain := range domains {
|
|
rule[0] = "p"
|
|
rule[2] = domain.Name
|
|
for name, roles := range domain.Roles {
|
|
rule[1] = "role:" + name
|
|
for _, role := range roles {
|
|
rule[3] = role.Resource
|
|
rule[4] = formatActions(role.Actions)
|
|
|
|
if err := a.importPolicy(model, rule[0:5]); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
for _, policy := range domain.Policies {
|
|
rule[1] = policy.Username
|
|
rule[3] = policy.Resource
|
|
rule[4] = formatActions(policy.Actions)
|
|
|
|
if err := a.importPolicy(model, rule[0:5]); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
rule[0] = "g"
|
|
rule[3] = domain.Name
|
|
|
|
for _, ug := range domain.UserRoles {
|
|
rule[1] = ug.Username
|
|
rule[2] = "role:" + ug.Role
|
|
|
|
if err := a.importPolicy(model, rule[0:4]); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
a.domains = domains
|
|
|
|
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{
|
|
"subject": 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 {
|
|
jsondata, err := json.MarshalIndent(a.domains, "", " ")
|
|
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 = formatActions(rule[3])
|
|
|
|
a.logger.Debug().WithFields(log.Fields{
|
|
"subject": 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{
|
|
"subject": username,
|
|
"role": role,
|
|
"domain": domain,
|
|
}).Log("Adding role mapping")
|
|
} else {
|
|
return fmt.Errorf("unknown ptype: %s", ptype)
|
|
}
|
|
|
|
var dom *Domain = nil
|
|
for i := range a.domains {
|
|
if a.domains[i].Name == domain {
|
|
dom = &a.domains[i]
|
|
break
|
|
}
|
|
}
|
|
|
|
if dom == nil {
|
|
g := Domain{
|
|
Name: domain,
|
|
Roles: map[string][]Role{},
|
|
UserRoles: []MapUserRole{},
|
|
Policies: []DomainPolicy{},
|
|
}
|
|
|
|
a.domains = append(a.domains, g)
|
|
dom = &a.domains[len(a.domains)-1]
|
|
}
|
|
|
|
if ptype == "p" {
|
|
if strings.HasPrefix(username, "role:") {
|
|
if dom.Roles == nil {
|
|
dom.Roles = make(map[string][]Role)
|
|
}
|
|
|
|
role := strings.TrimPrefix(username, "role:")
|
|
dom.Roles[role] = append(dom.Roles[role], Role{
|
|
Resource: resource,
|
|
Actions: actions,
|
|
})
|
|
} else {
|
|
dom.Policies = append(dom.Policies, DomainPolicy{
|
|
Username: username,
|
|
Role: Role{
|
|
Resource: resource,
|
|
Actions: actions,
|
|
},
|
|
})
|
|
}
|
|
} else {
|
|
dom.UserRoles = append(dom.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 = formatActions(rule[3])
|
|
} else if ptype == "g" {
|
|
if len(rule) < 3 {
|
|
return false, fmt.Errorf("invalid rule length. must be 'user, role, domain'")
|
|
}
|
|
|
|
username = rule[0]
|
|
role = rule[1]
|
|
domain = rule[2]
|
|
} else {
|
|
return false, fmt.Errorf("unknown ptype: %s", ptype)
|
|
}
|
|
|
|
var dom *Domain = nil
|
|
for i := range a.domains {
|
|
if a.domains[i].Name == domain {
|
|
dom = &a.domains[i]
|
|
break
|
|
}
|
|
}
|
|
|
|
if dom == nil {
|
|
// if we can't find any domain 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 := dom.Roles[username]
|
|
if !ok {
|
|
// unknown role, policy doesn't exist
|
|
return false, nil
|
|
}
|
|
|
|
for _, role := range roles {
|
|
if role.Resource == resource && formatActions(role.Actions) == actions {
|
|
return true, nil
|
|
}
|
|
}
|
|
} else {
|
|
for _, p := range dom.Policies {
|
|
if p.Username == username && p.Resource == resource && formatActions(p.Actions) == actions {
|
|
return true, nil
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
role = strings.TrimPrefix(role, "role:")
|
|
for _, user := range dom.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 = formatActions(rule[3])
|
|
|
|
a.logger.Debug().WithFields(log.Fields{
|
|
"subject": 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{
|
|
"subject": username,
|
|
"role": role,
|
|
"domain": domain,
|
|
}).Log("Removing role mapping")
|
|
} else {
|
|
return fmt.Errorf("unknown ptype: %s", ptype)
|
|
}
|
|
|
|
var dom *Domain = nil
|
|
for i := range a.domains {
|
|
if a.domains[i].Name == domain {
|
|
dom = &a.domains[i]
|
|
break
|
|
}
|
|
}
|
|
|
|
if ptype == "p" {
|
|
isRole := false
|
|
if strings.HasPrefix(username, "role:") {
|
|
isRole = true
|
|
username = strings.TrimPrefix(username, "role:")
|
|
}
|
|
|
|
if isRole {
|
|
roles := dom.Roles[username]
|
|
|
|
newRoles := []Role{}
|
|
|
|
for _, role := range roles {
|
|
if role.Resource == resource && formatActions(role.Actions) == actions {
|
|
continue
|
|
}
|
|
|
|
newRoles = append(newRoles, role)
|
|
}
|
|
|
|
dom.Roles[username] = newRoles
|
|
} else {
|
|
policies := []DomainPolicy{}
|
|
|
|
for _, p := range dom.Policies {
|
|
if p.Username == username && p.Resource == resource && formatActions(p.Actions) == actions {
|
|
continue
|
|
}
|
|
|
|
policies = append(policies, p)
|
|
}
|
|
|
|
dom.Policies = policies
|
|
}
|
|
} else {
|
|
role = strings.TrimPrefix(role, "role:")
|
|
|
|
users := []MapUserRole{}
|
|
|
|
for _, user := range dom.UserRoles {
|
|
if user.Username == username && user.Role == role {
|
|
continue
|
|
}
|
|
|
|
users = append(users, user)
|
|
}
|
|
|
|
dom.UserRoles = users
|
|
}
|
|
|
|
// Remove the group if there are no rules and policies
|
|
if len(dom.Roles) == 0 && len(dom.UserRoles) == 0 && len(dom.Policies) == 0 {
|
|
groups := []Domain{}
|
|
|
|
for _, g := range a.domains {
|
|
if g.Name == dom.Name {
|
|
continue
|
|
}
|
|
|
|
groups = append(groups, g)
|
|
}
|
|
|
|
a.domains = groups
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Adapter
|
|
func (a *adapter) RemoveFilteredPolicy(sec string, ptype string, fieldIndex int, fieldValues ...string) error {
|
|
return fmt.Errorf("not implemented")
|
|
}
|
|
|
|
func (a *adapter) AllDomains() []string {
|
|
a.lock.Lock()
|
|
defer a.lock.Unlock()
|
|
|
|
names := []string{}
|
|
|
|
for _, domain := range a.domains {
|
|
if domain.Name[0] == '$' {
|
|
continue
|
|
}
|
|
|
|
names = append(names, domain.Name)
|
|
}
|
|
|
|
return names
|
|
}
|
|
|
|
func (a *adapter) HasDomain(name string) bool {
|
|
a.lock.Lock()
|
|
defer a.lock.Unlock()
|
|
|
|
for _, domain := range a.domains {
|
|
if domain.Name[0] == '$' {
|
|
continue
|
|
}
|
|
|
|
if domain.Name == name {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
type Domain struct {
|
|
Name string `json:"name"`
|
|
Roles map[string][]Role `json:"roles"`
|
|
UserRoles []MapUserRole `json:"userroles"`
|
|
Policies []DomainPolicy `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 DomainPolicy struct {
|
|
Username string `json:"username"`
|
|
Role
|
|
}
|
|
|
|
func formatActions(actions string) string {
|
|
a := strings.Split(actions, "|")
|
|
|
|
sort.Strings(a)
|
|
|
|
return strings.Join(a, "|")
|
|
}
|