mirror of
https://github.com/datarhei/core.git
synced 2025-10-06 00:17:07 +08:00
537 lines
11 KiB
Go
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
|
|
}
|