mirror of
https://github.com/datarhei/core.git
synced 2025-10-10 02:10:17 +08:00
Create identity and access packages for IAM
This commit is contained in:
586
iam/access/adapter.go
Normal file
586
iam/access/adapter.go
Normal file
@@ -0,0 +1,586 @@
|
||||
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
|
||||
}
|
||||
|
||||
model.ClearPolicy()
|
||||
|
||||
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, "|")
|
||||
}
|
Reference in New Issue
Block a user