mirror of
https://github.com/netbirdio/netbird.git
synced 2026-04-22 02:06:39 +00:00
[management] fix possible race condition on user role change (#5395)
This commit is contained in:
@@ -737,6 +737,14 @@ func (am *DefaultAccountManager) processUserUpdate(ctx context.Context, transact
|
|||||||
return false, nil, nil, nil, status.Errorf(status.InvalidArgument, "provided user update is nil")
|
return false, nil, nil, nil, status.Errorf(status.InvalidArgument, "provided user update is nil")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if initiatorUserId != activity.SystemInitiator {
|
||||||
|
freshInitiator, err := transaction.GetUserByUserID(ctx, store.LockingStrengthUpdate, initiatorUserId)
|
||||||
|
if err != nil {
|
||||||
|
return false, nil, nil, nil, fmt.Errorf("failed to re-read initiator user in transaction: %w", err)
|
||||||
|
}
|
||||||
|
initiatorUser = freshInitiator
|
||||||
|
}
|
||||||
|
|
||||||
oldUser, isNewUser, err := getUserOrCreateIfNotExists(ctx, transaction, accountID, update, addIfNotExists)
|
oldUser, isNewUser, err := getUserOrCreateIfNotExists(ctx, transaction, accountID, update, addIfNotExists)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, nil, nil, nil, err
|
return false, nil, nil, nil, err
|
||||||
@@ -864,7 +872,10 @@ func validateUserUpdate(groupsMap map[string]*types.Group, initiatorUser, oldUse
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// @todo double check these
|
if !initiatorUser.HasAdminPower() {
|
||||||
|
return status.Errorf(status.PermissionDenied, "only admins and owners can update users")
|
||||||
|
}
|
||||||
|
|
||||||
if initiatorUser.HasAdminPower() && initiatorUser.Id == update.Id && oldUser.Blocked != update.Blocked {
|
if initiatorUser.HasAdminPower() && initiatorUser.Id == update.Id && oldUser.Blocked != update.Blocked {
|
||||||
return status.Errorf(status.PermissionDenied, "admins can't block or unblock themselves")
|
return status.Errorf(status.PermissionDenied, "admins can't block or unblock themselves")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2031,3 +2031,87 @@ func TestUser_Operations_WithEmbeddedIDP(t *testing.T) {
|
|||||||
t.Logf("Duplicate email error: %v", err)
|
t.Logf("Duplicate email error: %v", err)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestValidateUserUpdate_RejectsNonAdminInitiator(t *testing.T) {
|
||||||
|
groupsMap := map[string]*types.Group{}
|
||||||
|
|
||||||
|
initiator := &types.User{
|
||||||
|
Id: "initiator",
|
||||||
|
Role: types.UserRoleUser,
|
||||||
|
}
|
||||||
|
oldUser := &types.User{
|
||||||
|
Id: "target",
|
||||||
|
Role: types.UserRoleUser,
|
||||||
|
}
|
||||||
|
update := &types.User{
|
||||||
|
Id: "target",
|
||||||
|
Role: types.UserRoleOwner,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := validateUserUpdate(groupsMap, initiator, oldUser, update)
|
||||||
|
require.Error(t, err, "regular user should not be able to promote to owner")
|
||||||
|
assert.Contains(t, err.Error(), "only admins and owners can update users")
|
||||||
|
}
|
||||||
|
|
||||||
|
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(), "only admins and owners can update users")
|
||||||
|
|
||||||
|
targetUser, err := s.GetUserByUserID(context.Background(), store.LockingStrengthNone, targetID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, types.UserRoleUser, targetUser.Role)
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user