diff --git a/iam/identity/auth0.go b/iam/identity/auth0.go new file mode 100644 index 00000000..438b276b --- /dev/null +++ b/iam/identity/auth0.go @@ -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 +} diff --git a/iam/identity/identity.go b/iam/identity/identity.go index cd4615f9..1a587a7d 100644 --- a/iam/identity/identity.go +++ b/iam/identity/identity.go @@ -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 -} diff --git a/iam/identity/identity_test.go b/iam/identity/identity_test.go index 1bd8db58..8ddc3553 100644 --- a/iam/identity/identity_test.go +++ b/iam/identity/identity_test.go @@ -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) diff --git a/maps/copy.go b/maps/copy.go new file mode 100644 index 00000000..63a8507c --- /dev/null +++ b/maps/copy.go @@ -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 +} diff --git a/maps/copy_test.go b/maps/copy_test.go new file mode 100644 index 00000000..da82826c --- /dev/null +++ b/maps/copy_test.go @@ -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) +}