mirror of
https://github.com/datarhei/core.git
synced 2025-10-08 17:30:52 +08:00
Allow an alias for an identity name
This commit is contained in:
@@ -979,7 +979,7 @@ func (a *api) start(ctx context.Context) error {
|
||||
}
|
||||
|
||||
if identity != nil {
|
||||
u.User = url.UserPassword(config.Owner, identity.GetServiceBasicAuth())
|
||||
u.User = url.UserPassword(identity.Name(), identity.GetServiceBasicAuth())
|
||||
} else {
|
||||
u.User = url.User(config.Owner)
|
||||
}
|
||||
@@ -1007,7 +1007,7 @@ func (a *api) start(ctx context.Context) error {
|
||||
}
|
||||
|
||||
if identity != nil {
|
||||
u.User = url.UserPassword(config.Owner, identity.GetServiceBasicAuth())
|
||||
u.User = url.UserPassword(identity.Name(), identity.GetServiceBasicAuth())
|
||||
} else {
|
||||
u.User = url.User(config.Owner)
|
||||
}
|
||||
@@ -1037,7 +1037,7 @@ func (a *api) start(ctx context.Context) error {
|
||||
}
|
||||
|
||||
if identity != nil {
|
||||
u.User = url.UserPassword(config.Owner, identity.GetServiceBasicAuth())
|
||||
u.User = url.UserPassword(identity.Name(), identity.GetServiceBasicAuth())
|
||||
}
|
||||
|
||||
if len(config.Domain) != 0 {
|
||||
|
@@ -9,6 +9,7 @@ import (
|
||||
enctoken "github.com/datarhei/core/v16/encoding/token"
|
||||
"github.com/datarhei/core/v16/iam/jwks"
|
||||
"github.com/datarhei/core/v16/log"
|
||||
"github.com/datarhei/core/v16/slices"
|
||||
"github.com/google/uuid"
|
||||
|
||||
jwtgo "github.com/golang-jwt/jwt/v4"
|
||||
@@ -21,6 +22,7 @@ import (
|
||||
|
||||
type User struct {
|
||||
Name string `json:"name"`
|
||||
Alias string `json:"alias"`
|
||||
Superuser bool `json:"superuser"`
|
||||
Auth UserAuth `json:"auth"`
|
||||
}
|
||||
@@ -55,6 +57,12 @@ func (u *User) Validate() error {
|
||||
return fmt.Errorf("name is not allowed to start with $")
|
||||
}
|
||||
|
||||
if len(u.Alias) != 0 {
|
||||
if strings.HasPrefix(u.Alias, "$") {
|
||||
return fmt.Errorf("alias is not allowed to start with $")
|
||||
}
|
||||
}
|
||||
|
||||
if len(u.Auth.API.Auth0.User) != 0 {
|
||||
t, err := newAuth0Tenant(u.Auth.API.Auth0.Tenant)
|
||||
if err != nil {
|
||||
@@ -78,19 +86,15 @@ func (u *User) marshalIdentity() *identity {
|
||||
func (u *User) clone() User {
|
||||
user := *u
|
||||
|
||||
user.Auth.Services.Basic = make([]string, len(u.Auth.Services.Basic))
|
||||
copy(user.Auth.Services.Basic, u.Auth.Services.Basic)
|
||||
|
||||
user.Auth.Services.Token = make([]string, len(u.Auth.Services.Token))
|
||||
copy(user.Auth.Services.Token, u.Auth.Services.Token)
|
||||
|
||||
user.Auth.Services.Session = make([]string, len(u.Auth.Services.Session))
|
||||
copy(user.Auth.Services.Session, u.Auth.Services.Session)
|
||||
user.Auth.Services.Basic = slices.Copy(u.Auth.Services.Basic)
|
||||
user.Auth.Services.Token = slices.Copy(u.Auth.Services.Token)
|
||||
user.Auth.Services.Session = slices.Copy(u.Auth.Services.Session)
|
||||
|
||||
return user
|
||||
}
|
||||
|
||||
type Verifier interface {
|
||||
// Name returns the name of the identity, if an alias is available the alias will be returned.
|
||||
Name() string
|
||||
|
||||
VerifyJWT(jwt string) (bool, error)
|
||||
@@ -123,6 +127,10 @@ type identity struct {
|
||||
}
|
||||
|
||||
func (i *identity) Name() string {
|
||||
if len(i.user.Alias) != 0 {
|
||||
return i.user.Alias
|
||||
}
|
||||
|
||||
return i.user.Name
|
||||
}
|
||||
|
||||
@@ -395,7 +403,7 @@ func (i *identity) VerifyServiceSession(jwt string) (bool, interface{}, error) {
|
||||
subject = sub.(string)
|
||||
}
|
||||
|
||||
if subject != i.user.Name {
|
||||
if subject != i.user.Name && subject != i.user.Alias {
|
||||
return false, nil, fmt.Errorf("wrong subject")
|
||||
}
|
||||
|
||||
@@ -495,6 +503,7 @@ type identityManager struct {
|
||||
root *identity
|
||||
|
||||
identities map[string]*identity
|
||||
aliases map[string]*identity
|
||||
tenants map[string]*auth0Tenant
|
||||
|
||||
auth0UserIdentityMap map[string]string
|
||||
@@ -520,6 +529,7 @@ type Config struct {
|
||||
func New(config Config) (Manager, error) {
|
||||
im := &identityManager{
|
||||
identities: map[string]*identity{},
|
||||
aliases: map[string]*identity{},
|
||||
tenants: map[string]*auth0Tenant{},
|
||||
auth0UserIdentityMap: map[string]string{},
|
||||
adapter: config.Adapter,
|
||||
@@ -561,6 +571,7 @@ func (im *identityManager) Close() {
|
||||
im.adapter = nil
|
||||
im.auth0UserIdentityMap = map[string]string{}
|
||||
im.identities = map[string]*identity{}
|
||||
im.aliases = map[string]*identity{}
|
||||
im.root = nil
|
||||
|
||||
for _, t := range im.tenants {
|
||||
@@ -587,7 +598,7 @@ func (im *identityManager) Reload() error {
|
||||
names := []string{}
|
||||
|
||||
for name := range im.identities {
|
||||
names = append(names, name)
|
||||
im.delete(name)
|
||||
}
|
||||
|
||||
for _, name := range names {
|
||||
@@ -604,6 +615,17 @@ func (im *identityManager) Reload() error {
|
||||
continue
|
||||
}
|
||||
|
||||
if len(u.Alias) != 0 {
|
||||
if im.root != nil && im.root.user.Alias == u.Alias {
|
||||
continue
|
||||
}
|
||||
|
||||
_, ok := im.aliases[u.Alias]
|
||||
if ok {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if err := u.Validate(); err != nil {
|
||||
return fmt.Errorf("invalid user from adapter: %s, %w", u.Name, err)
|
||||
}
|
||||
@@ -614,6 +636,9 @@ func (im *identityManager) Reload() error {
|
||||
}
|
||||
|
||||
im.identities[identity.user.Name] = identity
|
||||
if len(identity.user.Alias) != 0 {
|
||||
im.aliases[identity.user.Alias] = identity
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -628,12 +653,23 @@ func (im *identityManager) Create(u User) error {
|
||||
defer im.lock.Unlock()
|
||||
|
||||
if im.root != nil && im.root.user.Name == u.Name {
|
||||
return fmt.Errorf("identity already exists")
|
||||
return fmt.Errorf("identity name already exists")
|
||||
}
|
||||
|
||||
_, ok := im.identities[u.Name]
|
||||
if ok {
|
||||
return fmt.Errorf("identity already exists")
|
||||
return fmt.Errorf("identity name already exists")
|
||||
}
|
||||
|
||||
if len(u.Alias) != 0 {
|
||||
if im.root != nil && im.root.user.Alias == u.Alias {
|
||||
return fmt.Errorf("identity alias already exists")
|
||||
}
|
||||
|
||||
_, ok := im.aliases[u.Alias]
|
||||
if ok {
|
||||
return fmt.Errorf("identity alias already exists")
|
||||
}
|
||||
}
|
||||
|
||||
identity, err := im.create(u)
|
||||
@@ -642,6 +678,9 @@ func (im *identityManager) Create(u User) error {
|
||||
}
|
||||
|
||||
im.identities[identity.user.Name] = identity
|
||||
if len(identity.user.Alias) != 0 {
|
||||
im.aliases[identity.user.Alias] = identity
|
||||
}
|
||||
|
||||
if im.autosave {
|
||||
im.save()
|
||||
@@ -701,13 +740,22 @@ func (im *identityManager) Update(name string, u User) error {
|
||||
return fmt.Errorf("not found")
|
||||
}
|
||||
|
||||
if name != u.Name {
|
||||
if oldidentity.user.Name != u.Name {
|
||||
_, err := im.getIdentity(u.Name)
|
||||
if err == nil {
|
||||
return fmt.Errorf("identity already exist")
|
||||
}
|
||||
}
|
||||
|
||||
if len(u.Alias) != 0 {
|
||||
if oldidentity.user.Alias != u.Alias {
|
||||
_, err := im.getIdentityFromAlias(u.Alias)
|
||||
if err == nil {
|
||||
return fmt.Errorf("identity alias already exist")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
err := im.delete(name)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -719,12 +767,18 @@ func (im *identityManager) Update(name string, u User) error {
|
||||
return err
|
||||
} else {
|
||||
im.identities[identity.user.Name] = identity
|
||||
if len(identity.user.Alias) != 0 {
|
||||
im.aliases[identity.user.Alias] = identity
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
im.identities[identity.user.Name] = identity
|
||||
if len(identity.user.Alias) != 0 {
|
||||
im.aliases[identity.user.Alias] = identity
|
||||
}
|
||||
|
||||
im.logger.Debug().WithFields(log.Fields{
|
||||
"oldname": name,
|
||||
@@ -761,6 +815,7 @@ func (im *identityManager) delete(name string) error {
|
||||
}
|
||||
|
||||
delete(im.identities, name)
|
||||
delete(im.aliases, identity.user.Alias)
|
||||
|
||||
identity.lock.Lock()
|
||||
identity.valid = false
|
||||
@@ -841,14 +896,33 @@ func (im *identityManager) getIdentity(name string) (*identity, error) {
|
||||
return identity, nil
|
||||
}
|
||||
|
||||
func (im *identityManager) getIdentityFromAlias(alias string) (*identity, error) {
|
||||
var identity *identity = nil
|
||||
|
||||
if im.root.user.Alias == alias {
|
||||
identity = im.root
|
||||
} else {
|
||||
identity = im.aliases[alias]
|
||||
}
|
||||
|
||||
if identity == nil {
|
||||
return nil, fmt.Errorf("not found")
|
||||
}
|
||||
|
||||
return im.getIdentity(identity.user.Name)
|
||||
}
|
||||
|
||||
func (im *identityManager) Get(name string) (User, error) {
|
||||
im.lock.RLock()
|
||||
defer im.lock.RUnlock()
|
||||
|
||||
identity, err := im.getIdentity(name)
|
||||
if err != nil {
|
||||
identity, err = im.getIdentityFromAlias(name)
|
||||
if err != nil {
|
||||
return User{}, err
|
||||
}
|
||||
}
|
||||
|
||||
user := identity.user.clone()
|
||||
|
||||
@@ -859,7 +933,12 @@ func (im *identityManager) GetVerifier(name string) (Verifier, error) {
|
||||
im.lock.RLock()
|
||||
defer im.lock.RUnlock()
|
||||
|
||||
return im.getIdentity(name)
|
||||
identity, err := im.getIdentity(name)
|
||||
if err != nil {
|
||||
identity, err = im.getIdentityFromAlias(name)
|
||||
}
|
||||
|
||||
return identity, err
|
||||
}
|
||||
|
||||
func (im *identityManager) GetVerifierFromAuth0(name string) (Verifier, error) {
|
||||
|
@@ -2,6 +2,7 @@ package identity
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/datarhei/core/v16/io/fs"
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -35,6 +36,23 @@ func TestUserName(t *testing.T) {
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestUserAlias(t *testing.T) {
|
||||
user := User{
|
||||
Name: "foober",
|
||||
}
|
||||
|
||||
err := user.Validate()
|
||||
require.NoError(t, err)
|
||||
|
||||
user.Alias = "foobar"
|
||||
err = user.Validate()
|
||||
require.NoError(t, err)
|
||||
|
||||
user.Alias = "$foob:ar"
|
||||
err = user.Validate()
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestIdentity(t *testing.T) {
|
||||
user := User{
|
||||
Name: "foobar",
|
||||
@@ -44,6 +62,9 @@ func TestIdentity(t *testing.T) {
|
||||
|
||||
require.Equal(t, "foobar", identity.Name())
|
||||
|
||||
identity.user.Alias = "raboof"
|
||||
require.Equal(t, "raboof", identity.Name())
|
||||
|
||||
require.False(t, identity.isValid())
|
||||
identity.valid = true
|
||||
require.True(t, identity.isValid())
|
||||
@@ -198,6 +219,32 @@ func TestIdentityServiceTokenAuth(t *testing.T) {
|
||||
require.Equal(t, "foobar:terces", token)
|
||||
}
|
||||
|
||||
func TestIdentityServiceSessionAuth(t *testing.T) {
|
||||
user := User{
|
||||
Name: "foobar",
|
||||
}
|
||||
|
||||
identity := user.marshalIdentity()
|
||||
|
||||
session := identity.GetServiceSession(nil, time.Hour)
|
||||
require.Empty(t, session)
|
||||
|
||||
identity.user.Auth.Services.Session = []string{"bla"}
|
||||
|
||||
session = identity.GetServiceSession(nil, time.Hour)
|
||||
require.Empty(t, session)
|
||||
|
||||
identity.valid = true
|
||||
|
||||
session = identity.GetServiceSession(nil, time.Hour)
|
||||
require.NotEmpty(t, session)
|
||||
|
||||
ok, data, err := identity.VerifyServiceSession(session)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, nil, data)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestJWT(t *testing.T) {
|
||||
adapter, err := createAdapter()
|
||||
require.NoError(t, err)
|
||||
@@ -257,7 +304,7 @@ func TestCreateUser(t *testing.T) {
|
||||
|
||||
im, err := New(Config{
|
||||
Adapter: adapter,
|
||||
Superuser: User{Name: "foobar"},
|
||||
Superuser: User{Name: "foobar", Alias: "foobalias"},
|
||||
JWTRealm: "test-realm",
|
||||
JWTSecret: "abc123",
|
||||
Logger: nil,
|
||||
@@ -268,11 +315,55 @@ func TestCreateUser(t *testing.T) {
|
||||
err = im.Create(User{Name: "foobar"})
|
||||
require.Error(t, err)
|
||||
|
||||
err = im.Create(User{Name: "foobaz"})
|
||||
err = im.Create(User{Name: "foobaz", Alias: "foobalias"})
|
||||
require.Error(t, err)
|
||||
|
||||
err = im.Create(User{Name: "foobaz", Alias: "alias"})
|
||||
require.NoError(t, err)
|
||||
|
||||
err = im.Create(User{Name: "foobaz"})
|
||||
err = im.Create(User{Name: "fooboz", Alias: "alias"})
|
||||
require.Error(t, err)
|
||||
|
||||
err = im.Create(User{Name: "foobaz", Alias: "somealias"})
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestAlias(t *testing.T) {
|
||||
adapter, err := createAdapter()
|
||||
require.NoError(t, err)
|
||||
|
||||
im, err := New(Config{
|
||||
Adapter: adapter,
|
||||
Superuser: User{Name: "foobar", Alias: "foobalias"},
|
||||
JWTRealm: "test-realm",
|
||||
JWTSecret: "abc123",
|
||||
Logger: nil,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, im)
|
||||
|
||||
err = im.Create(User{Name: "foobaz", Alias: "alias"})
|
||||
require.NoError(t, err)
|
||||
|
||||
identity, err := im.Get("foobar")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "foobar", identity.Name)
|
||||
require.Equal(t, "foobalias", identity.Alias)
|
||||
|
||||
identity, err = im.Get("foobalias")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "foobar", identity.Name)
|
||||
require.Equal(t, "foobalias", identity.Alias)
|
||||
|
||||
identity, err = im.Get("foobaz")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "foobaz", identity.Name)
|
||||
require.Equal(t, "alias", identity.Alias)
|
||||
|
||||
identity, err = im.Get("alias")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "foobaz", identity.Name)
|
||||
require.Equal(t, "alias", identity.Alias)
|
||||
}
|
||||
|
||||
func TestCreateUserAuth0(t *testing.T) {
|
||||
@@ -421,13 +512,17 @@ func TestLoadAndSave(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, []byte("[]"), data)
|
||||
|
||||
err = im.Create(User{Name: "foobaz"})
|
||||
err = im.Create(User{Name: "foobaz", Alias: "alias"})
|
||||
require.NoError(t, err)
|
||||
|
||||
identity, err := im.GetVerifier("foobaz")
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, identity)
|
||||
|
||||
identity, err = im.GetVerifier("alias")
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, identity)
|
||||
|
||||
err = im.Save()
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -444,6 +539,10 @@ func TestLoadAndSave(t *testing.T) {
|
||||
identity, err = im.GetVerifier("foobaz")
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, identity)
|
||||
|
||||
identity, err = im.GetVerifier("alias")
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, identity)
|
||||
}
|
||||
|
||||
func TestUpdateUser(t *testing.T) {
|
||||
@@ -494,6 +593,55 @@ func TestUpdateUser(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestUpdateUserAlias(t *testing.T) {
|
||||
adapter, err := createAdapter()
|
||||
require.NoError(t, err)
|
||||
|
||||
im, err := New(Config{
|
||||
Adapter: adapter,
|
||||
Superuser: User{Name: "foobar", Alias: "superalias"},
|
||||
JWTRealm: "test-realm",
|
||||
JWTSecret: "abc123",
|
||||
Logger: nil,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, im)
|
||||
|
||||
err = im.Create(User{Name: "fooboz"})
|
||||
require.NoError(t, err)
|
||||
|
||||
identity, err := im.GetVerifier("fooboz")
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, identity)
|
||||
require.Equal(t, "fooboz", identity.Name())
|
||||
|
||||
_, err = im.GetVerifier("alias")
|
||||
require.Error(t, err)
|
||||
|
||||
err = im.Update("fooboz", User{Name: "fooboz", Alias: "alias"})
|
||||
require.NoError(t, err)
|
||||
|
||||
identity, err = im.GetVerifier("fooboz")
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, identity)
|
||||
require.Equal(t, "alias", identity.Name())
|
||||
|
||||
err = im.Create(User{Name: "barfoo", Alias: "alias2"})
|
||||
require.NoError(t, err)
|
||||
|
||||
err = im.Update("fooboz", User{Name: "fooboz", Alias: "alias2"})
|
||||
require.Error(t, err)
|
||||
|
||||
err = im.Update("fooboz", User{Name: "barfoo", Alias: "alias"})
|
||||
require.Error(t, err)
|
||||
|
||||
err = im.Update("fooboz", User{Name: "fooboz", Alias: "superalias"})
|
||||
require.Error(t, err)
|
||||
|
||||
err = im.Update("fooboz", User{Name: "foobar", Alias: ""})
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestUpdateUserAuth0(t *testing.T) {
|
||||
adapter, err := createAdapter()
|
||||
require.NoError(t, err)
|
||||
@@ -582,6 +730,7 @@ func TestRemoveUser(t *testing.T) {
|
||||
Services: UserAuthServices{
|
||||
Basic: []string{"secret"},
|
||||
Token: []string{"tokensecret"},
|
||||
Session: []string{"sessionsecret"},
|
||||
},
|
||||
},
|
||||
})
|
||||
@@ -603,6 +752,13 @@ func TestRemoveUser(t *testing.T) {
|
||||
require.True(t, ok)
|
||||
require.NoError(t, err)
|
||||
|
||||
session := identity.GetServiceSession(nil, time.Hour)
|
||||
|
||||
ok, data, err := identity.VerifyServiceSession(session)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, nil, data)
|
||||
require.NoError(t, err)
|
||||
|
||||
access, refresh, err := im.CreateJWT("foobaz")
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -629,6 +785,11 @@ func TestRemoveUser(t *testing.T) {
|
||||
require.False(t, ok)
|
||||
require.Error(t, err)
|
||||
|
||||
ok, data, err = identity.VerifyServiceSession(session)
|
||||
require.False(t, ok)
|
||||
require.Equal(t, nil, data)
|
||||
require.Error(t, err)
|
||||
|
||||
ok, err = identity.VerifyJWT(access)
|
||||
require.False(t, ok)
|
||||
require.Error(t, err)
|
||||
|
Reference in New Issue
Block a user