WIP: add casbin to access manager, allow to persist identities

This commit is contained in:
Ingo Oppermann
2023-02-06 17:07:20 +01:00
parent 8f1ff2d1a2
commit 11e55fc2c7
12 changed files with 919 additions and 95 deletions

View File

@@ -383,12 +383,50 @@ func (a *api) start() error {
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 {
return fmt.Errorf("iam: %w", err)
}
a.iam = iam
}
diskfs, err := fs.NewRootedDiskFilesystem(fs.RootedDiskConfig{
Root: cfg.Storage.Disk.Dir,
@@ -639,7 +677,7 @@ func (a *api) start() error {
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 {
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 {
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 {
return fmt.Errorf("unable to add Auth0 JWT validator: %w", err)
}
@@ -1303,6 +1341,10 @@ func (a *api) stop() {
return
}
if a.iam != nil {
a.iam.Close()
}
// Stop JWT authentication
if a.httpjwt != nil {
a.httpjwt.ClearValidators()

View File

@@ -46,8 +46,8 @@ func (v *localValidator) Validate(c echo.Context) (bool, string, error) {
return false, "", nil
}
identity := v.iam.GetIdentity(login.Username)
if identity == nil {
identity, err := v.iam.GetIdentity(login.Username)
if err != nil {
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, login.Username, nil
return true, identity.Name(), nil
}
func (v *localValidator) Cancel() {}
@@ -109,8 +109,8 @@ func (v *auth0Validator) Validate(c echo.Context) (bool, string, error) {
}
}
identity := v.iam.GetIdentityByAuth0(subject)
if identity == nil {
identity, err := v.iam.GetIdentityByAuth0(subject)
if err != nil {
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, subject, nil
return true, identity.Name(), nil
}
func (v *auth0Validator) Cancel() {}

View File

@@ -1,5 +1,12 @@
package iam
import (
"github.com/datarhei/core/v16/io/fs"
"github.com/casbin/casbin/v2"
"github.com/casbin/casbin/v2/model"
)
type AccessEnforcer interface {
Enforce(name, domain, resource, action string) bool
}
@@ -11,11 +18,41 @@ type AccessManager interface {
}
type access struct {
fs fs.Filesystem
enforcer *casbin.Enforcer
}
func NewAccessManager() (AccessManager, error) {
return &access{}, nil
func NewAccessManager(fs fs.Filesystem) (AccessManager, error) {
am := &access{
fs: fs,
}
m := model.NewModel()
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 (a *access) AddPolicy() {}
func (a *access) Enforce(name, domain, resource, action string) bool { return false }
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
View 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
View 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
}

View File

@@ -1,26 +1,55 @@
package iam
import "github.com/datarhei/core/v16/io/fs"
type IAM interface {
Enforce(user, domain, resource, action string) bool
GetIdentity(name string) IdentityVerifier
GetIdentityByAuth0(name string) IdentityVerifier
GetIdentity(name string) (IdentityVerifier, error)
GetIdentityByAuth0(name string) (IdentityVerifier, error)
Close()
}
type iam struct{}
type iam struct {
im IdentityManager
am AccessManager
}
func NewIAM() (IAM, error) {
return &iam{}, nil
func NewIAM(fs fs.Filesystem, superuser User) (IAM, error) {
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 {
return false
return i.am.Enforce(user, domain, resource, action)
}
func (i *iam) GetIdentity(name string) IdentityVerifier {
return nil
func (i *iam) GetIdentity(name string) (IdentityVerifier, error) {
return i.im.GetVerifier(name)
}
func (i *iam) GetIdentityByAuth0(name string) IdentityVerifier {
return nil
func (i *iam) GetIdentityByAuth0(name string) (IdentityVerifier, error) {
return i.im.GetVerifierByAuth0(name)
}

View File

@@ -1,11 +1,14 @@
package iam
import (
"encoding/json"
"fmt"
"os"
"regexp"
"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"
)
@@ -18,26 +21,33 @@ import (
type User struct {
Name string `json:"name"`
Superuser bool `json:"superuser"`
Auth struct {
API struct {
Userpass struct {
Enable bool `json:"enable"`
Password string `json:"password"`
} `json:"userpass"`
Auth0 struct {
Auth UserAuth `json:"auth"`
}
type UserAuth struct {
API UserAuthAPI `json:"api"`
Services UserAuthServices `json:"services"`
}
type UserAuthAPI struct {
Userpass UserAuthPassword `json:"userpass"`
Auth0 UserAuthAPIAuth0 `json:"auth0"`
}
type UserAuthAPIAuth0 struct {
Enable bool `json:"enable"`
User string `json:"user"`
Tenant Auth0Tenant `json:"tenant"`
} `json:"auth0"`
} `json:"api"`
Services struct {
Basic struct {
}
type UserAuthServices struct {
Basic UserAuthPassword `json:"basic"`
Token string `json:"token"`
}
type UserAuthPassword struct {
Enable bool `json:"enable"`
Password string `json:"password"`
} `json:"basic"`
Token string `json:"token"`
} `json:"services"`
} `json:"auth"`
}
func (u *User) validate() error {
@@ -83,6 +93,10 @@ type identity struct {
lock sync.RWMutex
}
func (i *identity) Name() string {
return i.user.Name
}
func (i *identity) VerifyAPIPassword(password string) bool {
i.lock.RLock()
defer i.lock.RUnlock()
@@ -238,6 +252,8 @@ func (i *identity) IsSuperuser() bool {
}
type IdentityVerifier interface {
Name() string
VerifyAPIPassword(password string) bool
VerifyAPIAuth0(jwt string) bool
@@ -252,11 +268,12 @@ type IdentityManager interface {
Remove(name string) error
Get(name string) (User, error)
GetVerifier(name string) (IdentityVerifier, error)
GetVerifierByAuth0(name string) (IdentityVerifier, error)
Rename(oldname, newname string) error
Update(name string, identity User) error
Load(path string) error
Save(path string) error
Save() error
Close()
}
type Auth0Tenant struct {
@@ -297,32 +314,72 @@ func newAuth0Tenant(tenant Auth0Tenant) (*auth0Tenant, error) {
return t, nil
}
func (a *auth0Tenant) Cancel() {
a.certs.Cancel()
}
type identityManager struct {
identities map[string]*identity
tenants map[string]*auth0Tenant
auth0UserIdentityMap map[string]string
fs fs.Filesystem
filePath string
lock sync.RWMutex
}
func NewIdentityManager() (IdentityManager, error) {
return &identityManager{
func NewIdentityManager(fs fs.Filesystem, superuser User) (IdentityManager, error) {
im := &identityManager{
identities: map[string]*identity{},
tenants: map[string]*auth0Tenant{},
auth0UserIdentityMap: map[string]string{},
}, nil
fs: fs,
filePath: "./users.json",
}
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 (i *identityManager) Create(u User) error {
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 {
return err
}
i.lock.Lock()
defer i.lock.Unlock()
im.lock.Lock()
defer im.lock.Unlock()
_, ok := i.identities[u.Name]
_, ok := im.identities[u.Name]
if ok {
return fmt.Errorf("identity already exists")
}
@@ -330,42 +387,42 @@ func (i *identityManager) Create(u User) error {
identity := u.marshalIdentity()
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")
}
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)
if err != nil {
return err
}
i.tenants[auth0Key] = tenant
im.tenants[auth0Key] = tenant
}
}
i.identities[identity.user.Name] = identity
im.identities[identity.user.Name] = identity
return nil
}
func (i *identityManager) Update(name string, identity User) error {
func (im *identityManager) Update(name string, identity User) error {
return nil
}
func (i *identityManager) Remove(name string) error {
i.lock.Lock()
defer i.lock.Unlock()
func (im *identityManager) Remove(name string) error {
im.lock.Lock()
defer im.lock.Unlock()
user, ok := i.identities[name]
user, ok := im.identities[name]
if !ok {
return nil
}
delete(i.identities, name)
delete(im.identities, name)
user.lock.Lock()
user.valid = false
@@ -374,11 +431,8 @@ func (i *identityManager) Remove(name string) error {
return nil
}
func (i *identityManager) getIdentity(name string) (*identity, error) {
i.lock.RLock()
defer i.lock.RUnlock()
identity, ok := i.identities[name]
func (im *identityManager) getIdentity(name string) (*identity, error) {
identity, ok := im.identities[name]
if !ok {
return nil, fmt.Errorf("not found")
}
@@ -386,55 +440,117 @@ func (i *identityManager) getIdentity(name string) (*identity, error) {
return identity, nil
}
func (i *identityManager) Get(name string) (User, error) {
i.lock.RLock()
defer i.lock.RUnlock()
func (im *identityManager) Get(name string) (User, error) {
im.lock.RLock()
defer im.lock.RUnlock()
identity, ok := i.identities[name]
if !ok {
identity, err := im.getIdentity(name)
if err != nil {
return User{}, fmt.Errorf("not found")
}
return identity.user, nil
}
func (i *identityManager) GetVerifier(name string) (IdentityVerifier, error) {
i.lock.RLock()
defer i.lock.RUnlock()
func (im *identityManager) GetVerifier(name string) (IdentityVerifier, error) {
im.lock.RLock()
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 {
return nil, fmt.Errorf("not found")
}
return identity, nil
return im.getIdentity(name)
}
func (i *identityManager) Rename(oldname, newname string) error {
i.lock.Lock()
defer i.lock.Unlock()
func (im *identityManager) Rename(oldname, newname string) error {
im.lock.Lock()
defer im.lock.Unlock()
identity, ok := i.identities[oldname]
identity, ok := im.identities[oldname]
if !ok {
return nil
}
if _, ok := i.identities[newname]; ok {
if _, ok := im.identities[newname]; ok {
return fmt.Errorf("the new name already exists")
}
delete(i.identities, oldname)
delete(im.identities, oldname)
identity.user.Name = newname
i.identities[newname] = identity
im.identities[newname] = identity
return nil
}
func (i *identityManager) Load(path string) error {
return fmt.Errorf("not implemented")
func (im *identityManager) load(filePath string) error {
if im.fs == nil {
return fmt.Errorf("no filesystem provided")
}
if _, err := im.fs.Stat(filePath); os.IsNotExist(err) {
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 (i *identityManager) Save(path string) error {
return fmt.Errorf("not implemented")
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
}