mirror of
https://github.com/netbirdio/netbird.git
synced 2026-04-24 11:16:38 +00:00
peerShouldReceiveUpdate waited 500ms for the expected update message, and every outer wrapper across the management/server test suite paired it with a 1s goroutine-drain timeout. Both were too tight for slower CI runners (MySQL, FreeBSD, loaded sqlite), producing intermittent "Timed out waiting for update message" failures in tests like TestDNSAccountPeersUpdate, TestPeerAccountPeersUpdate, and TestNameServerAccountPeersUpdate. Introduce peerUpdateTimeout (5s) next to the helper and use it both in the helper and in every outer wrapper so the two timeouts stay in sync. Only runs down on failure; passing tests return as soon as the channel delivers, so there is no slowdown on green runs.
2195 lines
67 KiB
Go
2195 lines
67 KiB
Go
package server
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"reflect"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/google/go-cmp/cmp"
|
|
"go.uber.org/mock/gomock"
|
|
"golang.org/x/exp/maps"
|
|
|
|
"github.com/netbirdio/netbird/management/internals/controllers/network_map"
|
|
nbcache "github.com/netbirdio/netbird/management/server/cache"
|
|
"github.com/netbirdio/netbird/management/server/permissions"
|
|
"github.com/netbirdio/netbird/management/server/permissions/modules"
|
|
"github.com/netbirdio/netbird/management/server/permissions/roles"
|
|
"github.com/netbirdio/netbird/management/server/users"
|
|
"github.com/netbirdio/netbird/management/server/util"
|
|
"github.com/netbirdio/netbird/shared/auth"
|
|
"github.com/netbirdio/netbird/shared/management/status"
|
|
|
|
nbpeer "github.com/netbirdio/netbird/management/server/peer"
|
|
"github.com/netbirdio/netbird/management/server/store"
|
|
"github.com/netbirdio/netbird/management/server/types"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
|
|
|
"github.com/netbirdio/netbird/idp/dex"
|
|
"github.com/netbirdio/netbird/management/server/activity"
|
|
"github.com/netbirdio/netbird/management/server/idp"
|
|
"github.com/netbirdio/netbird/management/server/integration_reference"
|
|
)
|
|
|
|
const (
|
|
mockAccountID = "accountID"
|
|
mockUserID = "userID"
|
|
mockServiceUserID = "serviceUserID"
|
|
mockRole = "user"
|
|
mockServiceUserName = "serviceUserName"
|
|
mockTargetUserId = "targetUserID"
|
|
mockTokenID1 = "tokenID1"
|
|
mockToken1 = "SoMeHaShEdToKeN1"
|
|
mockTokenID2 = "tokenID2"
|
|
mockToken2 = "SoMeHaShEdToKeN2"
|
|
mockTokenName = "tokenName"
|
|
mockEmptyTokenName = ""
|
|
mockExpiresIn = 7
|
|
mockWrongExpiresIn = 4506
|
|
)
|
|
|
|
func TestUser_CreatePAT_ForSameUser(t *testing.T) {
|
|
s, cleanup, err := store.NewTestStoreFromSQL(context.Background(), "", t.TempDir())
|
|
if err != nil {
|
|
t.Fatalf("Error when creating store: %s", err)
|
|
}
|
|
t.Cleanup(cleanup)
|
|
|
|
account := newAccountWithId(context.Background(), mockAccountID, mockUserID, "", "", "", false)
|
|
|
|
err = s.SaveAccount(context.Background(), account)
|
|
if err != nil {
|
|
t.Fatalf("Error when saving account: %s", err)
|
|
}
|
|
|
|
permissionsManager := permissions.NewManager(s)
|
|
am := DefaultAccountManager{
|
|
Store: s,
|
|
eventStore: &activity.InMemoryEventStore{},
|
|
permissionsManager: permissionsManager,
|
|
}
|
|
|
|
pat, err := am.CreatePAT(context.Background(), mockAccountID, mockUserID, mockUserID, mockTokenName, mockExpiresIn)
|
|
if err != nil {
|
|
t.Fatalf("Error when adding PAT to user: %s", err)
|
|
}
|
|
|
|
assert.Equal(t, pat.CreatedBy, mockUserID)
|
|
|
|
tokenID, err := am.Store.GetTokenIDByHashedToken(context.Background(), pat.HashedToken)
|
|
if err != nil {
|
|
t.Fatalf("Error when getting token ID by hashed token: %s", err)
|
|
}
|
|
|
|
if tokenID == "" {
|
|
t.Fatal("GetTokenIDByHashedToken failed after adding PAT")
|
|
}
|
|
|
|
assert.Equal(t, pat.ID, tokenID)
|
|
|
|
user, err := am.Store.GetUserByPATID(context.Background(), store.LockingStrengthNone, tokenID)
|
|
if err != nil {
|
|
t.Fatalf("Error when getting user by token ID: %s", err)
|
|
}
|
|
|
|
assert.Equal(t, mockUserID, user.Id)
|
|
}
|
|
|
|
func TestUser_CreatePAT_ForDifferentUser(t *testing.T) {
|
|
store, cleanup, err := store.NewTestStoreFromSQL(context.Background(), "", t.TempDir())
|
|
if err != nil {
|
|
t.Fatalf("Error when creating store: %s", err)
|
|
}
|
|
t.Cleanup(cleanup)
|
|
|
|
account := newAccountWithId(context.Background(), mockAccountID, mockUserID, "", "", "", false)
|
|
account.Users[mockTargetUserId] = &types.User{
|
|
Id: mockTargetUserId,
|
|
IsServiceUser: false,
|
|
}
|
|
err = store.SaveAccount(context.Background(), account)
|
|
if err != nil {
|
|
t.Fatalf("Error when saving account: %s", err)
|
|
}
|
|
|
|
permissionsManager := permissions.NewManager(store)
|
|
am := DefaultAccountManager{
|
|
Store: store,
|
|
eventStore: &activity.InMemoryEventStore{},
|
|
permissionsManager: permissionsManager,
|
|
}
|
|
|
|
_, err = am.CreatePAT(context.Background(), mockAccountID, mockUserID, mockTargetUserId, mockTokenName, mockExpiresIn)
|
|
assert.Errorf(t, err, "Creating PAT for different user should thorw error")
|
|
}
|
|
|
|
func TestUser_CreatePAT_ForServiceUser(t *testing.T) {
|
|
store, cleanup, err := store.NewTestStoreFromSQL(context.Background(), "", t.TempDir())
|
|
if err != nil {
|
|
t.Fatalf("Error when creating store: %s", err)
|
|
}
|
|
t.Cleanup(cleanup)
|
|
|
|
account := newAccountWithId(context.Background(), mockAccountID, mockUserID, "", "", "", false)
|
|
account.Users[mockTargetUserId] = &types.User{
|
|
Id: mockTargetUserId,
|
|
IsServiceUser: true,
|
|
}
|
|
err = store.SaveAccount(context.Background(), account)
|
|
if err != nil {
|
|
t.Fatalf("Error when saving account: %s", err)
|
|
}
|
|
|
|
permissionsManager := permissions.NewManager(store)
|
|
am := DefaultAccountManager{
|
|
Store: store,
|
|
eventStore: &activity.InMemoryEventStore{},
|
|
permissionsManager: permissionsManager,
|
|
}
|
|
|
|
pat, err := am.CreatePAT(context.Background(), mockAccountID, mockUserID, mockTargetUserId, mockTokenName, mockExpiresIn)
|
|
if err != nil {
|
|
t.Fatalf("Error when adding PAT to user: %s", err)
|
|
}
|
|
|
|
assert.Equal(t, pat.CreatedBy, mockUserID)
|
|
}
|
|
|
|
func TestUser_CreatePAT_WithWrongExpiration(t *testing.T) {
|
|
store, cleanup, err := store.NewTestStoreFromSQL(context.Background(), "", t.TempDir())
|
|
if err != nil {
|
|
t.Fatalf("Error when creating store: %s", err)
|
|
}
|
|
t.Cleanup(cleanup)
|
|
|
|
account := newAccountWithId(context.Background(), mockAccountID, mockUserID, "", "", "", false)
|
|
|
|
err = store.SaveAccount(context.Background(), account)
|
|
if err != nil {
|
|
t.Fatalf("Error when saving account: %s", err)
|
|
}
|
|
|
|
permissionsManager := permissions.NewManager(store)
|
|
am := DefaultAccountManager{
|
|
Store: store,
|
|
eventStore: &activity.InMemoryEventStore{},
|
|
permissionsManager: permissionsManager,
|
|
}
|
|
|
|
_, err = am.CreatePAT(context.Background(), mockAccountID, mockUserID, mockUserID, mockTokenName, mockWrongExpiresIn)
|
|
assert.Errorf(t, err, "Wrong expiration should thorw error")
|
|
}
|
|
|
|
func TestUser_CreatePAT_WithEmptyName(t *testing.T) {
|
|
store, cleanup, err := store.NewTestStoreFromSQL(context.Background(), "", t.TempDir())
|
|
if err != nil {
|
|
t.Fatalf("Error when creating store: %s", err)
|
|
}
|
|
t.Cleanup(cleanup)
|
|
|
|
account := newAccountWithId(context.Background(), mockAccountID, mockUserID, "", "", "", false)
|
|
|
|
err = store.SaveAccount(context.Background(), account)
|
|
if err != nil {
|
|
t.Fatalf("Error when saving account: %s", err)
|
|
}
|
|
|
|
permissionsManager := permissions.NewManager(store)
|
|
am := DefaultAccountManager{
|
|
Store: store,
|
|
eventStore: &activity.InMemoryEventStore{},
|
|
permissionsManager: permissionsManager,
|
|
}
|
|
|
|
_, err = am.CreatePAT(context.Background(), mockAccountID, mockUserID, mockUserID, mockEmptyTokenName, mockExpiresIn)
|
|
assert.Errorf(t, err, "Wrong expiration should thorw error")
|
|
}
|
|
|
|
func TestUser_DeletePAT(t *testing.T) {
|
|
store, cleanup, err := store.NewTestStoreFromSQL(context.Background(), "", t.TempDir())
|
|
if err != nil {
|
|
t.Fatalf("Error when creating store: %s", err)
|
|
}
|
|
t.Cleanup(cleanup)
|
|
|
|
account := newAccountWithId(context.Background(), mockAccountID, mockUserID, "", "", "", false)
|
|
account.Users[mockUserID] = &types.User{
|
|
Id: mockUserID,
|
|
PATs: map[string]*types.PersonalAccessToken{
|
|
mockTokenID1: {
|
|
ID: mockTokenID1,
|
|
HashedToken: mockToken1,
|
|
},
|
|
},
|
|
Role: types.UserRoleAdmin,
|
|
}
|
|
err = store.SaveAccount(context.Background(), account)
|
|
if err != nil {
|
|
t.Fatalf("Error when saving account: %s", err)
|
|
}
|
|
|
|
permissionsManager := permissions.NewManager(store)
|
|
am := DefaultAccountManager{
|
|
Store: store,
|
|
eventStore: &activity.InMemoryEventStore{},
|
|
permissionsManager: permissionsManager,
|
|
}
|
|
|
|
err = am.DeletePAT(context.Background(), mockAccountID, mockUserID, mockUserID, mockTokenID1)
|
|
if err != nil {
|
|
t.Fatalf("Error when adding PAT to user: %s", err)
|
|
}
|
|
|
|
account, err = store.GetAccount(context.Background(), mockAccountID)
|
|
if err != nil {
|
|
t.Fatalf("Error when getting account: %s", err)
|
|
}
|
|
|
|
assert.Nil(t, account.Users[mockUserID].PATs[mockTokenID1])
|
|
}
|
|
|
|
func TestUser_GetPAT(t *testing.T) {
|
|
store, cleanup, err := store.NewTestStoreFromSQL(context.Background(), "", t.TempDir())
|
|
if err != nil {
|
|
t.Fatalf("Error when creating store: %s", err)
|
|
}
|
|
t.Cleanup(cleanup)
|
|
|
|
account := newAccountWithId(context.Background(), mockAccountID, mockUserID, "", "", "", false)
|
|
account.Users[mockUserID] = &types.User{
|
|
Id: mockUserID,
|
|
AccountID: mockAccountID,
|
|
PATs: map[string]*types.PersonalAccessToken{
|
|
mockTokenID1: {
|
|
ID: mockTokenID1,
|
|
HashedToken: mockToken1,
|
|
},
|
|
},
|
|
Role: types.UserRoleAdmin,
|
|
}
|
|
err = store.SaveAccount(context.Background(), account)
|
|
if err != nil {
|
|
t.Fatalf("Error when saving account: %s", err)
|
|
}
|
|
|
|
permissionsManager := permissions.NewManager(store)
|
|
am := DefaultAccountManager{
|
|
Store: store,
|
|
eventStore: &activity.InMemoryEventStore{},
|
|
permissionsManager: permissionsManager,
|
|
}
|
|
|
|
pat, err := am.GetPAT(context.Background(), mockAccountID, mockUserID, mockUserID, mockTokenID1)
|
|
if err != nil {
|
|
t.Fatalf("Error when adding PAT to user: %s", err)
|
|
}
|
|
|
|
assert.Equal(t, mockTokenID1, pat.ID)
|
|
assert.Equal(t, mockToken1, pat.HashedToken)
|
|
}
|
|
|
|
func TestUser_GetAllPATs(t *testing.T) {
|
|
store, cleanup, err := store.NewTestStoreFromSQL(context.Background(), "", t.TempDir())
|
|
if err != nil {
|
|
t.Fatalf("Error when creating store: %s", err)
|
|
}
|
|
t.Cleanup(cleanup)
|
|
|
|
account := newAccountWithId(context.Background(), mockAccountID, mockUserID, "", "", "", false)
|
|
account.Users[mockUserID] = &types.User{
|
|
Id: mockUserID,
|
|
AccountID: mockAccountID,
|
|
PATs: map[string]*types.PersonalAccessToken{
|
|
mockTokenID1: {
|
|
ID: mockTokenID1,
|
|
HashedToken: mockToken1,
|
|
},
|
|
mockTokenID2: {
|
|
ID: mockTokenID2,
|
|
HashedToken: mockToken2,
|
|
},
|
|
},
|
|
Role: types.UserRoleAdmin,
|
|
}
|
|
err = store.SaveAccount(context.Background(), account)
|
|
if err != nil {
|
|
t.Fatalf("Error when saving account: %s", err)
|
|
}
|
|
|
|
permissionsManager := permissions.NewManager(store)
|
|
am := DefaultAccountManager{
|
|
Store: store,
|
|
eventStore: &activity.InMemoryEventStore{},
|
|
permissionsManager: permissionsManager,
|
|
}
|
|
|
|
pats, err := am.GetAllPATs(context.Background(), mockAccountID, mockUserID, mockUserID)
|
|
if err != nil {
|
|
t.Fatalf("Error when adding PAT to user: %s", err)
|
|
}
|
|
|
|
assert.Equal(t, 2, len(pats))
|
|
}
|
|
|
|
func TestUser_PAT_CrossAccountProtection(t *testing.T) {
|
|
const (
|
|
accountAID = "accountA"
|
|
accountBID = "accountB"
|
|
userAID = "userA"
|
|
adminBID = "adminB"
|
|
serviceUserBID = "serviceUserB"
|
|
regularUserBID = "regularUserB"
|
|
tokenBID = "tokenB1"
|
|
hashedTokenB = "SoMeHaShEdToKeNB"
|
|
)
|
|
|
|
setupStore := func(t *testing.T) (*DefaultAccountManager, func()) {
|
|
t.Helper()
|
|
|
|
s, cleanup, err := store.NewTestStoreFromSQL(context.Background(), "", t.TempDir())
|
|
require.NoError(t, err, "creating store")
|
|
|
|
accountA := newAccountWithId(context.Background(), accountAID, userAID, "", "", "", false)
|
|
require.NoError(t, s.SaveAccount(context.Background(), accountA))
|
|
|
|
accountB := newAccountWithId(context.Background(), accountBID, adminBID, "", "", "", false)
|
|
accountB.Users[serviceUserBID] = &types.User{
|
|
Id: serviceUserBID,
|
|
AccountID: accountBID,
|
|
IsServiceUser: true,
|
|
ServiceUserName: "svcB",
|
|
Role: types.UserRoleAdmin,
|
|
PATs: map[string]*types.PersonalAccessToken{
|
|
tokenBID: {
|
|
ID: tokenBID,
|
|
HashedToken: hashedTokenB,
|
|
},
|
|
},
|
|
}
|
|
accountB.Users[regularUserBID] = &types.User{
|
|
Id: regularUserBID,
|
|
AccountID: accountBID,
|
|
Role: types.UserRoleUser,
|
|
}
|
|
require.NoError(t, s.SaveAccount(context.Background(), accountB))
|
|
|
|
pm := permissions.NewManager(s)
|
|
am := &DefaultAccountManager{
|
|
Store: s,
|
|
eventStore: &activity.InMemoryEventStore{},
|
|
permissionsManager: pm,
|
|
}
|
|
return am, cleanup
|
|
}
|
|
|
|
t.Run("CreatePAT for user in different account is denied", func(t *testing.T) {
|
|
am, cleanup := setupStore(t)
|
|
t.Cleanup(cleanup)
|
|
|
|
_, err := am.CreatePAT(context.Background(), accountAID, userAID, serviceUserBID, "xss-token", 7)
|
|
require.Error(t, err, "cross-account CreatePAT must fail")
|
|
|
|
_, err = am.CreatePAT(context.Background(), accountAID, userAID, regularUserBID, "xss-token", 7)
|
|
require.Error(t, err, "cross-account CreatePAT for regular user must fail")
|
|
|
|
_, err = am.CreatePAT(context.Background(), accountBID, adminBID, serviceUserBID, "legit-token", 7)
|
|
require.NoError(t, err, "same-account CreatePAT should succeed")
|
|
})
|
|
|
|
t.Run("DeletePAT for user in different account is denied", func(t *testing.T) {
|
|
am, cleanup := setupStore(t)
|
|
t.Cleanup(cleanup)
|
|
|
|
err := am.DeletePAT(context.Background(), accountAID, userAID, serviceUserBID, tokenBID)
|
|
require.Error(t, err, "cross-account DeletePAT must fail")
|
|
})
|
|
|
|
t.Run("GetPAT for user in different account is denied", func(t *testing.T) {
|
|
am, cleanup := setupStore(t)
|
|
t.Cleanup(cleanup)
|
|
|
|
_, err := am.GetPAT(context.Background(), accountAID, userAID, serviceUserBID, tokenBID)
|
|
require.Error(t, err, "cross-account GetPAT must fail")
|
|
})
|
|
|
|
t.Run("GetAllPATs for user in different account is denied", func(t *testing.T) {
|
|
am, cleanup := setupStore(t)
|
|
t.Cleanup(cleanup)
|
|
|
|
_, err := am.GetAllPATs(context.Background(), accountAID, userAID, serviceUserBID)
|
|
require.Error(t, err, "cross-account GetAllPATs must fail")
|
|
})
|
|
|
|
t.Run("CreatePAT with forged accountID targeting foreign user is denied", func(t *testing.T) {
|
|
am, cleanup := setupStore(t)
|
|
t.Cleanup(cleanup)
|
|
|
|
_, err := am.CreatePAT(context.Background(), accountAID, userAID, adminBID, "forged", 7)
|
|
require.Error(t, err, "forged accountID CreatePAT must fail")
|
|
})
|
|
}
|
|
|
|
func TestUser_Copy(t *testing.T) {
|
|
// this is an imaginary case which will never be in DB this way
|
|
user := types.User{
|
|
Id: "userId",
|
|
AccountID: "accountId",
|
|
Role: "role",
|
|
IsServiceUser: true,
|
|
ServiceUserName: "servicename",
|
|
AutoGroups: []string{"group1", "group2"},
|
|
PATs: map[string]*types.PersonalAccessToken{
|
|
"pat1": {
|
|
ID: "pat1",
|
|
Name: "First PAT",
|
|
HashedToken: "SoMeHaShEdToKeN",
|
|
ExpirationDate: util.ToPtr(time.Now().AddDate(0, 0, 7)),
|
|
CreatedBy: "userId",
|
|
CreatedAt: time.Now(),
|
|
LastUsed: util.ToPtr(time.Now()),
|
|
},
|
|
},
|
|
Blocked: false,
|
|
LastLogin: util.ToPtr(time.Now().UTC()),
|
|
CreatedAt: time.Now().UTC(),
|
|
Issued: "test",
|
|
IntegrationReference: integration_reference.IntegrationReference{
|
|
ID: 0,
|
|
IntegrationType: "test",
|
|
},
|
|
Email: "whatever@gmail.com",
|
|
Name: "John Doe",
|
|
}
|
|
|
|
err := validateStruct(user)
|
|
if err != nil {
|
|
t.Fatalf("Test needs update: dummy struct has not all fields set : %s", err)
|
|
}
|
|
|
|
copiedUser := user.Copy()
|
|
|
|
assert.True(t, cmp.Equal(user, *copiedUser))
|
|
}
|
|
|
|
// based on https://medium.com/@anajankow/fast-check-if-all-struct-fields-are-set-in-golang-bba1917213d2
|
|
func validateStruct(s interface{}) (err error) {
|
|
|
|
structType := reflect.TypeOf(s)
|
|
structVal := reflect.ValueOf(s)
|
|
fieldNum := structVal.NumField()
|
|
|
|
for i := 0; i < fieldNum; i++ {
|
|
field := structVal.Field(i)
|
|
fieldName := structType.Field(i).Name
|
|
|
|
// skip gorm internal fields
|
|
if json, ok := structType.Field(i).Tag.Lookup("json"); ok && json == "-" {
|
|
continue
|
|
}
|
|
|
|
isSet := field.IsValid() && (!field.IsZero() || field.Type().String() == "bool")
|
|
|
|
if !isSet {
|
|
err = fmt.Errorf("%v%s in not set; ", err, fieldName)
|
|
}
|
|
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
func TestUser_CreateServiceUser(t *testing.T) {
|
|
store, cleanup, err := store.NewTestStoreFromSQL(context.Background(), "", t.TempDir())
|
|
if err != nil {
|
|
t.Fatalf("Error when creating store: %s", err)
|
|
}
|
|
t.Cleanup(cleanup)
|
|
|
|
account := newAccountWithId(context.Background(), mockAccountID, mockUserID, "", "", "", false)
|
|
|
|
err = store.SaveAccount(context.Background(), account)
|
|
if err != nil {
|
|
t.Fatalf("Error when saving account: %s", err)
|
|
}
|
|
|
|
permissionsManager := permissions.NewManager(store)
|
|
am := DefaultAccountManager{
|
|
Store: store,
|
|
eventStore: &activity.InMemoryEventStore{},
|
|
permissionsManager: permissionsManager,
|
|
}
|
|
|
|
user, err := am.createServiceUser(context.Background(), mockAccountID, mockUserID, mockRole, mockServiceUserName, false, []string{"group1", "group2"})
|
|
if err != nil {
|
|
t.Fatalf("Error when creating service user: %s", err)
|
|
}
|
|
|
|
account, err = store.GetAccount(context.Background(), mockAccountID)
|
|
assert.NoError(t, err)
|
|
|
|
assert.Equal(t, 2, len(account.Users))
|
|
assert.NotNil(t, account.Users[user.ID])
|
|
assert.True(t, account.Users[user.ID].IsServiceUser)
|
|
assert.Equal(t, mockServiceUserName, account.Users[user.ID].ServiceUserName)
|
|
assert.Equal(t, types.UserRole(mockRole), account.Users[user.ID].Role)
|
|
assert.Equal(t, []string{"group1", "group2"}, account.Users[user.ID].AutoGroups)
|
|
assert.Equal(t, map[string]*types.PersonalAccessToken{}, account.Users[user.ID].PATs)
|
|
|
|
assert.Zero(t, user.Email)
|
|
assert.True(t, user.IsServiceUser)
|
|
assert.Equal(t, "active", user.Status)
|
|
|
|
_, err = am.createServiceUser(context.Background(), mockAccountID, mockUserID, types.UserRoleOwner, mockServiceUserName, false, nil)
|
|
if err == nil {
|
|
t.Fatal("should return error when creating service user with owner role")
|
|
}
|
|
}
|
|
|
|
func TestUser_CreateUser_ServiceUser(t *testing.T) {
|
|
store, cleanup, err := store.NewTestStoreFromSQL(context.Background(), "", t.TempDir())
|
|
if err != nil {
|
|
t.Fatalf("Error when creating store: %s", err)
|
|
}
|
|
t.Cleanup(cleanup)
|
|
|
|
account := newAccountWithId(context.Background(), mockAccountID, mockUserID, "", "", "", false)
|
|
|
|
err = store.SaveAccount(context.Background(), account)
|
|
if err != nil {
|
|
t.Fatalf("Error when saving account: %s", err)
|
|
}
|
|
|
|
permissionsManager := permissions.NewManager(store)
|
|
am := DefaultAccountManager{
|
|
Store: store,
|
|
eventStore: &activity.InMemoryEventStore{},
|
|
permissionsManager: permissionsManager,
|
|
}
|
|
|
|
user, err := am.CreateUser(context.Background(), mockAccountID, mockUserID, &types.UserInfo{
|
|
Name: mockServiceUserName,
|
|
Role: mockRole,
|
|
IsServiceUser: true,
|
|
AutoGroups: []string{"group1", "group2"},
|
|
})
|
|
|
|
if err != nil {
|
|
t.Fatalf("Error when creating user: %s", err)
|
|
}
|
|
|
|
account, err = store.GetAccount(context.Background(), mockAccountID)
|
|
assert.NoError(t, err)
|
|
|
|
assert.True(t, user.IsServiceUser)
|
|
assert.Equal(t, 2, len(account.Users))
|
|
assert.True(t, account.Users[user.ID].IsServiceUser)
|
|
assert.Equal(t, mockServiceUserName, account.Users[user.ID].ServiceUserName)
|
|
assert.Equal(t, types.UserRole(mockRole), account.Users[user.ID].Role)
|
|
assert.Equal(t, []string{"group1", "group2"}, account.Users[user.ID].AutoGroups)
|
|
|
|
assert.Equal(t, mockServiceUserName, user.Name)
|
|
assert.Equal(t, mockRole, user.Role)
|
|
assert.Equal(t, []string{"group1", "group2"}, user.AutoGroups)
|
|
assert.Equal(t, "active", user.Status)
|
|
}
|
|
|
|
func TestUser_CreateUser_RegularUser(t *testing.T) {
|
|
store, cleanup, err := store.NewTestStoreFromSQL(context.Background(), "", t.TempDir())
|
|
if err != nil {
|
|
t.Fatalf("Error when creating store: %s", err)
|
|
}
|
|
t.Cleanup(cleanup)
|
|
|
|
account := newAccountWithId(context.Background(), mockAccountID, mockUserID, "", "", "", false)
|
|
|
|
err = store.SaveAccount(context.Background(), account)
|
|
if err != nil {
|
|
t.Fatalf("Error when saving account: %s", err)
|
|
}
|
|
|
|
permissionsManager := permissions.NewManager(store)
|
|
am := DefaultAccountManager{
|
|
Store: store,
|
|
eventStore: &activity.InMemoryEventStore{},
|
|
permissionsManager: permissionsManager,
|
|
}
|
|
|
|
_, err = am.CreateUser(context.Background(), mockAccountID, mockUserID, &types.UserInfo{
|
|
Name: mockServiceUserName,
|
|
Role: mockRole,
|
|
IsServiceUser: false,
|
|
AutoGroups: []string{"group1", "group2"},
|
|
})
|
|
|
|
assert.Errorf(t, err, "Not configured IDP will throw error but right path used")
|
|
}
|
|
|
|
func TestUser_InviteNewUser(t *testing.T) {
|
|
store, cleanup, err := store.NewTestStoreFromSQL(context.Background(), "", t.TempDir())
|
|
if err != nil {
|
|
t.Fatalf("Error when creating store: %s", err)
|
|
}
|
|
t.Cleanup(cleanup)
|
|
|
|
account := newAccountWithId(context.Background(), mockAccountID, mockUserID, "", "", "", false)
|
|
|
|
err = store.SaveAccount(context.Background(), account)
|
|
if err != nil {
|
|
t.Fatalf("Error when saving account: %s", err)
|
|
}
|
|
|
|
permissionsManager := permissions.NewManager(store)
|
|
am := DefaultAccountManager{
|
|
Store: store,
|
|
eventStore: &activity.InMemoryEventStore{},
|
|
cacheLoading: map[string]chan struct{}{},
|
|
permissionsManager: permissionsManager,
|
|
}
|
|
|
|
cs, err := nbcache.NewStore(context.Background(), nbcache.DefaultIDPCacheExpirationMax, nbcache.DefaultIDPCacheCleanupInterval, nbcache.DefaultIDPCacheOpenConn)
|
|
require.NoError(t, err)
|
|
|
|
am.cacheManager = nbcache.NewAccountUserDataCache(am.loadAccount, cs)
|
|
|
|
mockData := []*idp.UserData{
|
|
{
|
|
Email: "user@test.com",
|
|
Name: "user",
|
|
ID: mockUserID,
|
|
},
|
|
}
|
|
|
|
idpMock := idp.MockIDP{
|
|
CreateUserFunc: func(_ context.Context, email, name, accountID, invitedByEmail string) (*idp.UserData, error) {
|
|
newData := &idp.UserData{
|
|
Email: email,
|
|
Name: name,
|
|
ID: "id",
|
|
}
|
|
|
|
mockData = append(mockData, newData)
|
|
|
|
return newData, nil
|
|
},
|
|
GetAccountFunc: func(_ context.Context, accountId string) ([]*idp.UserData, error) {
|
|
return mockData, nil
|
|
},
|
|
}
|
|
|
|
am.idpManager = &idpMock
|
|
|
|
// test if new invite with regular role works
|
|
_, err = am.inviteNewUser(context.Background(), mockAccountID, mockUserID, &types.UserInfo{
|
|
Name: mockServiceUserName,
|
|
Role: mockRole,
|
|
Email: "test@teste.com",
|
|
IsServiceUser: false,
|
|
AutoGroups: []string{"group1", "group2"},
|
|
})
|
|
|
|
assert.NoErrorf(t, err, "Invite user should not throw error")
|
|
|
|
// test if new invite with owner role fails
|
|
_, err = am.inviteNewUser(context.Background(), mockAccountID, mockUserID, &types.UserInfo{
|
|
Name: mockServiceUserName,
|
|
Role: string(types.UserRoleOwner),
|
|
Email: "test2@teste.com",
|
|
IsServiceUser: false,
|
|
AutoGroups: []string{"group1", "group2"},
|
|
})
|
|
|
|
assert.Errorf(t, err, "Invite user with owner role should throw error")
|
|
}
|
|
|
|
func TestUser_DeleteUser_ServiceUser(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
serviceUser *types.User
|
|
assertErrFunc assert.ErrorAssertionFunc
|
|
assertErrMessage string
|
|
}{
|
|
{
|
|
name: "Can delete service user",
|
|
serviceUser: &types.User{
|
|
Id: mockServiceUserID,
|
|
IsServiceUser: true,
|
|
ServiceUserName: mockServiceUserName,
|
|
},
|
|
assertErrFunc: assert.NoError,
|
|
},
|
|
{
|
|
name: "Cannot delete non-deletable service user",
|
|
serviceUser: &types.User{
|
|
Id: mockServiceUserID,
|
|
IsServiceUser: true,
|
|
ServiceUserName: mockServiceUserName,
|
|
NonDeletable: true,
|
|
},
|
|
assertErrFunc: assert.Error,
|
|
assertErrMessage: "service user is marked as non-deletable",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
store, cleanup, err := store.NewTestStoreFromSQL(context.Background(), "", t.TempDir())
|
|
if err != nil {
|
|
t.Fatalf("Error when creating store: %s", err)
|
|
}
|
|
t.Cleanup(cleanup)
|
|
|
|
account := newAccountWithId(context.Background(), mockAccountID, mockUserID, "", "", "", false)
|
|
account.Users[mockServiceUserID] = tt.serviceUser
|
|
|
|
err = store.SaveAccount(context.Background(), account)
|
|
if err != nil {
|
|
t.Fatalf("Error when saving account: %s", err)
|
|
}
|
|
|
|
permissionsManager := permissions.NewManager(store)
|
|
am := DefaultAccountManager{
|
|
Store: store,
|
|
eventStore: &activity.InMemoryEventStore{},
|
|
permissionsManager: permissionsManager,
|
|
}
|
|
|
|
err = am.DeleteUser(context.Background(), mockAccountID, mockUserID, mockServiceUserID)
|
|
tt.assertErrFunc(t, err, tt.assertErrMessage)
|
|
|
|
account, err2 := store.GetAccount(context.Background(), mockAccountID)
|
|
assert.NoError(t, err2)
|
|
|
|
if err != nil {
|
|
assert.Equal(t, 2, len(account.Users))
|
|
assert.NotNil(t, account.Users[mockServiceUserID])
|
|
} else {
|
|
assert.Equal(t, 1, len(account.Users))
|
|
assert.Nil(t, account.Users[mockServiceUserID])
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestUser_DeleteUser_SelfDelete(t *testing.T) {
|
|
store, cleanup, err := store.NewTestStoreFromSQL(context.Background(), "", t.TempDir())
|
|
if err != nil {
|
|
t.Fatalf("Error when creating store: %s", err)
|
|
}
|
|
t.Cleanup(cleanup)
|
|
|
|
account := newAccountWithId(context.Background(), mockAccountID, mockUserID, "", "", "", false)
|
|
|
|
err = store.SaveAccount(context.Background(), account)
|
|
if err != nil {
|
|
t.Fatalf("Error when saving account: %s", err)
|
|
}
|
|
|
|
permissionsManager := permissions.NewManager(store)
|
|
am := DefaultAccountManager{
|
|
Store: store,
|
|
eventStore: &activity.InMemoryEventStore{},
|
|
permissionsManager: permissionsManager,
|
|
}
|
|
|
|
err = am.DeleteUser(context.Background(), mockAccountID, mockUserID, mockUserID)
|
|
if err == nil {
|
|
t.Fatalf("failed to prevent self deletion")
|
|
}
|
|
}
|
|
|
|
func TestUser_DeleteUser_regularUser(t *testing.T) {
|
|
store, cleanup, err := store.NewTestStoreFromSQL(context.Background(), "", t.TempDir())
|
|
if err != nil {
|
|
t.Fatalf("Error when creating store: %s", err)
|
|
}
|
|
t.Cleanup(cleanup)
|
|
|
|
account := newAccountWithId(context.Background(), mockAccountID, mockUserID, "", "", "", false)
|
|
|
|
targetId := "user2"
|
|
account.Users[targetId] = &types.User{
|
|
Id: targetId,
|
|
IsServiceUser: true,
|
|
ServiceUserName: "user2username",
|
|
}
|
|
targetId = "user3"
|
|
account.Users[targetId] = &types.User{
|
|
Id: targetId,
|
|
IsServiceUser: false,
|
|
Issued: types.UserIssuedAPI,
|
|
}
|
|
targetId = "user4"
|
|
account.Users[targetId] = &types.User{
|
|
Id: targetId,
|
|
IsServiceUser: false,
|
|
Issued: types.UserIssuedIntegration,
|
|
}
|
|
|
|
targetId = "user5"
|
|
account.Users[targetId] = &types.User{
|
|
Id: targetId,
|
|
IsServiceUser: false,
|
|
Issued: types.UserIssuedAPI,
|
|
Role: types.UserRoleOwner,
|
|
}
|
|
|
|
err = store.SaveAccount(context.Background(), account)
|
|
if err != nil {
|
|
t.Fatalf("Error when saving account: %s", err)
|
|
}
|
|
|
|
ctrl := gomock.NewController(t)
|
|
networkMapControllerMock := network_map.NewMockController(ctrl)
|
|
networkMapControllerMock.EXPECT().
|
|
OnPeersDeleted(gomock.Any(), gomock.Any(), gomock.Any()).
|
|
Return(nil)
|
|
|
|
permissionsManager := permissions.NewManager(store)
|
|
am := DefaultAccountManager{
|
|
Store: store,
|
|
eventStore: &activity.InMemoryEventStore{},
|
|
permissionsManager: permissionsManager,
|
|
networkMapController: networkMapControllerMock,
|
|
}
|
|
|
|
testCases := []struct {
|
|
name string
|
|
userID string
|
|
assertErrFunc assert.ErrorAssertionFunc
|
|
assertErrMessage string
|
|
}{
|
|
{
|
|
name: "Delete service user successfully ",
|
|
userID: "user2",
|
|
assertErrFunc: assert.NoError,
|
|
},
|
|
{
|
|
name: "Delete regular user successfully ",
|
|
userID: "user3",
|
|
assertErrFunc: assert.NoError,
|
|
},
|
|
{
|
|
name: "Delete integration regular user permission denied ",
|
|
userID: "user4",
|
|
assertErrFunc: assert.Error,
|
|
assertErrMessage: "only admin service user can delete this user",
|
|
},
|
|
{
|
|
name: "Delete user with owner role should return permission denied ",
|
|
userID: "user5",
|
|
assertErrFunc: assert.Error,
|
|
assertErrMessage: "unable to delete a user with owner role",
|
|
},
|
|
}
|
|
|
|
for _, testCase := range testCases {
|
|
t.Run(testCase.name, func(t *testing.T) {
|
|
err = am.DeleteUser(context.Background(), mockAccountID, mockUserID, testCase.userID)
|
|
testCase.assertErrFunc(t, err, testCase.assertErrMessage)
|
|
})
|
|
}
|
|
|
|
}
|
|
|
|
func TestUser_DeleteUser_RegularUsers(t *testing.T) {
|
|
store, cleanup, err := store.NewTestStoreFromSQL(context.Background(), "", t.TempDir())
|
|
if err != nil {
|
|
t.Fatalf("Error when creating store: %s", err)
|
|
}
|
|
t.Cleanup(cleanup)
|
|
|
|
account := newAccountWithId(context.Background(), mockAccountID, mockUserID, "", "", "", false)
|
|
|
|
targetId := "user2"
|
|
account.Users[targetId] = &types.User{
|
|
Id: targetId,
|
|
IsServiceUser: true,
|
|
ServiceUserName: "user2username",
|
|
}
|
|
targetId = "user3"
|
|
account.Users[targetId] = &types.User{
|
|
Id: targetId,
|
|
IsServiceUser: false,
|
|
Issued: types.UserIssuedAPI,
|
|
}
|
|
targetId = "user4"
|
|
account.Users[targetId] = &types.User{
|
|
Id: targetId,
|
|
IsServiceUser: false,
|
|
Issued: types.UserIssuedIntegration,
|
|
}
|
|
|
|
targetId = "user5"
|
|
account.Users[targetId] = &types.User{
|
|
Id: targetId,
|
|
IsServiceUser: false,
|
|
Issued: types.UserIssuedAPI,
|
|
Role: types.UserRoleOwner,
|
|
}
|
|
account.Users["user6"] = &types.User{
|
|
Id: "user6",
|
|
IsServiceUser: false,
|
|
Issued: types.UserIssuedAPI,
|
|
}
|
|
account.Users["user7"] = &types.User{
|
|
Id: "user7",
|
|
IsServiceUser: false,
|
|
Issued: types.UserIssuedAPI,
|
|
}
|
|
account.Users["user8"] = &types.User{
|
|
Id: "user8",
|
|
IsServiceUser: false,
|
|
Issued: types.UserIssuedAPI,
|
|
Role: types.UserRoleAdmin,
|
|
}
|
|
account.Users["user9"] = &types.User{
|
|
Id: "user9",
|
|
IsServiceUser: false,
|
|
Issued: types.UserIssuedAPI,
|
|
Role: types.UserRoleAdmin,
|
|
}
|
|
|
|
err = store.SaveAccount(context.Background(), account)
|
|
if err != nil {
|
|
t.Fatalf("Error when saving account: %s", err)
|
|
}
|
|
|
|
ctrl := gomock.NewController(t)
|
|
networkMapControllerMock := network_map.NewMockController(ctrl)
|
|
networkMapControllerMock.EXPECT().
|
|
OnPeersDeleted(gomock.Any(), gomock.Any(), gomock.Any()).
|
|
Return(nil).
|
|
AnyTimes()
|
|
|
|
permissionsManager := permissions.NewManager(store)
|
|
am := DefaultAccountManager{
|
|
Store: store,
|
|
eventStore: &activity.InMemoryEventStore{},
|
|
integratedPeerValidator: MockIntegratedValidator{},
|
|
permissionsManager: permissionsManager,
|
|
networkMapController: networkMapControllerMock,
|
|
}
|
|
|
|
testCases := []struct {
|
|
name string
|
|
userIDs []string
|
|
expectedReasons []string
|
|
expectedDeleted []string
|
|
expectedNotDeleted []string
|
|
}{
|
|
{
|
|
name: "Delete service user successfully ",
|
|
userIDs: []string{"user2"},
|
|
expectedDeleted: []string{"user2"},
|
|
},
|
|
{
|
|
name: "Delete regular user successfully",
|
|
userIDs: []string{"user3"},
|
|
expectedDeleted: []string{"user3"},
|
|
},
|
|
{
|
|
name: "Delete integration regular user permission denied",
|
|
userIDs: []string{"user4"},
|
|
expectedReasons: []string{"only integration service user can delete this user"},
|
|
expectedNotDeleted: []string{"user4"},
|
|
},
|
|
{
|
|
name: "Delete user with owner role should return permission denied",
|
|
userIDs: []string{"user5"},
|
|
expectedReasons: []string{"unable to delete a user: user5 with owner role"},
|
|
expectedNotDeleted: []string{"user5"},
|
|
},
|
|
{
|
|
name: "Delete multiple users with mixed results",
|
|
userIDs: []string{"user5", "user5", "user6", "user7"},
|
|
expectedReasons: []string{"only integration service user can delete this user", "unable to delete a user: user5 with owner role"},
|
|
expectedDeleted: []string{"user6", "user7"},
|
|
expectedNotDeleted: []string{"user4", "user5"},
|
|
},
|
|
{
|
|
name: "Delete non-existent user",
|
|
userIDs: []string{"non-existent-user"},
|
|
expectedReasons: []string{"user: non-existent-user not found"},
|
|
expectedNotDeleted: []string{},
|
|
},
|
|
{
|
|
name: "Delete multiple regular users successfully",
|
|
userIDs: []string{"user8", "user9"},
|
|
expectedDeleted: []string{"user8", "user9"},
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
userInfos, err := am.BuildUserInfosForAccount(context.Background(), mockAccountID, mockUserID, maps.Values(account.Users))
|
|
assert.NoError(t, err)
|
|
|
|
err = am.DeleteRegularUsers(context.Background(), mockAccountID, mockUserID, tc.userIDs, userInfos)
|
|
if len(tc.expectedReasons) > 0 {
|
|
assert.Error(t, err)
|
|
var foundExpectedErrors int
|
|
|
|
wrappedErr, ok := err.(interface{ Unwrap() []error })
|
|
assert.Equal(t, ok, true)
|
|
|
|
for _, e := range wrappedErr.Unwrap() {
|
|
assert.Contains(t, tc.expectedReasons, e.Error(), "unexpected error message")
|
|
foundExpectedErrors++
|
|
}
|
|
|
|
assert.Equal(t, len(tc.expectedReasons), foundExpectedErrors, "not all expected errors were found")
|
|
} else {
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
acc, err := am.Store.GetAccount(context.Background(), account.Id)
|
|
assert.NoError(t, err)
|
|
|
|
for _, id := range tc.expectedDeleted {
|
|
_, exists := acc.Users[id]
|
|
assert.False(t, exists, "user should have been deleted: %s", id)
|
|
}
|
|
|
|
for _, id := range tc.expectedNotDeleted {
|
|
user, exists := acc.Users[id]
|
|
assert.True(t, exists, "user should not have been deleted: %s", id)
|
|
assert.NotNil(t, user, "user should exist: %s", id)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestDefaultAccountManager_GetUser(t *testing.T) {
|
|
store, cleanup, err := store.NewTestStoreFromSQL(context.Background(), "", t.TempDir())
|
|
if err != nil {
|
|
t.Fatalf("Error when creating store: %s", err)
|
|
}
|
|
t.Cleanup(cleanup)
|
|
|
|
account := newAccountWithId(context.Background(), mockAccountID, mockUserID, "", "", "", false)
|
|
|
|
err = store.SaveAccount(context.Background(), account)
|
|
if err != nil {
|
|
t.Fatalf("Error when saving account: %s", err)
|
|
}
|
|
|
|
permissionsManager := permissions.NewManager(store)
|
|
am := DefaultAccountManager{
|
|
Store: store,
|
|
eventStore: &activity.InMemoryEventStore{},
|
|
permissionsManager: permissionsManager,
|
|
}
|
|
|
|
claims := auth.UserAuth{
|
|
UserId: mockUserID,
|
|
AccountId: mockAccountID,
|
|
}
|
|
|
|
user, err := am.GetUserFromUserAuth(context.Background(), claims)
|
|
if err != nil {
|
|
t.Fatalf("Error when checking user role: %s", err)
|
|
}
|
|
|
|
assert.Equal(t, mockUserID, user.Id)
|
|
assert.True(t, user.HasAdminPower())
|
|
assert.False(t, user.IsBlocked())
|
|
}
|
|
|
|
func TestDefaultAccountManager_ListUsers(t *testing.T) {
|
|
store, cleanup, err := store.NewTestStoreFromSQL(context.Background(), "", t.TempDir())
|
|
if err != nil {
|
|
t.Fatalf("Error when creating store: %s", err)
|
|
}
|
|
t.Cleanup(cleanup)
|
|
|
|
account := newAccountWithId(context.Background(), mockAccountID, mockUserID, "", "", "", false)
|
|
account.Users["normal_user1"] = types.NewRegularUser("normal_user1", "", "")
|
|
account.Users["normal_user2"] = types.NewRegularUser("normal_user2", "", "")
|
|
|
|
err = store.SaveAccount(context.Background(), account)
|
|
if err != nil {
|
|
t.Fatalf("Error when saving account: %s", err)
|
|
}
|
|
|
|
permissionsManager := permissions.NewManager(store)
|
|
am := DefaultAccountManager{
|
|
Store: store,
|
|
eventStore: &activity.InMemoryEventStore{},
|
|
permissionsManager: permissionsManager,
|
|
}
|
|
|
|
users, err := am.ListUsers(context.Background(), mockAccountID)
|
|
if err != nil {
|
|
t.Fatalf("Error when checking user role: %s", err)
|
|
}
|
|
|
|
admins := 0
|
|
regular := 0
|
|
for _, user := range users {
|
|
if user.HasAdminPower() {
|
|
admins++
|
|
continue
|
|
}
|
|
regular++
|
|
}
|
|
assert.Equal(t, 3, len(users))
|
|
assert.Equal(t, 1, admins)
|
|
assert.Equal(t, 2, regular)
|
|
}
|
|
|
|
func TestDefaultAccountManager_ExternalCache(t *testing.T) {
|
|
store, cleanup, err := store.NewTestStoreFromSQL(context.Background(), "", t.TempDir())
|
|
if err != nil {
|
|
t.Fatalf("Error when creating store: %s", err)
|
|
}
|
|
t.Cleanup(cleanup)
|
|
|
|
account := newAccountWithId(context.Background(), mockAccountID, mockUserID, "", "", "", false)
|
|
externalUser := &types.User{
|
|
Id: "externalUser",
|
|
Role: types.UserRoleUser,
|
|
Issued: types.UserIssuedIntegration,
|
|
IntegrationReference: integration_reference.IntegrationReference{
|
|
ID: 1,
|
|
IntegrationType: "external",
|
|
},
|
|
}
|
|
account.Users[externalUser.Id] = externalUser
|
|
|
|
err = store.SaveAccount(context.Background(), account)
|
|
if err != nil {
|
|
t.Fatalf("Error when saving account: %s", err)
|
|
}
|
|
|
|
permissionsManager := permissions.NewManager(store)
|
|
am := DefaultAccountManager{
|
|
Store: store,
|
|
eventStore: &activity.InMemoryEventStore{},
|
|
idpManager: &idp.GoogleWorkspaceManager{}, // empty manager
|
|
cacheLoading: map[string]chan struct{}{},
|
|
permissionsManager: permissionsManager,
|
|
}
|
|
|
|
cacheStore, err := nbcache.NewStore(context.Background(), nbcache.DefaultIDPCacheExpirationMax, nbcache.DefaultIDPCacheCleanupInterval, nbcache.DefaultIDPCacheOpenConn)
|
|
assert.NoError(t, err)
|
|
am.externalCacheManager = nbcache.NewUserDataCache(cacheStore)
|
|
am.cacheManager = nbcache.NewAccountUserDataCache(am.loadAccount, cacheStore)
|
|
// pretend that we receive mockUserID from IDP
|
|
err = am.cacheManager.Set(am.ctx, mockAccountID, []*idp.UserData{{Name: mockUserID, ID: mockUserID}}, time.Minute)
|
|
assert.NoError(t, err)
|
|
|
|
cacheManager := am.GetExternalCacheManager()
|
|
tud := &idp.UserData{ID: externalUser.Id, Name: "Test User", Email: "user@example.com"}
|
|
cacheKeyUser := externalUser.IntegrationReference.CacheKey(mockAccountID, externalUser.Id)
|
|
err = cacheManager.Set(context.Background(), cacheKeyUser, tud, time.Minute)
|
|
assert.NoError(t, err)
|
|
cacheKeyAccount := externalUser.IntegrationReference.CacheKey(mockAccountID)
|
|
err = cacheManager.SetUsers(context.Background(), cacheKeyAccount, []*idp.UserData{tud}, time.Minute)
|
|
assert.NoError(t, err)
|
|
|
|
infos, err := am.GetUsersFromAccount(context.Background(), mockAccountID, mockUserID)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, 2, len(infos))
|
|
var user *types.UserInfo
|
|
for _, info := range infos {
|
|
if info.ID == externalUser.Id {
|
|
user = info
|
|
}
|
|
}
|
|
assert.NotNil(t, user)
|
|
assert.Equal(t, "user@example.com", user.Email)
|
|
}
|
|
|
|
func TestUser_IsAdmin(t *testing.T) {
|
|
|
|
user := types.NewAdminUser(mockUserID)
|
|
assert.True(t, user.HasAdminPower())
|
|
|
|
user = types.NewRegularUser(mockUserID, "", "")
|
|
assert.False(t, user.HasAdminPower())
|
|
}
|
|
|
|
func TestUser_GetUsersFromAccount_ForAdmin(t *testing.T) {
|
|
store, cleanup, err := store.NewTestStoreFromSQL(context.Background(), "", t.TempDir())
|
|
if err != nil {
|
|
t.Fatalf("Error when creating store: %s", err)
|
|
}
|
|
t.Cleanup(cleanup)
|
|
|
|
account := newAccountWithId(context.Background(), mockAccountID, mockUserID, "", "", "", false)
|
|
account.Users[mockServiceUserID] = &types.User{
|
|
Id: mockServiceUserID,
|
|
Role: "user",
|
|
IsServiceUser: true,
|
|
}
|
|
|
|
err = store.SaveAccount(context.Background(), account)
|
|
if err != nil {
|
|
t.Fatalf("Error when saving account: %s", err)
|
|
}
|
|
|
|
permissionsManager := permissions.NewManager(store)
|
|
am := DefaultAccountManager{
|
|
Store: store,
|
|
eventStore: &activity.InMemoryEventStore{},
|
|
permissionsManager: permissionsManager,
|
|
}
|
|
|
|
users, err := am.GetUsersFromAccount(context.Background(), mockAccountID, mockUserID)
|
|
if err != nil {
|
|
t.Fatalf("Error when getting users from account: %s", err)
|
|
}
|
|
|
|
assert.Equal(t, 2, len(users))
|
|
}
|
|
|
|
func TestUser_GetUsersFromAccount_ForUser(t *testing.T) {
|
|
store, cleanup, err := store.NewTestStoreFromSQL(context.Background(), "", t.TempDir())
|
|
if err != nil {
|
|
t.Fatalf("Error when creating store: %s", err)
|
|
}
|
|
t.Cleanup(cleanup)
|
|
|
|
account := newAccountWithId(context.Background(), mockAccountID, mockUserID, "", "", "", false)
|
|
account.Users[mockServiceUserID] = &types.User{
|
|
Id: mockServiceUserID,
|
|
Role: "user",
|
|
IsServiceUser: true,
|
|
}
|
|
|
|
err = store.SaveAccount(context.Background(), account)
|
|
if err != nil {
|
|
t.Fatalf("Error when saving account: %s", err)
|
|
}
|
|
|
|
permissionsManager := permissions.NewManager(store)
|
|
am := DefaultAccountManager{
|
|
Store: store,
|
|
eventStore: &activity.InMemoryEventStore{},
|
|
permissionsManager: permissionsManager,
|
|
}
|
|
|
|
users, err := am.GetUsersFromAccount(context.Background(), mockAccountID, mockServiceUserID)
|
|
if err != nil {
|
|
t.Fatalf("Error when getting users from account: %s", err)
|
|
}
|
|
|
|
// Service users should see all users
|
|
assert.Equal(t, 2, len(users))
|
|
}
|
|
|
|
func TestDefaultAccountManager_SaveUser(t *testing.T) {
|
|
manager, _, err := createManager(t)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
return
|
|
}
|
|
|
|
regularUserID := "regularUser"
|
|
serviceUserID := "serviceUser"
|
|
adminUserID := "adminUser"
|
|
ownerUserID := "ownerUser"
|
|
|
|
tt := []struct {
|
|
name string
|
|
initiatorID string
|
|
update *types.User
|
|
expectedErr bool
|
|
}{
|
|
{
|
|
name: "Should_Fail_To_Update_Admin_Role",
|
|
expectedErr: true,
|
|
initiatorID: adminUserID,
|
|
update: &types.User{
|
|
Id: adminUserID,
|
|
Role: types.UserRoleUser,
|
|
Blocked: false,
|
|
},
|
|
}, {
|
|
name: "Should_Fail_When_Admin_Blocks_Themselves",
|
|
expectedErr: true,
|
|
initiatorID: adminUserID,
|
|
update: &types.User{
|
|
Id: adminUserID,
|
|
Role: types.UserRoleAdmin,
|
|
Blocked: true,
|
|
},
|
|
},
|
|
{
|
|
name: "Should_Fail_To_Update_Non_Existing_User",
|
|
expectedErr: true,
|
|
initiatorID: adminUserID,
|
|
update: &types.User{
|
|
Id: userID,
|
|
Role: types.UserRoleAdmin,
|
|
Blocked: true,
|
|
},
|
|
},
|
|
{
|
|
name: "Should_Fail_To_Update_When_Initiator_Is_Not_An_Admin",
|
|
expectedErr: true,
|
|
initiatorID: regularUserID,
|
|
update: &types.User{
|
|
Id: adminUserID,
|
|
Role: types.UserRoleAdmin,
|
|
Blocked: true,
|
|
},
|
|
},
|
|
{
|
|
name: "Should_Update_User",
|
|
expectedErr: false,
|
|
initiatorID: adminUserID,
|
|
update: &types.User{
|
|
Id: regularUserID,
|
|
Role: types.UserRoleAdmin,
|
|
Blocked: true,
|
|
},
|
|
},
|
|
{
|
|
name: "Should_Transfer_Owner_Role_To_User",
|
|
expectedErr: false,
|
|
initiatorID: ownerUserID,
|
|
update: &types.User{
|
|
Id: adminUserID,
|
|
Role: types.UserRoleAdmin,
|
|
Blocked: false,
|
|
},
|
|
},
|
|
{
|
|
name: "Should_Fail_To_Transfer_Owner_Role_To_Service_User",
|
|
expectedErr: true,
|
|
initiatorID: ownerUserID,
|
|
update: &types.User{
|
|
Id: serviceUserID,
|
|
Role: types.UserRoleOwner,
|
|
Blocked: false,
|
|
},
|
|
},
|
|
{
|
|
name: "Should_Fail_To_Update_Owner_User_Role_By_Admin",
|
|
expectedErr: true,
|
|
initiatorID: adminUserID,
|
|
update: &types.User{
|
|
Id: ownerUserID,
|
|
Role: types.UserRoleAdmin,
|
|
Blocked: false,
|
|
},
|
|
},
|
|
{
|
|
name: "Should_Fail_To_Update_Owner_User_Role_By_User",
|
|
expectedErr: true,
|
|
initiatorID: regularUserID,
|
|
update: &types.User{
|
|
Id: ownerUserID,
|
|
Role: types.UserRoleAdmin,
|
|
Blocked: false,
|
|
},
|
|
},
|
|
{
|
|
name: "Should_Fail_To_Update_Owner_User_Role_By_Service_User",
|
|
expectedErr: true,
|
|
initiatorID: serviceUserID,
|
|
update: &types.User{
|
|
Id: ownerUserID,
|
|
Role: types.UserRoleAdmin,
|
|
Blocked: false,
|
|
},
|
|
},
|
|
{
|
|
name: "Should_Fail_To_Update_Owner_Role_By_Admin",
|
|
expectedErr: true,
|
|
initiatorID: adminUserID,
|
|
update: &types.User{
|
|
Id: regularUserID,
|
|
Role: types.UserRoleOwner,
|
|
Blocked: false,
|
|
},
|
|
},
|
|
{
|
|
name: "Should_Fail_To_Block_Owner_Role_By_Admin",
|
|
expectedErr: true,
|
|
initiatorID: adminUserID,
|
|
update: &types.User{
|
|
Id: ownerUserID,
|
|
Role: types.UserRoleOwner,
|
|
Blocked: true,
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tc := range tt {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
|
|
// create an account and an admin user
|
|
account, err := manager.GetOrCreateAccountByUser(context.Background(), auth.UserAuth{UserId: ownerUserID, Domain: "netbird.io"})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// create other users
|
|
account.Users[regularUserID] = types.NewRegularUser(regularUserID, "", "")
|
|
account.Users[adminUserID] = types.NewAdminUser(adminUserID)
|
|
account.Users[serviceUserID] = &types.User{IsServiceUser: true, Id: serviceUserID, Role: types.UserRoleAdmin, ServiceUserName: "service"}
|
|
err = manager.Store.SaveAccount(context.Background(), account)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
updated, err := manager.SaveUser(context.Background(), account.Id, tc.initiatorID, tc.update)
|
|
if tc.expectedErr {
|
|
require.Errorf(t, err, "expecting SaveUser to throw an error")
|
|
} else {
|
|
require.NoError(t, err, "expecting SaveUser not to throw an error")
|
|
assert.NotNil(t, updated)
|
|
|
|
assert.Equal(t, string(tc.update.Role), updated.Role)
|
|
assert.Equal(t, tc.update.IsBlocked(), updated.IsBlocked)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestUserAccountPeersUpdate(t *testing.T) {
|
|
// account groups propagation is enabled
|
|
manager, updateManager, account, peer1, peer2, peer3 := setupNetworkMapTest(t)
|
|
|
|
err := manager.CreateGroup(context.Background(), account.Id, userID, &types.Group{
|
|
ID: "groupA",
|
|
Name: "GroupA",
|
|
Peers: []string{peer1.ID, peer2.ID, peer3.ID},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
policy := &types.Policy{
|
|
Enabled: true,
|
|
Rules: []*types.PolicyRule{
|
|
{
|
|
Enabled: true,
|
|
Sources: []string{"groupA"},
|
|
Destinations: []string{"groupA"},
|
|
Bidirectional: true,
|
|
Action: types.PolicyTrafficActionAccept,
|
|
},
|
|
},
|
|
}
|
|
_, err = manager.SavePolicy(context.Background(), account.Id, userID, policy, true)
|
|
require.NoError(t, err)
|
|
|
|
updMsg := updateManager.CreateChannel(context.Background(), peer1.ID)
|
|
t.Cleanup(func() {
|
|
updateManager.CloseChannel(context.Background(), peer1.ID)
|
|
})
|
|
|
|
// Creating a new regular user should send peer update (as users are not filtered yet)
|
|
t.Run("creating new regular user with no groups", func(t *testing.T) {
|
|
done := make(chan struct{})
|
|
go func() {
|
|
peerShouldReceiveUpdate(t, updMsg)
|
|
close(done)
|
|
}()
|
|
|
|
_, err = manager.SaveOrAddUser(context.Background(), account.Id, userID, &types.User{
|
|
Id: "regularUser1",
|
|
AccountID: account.Id,
|
|
Role: types.UserRoleUser,
|
|
Issued: types.UserIssuedAPI,
|
|
}, true)
|
|
require.NoError(t, err)
|
|
|
|
select {
|
|
case <-done:
|
|
case <-time.After(time.Second):
|
|
t.Error("timeout waiting for peerShouldNotReceiveUpdate")
|
|
}
|
|
})
|
|
|
|
// updating user with no linked peers should update account peers and send peer update (as users are not filtered yet)
|
|
t.Run("updating user with no linked peers", func(t *testing.T) {
|
|
done := make(chan struct{})
|
|
go func() {
|
|
peerShouldReceiveUpdate(t, updMsg)
|
|
close(done)
|
|
}()
|
|
|
|
_, err = manager.SaveOrAddUser(context.Background(), account.Id, userID, &types.User{
|
|
Id: "regularUser1",
|
|
AccountID: account.Id,
|
|
Role: types.UserRoleUser,
|
|
Issued: types.UserIssuedAPI,
|
|
}, false)
|
|
require.NoError(t, err)
|
|
|
|
select {
|
|
case <-done:
|
|
case <-time.After(time.Second):
|
|
t.Error("timeout waiting for peerShouldNotReceiveUpdate")
|
|
}
|
|
})
|
|
|
|
// deleting user with no linked peers should not update account peers and not send peer update
|
|
t.Run("deleting user with no linked peers", func(t *testing.T) {
|
|
done := make(chan struct{})
|
|
go func() {
|
|
peerShouldReceiveUpdate(t, updMsg)
|
|
close(done)
|
|
}()
|
|
|
|
err = manager.DeleteUser(context.Background(), account.Id, userID, "regularUser1")
|
|
require.NoError(t, err)
|
|
|
|
select {
|
|
case <-done:
|
|
case <-time.After(time.Second):
|
|
t.Error("timeout waiting for peerShouldNotReceiveUpdate")
|
|
}
|
|
})
|
|
|
|
// create a user and add new peer with the user
|
|
_, err = manager.SaveOrAddUser(context.Background(), account.Id, userID, &types.User{
|
|
Id: "regularUser2",
|
|
AccountID: account.Id,
|
|
Role: types.UserRoleAdmin,
|
|
Issued: types.UserIssuedAPI,
|
|
}, true)
|
|
require.NoError(t, err)
|
|
|
|
key, err := wgtypes.GeneratePrivateKey()
|
|
require.NoError(t, err)
|
|
|
|
expectedPeerKey := key.PublicKey().String()
|
|
peer4, _, _, err := manager.AddPeer(context.Background(), "", "", "regularUser2", &nbpeer.Peer{
|
|
Key: expectedPeerKey,
|
|
Meta: nbpeer.PeerSystemMeta{Hostname: expectedPeerKey},
|
|
}, false)
|
|
require.NoError(t, err)
|
|
|
|
// updating user with linked peers should update account peers and send peer update
|
|
t.Run("updating user with linked peers", func(t *testing.T) {
|
|
done := make(chan struct{})
|
|
go func() {
|
|
peerShouldReceiveUpdate(t, updMsg)
|
|
close(done)
|
|
}()
|
|
|
|
_, err = manager.SaveOrAddUser(context.Background(), account.Id, userID, &types.User{
|
|
Id: "regularUser2",
|
|
AccountID: account.Id,
|
|
Role: types.UserRoleAdmin,
|
|
Issued: types.UserIssuedAPI,
|
|
}, false)
|
|
require.NoError(t, err)
|
|
|
|
select {
|
|
case <-done:
|
|
case <-time.After(peerUpdateTimeout):
|
|
t.Error("timeout waiting for peerShouldReceiveUpdate")
|
|
}
|
|
})
|
|
|
|
peer4UpdMsg := updateManager.CreateChannel(context.Background(), peer4.ID)
|
|
t.Cleanup(func() {
|
|
updateManager.CloseChannel(context.Background(), peer4.ID)
|
|
})
|
|
|
|
// deleting user with linked peers should update account peers and send peer update
|
|
t.Run("deleting user with linked peers", func(t *testing.T) {
|
|
done := make(chan struct{})
|
|
go func() {
|
|
peerShouldReceiveUpdate(t, peer4UpdMsg)
|
|
close(done)
|
|
}()
|
|
|
|
err = manager.DeleteUser(context.Background(), account.Id, userID, "regularUser2")
|
|
require.NoError(t, err)
|
|
|
|
select {
|
|
case <-done:
|
|
case <-time.After(peerUpdateTimeout):
|
|
t.Error("timeout waiting for peerShouldReceiveUpdate")
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestSaveOrAddUser_PreventAccountSwitch(t *testing.T) {
|
|
s, cleanup, err := store.NewTestStoreFromSQL(context.Background(), "", t.TempDir())
|
|
if err != nil {
|
|
t.Fatalf("Error when creating store: %s", err)
|
|
}
|
|
t.Cleanup(cleanup)
|
|
|
|
account1 := newAccountWithId(context.Background(), "account1", "ownerAccount1", "", "", "", false)
|
|
targetId := "user2"
|
|
account1.Users[targetId] = &types.User{
|
|
Id: targetId,
|
|
AccountID: account1.Id,
|
|
ServiceUserName: "user2username",
|
|
}
|
|
require.NoError(t, s.SaveAccount(context.Background(), account1))
|
|
|
|
account2 := newAccountWithId(context.Background(), "account2", "ownerAccount2", "", "", "", false)
|
|
require.NoError(t, s.SaveAccount(context.Background(), account2))
|
|
|
|
permissionsManager := permissions.NewManager(s)
|
|
am := DefaultAccountManager{
|
|
Store: s,
|
|
eventStore: &activity.InMemoryEventStore{},
|
|
permissionsManager: permissionsManager,
|
|
}
|
|
|
|
_, err = am.SaveOrAddUser(context.Background(), "account2", "ownerAccount2", account1.Users[targetId], true)
|
|
assert.Error(t, err, "update user to another account should fail")
|
|
|
|
user, err := s.GetUserByUserID(context.Background(), store.LockingStrengthNone, targetId)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, account1.Users[targetId].Id, user.Id)
|
|
assert.Equal(t, account1.Users[targetId].AccountID, user.AccountID)
|
|
assert.Equal(t, account1.Users[targetId].AutoGroups, user.AutoGroups)
|
|
}
|
|
|
|
func TestDefaultAccountManager_GetCurrentUserInfo(t *testing.T) {
|
|
store, cleanup, err := store.NewTestStoreFromSQL(context.Background(), "", t.TempDir())
|
|
if err != nil {
|
|
t.Fatalf("Error when creating store: %s", err)
|
|
}
|
|
t.Cleanup(cleanup)
|
|
|
|
account1 := newAccountWithId(context.Background(), "account1", "account1Owner", "", "", "", false)
|
|
account1.Settings.RegularUsersViewBlocked = false
|
|
account1.Users["blocked-user"] = &types.User{
|
|
Id: "blocked-user",
|
|
AccountID: account1.Id,
|
|
Blocked: true,
|
|
}
|
|
account1.Users["service-user"] = &types.User{
|
|
Id: "service-user",
|
|
IsServiceUser: true,
|
|
ServiceUserName: "service-user",
|
|
}
|
|
account1.Users["regular-user"] = &types.User{
|
|
Id: "regular-user",
|
|
Role: types.UserRoleUser,
|
|
}
|
|
account1.Users["admin-user"] = &types.User{
|
|
Id: "admin-user",
|
|
Role: types.UserRoleAdmin,
|
|
}
|
|
require.NoError(t, store.SaveAccount(context.Background(), account1))
|
|
|
|
account2 := newAccountWithId(context.Background(), "account2", "account2Owner", "", "", "", false)
|
|
account2.Users["settings-blocked-user"] = &types.User{
|
|
Id: "settings-blocked-user",
|
|
Role: types.UserRoleUser,
|
|
}
|
|
require.NoError(t, store.SaveAccount(context.Background(), account2))
|
|
|
|
permissionsManager := permissions.NewManager(store)
|
|
am := DefaultAccountManager{
|
|
Store: store,
|
|
eventStore: &activity.InMemoryEventStore{},
|
|
permissionsManager: permissionsManager,
|
|
}
|
|
|
|
tt := []struct {
|
|
name string
|
|
userAuth auth.UserAuth
|
|
expectedErr error
|
|
expectedResult *users.UserInfoWithPermissions
|
|
}{
|
|
{
|
|
name: "not found",
|
|
userAuth: auth.UserAuth{AccountId: account1.Id, UserId: "not-found"},
|
|
expectedErr: status.NewUserNotFoundError("not-found"),
|
|
},
|
|
{
|
|
name: "not part of account",
|
|
userAuth: auth.UserAuth{AccountId: account1.Id, UserId: "account2Owner"},
|
|
expectedErr: status.NewUserNotPartOfAccountError(),
|
|
},
|
|
{
|
|
name: "blocked",
|
|
userAuth: auth.UserAuth{AccountId: account1.Id, UserId: "blocked-user"},
|
|
expectedErr: status.NewUserBlockedError(),
|
|
},
|
|
{
|
|
name: "service user",
|
|
userAuth: auth.UserAuth{AccountId: account1.Id, UserId: "service-user"},
|
|
expectedErr: status.NewPermissionDeniedError(),
|
|
},
|
|
{
|
|
name: "owner user",
|
|
userAuth: auth.UserAuth{AccountId: account1.Id, UserId: "account1Owner"},
|
|
expectedResult: &users.UserInfoWithPermissions{
|
|
UserInfo: &types.UserInfo{
|
|
ID: "account1Owner",
|
|
Name: "",
|
|
Role: "owner",
|
|
AutoGroups: []string{},
|
|
Status: "active",
|
|
IsServiceUser: false,
|
|
IsBlocked: false,
|
|
NonDeletable: false,
|
|
LastLogin: time.Time{},
|
|
Issued: "api",
|
|
IntegrationReference: integration_reference.IntegrationReference{},
|
|
},
|
|
Permissions: mergeRolePermissions(roles.Owner),
|
|
},
|
|
},
|
|
{
|
|
name: "regular user",
|
|
userAuth: auth.UserAuth{AccountId: account1.Id, UserId: "regular-user"},
|
|
expectedResult: &users.UserInfoWithPermissions{
|
|
UserInfo: &types.UserInfo{
|
|
ID: "regular-user",
|
|
Name: "",
|
|
Role: "user",
|
|
Status: "active",
|
|
IsServiceUser: false,
|
|
IsBlocked: false,
|
|
NonDeletable: false,
|
|
LastLogin: time.Time{},
|
|
Issued: "api",
|
|
IntegrationReference: integration_reference.IntegrationReference{},
|
|
},
|
|
Permissions: mergeRolePermissions(roles.User),
|
|
},
|
|
},
|
|
{
|
|
name: "admin user",
|
|
userAuth: auth.UserAuth{AccountId: account1.Id, UserId: "admin-user"},
|
|
expectedResult: &users.UserInfoWithPermissions{
|
|
UserInfo: &types.UserInfo{
|
|
ID: "admin-user",
|
|
Name: "",
|
|
Role: "admin",
|
|
Status: "active",
|
|
IsServiceUser: false,
|
|
IsBlocked: false,
|
|
NonDeletable: false,
|
|
LastLogin: time.Time{},
|
|
Issued: "api",
|
|
IntegrationReference: integration_reference.IntegrationReference{},
|
|
},
|
|
Permissions: mergeRolePermissions(roles.Admin),
|
|
},
|
|
},
|
|
{
|
|
name: "settings blocked regular user",
|
|
userAuth: auth.UserAuth{AccountId: account2.Id, UserId: "settings-blocked-user"},
|
|
expectedResult: &users.UserInfoWithPermissions{
|
|
UserInfo: &types.UserInfo{
|
|
ID: "settings-blocked-user",
|
|
Name: "",
|
|
Role: "user",
|
|
Status: "active",
|
|
IsServiceUser: false,
|
|
IsBlocked: false,
|
|
NonDeletable: false,
|
|
LastLogin: time.Time{},
|
|
Issued: "api",
|
|
IntegrationReference: integration_reference.IntegrationReference{},
|
|
},
|
|
Permissions: mergeRolePermissions(roles.User),
|
|
Restricted: true,
|
|
},
|
|
},
|
|
|
|
{
|
|
name: "settings blocked regular user child account",
|
|
userAuth: auth.UserAuth{AccountId: account2.Id, UserId: "settings-blocked-user", IsChild: true},
|
|
expectedResult: &users.UserInfoWithPermissions{
|
|
UserInfo: &types.UserInfo{
|
|
ID: "settings-blocked-user",
|
|
Name: "",
|
|
Role: "user",
|
|
Status: "active",
|
|
IsServiceUser: false,
|
|
IsBlocked: false,
|
|
NonDeletable: false,
|
|
LastLogin: time.Time{},
|
|
Issued: "api",
|
|
IntegrationReference: integration_reference.IntegrationReference{},
|
|
},
|
|
Permissions: mergeRolePermissions(roles.User),
|
|
Restricted: false,
|
|
},
|
|
},
|
|
{
|
|
name: "settings blocked owner user",
|
|
userAuth: auth.UserAuth{AccountId: account2.Id, UserId: "account2Owner"},
|
|
expectedResult: &users.UserInfoWithPermissions{
|
|
UserInfo: &types.UserInfo{
|
|
ID: "account2Owner",
|
|
Name: "",
|
|
Role: "owner",
|
|
AutoGroups: []string{},
|
|
Status: "active",
|
|
IsServiceUser: false,
|
|
IsBlocked: false,
|
|
NonDeletable: false,
|
|
LastLogin: time.Time{},
|
|
Issued: "api",
|
|
IntegrationReference: integration_reference.IntegrationReference{},
|
|
},
|
|
Permissions: mergeRolePermissions(roles.Owner),
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tc := range tt {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
result, err := am.GetCurrentUserInfo(context.Background(), tc.userAuth)
|
|
|
|
if tc.expectedErr != nil {
|
|
assert.Equal(t, err, tc.expectedErr)
|
|
return
|
|
}
|
|
|
|
require.NoError(t, err)
|
|
assert.EqualValues(t, tc.expectedResult, result)
|
|
})
|
|
}
|
|
}
|
|
|
|
func mergeRolePermissions(role roles.RolePermissions) roles.Permissions {
|
|
permissions := roles.Permissions{}
|
|
|
|
for k := range modules.All {
|
|
if rolePermissions, ok := role.Permissions[k]; ok {
|
|
permissions[k] = rolePermissions
|
|
continue
|
|
}
|
|
permissions[k] = role.AutoAllowNew
|
|
}
|
|
|
|
return permissions
|
|
}
|
|
|
|
func TestApproveUser(t *testing.T) {
|
|
manager, _, err := createManager(t)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Create account with admin and pending approval user
|
|
account := newAccountWithId(context.Background(), "account-1", "admin-user", "example.com", "", "", false)
|
|
err = manager.Store.SaveAccount(context.Background(), account)
|
|
require.NoError(t, err)
|
|
|
|
// Create admin user
|
|
adminUser := types.NewAdminUser("admin-user")
|
|
adminUser.AccountID = account.Id
|
|
err = manager.Store.SaveUser(context.Background(), adminUser)
|
|
require.NoError(t, err)
|
|
|
|
// Create user pending approval
|
|
pendingUser := types.NewRegularUser("pending-user", "", "")
|
|
pendingUser.AccountID = account.Id
|
|
pendingUser.Blocked = true
|
|
pendingUser.PendingApproval = true
|
|
err = manager.Store.SaveUser(context.Background(), pendingUser)
|
|
require.NoError(t, err)
|
|
|
|
// Test successful approval
|
|
approvedUser, err := manager.ApproveUser(context.Background(), account.Id, adminUser.Id, pendingUser.Id)
|
|
require.NoError(t, err)
|
|
assert.False(t, approvedUser.IsBlocked)
|
|
assert.False(t, approvedUser.PendingApproval)
|
|
|
|
// Verify user is updated in store
|
|
updatedUser, err := manager.Store.GetUserByUserID(context.Background(), store.LockingStrengthNone, pendingUser.Id)
|
|
require.NoError(t, err)
|
|
assert.False(t, updatedUser.Blocked)
|
|
assert.False(t, updatedUser.PendingApproval)
|
|
|
|
// Test approval of non-pending user should fail
|
|
_, err = manager.ApproveUser(context.Background(), account.Id, adminUser.Id, pendingUser.Id)
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), "not pending approval")
|
|
|
|
// Test approval by non-admin should fail
|
|
regularUser := types.NewRegularUser("regular-user", "", "")
|
|
regularUser.AccountID = account.Id
|
|
err = manager.Store.SaveUser(context.Background(), regularUser)
|
|
require.NoError(t, err)
|
|
|
|
pendingUser2 := types.NewRegularUser("pending-user-2", "", "")
|
|
pendingUser2.AccountID = account.Id
|
|
pendingUser2.Blocked = true
|
|
pendingUser2.PendingApproval = true
|
|
err = manager.Store.SaveUser(context.Background(), pendingUser2)
|
|
require.NoError(t, err)
|
|
|
|
_, err = manager.ApproveUser(context.Background(), account.Id, regularUser.Id, pendingUser2.Id)
|
|
require.Error(t, err)
|
|
}
|
|
|
|
func TestRejectUser(t *testing.T) {
|
|
manager, _, err := createManager(t)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Create account with admin and pending approval user
|
|
account := newAccountWithId(context.Background(), "account-1", "admin-user", "example.com", "", "", false)
|
|
err = manager.Store.SaveAccount(context.Background(), account)
|
|
require.NoError(t, err)
|
|
|
|
// Create admin user
|
|
adminUser := types.NewAdminUser("admin-user")
|
|
adminUser.AccountID = account.Id
|
|
err = manager.Store.SaveUser(context.Background(), adminUser)
|
|
require.NoError(t, err)
|
|
|
|
// Create user pending approval
|
|
pendingUser := types.NewRegularUser("pending-user", "", "")
|
|
pendingUser.AccountID = account.Id
|
|
pendingUser.Blocked = true
|
|
pendingUser.PendingApproval = true
|
|
err = manager.Store.SaveUser(context.Background(), pendingUser)
|
|
require.NoError(t, err)
|
|
|
|
// Test successful rejection
|
|
err = manager.RejectUser(context.Background(), account.Id, adminUser.Id, pendingUser.Id)
|
|
require.NoError(t, err)
|
|
|
|
// Verify user is deleted from store
|
|
_, err = manager.Store.GetUserByUserID(context.Background(), store.LockingStrengthNone, pendingUser.Id)
|
|
require.Error(t, err)
|
|
|
|
// Test rejection of non-pending user should fail
|
|
regularUser := types.NewRegularUser("regular-user", "", "")
|
|
regularUser.AccountID = account.Id
|
|
err = manager.Store.SaveUser(context.Background(), regularUser)
|
|
require.NoError(t, err)
|
|
|
|
err = manager.RejectUser(context.Background(), account.Id, adminUser.Id, regularUser.Id)
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), "not pending approval")
|
|
|
|
// Test rejection by non-admin should fail
|
|
pendingUser2 := types.NewRegularUser("pending-user-2", "", "")
|
|
pendingUser2.AccountID = account.Id
|
|
pendingUser2.Blocked = true
|
|
pendingUser2.PendingApproval = true
|
|
err = manager.Store.SaveUser(context.Background(), pendingUser2)
|
|
require.NoError(t, err)
|
|
|
|
err = manager.RejectUser(context.Background(), account.Id, regularUser.Id, pendingUser2.Id)
|
|
require.Error(t, err)
|
|
}
|
|
|
|
func TestUser_Operations_WithEmbeddedIDP(t *testing.T) {
|
|
ctx := context.Background()
|
|
|
|
// Create temporary directory for Dex
|
|
tmpDir := t.TempDir()
|
|
dexDataDir := tmpDir + "/dex"
|
|
require.NoError(t, os.MkdirAll(dexDataDir, 0700))
|
|
|
|
// Create embedded IDP config
|
|
embeddedIdPConfig := &idp.EmbeddedIdPConfig{
|
|
Enabled: true,
|
|
Issuer: "http://localhost:5556/dex",
|
|
Storage: idp.EmbeddedStorageConfig{
|
|
Type: "sqlite3",
|
|
Config: idp.EmbeddedStorageTypeConfig{
|
|
File: dexDataDir + "/dex.db",
|
|
},
|
|
},
|
|
}
|
|
|
|
// Create embedded IDP manager
|
|
embeddedIdp, err := idp.NewEmbeddedIdPManager(ctx, embeddedIdPConfig, nil)
|
|
require.NoError(t, err)
|
|
defer func() { _ = embeddedIdp.Stop(ctx) }()
|
|
|
|
// Create test store
|
|
testStore, cleanup, err := store.NewTestStoreFromSQL(ctx, "", tmpDir)
|
|
require.NoError(t, err)
|
|
defer cleanup()
|
|
|
|
// Create account with owner user
|
|
account := newAccountWithId(ctx, mockAccountID, mockUserID, "", "owner@test.com", "Owner User", false)
|
|
require.NoError(t, testStore.SaveAccount(ctx, account))
|
|
|
|
// Create mock network map controller
|
|
ctrl := gomock.NewController(t)
|
|
networkMapControllerMock := network_map.NewMockController(ctrl)
|
|
networkMapControllerMock.EXPECT().
|
|
OnPeersDeleted(gomock.Any(), gomock.Any(), gomock.Any()).
|
|
Return(nil).
|
|
AnyTimes()
|
|
|
|
// Create account manager with embedded IDP
|
|
permissionsManager := permissions.NewManager(testStore)
|
|
am := DefaultAccountManager{
|
|
Store: testStore,
|
|
eventStore: &activity.InMemoryEventStore{},
|
|
permissionsManager: permissionsManager,
|
|
idpManager: embeddedIdp,
|
|
cacheLoading: map[string]chan struct{}{},
|
|
networkMapController: networkMapControllerMock,
|
|
}
|
|
|
|
// Initialize cache manager
|
|
cacheStore, err := nbcache.NewStore(ctx, nbcache.DefaultIDPCacheExpirationMax, nbcache.DefaultIDPCacheCleanupInterval, nbcache.DefaultIDPCacheOpenConn)
|
|
require.NoError(t, err)
|
|
am.cacheManager = nbcache.NewAccountUserDataCache(am.loadAccount, cacheStore)
|
|
am.externalCacheManager = nbcache.NewUserDataCache(cacheStore)
|
|
|
|
t.Run("create regular user returns password", func(t *testing.T) {
|
|
userInfo, err := am.CreateUser(ctx, mockAccountID, mockUserID, &types.UserInfo{
|
|
Email: "newuser@test.com",
|
|
Name: "New User",
|
|
Role: "user",
|
|
AutoGroups: []string{},
|
|
IsServiceUser: false,
|
|
})
|
|
require.NoError(t, err)
|
|
require.NotNil(t, userInfo)
|
|
|
|
// Verify user data
|
|
assert.Equal(t, "newuser@test.com", userInfo.Email)
|
|
assert.Equal(t, "New User", userInfo.Name)
|
|
assert.Equal(t, "user", userInfo.Role)
|
|
assert.NotEmpty(t, userInfo.ID)
|
|
|
|
// IMPORTANT: Password should be returned for embedded IDP
|
|
assert.NotEmpty(t, userInfo.Password, "Password should be returned for embedded IDP user")
|
|
t.Logf("Created user: ID=%s, Email=%s, Password=%s", userInfo.ID, userInfo.Email, userInfo.Password)
|
|
|
|
// Verify user ID is in Dex encoded format
|
|
rawUserID, connectorID, err := dex.DecodeDexUserID(userInfo.ID)
|
|
require.NoError(t, err)
|
|
assert.NotEmpty(t, rawUserID)
|
|
assert.Equal(t, "local", connectorID)
|
|
t.Logf("Decoded user ID: rawUserID=%s, connectorID=%s", rawUserID, connectorID)
|
|
|
|
// Verify user exists in database with correct data
|
|
dbUser, err := testStore.GetUserByUserID(ctx, store.LockingStrengthNone, userInfo.ID)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "newuser@test.com", dbUser.Email)
|
|
assert.Equal(t, "New User", dbUser.Name)
|
|
|
|
// Store user ID for delete test
|
|
createdUserID := userInfo.ID
|
|
|
|
t.Run("delete user works", func(t *testing.T) {
|
|
err := am.DeleteUser(ctx, mockAccountID, mockUserID, createdUserID)
|
|
require.NoError(t, err)
|
|
|
|
// Verify user is deleted from database
|
|
_, err = testStore.GetUserByUserID(ctx, store.LockingStrengthNone, createdUserID)
|
|
assert.Error(t, err, "User should be deleted from database")
|
|
})
|
|
})
|
|
|
|
t.Run("create service user does not return password", func(t *testing.T) {
|
|
userInfo, err := am.CreateUser(ctx, mockAccountID, mockUserID, &types.UserInfo{
|
|
Name: "Service User",
|
|
Role: "user",
|
|
AutoGroups: []string{},
|
|
IsServiceUser: true,
|
|
})
|
|
require.NoError(t, err)
|
|
require.NotNil(t, userInfo)
|
|
|
|
assert.True(t, userInfo.IsServiceUser)
|
|
assert.Equal(t, "Service User", userInfo.Name)
|
|
// Service users don't have passwords
|
|
assert.Empty(t, userInfo.Password, "Service users should not have passwords")
|
|
})
|
|
|
|
t.Run("duplicate email fails", func(t *testing.T) {
|
|
// Create first user
|
|
_, err := am.CreateUser(ctx, mockAccountID, mockUserID, &types.UserInfo{
|
|
Email: "duplicate@test.com",
|
|
Name: "First User",
|
|
Role: "user",
|
|
AutoGroups: []string{},
|
|
IsServiceUser: false,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// Try to create second user with same email
|
|
_, err = am.CreateUser(ctx, mockAccountID, mockUserID, &types.UserInfo{
|
|
Email: "duplicate@test.com",
|
|
Name: "Second User",
|
|
Role: "user",
|
|
AutoGroups: []string{},
|
|
IsServiceUser: false,
|
|
})
|
|
assert.Error(t, err, "Creating user with duplicate email should fail")
|
|
t.Logf("Duplicate email error: %v", err)
|
|
})
|
|
}
|
|
|
|
func TestProcessUserUpdate_RejectsStaleInitiatorRole(t *testing.T) {
|
|
s, cleanup, err := store.NewTestStoreFromSQL(context.Background(), "", t.TempDir())
|
|
require.NoError(t, err)
|
|
t.Cleanup(cleanup)
|
|
|
|
account := newAccountWithId(context.Background(), "account1", "owner1", "", "", "", false)
|
|
|
|
adminID := "admin1"
|
|
account.Users[adminID] = types.NewAdminUser(adminID)
|
|
|
|
targetID := "target1"
|
|
account.Users[targetID] = types.NewRegularUser(targetID, "", "")
|
|
|
|
require.NoError(t, s.SaveAccount(context.Background(), account))
|
|
|
|
demotedAdmin, err := s.GetUserByUserID(context.Background(), store.LockingStrengthNone, adminID)
|
|
require.NoError(t, err)
|
|
demotedAdmin.Role = types.UserRoleUser
|
|
require.NoError(t, s.SaveUser(context.Background(), demotedAdmin))
|
|
|
|
staleInitiator := &types.User{
|
|
Id: adminID,
|
|
AccountID: account.Id,
|
|
Role: types.UserRoleAdmin,
|
|
}
|
|
|
|
permissionsManager := permissions.NewManager(s)
|
|
am := DefaultAccountManager{
|
|
Store: s,
|
|
eventStore: &activity.InMemoryEventStore{},
|
|
permissionsManager: permissionsManager,
|
|
}
|
|
|
|
settings, err := s.GetAccountSettings(context.Background(), store.LockingStrengthNone, account.Id)
|
|
require.NoError(t, err)
|
|
|
|
groups, err := s.GetAccountGroups(context.Background(), store.LockingStrengthNone, account.Id)
|
|
require.NoError(t, err)
|
|
groupsMap := make(map[string]*types.Group, len(groups))
|
|
for _, g := range groups {
|
|
groupsMap[g.ID] = g
|
|
}
|
|
|
|
update := &types.User{
|
|
Id: targetID,
|
|
Role: types.UserRoleAdmin,
|
|
}
|
|
|
|
err = s.ExecuteInTransaction(context.Background(), func(tx store.Store) error {
|
|
_, _, _, _, txErr := am.processUserUpdate(
|
|
context.Background(), tx, groupsMap, account.Id, adminID, staleInitiator, update, false, settings,
|
|
)
|
|
return txErr
|
|
})
|
|
|
|
require.Error(t, err, "processUserUpdate should reject stale initiator whose role was demoted")
|
|
assert.Contains(t, err.Error(), "initiator role was changed during request processing")
|
|
|
|
targetUser, err := s.GetUserByUserID(context.Background(), store.LockingStrengthNone, targetID)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, types.UserRoleUser, targetUser.Role)
|
|
}
|