2021-12-27 13:17:15 +01:00
|
|
|
package server
|
|
|
|
|
|
|
|
import (
|
2024-07-03 11:33:02 +02:00
|
|
|
"context"
|
2024-08-08 17:01:38 +02:00
|
|
|
"errors"
|
2022-05-25 18:26:50 +02:00
|
|
|
"fmt"
|
2023-03-01 20:12:04 +01:00
|
|
|
"strings"
|
2023-08-18 19:23:11 +02:00
|
|
|
"time"
|
2023-03-01 20:12:04 +01:00
|
|
|
|
2023-04-22 12:57:51 +02:00
|
|
|
"github.com/google/uuid"
|
2023-01-02 15:11:32 +01:00
|
|
|
"github.com/netbirdio/netbird/management/server/activity"
|
2024-11-12 14:19:22 +01:00
|
|
|
nbContext "github.com/netbirdio/netbird/management/server/context"
|
2024-10-04 16:17:01 +02:00
|
|
|
nbgroup "github.com/netbirdio/netbird/management/server/group"
|
2022-09-22 09:06:32 +02:00
|
|
|
"github.com/netbirdio/netbird/management/server/idp"
|
2024-03-27 18:48:48 +01:00
|
|
|
"github.com/netbirdio/netbird/management/server/integration_reference"
|
2023-05-02 16:49:29 +02:00
|
|
|
"github.com/netbirdio/netbird/management/server/jwtclaims"
|
2023-11-28 13:45:26 +01:00
|
|
|
nbpeer "github.com/netbirdio/netbird/management/server/peer"
|
2022-11-11 20:36:45 +01:00
|
|
|
"github.com/netbirdio/netbird/management/server/status"
|
2024-11-19 21:42:27 +01:00
|
|
|
log "github.com/sirupsen/logrus"
|
2021-12-27 13:17:15 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
2024-10-10 14:14:56 +02:00
|
|
|
UserRoleOwner UserRole = "owner"
|
|
|
|
UserRoleAdmin UserRole = "admin"
|
|
|
|
UserRoleUser UserRole = "user"
|
|
|
|
UserRoleUnknown UserRole = "unknown"
|
|
|
|
UserRoleBillingAdmin UserRole = "billing_admin"
|
2022-10-13 18:26:31 +02:00
|
|
|
|
|
|
|
UserStatusActive UserStatus = "active"
|
|
|
|
UserStatusDisabled UserStatus = "disabled"
|
|
|
|
UserStatusInvited UserStatus = "invited"
|
2023-11-01 11:04:17 +01:00
|
|
|
|
|
|
|
UserIssuedAPI = "api"
|
|
|
|
UserIssuedIntegration = "integration"
|
2021-12-27 13:17:15 +01:00
|
|
|
)
|
|
|
|
|
2022-09-23 14:18:42 +02:00
|
|
|
// StrRoleToUserRole returns UserRole for a given strRole or UserRoleUnknown if the specified role is unknown
|
|
|
|
func StrRoleToUserRole(strRole string) UserRole {
|
|
|
|
switch strings.ToLower(strRole) {
|
2023-12-01 17:24:57 +01:00
|
|
|
case "owner":
|
|
|
|
return UserRoleOwner
|
2022-09-23 14:18:42 +02:00
|
|
|
case "admin":
|
|
|
|
return UserRoleAdmin
|
|
|
|
case "user":
|
|
|
|
return UserRoleUser
|
2024-10-10 14:14:56 +02:00
|
|
|
case "billing_admin":
|
|
|
|
return UserRoleBillingAdmin
|
2022-09-23 14:18:42 +02:00
|
|
|
default:
|
|
|
|
return UserRoleUnknown
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-13 18:26:31 +02:00
|
|
|
// UserStatus is the status of a User
|
|
|
|
type UserStatus string
|
|
|
|
|
|
|
|
// UserRole is the role of a User
|
2021-12-27 13:17:15 +01:00
|
|
|
type UserRole string
|
|
|
|
|
|
|
|
// User represents a user of the system
|
|
|
|
type User struct {
|
2023-10-12 15:42:36 +02:00
|
|
|
Id string `gorm:"primaryKey"`
|
|
|
|
// AccountID is a reference to Account that this object belongs
|
|
|
|
AccountID string `json:"-" gorm:"index"`
|
2023-04-22 12:57:51 +02:00
|
|
|
Role UserRole
|
|
|
|
IsServiceUser bool
|
2023-11-15 16:22:00 +01:00
|
|
|
// NonDeletable indicates whether the service user can be deleted
|
|
|
|
NonDeletable bool
|
2023-04-22 12:57:51 +02:00
|
|
|
// ServiceUserName is only set if IsServiceUser is true
|
|
|
|
ServiceUserName string
|
2022-09-22 09:06:32 +02:00
|
|
|
// AutoGroups is a list of Group IDs to auto-assign to peers registered by this user
|
2023-10-12 15:42:36 +02:00
|
|
|
AutoGroups []string `gorm:"serializer:json"`
|
|
|
|
PATs map[string]*PersonalAccessToken `gorm:"-"`
|
2024-11-19 21:42:27 +01:00
|
|
|
PATsG []PersonalAccessToken `json:"-" gorm:"foreignKey:UserID;references:id;constraint:OnDelete:CASCADE;"`
|
2023-05-11 18:09:36 +02:00
|
|
|
// Blocked indicates whether the user is blocked. Blocked users can't use the system.
|
|
|
|
Blocked bool
|
2023-08-18 19:23:11 +02:00
|
|
|
// LastLogin is the last time the user logged in to IdP
|
|
|
|
LastLogin time.Time
|
2024-03-02 13:49:40 +01:00
|
|
|
// CreatedAt records the time the user was created
|
|
|
|
CreatedAt time.Time
|
2023-11-01 11:04:17 +01:00
|
|
|
|
|
|
|
// Issued of the user
|
|
|
|
Issued string `gorm:"default:api"`
|
|
|
|
|
2024-03-27 18:48:48 +01:00
|
|
|
IntegrationReference integration_reference.IntegrationReference `gorm:"embedded;embeddedPrefix:integration_ref_"`
|
2021-12-27 13:17:15 +01:00
|
|
|
}
|
|
|
|
|
2023-05-11 18:09:36 +02:00
|
|
|
// IsBlocked returns true if the user is blocked, false otherwise
|
|
|
|
func (u *User) IsBlocked() bool {
|
|
|
|
return u.Blocked
|
|
|
|
}
|
|
|
|
|
2023-08-18 19:23:11 +02:00
|
|
|
func (u *User) LastDashboardLoginChanged(LastLogin time.Time) bool {
|
|
|
|
return LastLogin.After(u.LastLogin) && !u.LastLogin.IsZero()
|
|
|
|
}
|
|
|
|
|
2023-12-01 17:24:57 +01:00
|
|
|
// HasAdminPower returns true if the user has admin or owner roles, false otherwise
|
|
|
|
func (u *User) HasAdminPower() bool {
|
|
|
|
return u.Role == UserRoleAdmin || u.Role == UserRoleOwner
|
2022-11-05 10:24:50 +01:00
|
|
|
}
|
|
|
|
|
2024-09-27 16:10:50 +02:00
|
|
|
// IsAdminOrServiceUser checks if the user has admin power or is a service user.
|
|
|
|
func (u *User) IsAdminOrServiceUser() bool {
|
|
|
|
return u.HasAdminPower() || u.IsServiceUser
|
|
|
|
}
|
|
|
|
|
2024-11-07 22:37:47 +01:00
|
|
|
// IsRegularUser checks if the user is a regular user.
|
|
|
|
func (u *User) IsRegularUser() bool {
|
|
|
|
return !u.HasAdminPower() && !u.IsServiceUser
|
|
|
|
}
|
|
|
|
|
2023-05-11 18:09:36 +02:00
|
|
|
// ToUserInfo converts a User object to a UserInfo object.
|
2024-03-27 16:11:45 +01:00
|
|
|
func (u *User) ToUserInfo(userData *idp.UserData, settings *Settings) (*UserInfo, error) {
|
2022-09-22 09:06:32 +02:00
|
|
|
autoGroups := u.AutoGroups
|
|
|
|
if autoGroups == nil {
|
|
|
|
autoGroups = []string{}
|
|
|
|
}
|
|
|
|
|
2024-03-27 16:11:45 +01:00
|
|
|
dashboardViewPermissions := "full"
|
|
|
|
if !u.HasAdminPower() {
|
|
|
|
dashboardViewPermissions = "limited"
|
|
|
|
if settings.RegularUsersViewBlocked {
|
|
|
|
dashboardViewPermissions = "blocked"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-09-22 09:06:32 +02:00
|
|
|
if userData == nil {
|
|
|
|
return &UserInfo{
|
2023-04-22 12:57:51 +02:00
|
|
|
ID: u.Id,
|
|
|
|
Email: "",
|
|
|
|
Name: u.ServiceUserName,
|
|
|
|
Role: string(u.Role),
|
|
|
|
AutoGroups: u.AutoGroups,
|
|
|
|
Status: string(UserStatusActive),
|
|
|
|
IsServiceUser: u.IsServiceUser,
|
2023-05-11 18:09:36 +02:00
|
|
|
IsBlocked: u.Blocked,
|
2023-08-18 19:23:11 +02:00
|
|
|
LastLogin: u.LastLogin,
|
2023-11-01 11:04:17 +01:00
|
|
|
Issued: u.Issued,
|
2024-03-27 16:11:45 +01:00
|
|
|
Permissions: UserPermissions{
|
|
|
|
DashboardView: dashboardViewPermissions,
|
|
|
|
},
|
2022-09-22 09:06:32 +02:00
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
if userData.ID != u.Id {
|
|
|
|
return nil, fmt.Errorf("wrong UserData provided for user %s", u.Id)
|
|
|
|
}
|
|
|
|
|
2022-10-13 18:26:31 +02:00
|
|
|
userStatus := UserStatusActive
|
2022-10-19 17:51:41 +02:00
|
|
|
if userData.AppMetadata.WTPendingInvite != nil && *userData.AppMetadata.WTPendingInvite {
|
2022-10-13 18:26:31 +02:00
|
|
|
userStatus = UserStatusInvited
|
|
|
|
}
|
|
|
|
|
2022-09-22 09:06:32 +02:00
|
|
|
return &UserInfo{
|
2023-04-22 12:57:51 +02:00
|
|
|
ID: u.Id,
|
|
|
|
Email: userData.Email,
|
|
|
|
Name: userData.Name,
|
|
|
|
Role: string(u.Role),
|
|
|
|
AutoGroups: autoGroups,
|
|
|
|
Status: string(userStatus),
|
|
|
|
IsServiceUser: u.IsServiceUser,
|
2023-05-11 18:09:36 +02:00
|
|
|
IsBlocked: u.Blocked,
|
2023-08-18 19:23:11 +02:00
|
|
|
LastLogin: u.LastLogin,
|
2023-11-01 11:04:17 +01:00
|
|
|
Issued: u.Issued,
|
2024-03-27 16:11:45 +01:00
|
|
|
Permissions: UserPermissions{
|
|
|
|
DashboardView: dashboardViewPermissions,
|
|
|
|
},
|
2022-09-22 09:06:32 +02:00
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Copy the user
|
2021-12-27 13:17:15 +01:00
|
|
|
func (u *User) Copy() *User {
|
2023-03-16 11:32:55 +01:00
|
|
|
autoGroups := make([]string, len(u.AutoGroups))
|
|
|
|
copy(autoGroups, u.AutoGroups)
|
2023-03-20 16:14:55 +01:00
|
|
|
pats := make(map[string]*PersonalAccessToken, len(u.PATs))
|
|
|
|
for k, v := range u.PATs {
|
2023-08-22 17:56:39 +02:00
|
|
|
pats[k] = v.Copy()
|
2023-03-20 16:14:55 +01:00
|
|
|
}
|
2021-12-27 13:17:15 +01:00
|
|
|
return &User{
|
2023-11-01 11:04:17 +01:00
|
|
|
Id: u.Id,
|
|
|
|
AccountID: u.AccountID,
|
|
|
|
Role: u.Role,
|
|
|
|
AutoGroups: autoGroups,
|
|
|
|
IsServiceUser: u.IsServiceUser,
|
2023-11-15 16:22:00 +01:00
|
|
|
NonDeletable: u.NonDeletable,
|
2023-11-01 11:04:17 +01:00
|
|
|
ServiceUserName: u.ServiceUserName,
|
|
|
|
PATs: pats,
|
|
|
|
Blocked: u.Blocked,
|
|
|
|
LastLogin: u.LastLogin,
|
2024-03-02 13:49:40 +01:00
|
|
|
CreatedAt: u.CreatedAt,
|
2023-11-01 11:04:17 +01:00
|
|
|
Issued: u.Issued,
|
|
|
|
IntegrationReference: u.IntegrationReference,
|
2021-12-27 13:17:15 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewUser creates a new user
|
2023-11-15 16:22:00 +01:00
|
|
|
func NewUser(id string, role UserRole, isServiceUser bool, nonDeletable bool, serviceUserName string, autoGroups []string, issued string) *User {
|
2021-12-27 13:17:15 +01:00
|
|
|
return &User{
|
2023-04-22 12:57:51 +02:00
|
|
|
Id: id,
|
|
|
|
Role: role,
|
|
|
|
IsServiceUser: isServiceUser,
|
2023-11-15 16:22:00 +01:00
|
|
|
NonDeletable: nonDeletable,
|
2023-04-22 12:57:51 +02:00
|
|
|
ServiceUserName: serviceUserName,
|
|
|
|
AutoGroups: autoGroups,
|
2023-11-01 11:04:17 +01:00
|
|
|
Issued: issued,
|
2024-03-02 13:49:40 +01:00
|
|
|
CreatedAt: time.Now().UTC(),
|
2021-12-27 13:17:15 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-22 12:57:51 +02:00
|
|
|
// NewRegularUser creates a new user with role UserRoleUser
|
2022-03-01 15:22:18 +01:00
|
|
|
func NewRegularUser(id string) *User {
|
2023-11-15 16:22:00 +01:00
|
|
|
return NewUser(id, UserRoleUser, false, false, "", []string{}, UserIssuedAPI)
|
2022-03-01 15:22:18 +01:00
|
|
|
}
|
|
|
|
|
2021-12-27 13:17:15 +01:00
|
|
|
// NewAdminUser creates a new user with role UserRoleAdmin
|
|
|
|
func NewAdminUser(id string) *User {
|
2023-11-15 16:22:00 +01:00
|
|
|
return NewUser(id, UserRoleAdmin, false, false, "", []string{}, UserIssuedAPI)
|
2023-04-22 12:57:51 +02:00
|
|
|
}
|
|
|
|
|
2023-12-01 17:24:57 +01:00
|
|
|
// NewOwnerUser creates a new user with role UserRoleOwner
|
|
|
|
func NewOwnerUser(id string) *User {
|
|
|
|
return NewUser(id, UserRoleOwner, false, false, "", []string{}, UserIssuedAPI)
|
|
|
|
}
|
|
|
|
|
2023-04-22 12:57:51 +02:00
|
|
|
// createServiceUser creates a new service user under the given account.
|
2024-07-03 11:33:02 +02:00
|
|
|
func (am *DefaultAccountManager) createServiceUser(ctx context.Context, accountID string, initiatorUserID string, role UserRole, serviceUserName string, nonDeletable bool, autoGroups []string) (*UserInfo, error) {
|
2024-07-31 14:53:32 +02:00
|
|
|
unlock := am.Store.AcquireWriteLockByUID(ctx, accountID)
|
2023-04-22 12:57:51 +02:00
|
|
|
defer unlock()
|
|
|
|
|
2024-11-19 21:42:27 +01:00
|
|
|
initiatorUser, err := am.Store.GetUserByUserID(ctx, LockingStrengthShare, initiatorUserID)
|
2023-04-22 12:57:51 +02:00
|
|
|
if err != nil {
|
2024-11-19 21:42:27 +01:00
|
|
|
return nil, err
|
2023-04-22 12:57:51 +02:00
|
|
|
}
|
|
|
|
|
2024-11-19 21:42:27 +01:00
|
|
|
if initiatorUser.AccountID != accountID {
|
|
|
|
return nil, status.NewUserNotPartOfAccountError()
|
2023-04-22 12:57:51 +02:00
|
|
|
}
|
2024-11-19 21:42:27 +01:00
|
|
|
|
|
|
|
if !initiatorUser.HasAdminPower() {
|
|
|
|
return nil, status.NewAdminPermissionError()
|
2023-12-01 17:24:57 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if role == UserRoleOwner {
|
2024-11-19 21:42:27 +01:00
|
|
|
return nil, status.NewServiceUserRoleInvalidError()
|
2023-04-22 12:57:51 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
newUserID := uuid.New().String()
|
2023-11-15 16:22:00 +01:00
|
|
|
newUser := NewUser(newUserID, role, true, nonDeletable, serviceUserName, autoGroups, UserIssuedAPI)
|
2024-11-19 21:42:27 +01:00
|
|
|
newUser.AccountID = accountID
|
2024-07-03 11:33:02 +02:00
|
|
|
log.WithContext(ctx).Debugf("New User: %v", newUser)
|
2023-04-22 12:57:51 +02:00
|
|
|
|
2024-11-19 21:42:27 +01:00
|
|
|
if err = am.Store.SaveUser(ctx, LockingStrengthUpdate, newUser); err != nil {
|
2023-04-22 12:57:51 +02:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
meta := map[string]any{"name": newUser.ServiceUserName}
|
2024-07-03 11:33:02 +02:00
|
|
|
am.StoreEvent(ctx, initiatorUserID, newUser.Id, accountID, activity.ServiceUserCreated, meta)
|
2023-04-22 12:57:51 +02:00
|
|
|
|
|
|
|
return &UserInfo{
|
|
|
|
ID: newUser.Id,
|
|
|
|
Email: "",
|
|
|
|
Name: newUser.ServiceUserName,
|
|
|
|
Role: string(newUser.Role),
|
|
|
|
AutoGroups: newUser.AutoGroups,
|
|
|
|
Status: string(UserStatusActive),
|
|
|
|
IsServiceUser: true,
|
2023-08-18 19:23:11 +02:00
|
|
|
LastLogin: time.Time{},
|
2023-11-01 11:04:17 +01:00
|
|
|
Issued: UserIssuedAPI,
|
2023-04-22 12:57:51 +02:00
|
|
|
}, nil
|
2021-12-27 13:17:15 +01:00
|
|
|
}
|
|
|
|
|
2022-10-13 18:26:31 +02:00
|
|
|
// CreateUser creates a new user under the given account. Effectively this is a user invite.
|
2024-07-03 11:33:02 +02:00
|
|
|
func (am *DefaultAccountManager) CreateUser(ctx context.Context, accountID, userID string, user *UserInfo) (*UserInfo, error) {
|
2023-04-22 12:57:51 +02:00
|
|
|
if user.IsServiceUser {
|
2024-07-03 11:33:02 +02:00
|
|
|
return am.createServiceUser(ctx, accountID, userID, StrRoleToUserRole(user.Role), user.Name, user.NonDeletable, user.AutoGroups)
|
2023-04-22 12:57:51 +02:00
|
|
|
}
|
2024-07-03 11:33:02 +02:00
|
|
|
return am.inviteNewUser(ctx, accountID, userID, user)
|
2023-04-22 12:57:51 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// inviteNewUser Invites a USer to a given account and creates reference in datastore
|
2024-07-03 11:33:02 +02:00
|
|
|
func (am *DefaultAccountManager) inviteNewUser(ctx context.Context, accountID, userID string, invite *UserInfo) (*UserInfo, error) {
|
2022-10-13 18:26:31 +02:00
|
|
|
if am.idpManager == nil {
|
2022-11-11 20:36:45 +01:00
|
|
|
return nil, status.Errorf(status.PreconditionFailed, "IdP manager must be enabled to send user invites")
|
2022-10-13 18:26:31 +02:00
|
|
|
}
|
|
|
|
|
2024-11-19 21:42:27 +01:00
|
|
|
if err := validateUserInvite(invite); err != nil {
|
|
|
|
return nil, err
|
2023-11-28 13:09:33 +01:00
|
|
|
}
|
|
|
|
|
2024-11-19 21:42:27 +01:00
|
|
|
initiatorUser, err := am.Store.GetUserByUserID(ctx, LockingStrengthShare, userID)
|
2022-10-13 18:26:31 +02:00
|
|
|
if err != nil {
|
2024-11-19 21:42:27 +01:00
|
|
|
return nil, err
|
2022-10-13 18:26:31 +02:00
|
|
|
}
|
|
|
|
|
2024-11-19 21:42:27 +01:00
|
|
|
if initiatorUser.AccountID != accountID {
|
|
|
|
return nil, status.NewUserNotPartOfAccountError()
|
2023-10-17 15:54:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
inviterID := userID
|
|
|
|
if initiatorUser.IsServiceUser {
|
2024-11-19 21:42:27 +01:00
|
|
|
createdBy, err := am.Store.GetAccountCreatedBy(ctx, LockingStrengthShare, accountID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
inviterID = createdBy
|
2023-10-17 15:54:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// inviterUser is the one who is inviting the new user
|
2024-11-19 21:42:27 +01:00
|
|
|
inviterUser, err := am.lookupUserInCache(ctx, inviterID, accountID)
|
|
|
|
if err != nil {
|
2023-10-17 15:54:50 +02:00
|
|
|
return nil, status.Errorf(status.NotFound, "inviter user with ID %s doesn't exist in IdP", inviterID)
|
2023-08-16 23:05:22 +02:00
|
|
|
}
|
|
|
|
|
2022-10-13 18:26:31 +02:00
|
|
|
// check if the user is already registered with this email => reject
|
2024-07-03 11:33:02 +02:00
|
|
|
user, err := am.lookupUserInCacheByEmail(ctx, invite.Email, accountID)
|
2022-10-13 18:26:31 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if user != nil {
|
2023-05-11 18:09:36 +02:00
|
|
|
return nil, status.Errorf(status.UserAlreadyExists, "can't invite a user with an existing NetBird account")
|
2022-10-13 18:26:31 +02:00
|
|
|
}
|
|
|
|
|
2024-07-03 11:33:02 +02:00
|
|
|
users, err := am.idpManager.GetUserByEmail(ctx, invite.Email)
|
2022-10-13 18:26:31 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(users) > 0 {
|
2023-05-11 18:09:36 +02:00
|
|
|
return nil, status.Errorf(status.UserAlreadyExists, "can't invite a user with an existing NetBird account")
|
2022-10-13 18:26:31 +02:00
|
|
|
}
|
|
|
|
|
2024-07-03 11:33:02 +02:00
|
|
|
idpUser, err := am.idpManager.CreateUser(ctx, invite.Email, invite.Name, accountID, inviterUser.Email)
|
2022-10-13 18:26:31 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
newUser := &User{
|
2023-11-01 11:04:17 +01:00
|
|
|
Id: idpUser.ID,
|
2024-11-19 21:42:27 +01:00
|
|
|
AccountID: accountID,
|
|
|
|
Role: StrRoleToUserRole(invite.Role),
|
2023-11-01 11:04:17 +01:00
|
|
|
AutoGroups: invite.AutoGroups,
|
|
|
|
Issued: invite.Issued,
|
|
|
|
IntegrationReference: invite.IntegrationReference,
|
2024-03-02 13:49:40 +01:00
|
|
|
CreatedAt: time.Now().UTC(),
|
2022-10-13 18:26:31 +02:00
|
|
|
}
|
|
|
|
|
2024-11-19 21:42:27 +01:00
|
|
|
settings, err := am.Store.GetAccountSettings(ctx, LockingStrengthShare, accountID)
|
2022-10-13 18:26:31 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2024-11-19 21:42:27 +01:00
|
|
|
if err = am.Store.SaveUser(ctx, LockingStrengthUpdate, newUser); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
_, err = am.refreshCache(ctx, accountID)
|
2022-10-13 18:26:31 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2024-07-03 11:33:02 +02:00
|
|
|
am.StoreEvent(ctx, userID, newUser.Id, accountID, activity.UserInvited, nil)
|
2023-01-02 15:11:32 +01:00
|
|
|
|
2024-11-19 21:42:27 +01:00
|
|
|
return newUser.ToUserInfo(idpUser, settings)
|
2023-05-11 18:09:36 +02:00
|
|
|
}
|
|
|
|
|
2024-09-27 16:10:50 +02:00
|
|
|
func (am *DefaultAccountManager) GetUserByID(ctx context.Context, id string) (*User, error) {
|
|
|
|
return am.Store.GetUserByUserID(ctx, LockingStrengthShare, id)
|
|
|
|
}
|
|
|
|
|
2023-05-11 18:09:36 +02:00
|
|
|
// GetUser looks up a user by provided authorization claims.
|
|
|
|
// It will also create an account if didn't exist for this user before.
|
2024-07-03 11:33:02 +02:00
|
|
|
func (am *DefaultAccountManager) GetUser(ctx context.Context, claims jwtclaims.AuthorizationClaims) (*User, error) {
|
2024-09-27 16:10:50 +02:00
|
|
|
accountID, userID, err := am.GetAccountIDFromToken(ctx, claims)
|
2023-05-11 18:09:36 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("failed to get account with token claims %v", err)
|
|
|
|
}
|
2022-10-13 18:26:31 +02:00
|
|
|
|
2024-09-27 16:10:50 +02:00
|
|
|
user, err := am.Store.GetUserByUserID(ctx, LockingStrengthShare, userID)
|
2023-10-23 16:08:21 +02:00
|
|
|
if err != nil {
|
2024-09-27 16:10:50 +02:00
|
|
|
return nil, err
|
2023-05-11 18:09:36 +02:00
|
|
|
}
|
2023-08-18 19:23:11 +02:00
|
|
|
|
2024-09-27 16:10:50 +02:00
|
|
|
// this code should be outside of the am.GetAccountIDFromToken(claims) because this method is called also by the gRPC
|
2023-08-18 19:23:11 +02:00
|
|
|
// server when user authenticates a device. And we need to separate the Dashboard login event from the Device login event.
|
|
|
|
newLogin := user.LastDashboardLoginChanged(claims.LastLogin)
|
2023-10-23 16:08:21 +02:00
|
|
|
|
2024-09-27 16:10:50 +02:00
|
|
|
err = am.Store.SaveUserLastLogin(ctx, accountID, userID, claims.LastLogin)
|
2023-10-23 16:08:21 +02:00
|
|
|
if err != nil {
|
2024-07-03 11:33:02 +02:00
|
|
|
log.WithContext(ctx).Errorf("failed saving user last login: %v", err)
|
2023-10-23 16:08:21 +02:00
|
|
|
}
|
|
|
|
|
2023-08-18 19:23:11 +02:00
|
|
|
if newLogin {
|
|
|
|
meta := map[string]any{"timestamp": claims.LastLogin}
|
2024-09-27 16:10:50 +02:00
|
|
|
am.StoreEvent(ctx, claims.UserId, claims.UserId, accountID, activity.DashboardLogin, meta)
|
2023-08-18 19:23:11 +02:00
|
|
|
}
|
|
|
|
|
2023-05-11 18:09:36 +02:00
|
|
|
return user, nil
|
2022-10-13 18:26:31 +02:00
|
|
|
}
|
|
|
|
|
2023-11-13 14:04:18 +01:00
|
|
|
// ListUsers returns lists of all users under the account.
|
2023-11-14 12:25:21 +01:00
|
|
|
// It doesn't populate user information such as email or name.
|
2024-07-03 11:33:02 +02:00
|
|
|
func (am *DefaultAccountManager) ListUsers(ctx context.Context, accountID string) ([]*User, error) {
|
2024-07-31 14:53:32 +02:00
|
|
|
unlock := am.Store.AcquireWriteLockByUID(ctx, accountID)
|
2023-11-13 14:04:18 +01:00
|
|
|
defer unlock()
|
2024-11-19 21:42:27 +01:00
|
|
|
return am.Store.GetAccountUsers(ctx, LockingStrengthShare, accountID)
|
2023-11-13 14:04:18 +01:00
|
|
|
}
|
|
|
|
|
2024-11-19 21:42:27 +01:00
|
|
|
func (am *DefaultAccountManager) deleteServiceUser(ctx context.Context, accountID string, initiatorUserID string, targetUser *User) error {
|
|
|
|
if err := am.Store.DeleteUser(ctx, LockingStrengthUpdate, accountID, targetUser.Id); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2024-03-02 13:49:40 +01:00
|
|
|
meta := map[string]any{"name": targetUser.ServiceUserName, "created_at": targetUser.CreatedAt}
|
2024-11-19 21:42:27 +01:00
|
|
|
am.StoreEvent(ctx, initiatorUserID, targetUser.Id, accountID, activity.ServiceUserDeleted, meta)
|
|
|
|
return nil
|
2023-10-03 16:46:58 +02:00
|
|
|
}
|
|
|
|
|
2023-04-22 12:57:51 +02:00
|
|
|
// DeleteUser deletes a user from the given account.
|
2024-11-19 21:42:27 +01:00
|
|
|
func (am *DefaultAccountManager) DeleteUser(ctx context.Context, accountID, initiatorUserID, targetUserID string) error {
|
2023-09-23 10:47:49 +02:00
|
|
|
if initiatorUserID == targetUserID {
|
|
|
|
return status.Errorf(status.InvalidArgument, "self deletion is not allowed")
|
|
|
|
}
|
2024-11-19 21:42:27 +01:00
|
|
|
|
2024-07-31 14:53:32 +02:00
|
|
|
unlock := am.Store.AcquireWriteLockByUID(ctx, accountID)
|
2023-04-22 12:57:51 +02:00
|
|
|
defer unlock()
|
|
|
|
|
2024-11-19 21:42:27 +01:00
|
|
|
initiatorUser, err := am.Store.GetUserByUserID(ctx, LockingStrengthShare, initiatorUserID)
|
2023-04-22 12:57:51 +02:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2024-11-19 21:42:27 +01:00
|
|
|
if initiatorUser.AccountID != accountID {
|
|
|
|
return status.NewUserNotPartOfAccountError()
|
2023-04-22 12:57:51 +02:00
|
|
|
}
|
2024-11-19 21:42:27 +01:00
|
|
|
|
|
|
|
if !initiatorUser.HasAdminPower() {
|
|
|
|
return status.NewAdminPermissionError()
|
2023-04-22 12:57:51 +02:00
|
|
|
}
|
|
|
|
|
2024-11-19 21:42:27 +01:00
|
|
|
targetUser, err := am.Store.GetUserByUserID(ctx, LockingStrengthShare, targetUserID)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
2023-09-19 18:08:40 +02:00
|
|
|
}
|
|
|
|
|
2023-12-01 17:24:57 +01:00
|
|
|
if targetUser.Role == UserRoleOwner {
|
2024-11-19 21:42:27 +01:00
|
|
|
return status.NewOwnerDeletePermissionError()
|
2023-12-01 17:24:57 +01:00
|
|
|
}
|
|
|
|
|
2023-11-07 15:02:51 +01:00
|
|
|
// disable deleting integration user if the initiator is not admin service user
|
2024-11-19 21:42:27 +01:00
|
|
|
if targetUser.Issued == UserIssuedIntegration && !initiatorUser.IsServiceUser {
|
2023-12-01 17:24:57 +01:00
|
|
|
return status.Errorf(status.PermissionDenied, "only integration service user can delete this user")
|
2023-11-01 11:04:17 +01:00
|
|
|
}
|
|
|
|
|
2023-10-03 16:46:58 +02:00
|
|
|
// handle service user first and exit, no need to fetch extra data from IDP, etc
|
|
|
|
if targetUser.IsServiceUser {
|
2023-11-15 16:22:00 +01:00
|
|
|
if targetUser.NonDeletable {
|
|
|
|
return status.Errorf(status.PermissionDenied, "service user is marked as non-deletable")
|
|
|
|
}
|
|
|
|
|
2024-11-19 21:42:27 +01:00
|
|
|
return am.deleteServiceUser(ctx, accountID, initiatorUserID, targetUser)
|
2023-10-01 19:51:39 +02:00
|
|
|
}
|
|
|
|
|
2024-11-19 21:42:27 +01:00
|
|
|
updateAccountPeers, err := am.deleteRegularUser(ctx, accountID, initiatorUserID, targetUserID)
|
2023-04-22 12:57:51 +02:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2024-10-23 12:05:02 +02:00
|
|
|
if updateAccountPeers {
|
2024-11-19 21:42:27 +01:00
|
|
|
am.updateAccountPeers(ctx, accountID)
|
2024-10-23 12:05:02 +02:00
|
|
|
}
|
2023-10-04 15:08:50 +02:00
|
|
|
|
|
|
|
return nil
|
2023-10-03 16:46:58 +02:00
|
|
|
}
|
|
|
|
|
2023-07-03 12:20:19 +02:00
|
|
|
// InviteUser resend invitations to users who haven't activated their accounts prior to the expiration period.
|
2024-07-03 11:33:02 +02:00
|
|
|
func (am *DefaultAccountManager) InviteUser(ctx context.Context, accountID string, initiatorUserID string, targetUserID string) error {
|
2023-07-03 12:20:19 +02:00
|
|
|
if am.idpManager == nil {
|
|
|
|
return status.Errorf(status.PreconditionFailed, "IdP manager must be enabled to send user invites")
|
|
|
|
}
|
|
|
|
|
2024-11-19 21:42:27 +01:00
|
|
|
initiatorUser, err := am.Store.GetUserByUserID(ctx, LockingStrengthShare, initiatorUserID)
|
2023-07-03 12:20:19 +02:00
|
|
|
if err != nil {
|
2024-11-19 21:42:27 +01:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if initiatorUser.AccountID != accountID {
|
|
|
|
return status.NewUserNotPartOfAccountError()
|
2023-07-03 12:20:19 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// check if the user is already registered with this ID
|
2024-11-19 21:42:27 +01:00
|
|
|
user, err := am.lookupUserInCache(ctx, targetUserID, accountID)
|
2023-07-03 12:20:19 +02:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if user == nil {
|
|
|
|
return status.Errorf(status.NotFound, "user account %s doesn't exist", targetUserID)
|
|
|
|
}
|
|
|
|
|
|
|
|
// check if user account is already invited and account is not activated
|
|
|
|
pendingInvite := user.AppMetadata.WTPendingInvite
|
|
|
|
if pendingInvite == nil || !*pendingInvite {
|
|
|
|
return status.Errorf(status.PreconditionFailed, "can't invite a user with an activated NetBird account")
|
|
|
|
}
|
|
|
|
|
2024-07-03 11:33:02 +02:00
|
|
|
err = am.idpManager.InviteUserByID(ctx, user.ID)
|
2023-07-03 12:20:19 +02:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2024-07-03 11:33:02 +02:00
|
|
|
am.StoreEvent(ctx, initiatorUserID, user.ID, accountID, activity.UserInvited, nil)
|
2023-07-03 12:20:19 +02:00
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-03-30 13:58:44 +02:00
|
|
|
// CreatePAT creates a new PAT for the given user
|
2024-07-03 11:33:02 +02:00
|
|
|
func (am *DefaultAccountManager) CreatePAT(ctx context.Context, accountID string, initiatorUserID string, targetUserID string, tokenName string, expiresIn int) (*PersonalAccessTokenGenerated, error) {
|
2024-07-31 14:53:32 +02:00
|
|
|
unlock := am.Store.AcquireWriteLockByUID(ctx, accountID)
|
2023-03-16 15:57:44 +01:00
|
|
|
defer unlock()
|
|
|
|
|
2023-03-30 13:58:44 +02:00
|
|
|
if tokenName == "" {
|
|
|
|
return nil, status.Errorf(status.InvalidArgument, "token name can't be empty")
|
|
|
|
}
|
|
|
|
|
|
|
|
if expiresIn < 1 || expiresIn > 365 {
|
|
|
|
return nil, status.Errorf(status.InvalidArgument, "expiration has to be between 1 and 365")
|
|
|
|
}
|
|
|
|
|
2024-11-19 21:42:27 +01:00
|
|
|
initiatorUser, err := am.Store.GetUserByUserID(ctx, LockingStrengthShare, initiatorUserID)
|
2023-03-16 15:57:44 +01:00
|
|
|
if err != nil {
|
2023-03-30 13:58:44 +02:00
|
|
|
return nil, err
|
2023-03-16 15:57:44 +01:00
|
|
|
}
|
|
|
|
|
2024-11-19 21:42:27 +01:00
|
|
|
if initiatorUser.AccountID != accountID {
|
|
|
|
return nil, status.NewUserNotPartOfAccountError()
|
2023-03-16 15:57:44 +01:00
|
|
|
}
|
|
|
|
|
2024-11-19 21:42:27 +01:00
|
|
|
if initiatorUserID != targetUserID && initiatorUser.IsRegularUser() {
|
|
|
|
return nil, status.NewAdminPermissionError()
|
2023-04-22 12:57:51 +02:00
|
|
|
}
|
|
|
|
|
2024-11-19 21:42:27 +01:00
|
|
|
targetUser, err := am.Store.GetUserByUserID(ctx, LockingStrengthShare, initiatorUserID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2023-04-22 12:57:51 +02:00
|
|
|
}
|
|
|
|
|
2024-11-19 21:42:27 +01:00
|
|
|
pat, err := CreateNewPAT(tokenName, expiresIn, targetUserID, initiatorUser.Id)
|
2023-03-30 13:58:44 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, status.Errorf(status.Internal, "failed to create PAT: %v", err)
|
|
|
|
}
|
|
|
|
|
2024-11-19 21:42:27 +01:00
|
|
|
if err = am.Store.SavePAT(ctx, LockingStrengthUpdate, &pat.PersonalAccessToken); err != nil {
|
|
|
|
return nil, err
|
2023-03-30 13:58:44 +02:00
|
|
|
}
|
2023-03-20 16:14:55 +01:00
|
|
|
|
2023-04-22 12:57:51 +02:00
|
|
|
meta := map[string]any{"name": pat.Name, "is_service_user": targetUser.IsServiceUser, "user_name": targetUser.ServiceUserName}
|
2024-07-03 11:33:02 +02:00
|
|
|
am.StoreEvent(ctx, initiatorUserID, targetUserID, accountID, activity.PersonalAccessTokenCreated, meta)
|
2023-03-31 17:41:22 +02:00
|
|
|
|
2023-03-30 13:58:44 +02:00
|
|
|
return pat, nil
|
2023-03-20 16:14:55 +01:00
|
|
|
}
|
|
|
|
|
2023-03-20 16:38:17 +01:00
|
|
|
// DeletePAT deletes a specific PAT from a user
|
2024-07-03 11:33:02 +02:00
|
|
|
func (am *DefaultAccountManager) DeletePAT(ctx context.Context, accountID string, initiatorUserID string, targetUserID string, tokenID string) error {
|
2024-07-31 14:53:32 +02:00
|
|
|
unlock := am.Store.AcquireWriteLockByUID(ctx, accountID)
|
2023-03-20 16:14:55 +01:00
|
|
|
defer unlock()
|
|
|
|
|
2024-11-19 21:42:27 +01:00
|
|
|
initiatorUser, err := am.Store.GetUserByUserID(ctx, LockingStrengthShare, initiatorUserID)
|
2023-03-20 16:14:55 +01:00
|
|
|
if err != nil {
|
2024-11-19 21:42:27 +01:00
|
|
|
return err
|
2023-03-20 16:14:55 +01:00
|
|
|
}
|
|
|
|
|
2024-11-19 21:42:27 +01:00
|
|
|
if initiatorUser.AccountID != accountID {
|
|
|
|
return status.NewUserNotPartOfAccountError()
|
2023-04-22 12:57:51 +02:00
|
|
|
}
|
|
|
|
|
2024-11-19 21:42:27 +01:00
|
|
|
if initiatorUserID != targetUserID && initiatorUser.IsRegularUser() {
|
|
|
|
return status.NewAdminPermissionError()
|
2023-04-22 12:57:51 +02:00
|
|
|
}
|
|
|
|
|
2024-11-19 21:42:27 +01:00
|
|
|
pat, err := am.Store.GetPATByID(ctx, LockingStrengthShare, targetUserID, tokenID)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
2023-03-20 16:14:55 +01:00
|
|
|
}
|
|
|
|
|
2024-11-19 21:42:27 +01:00
|
|
|
targetUser, err := am.Store.GetUserByUserID(ctx, LockingStrengthShare, targetUserID)
|
2023-03-20 16:14:55 +01:00
|
|
|
if err != nil {
|
2024-11-19 21:42:27 +01:00
|
|
|
return err
|
2023-03-20 16:14:55 +01:00
|
|
|
}
|
2024-11-19 21:42:27 +01:00
|
|
|
|
|
|
|
if err = am.Store.DeletePAT(ctx, LockingStrengthUpdate, targetUserID, tokenID); err != nil {
|
|
|
|
return err
|
2023-03-20 16:14:55 +01:00
|
|
|
}
|
2023-03-31 17:41:22 +02:00
|
|
|
|
2023-04-22 12:57:51 +02:00
|
|
|
meta := map[string]any{"name": pat.Name, "is_service_user": targetUser.IsServiceUser, "user_name": targetUser.ServiceUserName}
|
2024-07-03 11:33:02 +02:00
|
|
|
am.StoreEvent(ctx, initiatorUserID, targetUserID, accountID, activity.PersonalAccessTokenDeleted, meta)
|
2023-03-31 17:41:22 +02:00
|
|
|
|
2023-03-30 13:58:44 +02:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetPAT returns a specific PAT from a user
|
2024-07-03 11:33:02 +02:00
|
|
|
func (am *DefaultAccountManager) GetPAT(ctx context.Context, accountID string, initiatorUserID string, targetUserID string, tokenID string) (*PersonalAccessToken, error) {
|
2024-09-27 16:10:50 +02:00
|
|
|
initiatorUser, err := am.Store.GetUserByUserID(ctx, LockingStrengthShare, initiatorUserID)
|
2023-03-30 13:58:44 +02:00
|
|
|
if err != nil {
|
2024-09-27 16:10:50 +02:00
|
|
|
return nil, err
|
2023-03-30 13:58:44 +02:00
|
|
|
}
|
|
|
|
|
2024-11-19 21:42:27 +01:00
|
|
|
if initiatorUser.AccountID != accountID {
|
|
|
|
return nil, status.NewUserNotPartOfAccountError()
|
2023-04-22 12:57:51 +02:00
|
|
|
}
|
|
|
|
|
2024-11-19 21:42:27 +01:00
|
|
|
if initiatorUserID != targetUserID && initiatorUser.IsRegularUser() {
|
|
|
|
return nil, status.NewAdminPermissionError()
|
2023-03-30 13:58:44 +02:00
|
|
|
}
|
|
|
|
|
2024-11-19 21:42:27 +01:00
|
|
|
return am.Store.GetPATByID(ctx, LockingStrengthShare, targetUserID, tokenID)
|
2023-03-30 13:58:44 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// GetAllPATs returns all PATs for a user
|
2024-07-03 11:33:02 +02:00
|
|
|
func (am *DefaultAccountManager) GetAllPATs(ctx context.Context, accountID string, initiatorUserID string, targetUserID string) ([]*PersonalAccessToken, error) {
|
2024-09-27 16:10:50 +02:00
|
|
|
initiatorUser, err := am.Store.GetUserByUserID(ctx, LockingStrengthShare, initiatorUserID)
|
2023-03-30 13:58:44 +02:00
|
|
|
if err != nil {
|
2024-09-27 16:10:50 +02:00
|
|
|
return nil, err
|
2023-03-30 13:58:44 +02:00
|
|
|
}
|
|
|
|
|
2024-11-19 21:42:27 +01:00
|
|
|
if initiatorUser.AccountID != accountID {
|
|
|
|
return nil, status.NewUserNotPartOfAccountError()
|
2023-04-22 12:57:51 +02:00
|
|
|
}
|
|
|
|
|
2024-11-19 21:42:27 +01:00
|
|
|
if initiatorUserID != targetUserID && initiatorUser.IsRegularUser() {
|
|
|
|
return nil, status.NewAdminPermissionError()
|
2023-03-30 13:58:44 +02:00
|
|
|
}
|
|
|
|
|
2024-11-19 21:42:27 +01:00
|
|
|
return am.Store.GetUserPATs(ctx, LockingStrengthShare, targetUserID)
|
2023-03-16 15:57:44 +01:00
|
|
|
}
|
|
|
|
|
2024-07-15 16:04:06 +02:00
|
|
|
// SaveUser saves updates to the given user. If the user doesn't exist, it will throw status.NotFound error.
|
2024-07-03 11:33:02 +02:00
|
|
|
func (am *DefaultAccountManager) SaveUser(ctx context.Context, accountID, initiatorUserID string, update *User) (*UserInfo, error) {
|
|
|
|
return am.SaveOrAddUser(ctx, accountID, initiatorUserID, update, false) // false means do not create user and throw status.NotFound
|
2023-11-13 14:04:18 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// SaveOrAddUser updates the given user. If addIfNotExists is set to true it will add user when no exist
|
|
|
|
// Only User.AutoGroups, User.Role, and User.Blocked fields are allowed to be updated for now.
|
2024-07-03 11:33:02 +02:00
|
|
|
func (am *DefaultAccountManager) SaveOrAddUser(ctx context.Context, accountID, initiatorUserID string, update *User, addIfNotExists bool) (*UserInfo, error) {
|
2024-07-15 16:04:06 +02:00
|
|
|
updatedUsers, err := am.SaveOrAddUsers(ctx, accountID, initiatorUserID, []*User{update}, addIfNotExists)
|
2023-05-11 18:09:36 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2024-07-15 16:04:06 +02:00
|
|
|
if len(updatedUsers) == 0 {
|
|
|
|
return nil, status.Errorf(status.Internal, "user was not updated")
|
2022-09-22 09:06:32 +02:00
|
|
|
}
|
|
|
|
|
2024-07-15 16:04:06 +02:00
|
|
|
return updatedUsers[0], nil
|
|
|
|
}
|
2023-05-11 18:09:36 +02:00
|
|
|
|
2024-07-15 16:04:06 +02:00
|
|
|
// SaveOrAddUsers updates existing users or adds new users to the account.
|
|
|
|
func (am *DefaultAccountManager) SaveOrAddUsers(ctx context.Context, accountID, initiatorUserID string, updates []*User, addIfNotExists bool) ([]*UserInfo, error) {
|
|
|
|
if len(updates) == 0 {
|
|
|
|
return nil, nil //nolint:nilnil
|
2023-05-11 18:09:36 +02:00
|
|
|
}
|
|
|
|
|
2024-11-19 21:42:27 +01:00
|
|
|
initiatorUser, err := am.Store.GetUserByUserID(ctx, LockingStrengthShare, initiatorUserID)
|
2024-07-15 16:04:06 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2022-09-22 09:06:32 +02:00
|
|
|
}
|
|
|
|
|
2024-11-19 21:42:27 +01:00
|
|
|
if initiatorUser.AccountID != accountID {
|
|
|
|
return nil, status.NewUserNotPartOfAccountError()
|
2023-12-01 17:24:57 +01:00
|
|
|
}
|
|
|
|
|
2024-07-15 16:04:06 +02:00
|
|
|
if !initiatorUser.HasAdminPower() || initiatorUser.IsBlocked() {
|
2024-11-19 21:42:27 +01:00
|
|
|
return nil, status.NewAdminPermissionError()
|
|
|
|
}
|
|
|
|
|
|
|
|
settings, err := am.Store.GetAccountSettings(ctx, LockingStrengthShare, accountID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2023-12-01 17:24:57 +01:00
|
|
|
}
|
|
|
|
|
2024-11-19 21:42:27 +01:00
|
|
|
var updateAccountPeers bool
|
|
|
|
var peersToExpire []*nbpeer.Peer
|
|
|
|
var addUserEvents []func()
|
|
|
|
var usersToSave = make([]*User, 0, len(updates))
|
|
|
|
var updatedUsersInfo = make([]*UserInfo, 0, len(updates))
|
2023-12-01 17:24:57 +01:00
|
|
|
|
2024-11-19 21:42:27 +01:00
|
|
|
groups, err := am.Store.GetAccountGroups(ctx, LockingStrengthShare, accountID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("error getting account groups: %w", err)
|
|
|
|
}
|
2023-12-01 17:24:57 +01:00
|
|
|
|
2024-11-19 21:42:27 +01:00
|
|
|
groupsMap := make(map[string]*nbgroup.Group, len(groups))
|
|
|
|
for _, group := range groups {
|
|
|
|
groupsMap[group.ID] = group
|
|
|
|
}
|
2024-10-23 12:05:02 +02:00
|
|
|
|
2024-11-19 21:42:27 +01:00
|
|
|
err = am.Store.ExecuteInTransaction(ctx, func(transaction Store) error {
|
|
|
|
for _, update := range updates {
|
|
|
|
if update == nil {
|
|
|
|
return status.Errorf(status.InvalidArgument, "provided user update is nil")
|
2024-07-15 16:04:06 +02:00
|
|
|
}
|
2023-12-01 17:24:57 +01:00
|
|
|
|
2024-11-19 21:42:27 +01:00
|
|
|
userHadPeers, updatedUser, userPeersToExpire, userEvents, err := processUserUpdate(
|
|
|
|
ctx, am, transaction, groupsMap, initiatorUser, update, addIfNotExists, settings,
|
|
|
|
)
|
2024-07-15 16:04:06 +02:00
|
|
|
if err != nil {
|
2024-11-19 21:42:27 +01:00
|
|
|
return fmt.Errorf("failed to process user update: %w", err)
|
|
|
|
}
|
|
|
|
usersToSave = append(usersToSave, updatedUser)
|
|
|
|
addUserEvents = append(addUserEvents, userEvents...)
|
|
|
|
peersToExpire = append(peersToExpire, userPeersToExpire...)
|
|
|
|
|
|
|
|
if userHadPeers {
|
|
|
|
updateAccountPeers = true
|
2024-07-15 16:04:06 +02:00
|
|
|
}
|
2023-05-11 18:09:36 +02:00
|
|
|
|
2024-11-20 20:45:14 +01:00
|
|
|
updatedUserInfo, err := am.getUserInfo(ctx, transaction, updatedUser, accountID)
|
2024-11-19 21:42:27 +01:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("failed to get user info: %w", err)
|
|
|
|
}
|
|
|
|
updatedUsersInfo = append(updatedUsersInfo, updatedUserInfo)
|
2023-05-11 18:09:36 +02:00
|
|
|
}
|
2022-09-22 09:06:32 +02:00
|
|
|
|
2024-11-19 21:42:27 +01:00
|
|
|
return transaction.SaveUsers(ctx, LockingStrengthUpdate, usersToSave)
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2022-09-22 09:06:32 +02:00
|
|
|
|
2024-11-19 21:42:27 +01:00
|
|
|
for _, addUserEvent := range addUserEvents {
|
|
|
|
addUserEvent()
|
2024-07-15 16:04:06 +02:00
|
|
|
}
|
2023-05-11 18:09:36 +02:00
|
|
|
|
2024-11-19 21:42:27 +01:00
|
|
|
if len(peersToExpire) > 0 {
|
|
|
|
if err := am.expireAndUpdatePeers(ctx, accountID, peersToExpire); err != nil {
|
2024-07-03 11:33:02 +02:00
|
|
|
log.WithContext(ctx).Errorf("failed update expired peers: %s", err)
|
2023-09-19 18:08:40 +02:00
|
|
|
return nil, err
|
2023-05-11 18:09:36 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-11-19 21:42:27 +01:00
|
|
|
if settings.GroupsPropagationEnabled && updateAccountPeers {
|
|
|
|
if err = am.Store.IncrementNetworkSerial(ctx, LockingStrengthUpdate, accountID); err != nil {
|
|
|
|
return nil, fmt.Errorf("failed to increment network serial: %w", err)
|
|
|
|
}
|
|
|
|
am.updateAccountPeers(ctx, accountID)
|
2024-07-15 16:04:06 +02:00
|
|
|
}
|
|
|
|
|
2024-11-19 21:42:27 +01:00
|
|
|
return updatedUsersInfo, nil
|
2024-07-15 16:04:06 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// prepareUserUpdateEvents prepares a list user update events based on the changes between the old and new user data.
|
2024-11-19 21:42:27 +01:00
|
|
|
func (am *DefaultAccountManager) prepareUserUpdateEvents(ctx context.Context, groupsMap map[string]*nbgroup.Group, accountID string, initiatorUserID string, oldUser, newUser *User, transferredOwnerRole bool) []func() {
|
2024-07-15 16:04:06 +02:00
|
|
|
var eventsToStore []func()
|
2023-05-17 09:54:20 +02:00
|
|
|
|
2024-07-15 16:04:06 +02:00
|
|
|
if oldUser.IsBlocked() != newUser.IsBlocked() {
|
|
|
|
if newUser.IsBlocked() {
|
|
|
|
eventsToStore = append(eventsToStore, func() {
|
2024-11-19 21:42:27 +01:00
|
|
|
am.StoreEvent(ctx, initiatorUserID, oldUser.Id, accountID, activity.UserBlocked, nil)
|
2024-07-15 16:04:06 +02:00
|
|
|
})
|
|
|
|
} else {
|
|
|
|
eventsToStore = append(eventsToStore, func() {
|
2024-11-19 21:42:27 +01:00
|
|
|
am.StoreEvent(ctx, initiatorUserID, oldUser.Id, accountID, activity.UserUnblocked, nil)
|
2024-07-15 16:04:06 +02:00
|
|
|
})
|
2023-01-02 15:11:32 +01:00
|
|
|
}
|
2024-07-15 16:04:06 +02:00
|
|
|
}
|
2023-01-02 15:11:32 +01:00
|
|
|
|
2024-07-15 16:04:06 +02:00
|
|
|
switch {
|
|
|
|
case transferredOwnerRole:
|
|
|
|
eventsToStore = append(eventsToStore, func() {
|
2024-11-19 21:42:27 +01:00
|
|
|
am.StoreEvent(ctx, initiatorUserID, oldUser.Id, accountID, activity.TransferredOwnerRole, nil)
|
2024-07-15 16:04:06 +02:00
|
|
|
})
|
|
|
|
case oldUser.Role != newUser.Role:
|
|
|
|
eventsToStore = append(eventsToStore, func() {
|
2024-11-19 21:42:27 +01:00
|
|
|
am.StoreEvent(ctx, initiatorUserID, oldUser.Id, accountID, activity.UserRoleUpdated, map[string]any{"role": newUser.Role})
|
2024-07-15 16:04:06 +02:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
if newUser.AutoGroups != nil {
|
|
|
|
removedGroups := difference(oldUser.AutoGroups, newUser.AutoGroups)
|
|
|
|
addedGroups := difference(newUser.AutoGroups, oldUser.AutoGroups)
|
|
|
|
for _, g := range removedGroups {
|
2024-11-19 21:42:27 +01:00
|
|
|
group, ok := groupsMap[g]
|
|
|
|
if ok {
|
2024-07-15 16:04:06 +02:00
|
|
|
eventsToStore = append(eventsToStore, func() {
|
2024-11-19 21:42:27 +01:00
|
|
|
meta := map[string]any{"group": group.Name, "group_id": group.ID, "is_service_user": newUser.IsServiceUser, "user_name": newUser.ServiceUserName}
|
|
|
|
am.StoreEvent(ctx, initiatorUserID, oldUser.Id, accountID, activity.GroupRemovedFromUser, meta)
|
2024-07-15 16:04:06 +02:00
|
|
|
})
|
|
|
|
} else {
|
2024-11-19 21:42:27 +01:00
|
|
|
log.WithContext(ctx).Errorf("group %s not found while saving user activity event of account %s", g, accountID)
|
2024-07-15 16:04:06 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
for _, g := range addedGroups {
|
2024-11-19 21:42:27 +01:00
|
|
|
group, ok := groupsMap[g]
|
|
|
|
if ok {
|
2024-07-15 16:04:06 +02:00
|
|
|
eventsToStore = append(eventsToStore, func() {
|
2024-11-19 21:42:27 +01:00
|
|
|
meta := map[string]any{"group": group.Name, "group_id": group.ID, "is_service_user": newUser.IsServiceUser, "user_name": newUser.ServiceUserName}
|
|
|
|
am.StoreEvent(ctx, initiatorUserID, oldUser.Id, accountID, activity.GroupAddedToUser, meta)
|
2024-07-15 16:04:06 +02:00
|
|
|
})
|
2023-01-02 15:11:32 +01:00
|
|
|
}
|
|
|
|
}
|
2024-07-15 16:04:06 +02:00
|
|
|
}
|
2023-01-02 15:11:32 +01:00
|
|
|
|
2024-07-15 16:04:06 +02:00
|
|
|
return eventsToStore
|
|
|
|
}
|
|
|
|
|
2024-11-19 21:42:27 +01:00
|
|
|
func processUserUpdate(ctx context.Context, am *DefaultAccountManager, transaction Store, groupsMap map[string]*nbgroup.Group,
|
|
|
|
initiatorUser, update *User, addIfNotExists bool, settings *Settings) (bool, *User, []*nbpeer.Peer, []func(), error) {
|
|
|
|
|
|
|
|
if update == nil {
|
|
|
|
return false, nil, nil, nil, status.Errorf(status.InvalidArgument, "provided user update is nil")
|
|
|
|
}
|
|
|
|
|
|
|
|
oldUser, err := getUserOrCreateIfNotExists(ctx, transaction, update, addIfNotExists)
|
|
|
|
if err != nil {
|
|
|
|
return false, nil, nil, nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := validateUserUpdate(groupsMap, initiatorUser, oldUser, update); err != nil {
|
|
|
|
return false, nil, nil, nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// only auto groups, revoked status, and integration reference can be updated for now
|
|
|
|
updatedUser := oldUser.Copy()
|
|
|
|
updatedUser.AccountID = initiatorUser.AccountID
|
|
|
|
updatedUser.Role = update.Role
|
|
|
|
updatedUser.Blocked = update.Blocked
|
|
|
|
updatedUser.AutoGroups = update.AutoGroups
|
|
|
|
// these two fields can't be set via API, only via direct call to the method
|
|
|
|
updatedUser.Issued = update.Issued
|
|
|
|
updatedUser.IntegrationReference = update.IntegrationReference
|
|
|
|
|
|
|
|
transferredOwnerRole, err := handleOwnerRoleTransfer(ctx, transaction, initiatorUser, update)
|
|
|
|
if err != nil {
|
|
|
|
return false, nil, nil, nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
userPeers, err := transaction.GetUserPeers(ctx, LockingStrengthUpdate, updatedUser.AccountID, update.Id)
|
|
|
|
if err != nil {
|
|
|
|
return false, nil, nil, nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
var peersToExpire []*nbpeer.Peer
|
|
|
|
|
|
|
|
if !oldUser.IsBlocked() && update.IsBlocked() {
|
|
|
|
peersToExpire = userPeers
|
|
|
|
}
|
|
|
|
|
|
|
|
if update.AutoGroups != nil && settings.GroupsPropagationEnabled {
|
|
|
|
removedGroups := difference(oldUser.AutoGroups, update.AutoGroups)
|
|
|
|
updatedGroups, err := am.updateUserPeersInGroups(groupsMap, userPeers, update.AutoGroups, removedGroups)
|
|
|
|
if err != nil {
|
|
|
|
return false, nil, nil, nil, fmt.Errorf("error modifying user peers in groups: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if err = transaction.SaveGroups(ctx, LockingStrengthUpdate, updatedGroups); err != nil {
|
|
|
|
return false, nil, nil, nil, fmt.Errorf("error saving groups: %w", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
updateAccountPeers := len(userPeers) > 0
|
|
|
|
userEventsToAdd := am.prepareUserUpdateEvents(ctx, groupsMap, updatedUser.AccountID, initiatorUser.Id, oldUser, updatedUser, transferredOwnerRole)
|
|
|
|
|
|
|
|
return updateAccountPeers, updatedUser, peersToExpire, userEventsToAdd, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// getUserOrCreateIfNotExists retrieves the existing user or creates a new one if it doesn't exist.
|
|
|
|
func getUserOrCreateIfNotExists(ctx context.Context, transaction Store, update *User, addIfNotExists bool) (*User, error) {
|
|
|
|
existingUser, err := transaction.GetUserByUserID(ctx, LockingStrengthShare, update.Id)
|
|
|
|
if err != nil {
|
|
|
|
if sErr, ok := status.FromError(err); ok && sErr.Type() == status.NotFound {
|
|
|
|
if !addIfNotExists {
|
|
|
|
return nil, status.Errorf(status.NotFound, "user to update doesn't exist: %s", update.Id)
|
|
|
|
}
|
|
|
|
return update, nil // use all fields from update if addIfNotExists is true
|
|
|
|
}
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return existingUser, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func handleOwnerRoleTransfer(ctx context.Context, transaction Store, initiatorUser, update *User) (bool, error) {
|
2024-07-15 16:04:06 +02:00
|
|
|
if initiatorUser.Role == UserRoleOwner && initiatorUser.Id != update.Id && update.Role == UserRoleOwner {
|
|
|
|
newInitiatorUser := initiatorUser.Copy()
|
|
|
|
newInitiatorUser.Role = UserRoleAdmin
|
2024-11-19 21:42:27 +01:00
|
|
|
|
|
|
|
if err := transaction.SaveUser(ctx, LockingStrengthUpdate, newInitiatorUser); err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
return true, nil
|
2024-07-15 16:04:06 +02:00
|
|
|
}
|
2024-11-19 21:42:27 +01:00
|
|
|
return false, nil
|
2024-07-15 16:04:06 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// getUserInfo retrieves the UserInfo for a given User and Account.
|
|
|
|
// If the AccountManager has a non-nil idpManager and the User is not a service user,
|
|
|
|
// it will attempt to look up the UserData from the cache.
|
2024-11-20 20:45:14 +01:00
|
|
|
func (am *DefaultAccountManager) getUserInfo(ctx context.Context, transaction Store, user *User, accountID string) (*UserInfo, error) {
|
|
|
|
settings, err := transaction.GetAccountSettings(ctx, LockingStrengthShare, accountID)
|
2024-11-19 21:42:27 +01:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2024-07-15 16:04:06 +02:00
|
|
|
if !isNil(am.idpManager) && !user.IsServiceUser {
|
2024-11-19 21:42:27 +01:00
|
|
|
userData, err := am.lookupUserInCache(ctx, user.Id, accountID)
|
2022-09-22 09:06:32 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2024-11-19 21:42:27 +01:00
|
|
|
return user.ToUserInfo(userData, settings)
|
2024-07-15 16:04:06 +02:00
|
|
|
}
|
2024-11-19 21:42:27 +01:00
|
|
|
return user.ToUserInfo(nil, settings)
|
2024-07-15 16:04:06 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// validateUserUpdate validates the update operation for a user.
|
2024-11-19 21:42:27 +01:00
|
|
|
func validateUserUpdate(groupsMap map[string]*nbgroup.Group, initiatorUser, oldUser, update *User) error {
|
2024-07-15 16:04:06 +02:00
|
|
|
if initiatorUser.HasAdminPower() && initiatorUser.Id == update.Id && oldUser.Blocked != update.Blocked {
|
|
|
|
return status.Errorf(status.PermissionDenied, "admins can't block or unblock themselves")
|
|
|
|
}
|
|
|
|
if initiatorUser.HasAdminPower() && initiatorUser.Id == update.Id && update.Role != initiatorUser.Role {
|
|
|
|
return status.Errorf(status.PermissionDenied, "admins can't change their role")
|
|
|
|
}
|
|
|
|
if initiatorUser.Role == UserRoleAdmin && oldUser.Role == UserRoleOwner && update.Role != oldUser.Role {
|
|
|
|
return status.Errorf(status.PermissionDenied, "only owners can remove owner role from their user")
|
|
|
|
}
|
|
|
|
if initiatorUser.Role == UserRoleAdmin && oldUser.Role == UserRoleOwner && update.IsBlocked() && !oldUser.IsBlocked() {
|
|
|
|
return status.Errorf(status.PermissionDenied, "unable to block owner user")
|
|
|
|
}
|
|
|
|
if initiatorUser.Role == UserRoleAdmin && update.Role == UserRoleOwner && update.Role != oldUser.Role {
|
|
|
|
return status.Errorf(status.PermissionDenied, "only owners can add owner role to other users")
|
|
|
|
}
|
|
|
|
if oldUser.IsServiceUser && update.Role == UserRoleOwner {
|
|
|
|
return status.Errorf(status.PermissionDenied, "can't update a service user with owner role")
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, newGroupID := range update.AutoGroups {
|
2024-11-19 21:42:27 +01:00
|
|
|
group, ok := groupsMap[newGroupID]
|
2024-08-12 12:48:05 +02:00
|
|
|
if !ok {
|
2024-07-15 16:04:06 +02:00
|
|
|
return status.Errorf(status.InvalidArgument, "provided group ID %s in the user %s update doesn't exist",
|
|
|
|
newGroupID, update.Id)
|
|
|
|
}
|
2024-08-12 12:48:05 +02:00
|
|
|
if group.Name == "All" {
|
|
|
|
return status.Errorf(status.InvalidArgument, "can't add All group to the user")
|
|
|
|
}
|
2022-09-22 09:06:32 +02:00
|
|
|
}
|
2024-07-15 16:04:06 +02:00
|
|
|
|
|
|
|
return nil
|
2022-09-22 09:06:32 +02:00
|
|
|
}
|
|
|
|
|
2021-12-27 13:17:15 +01:00
|
|
|
// GetOrCreateAccountByUser returns an existing account for a given user id or creates a new one if doesn't exist
|
2024-07-03 11:33:02 +02:00
|
|
|
func (am *DefaultAccountManager) GetOrCreateAccountByUser(ctx context.Context, userID, domain string) (*Account, error) {
|
2024-05-23 17:09:58 +02:00
|
|
|
start := time.Now()
|
2024-07-03 11:33:02 +02:00
|
|
|
unlock := am.Store.AcquireGlobalLock(ctx)
|
2022-11-07 17:52:23 +01:00
|
|
|
defer unlock()
|
2024-07-03 11:33:02 +02:00
|
|
|
log.WithContext(ctx).Debugf("Acquired global lock in %s for user %s", time.Since(start), userID)
|
2021-12-27 13:17:15 +01:00
|
|
|
|
2022-03-01 15:22:18 +01:00
|
|
|
lowerDomain := strings.ToLower(domain)
|
|
|
|
|
2024-07-03 11:33:02 +02:00
|
|
|
account, err := am.Store.GetAccountByUser(ctx, userID)
|
2021-12-27 13:17:15 +01:00
|
|
|
if err != nil {
|
2022-11-11 20:36:45 +01:00
|
|
|
if s, ok := status.FromError(err); ok && s.Type() == status.NotFound {
|
2024-07-03 11:33:02 +02:00
|
|
|
account, err = am.newAccount(ctx, userID, lowerDomain)
|
2022-06-20 18:20:43 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2024-07-03 11:33:02 +02:00
|
|
|
err = am.Store.SaveAccount(ctx, account)
|
2021-12-27 13:17:15 +01:00
|
|
|
if err != nil {
|
2022-11-11 20:36:45 +01:00
|
|
|
return nil, err
|
2021-12-27 13:17:15 +01:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// other error
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-11-07 17:52:23 +01:00
|
|
|
userObj := account.Users[userID]
|
2022-06-20 18:20:43 +02:00
|
|
|
|
2024-07-10 14:08:35 +02:00
|
|
|
if lowerDomain != "" && account.Domain != lowerDomain && userObj.Role == UserRoleOwner {
|
2022-03-01 15:22:18 +01:00
|
|
|
account.Domain = lowerDomain
|
2024-07-03 11:33:02 +02:00
|
|
|
err = am.Store.SaveAccount(ctx, account)
|
2022-02-11 17:18:18 +01:00
|
|
|
if err != nil {
|
2022-11-11 20:36:45 +01:00
|
|
|
return nil, status.Errorf(status.Internal, "failed updating account with domain")
|
2022-02-11 17:18:18 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-27 13:17:15 +01:00
|
|
|
return account, nil
|
|
|
|
}
|
|
|
|
|
2022-11-05 10:24:50 +01:00
|
|
|
// GetUsersFromAccount performs a batched request for users from IDP by account ID apply filter on what data to return
|
|
|
|
// based on provided user role.
|
2024-07-03 11:33:02 +02:00
|
|
|
func (am *DefaultAccountManager) GetUsersFromAccount(ctx context.Context, accountID, userID string) ([]*UserInfo, error) {
|
2024-11-19 21:42:27 +01:00
|
|
|
accountUsers, err := am.Store.GetAccountUsers(ctx, LockingStrengthShare, accountID)
|
2022-09-22 09:06:32 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2024-11-19 21:42:27 +01:00
|
|
|
user, err := am.Store.GetUserByUserID(ctx, LockingStrengthShare, userID)
|
2022-11-05 10:24:50 +01:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2024-11-19 21:42:27 +01:00
|
|
|
if user.AccountID != accountID {
|
|
|
|
return nil, status.NewUserNotPartOfAccountError()
|
|
|
|
}
|
|
|
|
|
2022-09-22 09:06:32 +02:00
|
|
|
queriedUsers := make([]*idp.UserData, 0)
|
|
|
|
if !isNil(am.idpManager) {
|
2024-11-19 21:42:27 +01:00
|
|
|
users := make(map[string]userLoggedInOnce, len(accountUsers))
|
2023-11-13 14:04:18 +01:00
|
|
|
usersFromIntegration := make([]*idp.UserData, 0)
|
2024-11-19 21:42:27 +01:00
|
|
|
for _, user := range accountUsers {
|
2023-11-20 12:05:32 +01:00
|
|
|
if user.Issued == UserIssuedIntegration {
|
2023-11-13 14:04:18 +01:00
|
|
|
key := user.IntegrationReference.CacheKey(accountID, user.Id)
|
|
|
|
info, err := am.externalCacheManager.Get(am.ctx, key)
|
|
|
|
if err != nil {
|
2024-07-03 11:33:02 +02:00
|
|
|
log.WithContext(ctx).Infof("Get ExternalCache for key: %s, error: %s", key, err)
|
2024-04-22 11:10:27 +02:00
|
|
|
users[user.Id] = true
|
2023-11-13 14:04:18 +01:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
usersFromIntegration = append(usersFromIntegration, info)
|
|
|
|
continue
|
|
|
|
}
|
2023-04-22 12:57:51 +02:00
|
|
|
if !user.IsServiceUser {
|
2024-04-22 11:10:27 +02:00
|
|
|
users[user.Id] = userLoggedInOnce(!user.LastLogin.IsZero())
|
2023-04-22 12:57:51 +02:00
|
|
|
}
|
2022-10-13 18:26:31 +02:00
|
|
|
}
|
2024-07-03 11:33:02 +02:00
|
|
|
queriedUsers, err = am.lookupCache(ctx, users, accountID)
|
2022-09-22 09:06:32 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2024-07-03 11:33:02 +02:00
|
|
|
log.WithContext(ctx).Debugf("Got %d users from ExternalCache for account %s", len(usersFromIntegration), accountID)
|
|
|
|
log.WithContext(ctx).Debugf("Got %d users from InternalCache for account %s", len(queriedUsers), accountID)
|
2023-11-13 14:04:18 +01:00
|
|
|
queriedUsers = append(queriedUsers, usersFromIntegration...)
|
2022-09-22 09:06:32 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
userInfos := make([]*UserInfo, 0)
|
|
|
|
|
2024-11-19 21:42:27 +01:00
|
|
|
settings, err := am.Store.GetAccountSettings(ctx, LockingStrengthShare, accountID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2022-09-22 09:06:32 +02:00
|
|
|
// in case of self-hosted, or IDP doesn't return anything, we will return the locally stored userInfo
|
|
|
|
if len(queriedUsers) == 0 {
|
2024-11-19 21:42:27 +01:00
|
|
|
for _, accountUser := range accountUsers {
|
|
|
|
if user.IsRegularUser() && user.Id != accountUser.Id {
|
2022-11-05 10:24:50 +01:00
|
|
|
// if user is not an admin then show only current user and do not show other users
|
|
|
|
continue
|
|
|
|
}
|
2024-11-19 21:42:27 +01:00
|
|
|
info, err := accountUser.ToUserInfo(nil, settings)
|
2022-09-22 09:06:32 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
userInfos = append(userInfos, info)
|
|
|
|
}
|
|
|
|
return userInfos, nil
|
|
|
|
}
|
|
|
|
|
2024-11-19 21:42:27 +01:00
|
|
|
for _, localUser := range accountUsers {
|
|
|
|
if user.IsRegularUser() && user.Id != localUser.Id {
|
2022-11-05 10:24:50 +01:00
|
|
|
// if user is not an admin then show only current user and do not show other users
|
|
|
|
continue
|
|
|
|
}
|
2022-09-22 09:06:32 +02:00
|
|
|
|
2023-04-22 12:57:51 +02:00
|
|
|
var info *UserInfo
|
|
|
|
if queriedUser, contains := findUserInIDPUserdata(localUser.Id, queriedUsers); contains {
|
2024-11-19 21:42:27 +01:00
|
|
|
info, err = localUser.ToUserInfo(queriedUser, settings)
|
2022-09-22 09:06:32 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2023-04-22 12:57:51 +02:00
|
|
|
} else {
|
|
|
|
name := ""
|
|
|
|
if localUser.IsServiceUser {
|
|
|
|
name = localUser.ServiceUserName
|
|
|
|
}
|
2024-03-27 16:11:45 +01:00
|
|
|
|
|
|
|
dashboardViewPermissions := "full"
|
|
|
|
if !localUser.HasAdminPower() {
|
|
|
|
dashboardViewPermissions = "limited"
|
2024-11-19 21:42:27 +01:00
|
|
|
if settings.RegularUsersViewBlocked {
|
2024-03-27 16:11:45 +01:00
|
|
|
dashboardViewPermissions = "blocked"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-22 12:57:51 +02:00
|
|
|
info = &UserInfo{
|
|
|
|
ID: localUser.Id,
|
|
|
|
Email: "",
|
|
|
|
Name: name,
|
|
|
|
Role: string(localUser.Role),
|
|
|
|
AutoGroups: localUser.AutoGroups,
|
|
|
|
Status: string(UserStatusActive),
|
|
|
|
IsServiceUser: localUser.IsServiceUser,
|
2023-11-15 16:22:00 +01:00
|
|
|
NonDeletable: localUser.NonDeletable,
|
2024-03-27 16:11:45 +01:00
|
|
|
Permissions: UserPermissions{DashboardView: dashboardViewPermissions},
|
2023-04-22 12:57:51 +02:00
|
|
|
}
|
2022-09-22 09:06:32 +02:00
|
|
|
}
|
2023-04-22 12:57:51 +02:00
|
|
|
userInfos = append(userInfos, info)
|
2022-09-22 09:06:32 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return userInfos, nil
|
|
|
|
}
|
2023-04-22 12:57:51 +02:00
|
|
|
|
2023-09-19 18:08:40 +02:00
|
|
|
// expireAndUpdatePeers expires all peers of the given user and updates them in the account
|
2024-11-14 17:33:57 +01:00
|
|
|
func (am *DefaultAccountManager) expireAndUpdatePeers(ctx context.Context, accountID string, peers []*nbpeer.Peer) error {
|
2023-09-19 18:08:40 +02:00
|
|
|
var peerIDs []string
|
|
|
|
for _, peer := range peers {
|
2024-11-12 14:19:22 +01:00
|
|
|
// nolint:staticcheck
|
|
|
|
ctx = context.WithValue(ctx, nbContext.PeerIDKey, peer.Key)
|
|
|
|
|
2023-09-29 17:37:04 +02:00
|
|
|
if peer.Status.LoginExpired {
|
|
|
|
continue
|
|
|
|
}
|
2023-09-19 18:08:40 +02:00
|
|
|
peerIDs = append(peerIDs, peer.ID)
|
|
|
|
peer.MarkLoginExpired(true)
|
2024-11-12 14:19:22 +01:00
|
|
|
|
2024-11-14 17:33:57 +01:00
|
|
|
if err := am.Store.SavePeerStatus(ctx, LockingStrengthUpdate, accountID, peer.ID, *peer.Status); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2023-11-08 11:35:37 +01:00
|
|
|
am.StoreEvent(
|
2024-07-03 11:33:02 +02:00
|
|
|
ctx,
|
2024-11-14 17:33:57 +01:00
|
|
|
peer.UserID, peer.ID, accountID,
|
2023-09-19 18:08:40 +02:00
|
|
|
activity.PeerLoginExpired, peer.EventMeta(am.GetDNSDomain()),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(peerIDs) != 0 {
|
|
|
|
// this will trigger peer disconnect from the management service
|
2024-07-03 11:33:02 +02:00
|
|
|
am.peersUpdateManager.CloseChannels(ctx, peerIDs)
|
2024-11-14 17:33:57 +01:00
|
|
|
am.updateAccountPeers(ctx, accountID)
|
2023-09-19 18:08:40 +02:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-07-03 11:33:02 +02:00
|
|
|
func (am *DefaultAccountManager) deleteUserFromIDP(ctx context.Context, targetUserID, accountID string) error {
|
2023-09-19 18:08:40 +02:00
|
|
|
if am.userDeleteFromIDPEnabled {
|
2024-07-03 11:33:02 +02:00
|
|
|
log.WithContext(ctx).Debugf("user %s deleted from IdP", targetUserID)
|
|
|
|
err := am.idpManager.DeleteUser(ctx, targetUserID)
|
2023-09-19 18:08:40 +02:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("failed to delete user %s from IdP: %s", targetUserID, err)
|
|
|
|
}
|
|
|
|
} else {
|
2024-07-03 11:33:02 +02:00
|
|
|
err := am.idpManager.UpdateUserAppMetadata(ctx, targetUserID, idp.AppMetadata{})
|
2023-09-19 18:08:40 +02:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("failed to remove user %s app metadata in IdP: %s", targetUserID, err)
|
|
|
|
}
|
2024-01-01 19:17:44 +01:00
|
|
|
}
|
2024-07-03 11:33:02 +02:00
|
|
|
err := am.removeUserFromCache(ctx, accountID, targetUserID)
|
2024-01-01 19:17:44 +01:00
|
|
|
if err != nil {
|
2024-07-03 11:33:02 +02:00
|
|
|
log.WithContext(ctx).Errorf("remove user from account (%q) cache failed with error: %v", accountID, err)
|
2023-09-19 18:08:40 +02:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-07-03 11:33:02 +02:00
|
|
|
func (am *DefaultAccountManager) getEmailAndNameOfTargetUser(ctx context.Context, accountId, initiatorId, targetId string) (string, string, error) {
|
|
|
|
userInfos, err := am.GetUsersFromAccount(ctx, accountId, initiatorId)
|
2023-09-19 18:08:40 +02:00
|
|
|
if err != nil {
|
2023-09-23 10:47:49 +02:00
|
|
|
return "", "", err
|
2023-09-19 18:08:40 +02:00
|
|
|
}
|
|
|
|
for _, ui := range userInfos {
|
|
|
|
if ui.ID == targetId {
|
2023-09-23 10:47:49 +02:00
|
|
|
return ui.Email, ui.Name, nil
|
2023-09-19 18:08:40 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-09-23 10:47:49 +02:00
|
|
|
return "", "", fmt.Errorf("user info not found for user: %s", targetId)
|
2023-09-19 18:08:40 +02:00
|
|
|
}
|
|
|
|
|
2024-08-08 17:01:38 +02:00
|
|
|
// DeleteRegularUsers deletes regular users from an account.
|
|
|
|
// Note: This function does not acquire the global lock.
|
|
|
|
// It is the caller's responsibility to ensure proper locking is in place before invoking this method.
|
|
|
|
//
|
|
|
|
// If an error occurs while deleting the user, the function skips it and continues deleting other users.
|
|
|
|
// Errors are collected and returned at the end.
|
|
|
|
func (am *DefaultAccountManager) DeleteRegularUsers(ctx context.Context, accountID, initiatorUserID string, targetUserIDs []string) error {
|
2024-11-19 21:42:27 +01:00
|
|
|
initiatorUser, err := am.Store.GetUserByUserID(ctx, LockingStrengthShare, initiatorUserID)
|
2024-08-08 17:01:38 +02:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2024-11-19 21:42:27 +01:00
|
|
|
if !initiatorUser.HasAdminPower() {
|
|
|
|
return status.NewAdminPermissionError()
|
2024-08-08 17:01:38 +02:00
|
|
|
}
|
|
|
|
|
2024-11-19 21:42:27 +01:00
|
|
|
var allErrors error
|
|
|
|
var updateAccountPeers bool
|
2024-08-08 17:01:38 +02:00
|
|
|
|
|
|
|
for _, targetUserID := range targetUserIDs {
|
|
|
|
if initiatorUserID == targetUserID {
|
|
|
|
allErrors = errors.Join(allErrors, errors.New("self deletion is not allowed"))
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2024-11-19 21:42:27 +01:00
|
|
|
targetUser, err := am.Store.GetUserByUserID(ctx, LockingStrengthShare, targetUserID)
|
|
|
|
if err != nil {
|
|
|
|
allErrors = errors.Join(allErrors, err)
|
2024-08-08 17:01:38 +02:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if targetUser.Role == UserRoleOwner {
|
|
|
|
allErrors = errors.Join(allErrors, fmt.Errorf("unable to delete a user: %s with owner role", targetUserID))
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// disable deleting integration user if the initiator is not admin service user
|
2024-11-19 21:42:27 +01:00
|
|
|
if targetUser.Issued == UserIssuedIntegration && !initiatorUser.IsServiceUser {
|
2024-08-08 17:01:38 +02:00
|
|
|
allErrors = errors.Join(allErrors, errors.New("only integration service user can delete this user"))
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2024-11-19 21:42:27 +01:00
|
|
|
userHadPeers, err := am.deleteRegularUser(ctx, accountID, initiatorUserID, targetUserID)
|
2024-08-08 17:01:38 +02:00
|
|
|
if err != nil {
|
2024-11-19 21:42:27 +01:00
|
|
|
allErrors = errors.Join(allErrors, err)
|
2024-08-08 17:01:38 +02:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2024-11-19 21:42:27 +01:00
|
|
|
if userHadPeers {
|
2024-10-23 12:05:02 +02:00
|
|
|
updateAccountPeers = true
|
|
|
|
}
|
2024-08-08 17:01:38 +02:00
|
|
|
}
|
|
|
|
|
2024-10-23 12:05:02 +02:00
|
|
|
if updateAccountPeers {
|
2024-11-08 16:38:32 +01:00
|
|
|
am.updateAccountPeers(ctx, accountID)
|
2024-10-23 12:05:02 +02:00
|
|
|
}
|
2024-08-08 17:01:38 +02:00
|
|
|
|
|
|
|
return allErrors
|
|
|
|
}
|
|
|
|
|
2024-11-19 21:42:27 +01:00
|
|
|
// deleteRegularUser deletes a specified user and their related peers from the account.
|
|
|
|
func (am *DefaultAccountManager) deleteRegularUser(ctx context.Context, accountID, initiatorUserID, targetUserID string) (bool, error) {
|
|
|
|
tuEmail, tuName, err := am.getEmailAndNameOfTargetUser(ctx, accountID, initiatorUserID, targetUserID)
|
2024-08-08 17:01:38 +02:00
|
|
|
if err != nil {
|
|
|
|
log.WithContext(ctx).Errorf("failed to resolve email address: %s", err)
|
2024-11-19 21:42:27 +01:00
|
|
|
return false, err
|
2024-08-08 17:01:38 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if !isNil(am.idpManager) {
|
|
|
|
// Delete if the user already exists in the IdP. Necessary in cases where a user account
|
|
|
|
// was created where a user account was provisioned but the user did not sign in
|
2024-11-19 21:42:27 +01:00
|
|
|
_, err = am.idpManager.GetUserDataByID(ctx, targetUserID, idp.AppMetadata{WTAccountID: accountID})
|
2024-08-08 17:01:38 +02:00
|
|
|
if err == nil {
|
2024-11-19 21:42:27 +01:00
|
|
|
err = am.deleteUserFromIDP(ctx, targetUserID, accountID)
|
2024-08-08 17:01:38 +02:00
|
|
|
if err != nil {
|
|
|
|
log.WithContext(ctx).Debugf("failed to delete user from IDP: %s", targetUserID)
|
2024-11-19 21:42:27 +01:00
|
|
|
return false, err
|
2024-08-08 17:01:38 +02:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
log.WithContext(ctx).Debugf("skipped deleting user %s from IDP, error: %v", targetUserID, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-11-19 21:42:27 +01:00
|
|
|
var addPeerRemovedEvents []func()
|
|
|
|
var updateAccountPeers bool
|
|
|
|
var targetUser *User
|
|
|
|
|
|
|
|
err = am.Store.ExecuteInTransaction(ctx, func(transaction Store) error {
|
|
|
|
targetUser, err = transaction.GetUserByUserID(ctx, LockingStrengthShare, targetUserID)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("failed to get user to delete: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
userPeers, err := transaction.GetUserPeers(ctx, LockingStrengthShare, accountID, targetUserID)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("failed to get user peers: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(userPeers) > 0 {
|
|
|
|
updateAccountPeers = true
|
|
|
|
addPeerRemovedEvents, err = deletePeers(ctx, am, transaction, accountID, targetUserID, userPeers)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("failed to delete user peers: %w", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if err = transaction.DeleteUser(ctx, LockingStrengthUpdate, accountID, targetUserID); err != nil {
|
|
|
|
return fmt.Errorf("failed to delete user: %s %w", targetUserID, err)
|
|
|
|
}
|
2024-08-08 17:01:38 +02:00
|
|
|
|
2024-11-19 21:42:27 +01:00
|
|
|
return nil
|
|
|
|
})
|
2024-08-08 17:01:38 +02:00
|
|
|
if err != nil {
|
2024-11-19 21:42:27 +01:00
|
|
|
return false, err
|
2024-08-08 17:01:38 +02:00
|
|
|
}
|
|
|
|
|
2024-11-19 21:42:27 +01:00
|
|
|
for _, addPeerRemovedEvent := range addPeerRemovedEvents {
|
|
|
|
addPeerRemovedEvent()
|
2024-08-08 17:01:38 +02:00
|
|
|
}
|
2024-11-19 21:42:27 +01:00
|
|
|
meta := map[string]any{"name": tuName, "email": tuEmail, "created_at": targetUser.CreatedAt}
|
|
|
|
am.StoreEvent(ctx, initiatorUserID, targetUser.Id, accountID, activity.UserDeleted, meta)
|
2024-08-08 17:01:38 +02:00
|
|
|
|
2024-11-19 21:42:27 +01:00
|
|
|
return updateAccountPeers, nil
|
2024-08-08 17:01:38 +02:00
|
|
|
}
|
|
|
|
|
2024-10-04 16:17:01 +02:00
|
|
|
// updateUserPeersInGroups updates the user's peers in the specified groups by adding or removing them.
|
|
|
|
func (am *DefaultAccountManager) updateUserPeersInGroups(accountGroups map[string]*nbgroup.Group, peers []*nbpeer.Peer, groupsToAdd,
|
|
|
|
groupsToRemove []string) (groupsToUpdate []*nbgroup.Group, err error) {
|
|
|
|
|
|
|
|
if len(groupsToAdd) == 0 && len(groupsToRemove) == 0 {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
userPeerIDMap := make(map[string]struct{}, len(peers))
|
|
|
|
for _, peer := range peers {
|
|
|
|
userPeerIDMap[peer.ID] = struct{}{}
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, gid := range groupsToAdd {
|
|
|
|
group, ok := accountGroups[gid]
|
|
|
|
if !ok {
|
|
|
|
return nil, errors.New("group not found")
|
|
|
|
}
|
|
|
|
addUserPeersToGroup(userPeerIDMap, group)
|
|
|
|
groupsToUpdate = append(groupsToUpdate, group)
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, gid := range groupsToRemove {
|
|
|
|
group, ok := accountGroups[gid]
|
|
|
|
if !ok {
|
|
|
|
return nil, errors.New("group not found")
|
|
|
|
}
|
|
|
|
removeUserPeersFromGroup(userPeerIDMap, group)
|
|
|
|
groupsToUpdate = append(groupsToUpdate, group)
|
|
|
|
}
|
|
|
|
|
|
|
|
return groupsToUpdate, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// addUserPeersToGroup adds the user's peers to the group.
|
|
|
|
func addUserPeersToGroup(userPeerIDs map[string]struct{}, group *nbgroup.Group) {
|
|
|
|
groupPeers := make(map[string]struct{}, len(group.Peers))
|
|
|
|
for _, pid := range group.Peers {
|
|
|
|
groupPeers[pid] = struct{}{}
|
|
|
|
}
|
|
|
|
|
|
|
|
for pid := range userPeerIDs {
|
|
|
|
groupPeers[pid] = struct{}{}
|
|
|
|
}
|
|
|
|
|
|
|
|
group.Peers = make([]string, 0, len(groupPeers))
|
|
|
|
for pid := range groupPeers {
|
|
|
|
group.Peers = append(group.Peers, pid)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// removeUserPeersFromGroup removes user's peers from the group.
|
|
|
|
func removeUserPeersFromGroup(userPeerIDs map[string]struct{}, group *nbgroup.Group) {
|
|
|
|
// skip removing peers from group All
|
|
|
|
if group.Name == "All" {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
updatedPeers := make([]string, 0, len(group.Peers))
|
|
|
|
for _, pid := range group.Peers {
|
|
|
|
if _, found := userPeerIDs[pid]; !found {
|
|
|
|
updatedPeers = append(updatedPeers, pid)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
group.Peers = updatedPeers
|
|
|
|
}
|
|
|
|
|
2023-04-22 12:57:51 +02:00
|
|
|
func findUserInIDPUserdata(userID string, userData []*idp.UserData) (*idp.UserData, bool) {
|
|
|
|
for _, user := range userData {
|
|
|
|
if user.ID == userID {
|
|
|
|
return user, true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil, false
|
|
|
|
}
|
2024-10-23 12:05:02 +02:00
|
|
|
|
2024-11-19 21:42:27 +01:00
|
|
|
func validateUserInvite(invite *UserInfo) error {
|
|
|
|
if invite == nil {
|
|
|
|
return fmt.Errorf("provided user update is nil")
|
2024-10-23 12:05:02 +02:00
|
|
|
}
|
2024-11-19 21:42:27 +01:00
|
|
|
|
|
|
|
invitedRole := StrRoleToUserRole(invite.Role)
|
|
|
|
|
|
|
|
switch {
|
|
|
|
case invite.Name == "":
|
|
|
|
return status.Errorf(status.InvalidArgument, "name can't be empty")
|
|
|
|
case invite.Email == "":
|
|
|
|
return status.Errorf(status.InvalidArgument, "email can't be empty")
|
|
|
|
case invitedRole == UserRoleOwner:
|
|
|
|
return status.Errorf(status.InvalidArgument, "can't invite a user with owner role")
|
|
|
|
default:
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
2024-10-23 12:05:02 +02:00
|
|
|
}
|