mirror of
https://github.com/datarhei/core.git
synced 2025-10-05 16:07:07 +08:00
WIP: add casbin to access manager, allow to persist identities
This commit is contained in:
@@ -383,12 +383,50 @@ func (a *api) start() error {
|
|||||||
a.sessions = sessions
|
a.sessions = sessions
|
||||||
}
|
}
|
||||||
|
|
||||||
iam, err := iam.NewIAM()
|
{
|
||||||
|
superuser := iam.User{
|
||||||
|
Name: cfg.API.Auth.Username,
|
||||||
|
Superuser: true,
|
||||||
|
Auth: iam.UserAuth{
|
||||||
|
API: iam.UserAuthAPI{
|
||||||
|
Userpass: iam.UserAuthPassword{
|
||||||
|
Enable: cfg.API.Auth.Enable,
|
||||||
|
Password: cfg.API.Auth.Password,
|
||||||
|
},
|
||||||
|
Auth0: iam.UserAuthAPIAuth0{
|
||||||
|
Enable: cfg.API.Auth.Auth0.Enable,
|
||||||
|
User: cfg.API.Auth.Auth0.Tenants[0].Users[0],
|
||||||
|
Tenant: iam.Auth0Tenant{
|
||||||
|
Domain: cfg.API.Auth.Auth0.Tenants[0].Domain,
|
||||||
|
Audience: cfg.API.Auth.Auth0.Tenants[0].Audience,
|
||||||
|
ClientID: cfg.API.Auth.Auth0.Tenants[0].ClientID,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Services: iam.UserAuthServices{
|
||||||
|
Basic: iam.UserAuthPassword{
|
||||||
|
Enable: cfg.Storage.Memory.Auth.Enable,
|
||||||
|
Password: cfg.Storage.Memory.Auth.Password,
|
||||||
|
},
|
||||||
|
Token: cfg.RTMP.Token,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
fs, err := fs.NewRootedDiskFilesystem(fs.RootedDiskConfig{
|
||||||
|
Root: filepath.Join(cfg.DB.Dir, "iam"),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
iam, err := iam.NewIAM(fs, superuser)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("iam: %w", err)
|
return fmt.Errorf("iam: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
a.iam = iam
|
a.iam = iam
|
||||||
|
}
|
||||||
|
|
||||||
diskfs, err := fs.NewRootedDiskFilesystem(fs.RootedDiskConfig{
|
diskfs, err := fs.NewRootedDiskFilesystem(fs.RootedDiskConfig{
|
||||||
Root: cfg.Storage.Disk.Dir,
|
Root: cfg.Storage.Disk.Dir,
|
||||||
@@ -639,7 +677,7 @@ func (a *api) start() error {
|
|||||||
return fmt.Errorf("unable to create JWT provider: %w", err)
|
return fmt.Errorf("unable to create JWT provider: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if validator, err := jwt.NewLocalValidator(cfg.API.Auth.Username, cfg.API.Auth.Password); err == nil {
|
if validator, err := jwt.NewLocalValidator(a.iam); err == nil {
|
||||||
if err := httpjwt.AddValidator(app.Name, validator); err != nil {
|
if err := httpjwt.AddValidator(app.Name, validator); err != nil {
|
||||||
return fmt.Errorf("unable to add local JWT validator: %w", err)
|
return fmt.Errorf("unable to add local JWT validator: %w", err)
|
||||||
}
|
}
|
||||||
@@ -649,7 +687,7 @@ func (a *api) start() error {
|
|||||||
|
|
||||||
if cfg.API.Auth.Auth0.Enable {
|
if cfg.API.Auth.Auth0.Enable {
|
||||||
for _, t := range cfg.API.Auth.Auth0.Tenants {
|
for _, t := range cfg.API.Auth.Auth0.Tenants {
|
||||||
if validator, err := jwt.NewAuth0Validator(t.Domain, t.Audience, t.ClientID, t.Users); err == nil {
|
if validator, err := jwt.NewAuth0Validator(a.iam); err == nil {
|
||||||
if err := httpjwt.AddValidator("https://"+t.Domain+"/", validator); err != nil {
|
if err := httpjwt.AddValidator("https://"+t.Domain+"/", validator); err != nil {
|
||||||
return fmt.Errorf("unable to add Auth0 JWT validator: %w", err)
|
return fmt.Errorf("unable to add Auth0 JWT validator: %w", err)
|
||||||
}
|
}
|
||||||
@@ -1303,6 +1341,10 @@ func (a *api) stop() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if a.iam != nil {
|
||||||
|
a.iam.Close()
|
||||||
|
}
|
||||||
|
|
||||||
// Stop JWT authentication
|
// Stop JWT authentication
|
||||||
if a.httpjwt != nil {
|
if a.httpjwt != nil {
|
||||||
a.httpjwt.ClearValidators()
|
a.httpjwt.ClearValidators()
|
||||||
|
@@ -46,8 +46,8 @@ func (v *localValidator) Validate(c echo.Context) (bool, string, error) {
|
|||||||
return false, "", nil
|
return false, "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
identity := v.iam.GetIdentity(login.Username)
|
identity, err := v.iam.GetIdentity(login.Username)
|
||||||
if identity == nil {
|
if err != nil {
|
||||||
return true, "", fmt.Errorf("invalid username or password")
|
return true, "", fmt.Errorf("invalid username or password")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,7 +55,7 @@ func (v *localValidator) Validate(c echo.Context) (bool, string, error) {
|
|||||||
return true, "", fmt.Errorf("invalid username or password")
|
return true, "", fmt.Errorf("invalid username or password")
|
||||||
}
|
}
|
||||||
|
|
||||||
return true, login.Username, nil
|
return true, identity.Name(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *localValidator) Cancel() {}
|
func (v *localValidator) Cancel() {}
|
||||||
@@ -109,8 +109,8 @@ func (v *auth0Validator) Validate(c echo.Context) (bool, string, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
identity := v.iam.GetIdentityByAuth0(subject)
|
identity, err := v.iam.GetIdentityByAuth0(subject)
|
||||||
if identity == nil {
|
if err != nil {
|
||||||
return true, "", fmt.Errorf("invalid token")
|
return true, "", fmt.Errorf("invalid token")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,7 +118,7 @@ func (v *auth0Validator) Validate(c echo.Context) (bool, string, error) {
|
|||||||
return true, "", fmt.Errorf("invalid token")
|
return true, "", fmt.Errorf("invalid token")
|
||||||
}
|
}
|
||||||
|
|
||||||
return true, subject, nil
|
return true, identity.Name(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *auth0Validator) Cancel() {}
|
func (v *auth0Validator) Cancel() {}
|
||||||
|
@@ -1,5 +1,12 @@
|
|||||||
package iam
|
package iam
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/datarhei/core/v16/io/fs"
|
||||||
|
|
||||||
|
"github.com/casbin/casbin/v2"
|
||||||
|
"github.com/casbin/casbin/v2/model"
|
||||||
|
)
|
||||||
|
|
||||||
type AccessEnforcer interface {
|
type AccessEnforcer interface {
|
||||||
Enforce(name, domain, resource, action string) bool
|
Enforce(name, domain, resource, action string) bool
|
||||||
}
|
}
|
||||||
@@ -11,11 +18,41 @@ type AccessManager interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type access struct {
|
type access struct {
|
||||||
|
fs fs.Filesystem
|
||||||
|
|
||||||
|
enforcer *casbin.Enforcer
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAccessManager() (AccessManager, error) {
|
func NewAccessManager(fs fs.Filesystem) (AccessManager, error) {
|
||||||
return &access{}, nil
|
am := &access{
|
||||||
|
fs: fs,
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *access) AddPolicy() {}
|
m := model.NewModel()
|
||||||
func (a *access) Enforce(name, domain, resource, action string) bool { return false }
|
m.AddDef("r", "r", "sub, dom, obj, act")
|
||||||
|
m.AddDef("p", "p", "sub, dom, obj, act")
|
||||||
|
m.AddDef("g", "g", "_, _, _")
|
||||||
|
m.AddDef("e", "e", "some(where (p.eft == allow))")
|
||||||
|
m.AddDef("m", "m", `g(r.sub, p.sub, r.dom) && r.dom == p.dom && ResourceMatch(r.obj, r.dom, p.obj) && ActionMatch(r.act, p.act) || r.sub == "$superuser"`)
|
||||||
|
|
||||||
|
a := newAdapter(fs, "./policy.json")
|
||||||
|
|
||||||
|
e, err := casbin.NewEnforcer(m, a)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
e.AddFunction("ResourceMatch", resourceMatchFunc)
|
||||||
|
e.AddFunction("ActionMatch", actionMatchFunc)
|
||||||
|
|
||||||
|
am.enforcer = e
|
||||||
|
|
||||||
|
return am, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (am *access) AddPolicy() {}
|
||||||
|
func (am *access) Enforce(name, domain, resource, action string) bool {
|
||||||
|
ok, _, _ := am.enforcer.EnforceEx(name, domain, resource, action)
|
||||||
|
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
485
iam/adapter.go
Normal file
485
iam/adapter.go
Normal file
@@ -0,0 +1,485 @@
|
|||||||
|
package iam
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/datarhei/core/v16/io/fs"
|
||||||
|
|
||||||
|
"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
|
||||||
|
groups []Group
|
||||||
|
lock sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func newAdapter(fs fs.Filesystem, filePath string) persist.Adapter {
|
||||||
|
return &adapter{filePath: filePath}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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")
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
logger := &log.DefaultLogger{}
|
||||||
|
logger.EnableLog(true)
|
||||||
|
|
||||||
|
model.SetLogger(logger)
|
||||||
|
*/
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
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]
|
||||||
|
} else if ptype == "g" {
|
||||||
|
username = rule[0]
|
||||||
|
role = rule[1]
|
||||||
|
domain = rule[2]
|
||||||
|
} 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]
|
||||||
|
} else if ptype == "g" {
|
||||||
|
username = rule[0]
|
||||||
|
role = rule[1]
|
||||||
|
domain = rule[2]
|
||||||
|
} 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")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *adapter) GetAllGroupNames() []string {
|
||||||
|
a.lock.Lock()
|
||||||
|
defer a.lock.Unlock()
|
||||||
|
|
||||||
|
groups := []string{}
|
||||||
|
|
||||||
|
for _, group := range a.groups {
|
||||||
|
groups = append(groups, group.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
return groups
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
115
iam/casbin.go
Normal file
115
iam/casbin.go
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
package iam
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gobwas/glob"
|
||||||
|
)
|
||||||
|
|
||||||
|
func resourceMatch(request, domain, policy string) bool {
|
||||||
|
reqPrefix, reqResource := getPrefix(request)
|
||||||
|
polPrefix, polResource := getPrefix(policy)
|
||||||
|
|
||||||
|
if reqPrefix != polPrefix {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("prefix: %s\n", reqPrefix)
|
||||||
|
fmt.Printf("requested resource: %s\n", reqResource)
|
||||||
|
fmt.Printf("requested domain: %s\n", domain)
|
||||||
|
fmt.Printf("policy resource: %s\n", polResource)
|
||||||
|
|
||||||
|
var match bool
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if reqPrefix == "processid" {
|
||||||
|
match, err = globMatch(polResource, reqResource)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
} else if reqPrefix == "api" {
|
||||||
|
match, err = globMatch(polResource, reqResource, rune('/'))
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
} else if reqPrefix == "fs" {
|
||||||
|
match, err = globMatch(polResource, reqResource, rune('/'))
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
} else if reqPrefix == "rtmp" {
|
||||||
|
match, err = globMatch(polResource, reqResource)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
} else if reqPrefix == "srt" {
|
||||||
|
match, err = globMatch(polResource, reqResource)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
match, err = globMatch(polResource, reqResource)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("match: %v\n", match)
|
||||||
|
|
||||||
|
return match
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceMatchFunc(args ...interface{}) (interface{}, error) {
|
||||||
|
request := args[0].(string)
|
||||||
|
domain := args[1].(string)
|
||||||
|
policy := args[2].(string)
|
||||||
|
|
||||||
|
return (bool)(resourceMatch(request, domain, policy)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func actionMatch(request string, policy string) bool {
|
||||||
|
request = strings.ToUpper(request)
|
||||||
|
actions := strings.Split(strings.ToUpper(policy), "|")
|
||||||
|
if len(actions) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, a := range actions {
|
||||||
|
if request == a {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func actionMatchFunc(args ...interface{}) (interface{}, error) {
|
||||||
|
request := args[0].(string)
|
||||||
|
policy := args[1].(string)
|
||||||
|
|
||||||
|
return (bool)(actionMatch(request, policy)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPrefix(s string) (string, string) {
|
||||||
|
splits := strings.SplitN(s, ":", 2)
|
||||||
|
|
||||||
|
if len(splits) == 0 {
|
||||||
|
return "", ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(splits) == 1 {
|
||||||
|
return "", splits[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
return splits[0], splits[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
func globMatch(pattern, name string, separators ...rune) (bool, error) {
|
||||||
|
g, err := glob.Compile(pattern, separators...)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return g.Match(name), nil
|
||||||
|
}
|
49
iam/iam.go
49
iam/iam.go
@@ -1,26 +1,55 @@
|
|||||||
package iam
|
package iam
|
||||||
|
|
||||||
|
import "github.com/datarhei/core/v16/io/fs"
|
||||||
|
|
||||||
type IAM interface {
|
type IAM interface {
|
||||||
Enforce(user, domain, resource, action string) bool
|
Enforce(user, domain, resource, action string) bool
|
||||||
|
|
||||||
GetIdentity(name string) IdentityVerifier
|
GetIdentity(name string) (IdentityVerifier, error)
|
||||||
GetIdentityByAuth0(name string) IdentityVerifier
|
GetIdentityByAuth0(name string) (IdentityVerifier, error)
|
||||||
|
|
||||||
|
Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
type iam struct{}
|
type iam struct {
|
||||||
|
im IdentityManager
|
||||||
|
am AccessManager
|
||||||
|
}
|
||||||
|
|
||||||
func NewIAM() (IAM, error) {
|
func NewIAM(fs fs.Filesystem, superuser User) (IAM, error) {
|
||||||
return &iam{}, nil
|
im, err := NewIdentityManager(fs, superuser)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
am, err := NewAccessManager(fs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &iam{
|
||||||
|
im: im,
|
||||||
|
am: am,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *iam) Close() {
|
||||||
|
i.im.Close()
|
||||||
|
i.im = nil
|
||||||
|
|
||||||
|
i.am = nil
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *iam) Enforce(user, domain, resource, action string) bool {
|
func (i *iam) Enforce(user, domain, resource, action string) bool {
|
||||||
return false
|
return i.am.Enforce(user, domain, resource, action)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *iam) GetIdentity(name string) IdentityVerifier {
|
func (i *iam) GetIdentity(name string) (IdentityVerifier, error) {
|
||||||
return nil
|
return i.im.GetVerifier(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *iam) GetIdentityByAuth0(name string) IdentityVerifier {
|
func (i *iam) GetIdentityByAuth0(name string) (IdentityVerifier, error) {
|
||||||
return nil
|
return i.im.GetVerifierByAuth0(name)
|
||||||
}
|
}
|
||||||
|
238
iam/identity.go
238
iam/identity.go
@@ -1,11 +1,14 @@
|
|||||||
package iam
|
package iam
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/datarhei/core/v16/http/jwt/jwks"
|
"github.com/datarhei/core/v16/iam/jwks"
|
||||||
|
"github.com/datarhei/core/v16/io/fs"
|
||||||
|
|
||||||
jwtgo "github.com/golang-jwt/jwt/v4"
|
jwtgo "github.com/golang-jwt/jwt/v4"
|
||||||
)
|
)
|
||||||
@@ -18,26 +21,33 @@ import (
|
|||||||
type User struct {
|
type User struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Superuser bool `json:"superuser"`
|
Superuser bool `json:"superuser"`
|
||||||
Auth struct {
|
Auth UserAuth `json:"auth"`
|
||||||
API struct {
|
}
|
||||||
Userpass struct {
|
|
||||||
Enable bool `json:"enable"`
|
type UserAuth struct {
|
||||||
Password string `json:"password"`
|
API UserAuthAPI `json:"api"`
|
||||||
} `json:"userpass"`
|
Services UserAuthServices `json:"services"`
|
||||||
Auth0 struct {
|
}
|
||||||
|
|
||||||
|
type UserAuthAPI struct {
|
||||||
|
Userpass UserAuthPassword `json:"userpass"`
|
||||||
|
Auth0 UserAuthAPIAuth0 `json:"auth0"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserAuthAPIAuth0 struct {
|
||||||
Enable bool `json:"enable"`
|
Enable bool `json:"enable"`
|
||||||
User string `json:"user"`
|
User string `json:"user"`
|
||||||
Tenant Auth0Tenant `json:"tenant"`
|
Tenant Auth0Tenant `json:"tenant"`
|
||||||
} `json:"auth0"`
|
}
|
||||||
} `json:"api"`
|
|
||||||
Services struct {
|
type UserAuthServices struct {
|
||||||
Basic struct {
|
Basic UserAuthPassword `json:"basic"`
|
||||||
|
Token string `json:"token"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserAuthPassword struct {
|
||||||
Enable bool `json:"enable"`
|
Enable bool `json:"enable"`
|
||||||
Password string `json:"password"`
|
Password string `json:"password"`
|
||||||
} `json:"basic"`
|
|
||||||
Token string `json:"token"`
|
|
||||||
} `json:"services"`
|
|
||||||
} `json:"auth"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *User) validate() error {
|
func (u *User) validate() error {
|
||||||
@@ -83,6 +93,10 @@ type identity struct {
|
|||||||
lock sync.RWMutex
|
lock sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (i *identity) Name() string {
|
||||||
|
return i.user.Name
|
||||||
|
}
|
||||||
|
|
||||||
func (i *identity) VerifyAPIPassword(password string) bool {
|
func (i *identity) VerifyAPIPassword(password string) bool {
|
||||||
i.lock.RLock()
|
i.lock.RLock()
|
||||||
defer i.lock.RUnlock()
|
defer i.lock.RUnlock()
|
||||||
@@ -238,6 +252,8 @@ func (i *identity) IsSuperuser() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type IdentityVerifier interface {
|
type IdentityVerifier interface {
|
||||||
|
Name() string
|
||||||
|
|
||||||
VerifyAPIPassword(password string) bool
|
VerifyAPIPassword(password string) bool
|
||||||
VerifyAPIAuth0(jwt string) bool
|
VerifyAPIAuth0(jwt string) bool
|
||||||
|
|
||||||
@@ -252,11 +268,12 @@ type IdentityManager interface {
|
|||||||
Remove(name string) error
|
Remove(name string) error
|
||||||
Get(name string) (User, error)
|
Get(name string) (User, error)
|
||||||
GetVerifier(name string) (IdentityVerifier, error)
|
GetVerifier(name string) (IdentityVerifier, error)
|
||||||
|
GetVerifierByAuth0(name string) (IdentityVerifier, error)
|
||||||
Rename(oldname, newname string) error
|
Rename(oldname, newname string) error
|
||||||
Update(name string, identity User) error
|
Update(name string, identity User) error
|
||||||
|
|
||||||
Load(path string) error
|
Save() error
|
||||||
Save(path string) error
|
Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
type Auth0Tenant struct {
|
type Auth0Tenant struct {
|
||||||
@@ -297,32 +314,72 @@ func newAuth0Tenant(tenant Auth0Tenant) (*auth0Tenant, error) {
|
|||||||
return t, nil
|
return t, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *auth0Tenant) Cancel() {
|
||||||
|
a.certs.Cancel()
|
||||||
|
}
|
||||||
|
|
||||||
type identityManager struct {
|
type identityManager struct {
|
||||||
identities map[string]*identity
|
identities map[string]*identity
|
||||||
tenants map[string]*auth0Tenant
|
tenants map[string]*auth0Tenant
|
||||||
|
|
||||||
auth0UserIdentityMap map[string]string
|
auth0UserIdentityMap map[string]string
|
||||||
|
|
||||||
|
fs fs.Filesystem
|
||||||
|
filePath string
|
||||||
|
|
||||||
lock sync.RWMutex
|
lock sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewIdentityManager() (IdentityManager, error) {
|
func NewIdentityManager(fs fs.Filesystem, superuser User) (IdentityManager, error) {
|
||||||
return &identityManager{
|
im := &identityManager{
|
||||||
identities: map[string]*identity{},
|
identities: map[string]*identity{},
|
||||||
tenants: map[string]*auth0Tenant{},
|
tenants: map[string]*auth0Tenant{},
|
||||||
auth0UserIdentityMap: map[string]string{},
|
auth0UserIdentityMap: map[string]string{},
|
||||||
}, nil
|
fs: fs,
|
||||||
|
filePath: "./users.json",
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *identityManager) Create(u User) error {
|
err := im.load(im.filePath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(im.identities) == 0 {
|
||||||
|
superuser.Superuser = true
|
||||||
|
im.Create(superuser)
|
||||||
|
|
||||||
|
im.save(im.filePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
return im, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (im *identityManager) Close() {
|
||||||
|
im.lock.Lock()
|
||||||
|
defer im.lock.Unlock()
|
||||||
|
|
||||||
|
im.fs = nil
|
||||||
|
im.auth0UserIdentityMap = map[string]string{}
|
||||||
|
im.identities = map[string]*identity{}
|
||||||
|
|
||||||
|
for _, t := range im.tenants {
|
||||||
|
t.Cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
im.tenants = map[string]*auth0Tenant{}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (im *identityManager) Create(u User) error {
|
||||||
if err := u.validate(); err != nil {
|
if err := u.validate(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
i.lock.Lock()
|
im.lock.Lock()
|
||||||
defer i.lock.Unlock()
|
defer im.lock.Unlock()
|
||||||
|
|
||||||
_, ok := i.identities[u.Name]
|
_, ok := im.identities[u.Name]
|
||||||
if ok {
|
if ok {
|
||||||
return fmt.Errorf("identity already exists")
|
return fmt.Errorf("identity already exists")
|
||||||
}
|
}
|
||||||
@@ -330,42 +387,42 @@ func (i *identityManager) Create(u User) error {
|
|||||||
identity := u.marshalIdentity()
|
identity := u.marshalIdentity()
|
||||||
|
|
||||||
if identity.user.Auth.API.Auth0.Enable {
|
if identity.user.Auth.API.Auth0.Enable {
|
||||||
if _, ok := i.auth0UserIdentityMap[identity.user.Auth.API.Auth0.User]; ok {
|
if _, ok := im.auth0UserIdentityMap[identity.user.Auth.API.Auth0.User]; ok {
|
||||||
return fmt.Errorf("the Auth0 user has already an identity")
|
return fmt.Errorf("the Auth0 user has already an identity")
|
||||||
}
|
}
|
||||||
|
|
||||||
auth0Key := identity.user.Auth.API.Auth0.Tenant.key()
|
auth0Key := identity.user.Auth.API.Auth0.Tenant.key()
|
||||||
|
|
||||||
if _, ok := i.tenants[auth0Key]; !ok {
|
if _, ok := im.tenants[auth0Key]; !ok {
|
||||||
tenant, err := newAuth0Tenant(identity.user.Auth.API.Auth0.Tenant)
|
tenant, err := newAuth0Tenant(identity.user.Auth.API.Auth0.Tenant)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
i.tenants[auth0Key] = tenant
|
im.tenants[auth0Key] = tenant
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
i.identities[identity.user.Name] = identity
|
im.identities[identity.user.Name] = identity
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *identityManager) Update(name string, identity User) error {
|
func (im *identityManager) Update(name string, identity User) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *identityManager) Remove(name string) error {
|
func (im *identityManager) Remove(name string) error {
|
||||||
i.lock.Lock()
|
im.lock.Lock()
|
||||||
defer i.lock.Unlock()
|
defer im.lock.Unlock()
|
||||||
|
|
||||||
user, ok := i.identities[name]
|
user, ok := im.identities[name]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
delete(i.identities, name)
|
delete(im.identities, name)
|
||||||
|
|
||||||
user.lock.Lock()
|
user.lock.Lock()
|
||||||
user.valid = false
|
user.valid = false
|
||||||
@@ -374,11 +431,8 @@ func (i *identityManager) Remove(name string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *identityManager) getIdentity(name string) (*identity, error) {
|
func (im *identityManager) getIdentity(name string) (*identity, error) {
|
||||||
i.lock.RLock()
|
identity, ok := im.identities[name]
|
||||||
defer i.lock.RUnlock()
|
|
||||||
|
|
||||||
identity, ok := i.identities[name]
|
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("not found")
|
return nil, fmt.Errorf("not found")
|
||||||
}
|
}
|
||||||
@@ -386,55 +440,117 @@ func (i *identityManager) getIdentity(name string) (*identity, error) {
|
|||||||
return identity, nil
|
return identity, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *identityManager) Get(name string) (User, error) {
|
func (im *identityManager) Get(name string) (User, error) {
|
||||||
i.lock.RLock()
|
im.lock.RLock()
|
||||||
defer i.lock.RUnlock()
|
defer im.lock.RUnlock()
|
||||||
|
|
||||||
identity, ok := i.identities[name]
|
identity, err := im.getIdentity(name)
|
||||||
if !ok {
|
if err != nil {
|
||||||
return User{}, fmt.Errorf("not found")
|
return User{}, fmt.Errorf("not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
return identity.user, nil
|
return identity.user, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *identityManager) GetVerifier(name string) (IdentityVerifier, error) {
|
func (im *identityManager) GetVerifier(name string) (IdentityVerifier, error) {
|
||||||
i.lock.RLock()
|
im.lock.RLock()
|
||||||
defer i.lock.RUnlock()
|
defer im.lock.RUnlock()
|
||||||
|
|
||||||
identity, ok := i.identities[name]
|
return im.getIdentity(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (im *identityManager) GetVerifierByAuth0(name string) (IdentityVerifier, error) {
|
||||||
|
im.lock.RLock()
|
||||||
|
defer im.lock.RUnlock()
|
||||||
|
|
||||||
|
name, ok := im.auth0UserIdentityMap[name]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("not found")
|
return nil, fmt.Errorf("not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
return identity, nil
|
return im.getIdentity(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *identityManager) Rename(oldname, newname string) error {
|
func (im *identityManager) Rename(oldname, newname string) error {
|
||||||
i.lock.Lock()
|
im.lock.Lock()
|
||||||
defer i.lock.Unlock()
|
defer im.lock.Unlock()
|
||||||
|
|
||||||
identity, ok := i.identities[oldname]
|
identity, ok := im.identities[oldname]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, ok := i.identities[newname]; ok {
|
if _, ok := im.identities[newname]; ok {
|
||||||
return fmt.Errorf("the new name already exists")
|
return fmt.Errorf("the new name already exists")
|
||||||
}
|
}
|
||||||
|
|
||||||
delete(i.identities, oldname)
|
delete(im.identities, oldname)
|
||||||
|
|
||||||
identity.user.Name = newname
|
identity.user.Name = newname
|
||||||
i.identities[newname] = identity
|
im.identities[newname] = identity
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *identityManager) Load(path string) error {
|
func (im *identityManager) load(filePath string) error {
|
||||||
return fmt.Errorf("not implemented")
|
if im.fs == nil {
|
||||||
|
return fmt.Errorf("no filesystem provided")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *identityManager) Save(path string) error {
|
if _, err := im.fs.Stat(filePath); os.IsNotExist(err) {
|
||||||
return fmt.Errorf("not implemented")
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := im.fs.ReadFile(filePath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
users := []User{}
|
||||||
|
|
||||||
|
err = json.Unmarshal(data, &users)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, u := range users {
|
||||||
|
err = im.Create(u)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (im *identityManager) Save() error {
|
||||||
|
return im.save(im.filePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (im *identityManager) save(filePath string) error {
|
||||||
|
if im.fs == nil {
|
||||||
|
return fmt.Errorf("no filesystem provided")
|
||||||
|
}
|
||||||
|
|
||||||
|
if filePath == "" {
|
||||||
|
return fmt.Errorf("invalid file path, file path cannot be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
im.lock.RLock()
|
||||||
|
defer im.lock.RUnlock()
|
||||||
|
|
||||||
|
users := []User{}
|
||||||
|
|
||||||
|
for _, u := range im.identities {
|
||||||
|
users = append(users, u.user)
|
||||||
|
}
|
||||||
|
|
||||||
|
jsondata, err := json.MarshalIndent(users, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _, err = im.fs.WriteFileSafe(filePath, jsondata)
|
||||||
|
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user