Allow an alias for an identity name

This commit is contained in:
Ingo Oppermann
2023-08-03 17:22:16 +03:00
parent 90974fed30
commit 2c2d460a19
3 changed files with 264 additions and 24 deletions

View File

@@ -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 {

View File

@@ -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) {

View File

@@ -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)