mirror of
https://github.com/datarhei/core.git
synced 2025-12-24 13:07:56 +08:00
Fix identity alias
This commit is contained in:
87
iam/identity/auth0.go
Normal file
87
iam/identity/auth0.go
Normal file
@@ -0,0 +1,87 @@
|
||||
package identity
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/datarhei/core/v16/iam/jwks"
|
||||
)
|
||||
|
||||
type Auth0Tenant struct {
|
||||
Domain string `json:"domain"`
|
||||
Audience string `json:"audience"`
|
||||
ClientID string `json:"client_id"`
|
||||
}
|
||||
|
||||
func (t *Auth0Tenant) key() string {
|
||||
return t.Domain + t.Audience
|
||||
}
|
||||
|
||||
type auth0Tenant struct {
|
||||
domain string
|
||||
issuer string
|
||||
audience string
|
||||
clientIDs []string
|
||||
certs jwks.JWKS
|
||||
|
||||
lock sync.Mutex
|
||||
}
|
||||
|
||||
func newAuth0Tenant(tenant Auth0Tenant) (*auth0Tenant, error) {
|
||||
t := &auth0Tenant{
|
||||
domain: tenant.Domain,
|
||||
issuer: "https://" + tenant.Domain + "/",
|
||||
audience: tenant.Audience,
|
||||
clientIDs: []string{tenant.ClientID},
|
||||
certs: nil,
|
||||
}
|
||||
|
||||
url := t.issuer + ".well-known/jwks.json"
|
||||
certs, err := jwks.NewFromURL(url, jwks.Config{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
t.certs = certs
|
||||
|
||||
return t, nil
|
||||
}
|
||||
|
||||
func (a *auth0Tenant) Cancel() {
|
||||
a.certs.Cancel()
|
||||
}
|
||||
|
||||
func (a *auth0Tenant) AddClientID(clientid string) {
|
||||
a.lock.Lock()
|
||||
defer a.lock.Unlock()
|
||||
|
||||
found := false
|
||||
for _, id := range a.clientIDs {
|
||||
if id == clientid {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if found {
|
||||
return
|
||||
}
|
||||
|
||||
a.clientIDs = append(a.clientIDs, clientid)
|
||||
}
|
||||
|
||||
func (a *auth0Tenant) RemoveClientID(clientid string) {
|
||||
a.lock.Lock()
|
||||
defer a.lock.Unlock()
|
||||
|
||||
clientids := []string{}
|
||||
|
||||
for _, id := range a.clientIDs {
|
||||
if id == clientid {
|
||||
continue
|
||||
}
|
||||
|
||||
clientids = append(clientids, id)
|
||||
}
|
||||
|
||||
a.clientIDs = clientids
|
||||
}
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"time"
|
||||
|
||||
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"
|
||||
@@ -94,8 +93,8 @@ func (u *User) clone() User {
|
||||
}
|
||||
|
||||
type Verifier interface {
|
||||
// Name returns the name of the identity, if an alias is available the alias will be returned.
|
||||
Name() string
|
||||
Name() string // Name returns the name of the identity.
|
||||
Alias() string // Alias returns the alias of the identity, or an empty string if no alias has been set.
|
||||
|
||||
VerifyJWT(jwt string) (bool, error)
|
||||
|
||||
@@ -127,13 +126,13 @@ type identity struct {
|
||||
}
|
||||
|
||||
func (i *identity) Name() string {
|
||||
if len(i.user.Alias) != 0 {
|
||||
return i.user.Alias
|
||||
}
|
||||
|
||||
return i.user.Name
|
||||
}
|
||||
|
||||
func (i *identity) Alias() string {
|
||||
return i.user.Alias
|
||||
}
|
||||
|
||||
func (i *identity) VerifyAPIPassword(password string) (bool, error) {
|
||||
i.lock.RLock()
|
||||
defer i.lock.RUnlock()
|
||||
@@ -503,7 +502,6 @@ type identityManager struct {
|
||||
root *identity
|
||||
|
||||
identities map[string]*identity
|
||||
aliases map[string]*identity
|
||||
tenants map[string]*auth0Tenant
|
||||
|
||||
auth0UserIdentityMap map[string]string
|
||||
@@ -529,7 +527,6 @@ 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,
|
||||
@@ -571,7 +568,6 @@ 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 {
|
||||
@@ -606,26 +602,10 @@ func (im *identityManager) Reload() error {
|
||||
}
|
||||
|
||||
for _, u := range users {
|
||||
if im.root != nil && u.Name == im.root.user.Name {
|
||||
if ok, _ := im.isNameAvailable(u.Name, u.Alias); !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
_, ok := im.identities[u.Name]
|
||||
if ok {
|
||||
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)
|
||||
}
|
||||
@@ -637,13 +617,47 @@ func (im *identityManager) Reload() error {
|
||||
|
||||
im.identities[identity.user.Name] = identity
|
||||
if len(identity.user.Alias) != 0 {
|
||||
im.aliases[identity.user.Alias] = identity
|
||||
im.identities[identity.user.Alias] = identity
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (im *identityManager) isNameAvailable(name, alias string) (bool, error) {
|
||||
if im.root == nil {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
if name == im.root.user.Name {
|
||||
return false, fmt.Errorf("name already exists")
|
||||
}
|
||||
|
||||
if name == im.root.user.Alias {
|
||||
return false, fmt.Errorf("name already exists")
|
||||
}
|
||||
|
||||
if _, ok := im.identities[name]; ok {
|
||||
return false, fmt.Errorf("name already exists")
|
||||
}
|
||||
|
||||
if len(alias) != 0 {
|
||||
if alias == im.root.user.Name {
|
||||
return false, fmt.Errorf("alias already exists")
|
||||
}
|
||||
|
||||
if alias == im.root.user.Alias {
|
||||
return false, fmt.Errorf("alias already exists")
|
||||
}
|
||||
|
||||
if _, ok := im.identities[alias]; ok {
|
||||
return false, fmt.Errorf("alias already exists")
|
||||
}
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (im *identityManager) Create(u User) error {
|
||||
if err := u.Validate(); err != nil {
|
||||
return err
|
||||
@@ -652,24 +666,8 @@ func (im *identityManager) Create(u User) error {
|
||||
im.lock.Lock()
|
||||
defer im.lock.Unlock()
|
||||
|
||||
if im.root != nil && im.root.user.Name == u.Name {
|
||||
return fmt.Errorf("identity name already exists")
|
||||
}
|
||||
|
||||
_, ok := im.identities[u.Name]
|
||||
if ok {
|
||||
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")
|
||||
}
|
||||
if ok, err := im.isNameAvailable(u.Name, u.Alias); !ok {
|
||||
return err
|
||||
}
|
||||
|
||||
identity, err := im.create(u)
|
||||
@@ -679,7 +677,7 @@ 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
|
||||
im.identities[identity.user.Alias] = identity
|
||||
}
|
||||
|
||||
if im.autosave {
|
||||
@@ -731,45 +729,41 @@ func (im *identityManager) Update(name string, u User) error {
|
||||
im.lock.Lock()
|
||||
defer im.lock.Unlock()
|
||||
|
||||
if im.root.user.Name == name {
|
||||
return fmt.Errorf("this identity can't be updated")
|
||||
if im.root.user.Name == name || im.root.user.Alias == name {
|
||||
return fmt.Errorf("this identity cannot be updated")
|
||||
}
|
||||
|
||||
oldidentity, ok := im.identities[name]
|
||||
if !ok {
|
||||
return fmt.Errorf("not found")
|
||||
return fmt.Errorf("identity not found")
|
||||
}
|
||||
|
||||
if oldidentity.user.Name != u.Name {
|
||||
_, err := im.getIdentity(u.Name)
|
||||
if err == nil {
|
||||
return fmt.Errorf("identity already exist")
|
||||
}
|
||||
delete(im.identities, oldidentity.user.Name)
|
||||
delete(im.identities, oldidentity.user.Alias)
|
||||
|
||||
ok, err := im.isNameAvailable(u.Name, u.Alias)
|
||||
|
||||
im.identities[oldidentity.user.Name] = oldidentity
|
||||
if len(oldidentity.user.Alias) != 0 {
|
||||
im.identities[oldidentity.user.Alias] = oldidentity
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
}
|
||||
if !ok {
|
||||
return err
|
||||
}
|
||||
|
||||
err := im.delete(name)
|
||||
err = im.delete(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
identity, err := im.create(u)
|
||||
if err != nil {
|
||||
if identity, err := im.create(oldidentity.user); err != nil {
|
||||
return err
|
||||
} else {
|
||||
im.identities[identity.user.Name] = identity
|
||||
if len(identity.user.Alias) != 0 {
|
||||
im.aliases[identity.user.Alias] = identity
|
||||
}
|
||||
// restore old identity
|
||||
im.create(oldidentity.user)
|
||||
im.identities[oldidentity.user.Name] = oldidentity
|
||||
if len(oldidentity.user.Alias) != 0 {
|
||||
im.identities[oldidentity.user.Alias] = oldidentity
|
||||
}
|
||||
|
||||
return err
|
||||
@@ -777,7 +771,7 @@ func (im *identityManager) Update(name string, u User) error {
|
||||
|
||||
im.identities[identity.user.Name] = identity
|
||||
if len(identity.user.Alias) != 0 {
|
||||
im.aliases[identity.user.Alias] = identity
|
||||
im.identities[identity.user.Alias] = identity
|
||||
}
|
||||
|
||||
im.logger.Debug().WithFields(log.Fields{
|
||||
@@ -805,17 +799,17 @@ func (im *identityManager) Delete(name string) error {
|
||||
}
|
||||
|
||||
func (im *identityManager) delete(name string) error {
|
||||
if im.root.user.Name == name {
|
||||
if im.root.user.Name == name || im.root.user.Alias == name {
|
||||
return fmt.Errorf("this identity can't be removed")
|
||||
}
|
||||
|
||||
identity, ok := im.identities[name]
|
||||
if !ok {
|
||||
return fmt.Errorf("not found")
|
||||
return fmt.Errorf("identity not found")
|
||||
}
|
||||
|
||||
delete(im.identities, name)
|
||||
delete(im.aliases, identity.user.Alias)
|
||||
delete(im.identities, identity.user.Name)
|
||||
delete(im.identities, identity.user.Alias)
|
||||
|
||||
identity.lock.Lock()
|
||||
identity.valid = false
|
||||
@@ -880,14 +874,14 @@ func (im *identityManager) delete(name string) error {
|
||||
func (im *identityManager) getIdentity(name string) (*identity, error) {
|
||||
var identity *identity = nil
|
||||
|
||||
if im.root.user.Name == name {
|
||||
if im.root.user.Name == name || im.root.user.Alias == name {
|
||||
identity = im.root
|
||||
} else {
|
||||
identity = im.identities[name]
|
||||
}
|
||||
|
||||
if identity == nil {
|
||||
return nil, fmt.Errorf("not found")
|
||||
return nil, fmt.Errorf("identity not found")
|
||||
}
|
||||
|
||||
identity.jwtRealm = im.jwtRealm
|
||||
@@ -896,32 +890,13 @@ 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
|
||||
}
|
||||
return User{}, err
|
||||
}
|
||||
|
||||
user := identity.user.clone()
|
||||
@@ -933,12 +908,7 @@ func (im *identityManager) GetVerifier(name string) (Verifier, error) {
|
||||
im.lock.RLock()
|
||||
defer im.lock.RUnlock()
|
||||
|
||||
identity, err := im.getIdentity(name)
|
||||
if err != nil {
|
||||
identity, err = im.getIdentityFromAlias(name)
|
||||
}
|
||||
|
||||
return identity, err
|
||||
return im.getIdentity(name)
|
||||
}
|
||||
|
||||
func (im *identityManager) GetVerifierFromAuth0(name string) (Verifier, error) {
|
||||
@@ -1058,83 +1028,3 @@ func (im *identityManager) CreateJWT(name string) (string, string, error) {
|
||||
|
||||
return at, rt, nil
|
||||
}
|
||||
|
||||
type Auth0Tenant struct {
|
||||
Domain string `json:"domain"`
|
||||
Audience string `json:"audience"`
|
||||
ClientID string `json:"client_id"`
|
||||
}
|
||||
|
||||
func (t *Auth0Tenant) key() string {
|
||||
return t.Domain + t.Audience
|
||||
}
|
||||
|
||||
type auth0Tenant struct {
|
||||
domain string
|
||||
issuer string
|
||||
audience string
|
||||
clientIDs []string
|
||||
certs jwks.JWKS
|
||||
|
||||
lock sync.Mutex
|
||||
}
|
||||
|
||||
func newAuth0Tenant(tenant Auth0Tenant) (*auth0Tenant, error) {
|
||||
t := &auth0Tenant{
|
||||
domain: tenant.Domain,
|
||||
issuer: "https://" + tenant.Domain + "/",
|
||||
audience: tenant.Audience,
|
||||
clientIDs: []string{tenant.ClientID},
|
||||
certs: nil,
|
||||
}
|
||||
|
||||
url := t.issuer + ".well-known/jwks.json"
|
||||
certs, err := jwks.NewFromURL(url, jwks.Config{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
t.certs = certs
|
||||
|
||||
return t, nil
|
||||
}
|
||||
|
||||
func (a *auth0Tenant) Cancel() {
|
||||
a.certs.Cancel()
|
||||
}
|
||||
|
||||
func (a *auth0Tenant) AddClientID(clientid string) {
|
||||
a.lock.Lock()
|
||||
defer a.lock.Unlock()
|
||||
|
||||
found := false
|
||||
for _, id := range a.clientIDs {
|
||||
if id == clientid {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if found {
|
||||
return
|
||||
}
|
||||
|
||||
a.clientIDs = append(a.clientIDs, clientid)
|
||||
}
|
||||
|
||||
func (a *auth0Tenant) RemoveClientID(clientid string) {
|
||||
a.lock.Lock()
|
||||
defer a.lock.Unlock()
|
||||
|
||||
clientids := []string{}
|
||||
|
||||
for _, id := range a.clientIDs {
|
||||
if id == clientid {
|
||||
continue
|
||||
}
|
||||
|
||||
clientids = append(clientids, id)
|
||||
}
|
||||
|
||||
a.clientIDs = clientids
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ func TestIdentity(t *testing.T) {
|
||||
require.Equal(t, "foobar", identity.Name())
|
||||
|
||||
identity.user.Alias = "raboof"
|
||||
require.Equal(t, "raboof", identity.Name())
|
||||
require.Equal(t, "raboof", identity.Alias())
|
||||
|
||||
require.False(t, identity.isValid())
|
||||
identity.valid = true
|
||||
@@ -364,6 +364,9 @@ func TestAlias(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "foobaz", identity.Name)
|
||||
require.Equal(t, "alias", identity.Alias)
|
||||
|
||||
err = im.Create(User{Name: "alias", Alias: "foobaz"})
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestCreateUserAuth0(t *testing.T) {
|
||||
@@ -624,7 +627,8 @@ func TestUpdateUserAlias(t *testing.T) {
|
||||
identity, err = im.GetVerifier("fooboz")
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, identity)
|
||||
require.Equal(t, "alias", identity.Name())
|
||||
require.Equal(t, "fooboz", identity.Name())
|
||||
require.Equal(t, "alias", identity.Alias())
|
||||
|
||||
err = im.Create(User{Name: "barfoo", Alias: "alias2"})
|
||||
require.NoError(t, err)
|
||||
|
||||
12
maps/copy.go
Normal file
12
maps/copy.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package maps
|
||||
|
||||
// Copy returns a shallow copy of a map
|
||||
func Copy[A comparable, B any](a map[A]B) map[A]B {
|
||||
b := map[A]B{}
|
||||
|
||||
for k, v := range a {
|
||||
b[k] = v
|
||||
}
|
||||
|
||||
return b
|
||||
}
|
||||
18
maps/copy_test.go
Normal file
18
maps/copy_test.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package maps
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestCopy(t *testing.T) {
|
||||
a := map[string]string{
|
||||
"foo": "bar",
|
||||
"bar": "foo",
|
||||
}
|
||||
|
||||
b := Copy(a)
|
||||
|
||||
require.Equal(t, a, b)
|
||||
}
|
||||
Reference in New Issue
Block a user