Files
core/iam/identity/manager.go
2023-09-18 15:41:42 +02:00

537 lines
11 KiB
Go

package identity
import (
"fmt"
"sync"
"time"
"github.com/datarhei/core/v16/log"
jwtgo "github.com/golang-jwt/jwt/v5"
"github.com/google/uuid"
)
type Manager interface {
Create(identity User) error
Update(nameOrAlias string, identity User) error
Delete(nameOrAlias string) error
Get(nameOrAlias string) (User, error)
GetVerifier(nameOrAlias string) (Verifier, error)
GetVerifierFromAuth0(auth0Name string) (Verifier, error)
GetDefaultVerifier() (Verifier, error)
Reload() error // Reload users from adapter
Save() error // Save users to adapter
List() []User // List all users
Validators() []string
CreateJWT(nameOrAlias string) (string, string, error)
Close()
}
type identityManager struct {
root *identity
userlist UserList
identities map[string]*identity
tenants map[string]*auth0Tenant
auth0UserIdentityMap map[string]string
adapter Adapter
autosave bool
logger log.Logger
jwtRealm string
jwtSecret []byte
lock sync.RWMutex
}
type Config struct {
Adapter Adapter
Superuser User
JWTRealm string
JWTSecret string
Logger log.Logger
}
func New(config Config) (Manager, error) {
im := &identityManager{
userlist: NewUserList(),
identities: map[string]*identity{},
tenants: map[string]*auth0Tenant{},
auth0UserIdentityMap: map[string]string{},
adapter: config.Adapter,
jwtRealm: config.JWTRealm,
jwtSecret: []byte(config.JWTSecret),
logger: config.Logger,
}
if im.logger == nil {
im.logger = log.New("")
}
if im.adapter == nil {
return nil, fmt.Errorf("no adapter provided")
}
config.Superuser.Superuser = true
identity, err := im.createIdentity(config.Superuser)
if err != nil {
return nil, err
}
im.root = identity
err = im.Reload()
if err != nil {
return nil, err
}
im.Save()
return im, nil
}
func (im *identityManager) Close() {
im.lock.Lock()
defer im.lock.Unlock()
im.adapter = nil
im.userlist = nil
im.identities = map[string]*identity{}
im.root = nil
for _, t := range im.tenants {
t.Cancel()
}
im.tenants = map[string]*auth0Tenant{}
}
func (im *identityManager) Reload() error {
users, err := im.adapter.LoadIdentities()
if err != nil {
return fmt.Errorf("load users from adapter: %w", err)
}
userlist := NewUserList()
now := time.Now()
for _, u := range users {
if u.CreatedAt.IsZero() {
u.CreatedAt = now
}
if u.UpdatedAt.IsZero() {
u.UpdatedAt = now
}
err := userlist.Add(u)
if err != nil {
return fmt.Errorf("invalid user %s from adapter: %w", u.Name, err)
}
}
userlist.Delete(im.root.user.Name)
userlist.Delete(im.root.user.Alias)
im.lock.Lock()
defer im.lock.Unlock()
im.autosave = false
defer func() {
im.autosave = true
}()
for name := range im.identities {
im.delete(name)
}
im.userlist = userlist
for _, u := range userlist.List() {
identity, err := im.createIdentity(u)
if err != nil {
continue
}
im.identities[u.Name] = identity
}
return nil
}
func (im *identityManager) Create(u User) error {
im.lock.Lock()
defer im.lock.Unlock()
if im.root.user.Name == u.Name || im.root.user.Alias == u.Name {
return fmt.Errorf("the identity %s already exists", u.Name)
}
if len(u.Alias) != 0 {
if im.root.user.Name == u.Alias || im.root.user.Alias == u.Alias {
return fmt.Errorf("the identity %s already exists", u.Alias)
}
}
now := time.Now()
u.CreatedAt = now
u.UpdatedAt = now
err := im.userlist.Add(u)
if err != nil {
return err
}
identity, err := im.createIdentity(u)
if err != nil {
return err
}
im.identities[identity.user.Name] = identity
if im.autosave {
im.save()
}
return nil
}
func (im *identityManager) createIdentity(u User) (*identity, error) {
u = u.clone()
identity := u.marshalIdentity()
if len(identity.user.Auth.API.Auth0.User) != 0 {
auth0Key := identity.user.Auth.API.Auth0.Tenant.key()
if tenant, ok := im.tenants[auth0Key]; !ok {
tenant, err := newAuth0Tenant(identity.user.Auth.API.Auth0.Tenant)
if err != nil {
return nil, err
}
im.tenants[auth0Key] = tenant
identity.tenant = tenant
} else {
tenant.AddClientID(identity.user.Auth.API.Auth0.Tenant.ClientID)
identity.tenant = tenant
}
im.auth0UserIdentityMap[identity.user.Auth.API.Auth0.User] = identity.user.Name
}
identity.valid = true
im.logger.Debug().WithField("name", identity.Name()).Log("Identity created")
return identity, nil
}
func (im *identityManager) Update(nameOrAlias string, u User) error {
im.lock.Lock()
defer im.lock.Unlock()
if im.root.user.Name == nameOrAlias || im.root.user.Alias == nameOrAlias {
return fmt.Errorf("the identity %s cannot be updated", nameOrAlias)
}
if im.root.user.Name == u.Name || im.root.user.Alias == u.Name {
return fmt.Errorf("the identity %s already exists", u.Name)
}
if len(u.Alias) != 0 {
if im.root.user.Name == u.Alias || im.root.user.Alias == u.Alias {
return fmt.Errorf("the identity %s already exists", u.Alias)
}
}
oldUser, err := im.userlist.Get(nameOrAlias)
if err != nil {
return err
}
u.CreatedAt = oldUser.CreatedAt
u.UpdatedAt = time.Now()
if err = im.userlist.Update(nameOrAlias, u); err != nil {
return err
}
user, err := im.userlist.Get(u.Name)
if err != nil {
return err
}
_, ok := im.identities[oldUser.Name]
if !ok {
return fmt.Errorf("identity not found")
}
identity, err := im.createIdentity(u)
if err != nil {
return err
}
im.identities[user.Name] = identity
im.logger.Debug().WithFields(log.Fields{
"oldname": oldUser.Name,
"newname": user.Name,
}).Log("Identity updated")
if im.autosave {
im.save()
}
return nil
}
func (im *identityManager) Delete(nameOrAlias string) error {
im.lock.Lock()
defer im.lock.Unlock()
err := im.delete(nameOrAlias)
if err != nil {
return err
}
return nil
}
func (im *identityManager) delete(nameOrAlias string) error {
if im.root.user.Name == nameOrAlias || im.root.user.Alias == nameOrAlias {
return fmt.Errorf("this identity can't be removed")
}
user, err := im.userlist.Get(nameOrAlias)
if err != nil {
return err
}
identity, ok := im.identities[user.Name]
if !ok {
return fmt.Errorf("identity not found")
}
im.userlist.Delete(user.Name)
delete(im.identities, user.Name)
identity.lock.Lock()
identity.valid = false
identity.lock.Unlock()
if len(user.Auth.API.Auth0.User) == 0 {
if im.autosave {
im.save()
}
return nil
}
delete(im.auth0UserIdentityMap, user.Auth.API.Auth0.User)
// find out if the tenant is still used somewhere else
found := false
for _, i := range im.identities {
if i.tenant == identity.tenant {
found = true
break
}
}
if !found {
identity.tenant.Cancel()
delete(im.tenants, identity.user.Auth.API.Auth0.Tenant.key())
if im.autosave {
im.save()
}
return nil
}
// find out if the tenant's clientid is still used somewhere else
found = false
for _, i := range im.identities {
if len(i.user.Auth.API.Auth0.User) == 0 {
continue
}
if i.user.Auth.API.Auth0.Tenant.ClientID == identity.user.Auth.API.Auth0.Tenant.ClientID {
found = true
break
}
}
if !found {
identity.tenant.RemoveClientID(identity.user.Auth.API.Auth0.Tenant.ClientID)
}
if im.autosave {
if err := im.save(); err != nil {
return err
}
}
return nil
}
func (im *identityManager) getIdentity(nameOrAlias string) (*identity, error) {
var identity *identity = nil
if im.root.user.Name == nameOrAlias || im.root.user.Alias == nameOrAlias {
identity = im.root
} else {
user, err := im.userlist.Get(nameOrAlias)
if err != nil {
return nil, fmt.Errorf("identity not found")
}
identity = im.identities[user.Name]
}
if identity == nil {
return nil, fmt.Errorf("identity not found")
}
identity.jwtRealm = im.jwtRealm
identity.jwtKeyFunc = func(*jwtgo.Token) (interface{}, error) { return im.jwtSecret, nil }
return identity, nil
}
func (im *identityManager) Get(nameOrAlias string) (User, error) {
im.lock.RLock()
defer im.lock.RUnlock()
identity, err := im.getIdentity(nameOrAlias)
if err != nil {
return User{}, err
}
user := identity.user.clone()
return user, nil
}
func (im *identityManager) GetVerifier(nameOrAlias string) (Verifier, error) {
im.lock.RLock()
defer im.lock.RUnlock()
return im.getIdentity(nameOrAlias)
}
func (im *identityManager) GetVerifierFromAuth0(auth0Name string) (Verifier, error) {
im.lock.RLock()
defer im.lock.RUnlock()
name, ok := im.auth0UserIdentityMap[auth0Name]
if !ok {
return nil, fmt.Errorf("not found")
}
return im.getIdentity(name)
}
func (im *identityManager) GetDefaultVerifier() (Verifier, error) {
return im.root, nil
}
func (im *identityManager) List() []User {
im.lock.RLock()
defer im.lock.RUnlock()
return im.userlist.List()
}
func (im *identityManager) Save() error {
im.lock.RLock()
defer im.lock.RUnlock()
return im.save()
}
func (im *identityManager) save() error {
users := im.userlist.List()
return im.adapter.SaveIdentities(users)
}
func (im *identityManager) Autosave(auto bool) {
im.lock.Lock()
defer im.lock.Unlock()
im.autosave = auto
}
func (im *identityManager) Validators() []string {
validators := []string{"localjwt"}
im.lock.RLock()
defer im.lock.RUnlock()
for _, t := range im.tenants {
for _, clientid := range t.clientIDs {
validators = append(validators, fmt.Sprintf("auth0 domain=%s audience=%s clientid=%s", t.domain, t.audience, clientid))
}
}
return validators
}
func (im *identityManager) CreateJWT(nameOrAlias string) (string, string, error) {
im.lock.RLock()
defer im.lock.RUnlock()
identity, err := im.getIdentity(nameOrAlias)
if err != nil {
return "", "", err
}
now := time.Now()
accessExpires := now.Add(time.Minute * 10)
refreshExpires := now.Add(time.Hour * 24)
// Create access token
accessToken := jwtgo.NewWithClaims(jwtgo.SigningMethodHS256, jwtgo.MapClaims{
"iss": im.jwtRealm,
"sub": identity.Name(),
"usefor": "access",
"iat": now.Unix(),
"exp": accessExpires.Unix(),
"exi": uint64(accessExpires.Sub(now).Seconds()),
"jti": uuid.New().String(),
})
// Generate encoded access token
at, err := accessToken.SignedString(im.jwtSecret)
if err != nil {
return "", "", err
}
// Create refresh token
refreshToken := jwtgo.NewWithClaims(jwtgo.SigningMethodHS256, jwtgo.MapClaims{
"iss": im.jwtRealm,
"sub": identity.Name(),
"usefor": "refresh",
"iat": now.Unix(),
"exp": refreshExpires.Unix(),
"exi": uint64(refreshExpires.Sub(now).Seconds()),
"jti": uuid.New().String(),
})
// Generate encoded refresh token
rt, err := refreshToken.SignedString(im.jwtSecret)
if err != nil {
return "", "", err
}
return at, rt, nil
}