2021-12-27 13:17:15 +01:00
|
|
|
package server
|
|
|
|
|
|
|
|
import (
|
2022-05-25 18:26:50 +02:00
|
|
|
"fmt"
|
2023-03-01 20:12:04 +01:00
|
|
|
"strings"
|
|
|
|
|
2023-04-22 12:57:51 +02:00
|
|
|
"github.com/google/uuid"
|
2023-03-01 20:12:04 +01:00
|
|
|
log "github.com/sirupsen/logrus"
|
|
|
|
|
2023-01-02 15:11:32 +01:00
|
|
|
"github.com/netbirdio/netbird/management/server/activity"
|
2022-09-22 09:06:32 +02:00
|
|
|
"github.com/netbirdio/netbird/management/server/idp"
|
2023-05-02 16:49:29 +02:00
|
|
|
"github.com/netbirdio/netbird/management/server/jwtclaims"
|
2022-11-11 20:36:45 +01:00
|
|
|
"github.com/netbirdio/netbird/management/server/status"
|
2021-12-27 13:17:15 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
2022-09-23 14:18:42 +02:00
|
|
|
UserRoleAdmin UserRole = "admin"
|
|
|
|
UserRoleUser UserRole = "user"
|
|
|
|
UserRoleUnknown UserRole = "unknown"
|
2022-10-13 18:26:31 +02:00
|
|
|
|
|
|
|
UserStatusActive UserStatus = "active"
|
|
|
|
UserStatusDisabled UserStatus = "disabled"
|
|
|
|
UserStatusInvited UserStatus = "invited"
|
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) {
|
|
|
|
case "admin":
|
|
|
|
return UserRoleAdmin
|
|
|
|
case "user":
|
|
|
|
return UserRoleUser
|
|
|
|
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-04-22 12:57:51 +02:00
|
|
|
Id string
|
|
|
|
Role UserRole
|
|
|
|
IsServiceUser bool
|
|
|
|
// 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
|
|
|
|
AutoGroups []string
|
2023-03-20 16:14:55 +01:00
|
|
|
PATs map[string]*PersonalAccessToken
|
2023-05-11 18:09:36 +02:00
|
|
|
// Blocked indicates whether the user is blocked. Blocked users can't use the system.
|
|
|
|
Blocked bool
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
// IsAdmin returns true if the user is an admin, false otherwise
|
2022-11-05 10:24:50 +01:00
|
|
|
func (u *User) IsAdmin() bool {
|
|
|
|
return u.Role == UserRoleAdmin
|
|
|
|
}
|
|
|
|
|
2023-05-11 18:09:36 +02:00
|
|
|
// ToUserInfo converts a User object to a UserInfo object.
|
|
|
|
func (u *User) ToUserInfo(userData *idp.UserData) (*UserInfo, error) {
|
2022-09-22 09:06:32 +02:00
|
|
|
autoGroups := u.AutoGroups
|
|
|
|
if autoGroups == nil {
|
|
|
|
autoGroups = []string{}
|
|
|
|
}
|
|
|
|
|
|
|
|
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,
|
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,
|
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 {
|
|
|
|
patCopy := new(PersonalAccessToken)
|
|
|
|
*patCopy = *v
|
|
|
|
pats[k] = patCopy
|
|
|
|
}
|
2021-12-27 13:17:15 +01:00
|
|
|
return &User{
|
2023-04-22 12:57:51 +02:00
|
|
|
Id: u.Id,
|
|
|
|
Role: u.Role,
|
|
|
|
AutoGroups: autoGroups,
|
|
|
|
IsServiceUser: u.IsServiceUser,
|
|
|
|
ServiceUserName: u.ServiceUserName,
|
|
|
|
PATs: pats,
|
2023-05-11 18:09:36 +02:00
|
|
|
Blocked: u.Blocked,
|
2021-12-27 13:17:15 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewUser creates a new user
|
2023-04-22 12:57:51 +02:00
|
|
|
func NewUser(id string, role UserRole, isServiceUser bool, serviceUserName string, autoGroups []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,
|
|
|
|
ServiceUserName: serviceUserName,
|
|
|
|
AutoGroups: autoGroups,
|
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-04-22 12:57:51 +02:00
|
|
|
return NewUser(id, UserRoleUser, false, "", []string{})
|
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-04-22 12:57:51 +02:00
|
|
|
return NewUser(id, UserRoleAdmin, false, "", []string{})
|
|
|
|
}
|
|
|
|
|
|
|
|
// createServiceUser creates a new service user under the given account.
|
2023-05-11 18:09:36 +02:00
|
|
|
func (am *DefaultAccountManager) createServiceUser(accountID string, initiatorUserID string, role UserRole, serviceUserName string, autoGroups []string) (*UserInfo, error) {
|
2023-04-22 12:57:51 +02:00
|
|
|
unlock := am.Store.AcquireAccountLock(accountID)
|
|
|
|
defer unlock()
|
|
|
|
|
|
|
|
account, err := am.Store.GetAccount(accountID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, status.Errorf(status.NotFound, "account %s doesn't exist", accountID)
|
|
|
|
}
|
|
|
|
|
2023-05-11 18:09:36 +02:00
|
|
|
executingUser := account.Users[initiatorUserID]
|
2023-04-22 12:57:51 +02:00
|
|
|
if executingUser == nil {
|
|
|
|
return nil, status.Errorf(status.NotFound, "user not found")
|
|
|
|
}
|
|
|
|
if executingUser.Role != UserRoleAdmin {
|
|
|
|
return nil, status.Errorf(status.PermissionDenied, "only admins can create service users")
|
|
|
|
}
|
|
|
|
|
|
|
|
newUserID := uuid.New().String()
|
|
|
|
newUser := NewUser(newUserID, role, true, serviceUserName, autoGroups)
|
|
|
|
log.Debugf("New User: %v", newUser)
|
|
|
|
account.Users[newUserID] = newUser
|
|
|
|
|
|
|
|
err = am.Store.SaveAccount(account)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
meta := map[string]any{"name": newUser.ServiceUserName}
|
2023-05-11 18:09:36 +02:00
|
|
|
am.storeEvent(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,
|
|
|
|
}, 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.
|
2023-04-22 12:57:51 +02:00
|
|
|
func (am *DefaultAccountManager) CreateUser(accountID, userID string, user *UserInfo) (*UserInfo, error) {
|
|
|
|
if user.IsServiceUser {
|
|
|
|
return am.createServiceUser(accountID, userID, StrRoleToUserRole(user.Role), user.Name, user.AutoGroups)
|
|
|
|
}
|
|
|
|
return am.inviteNewUser(accountID, userID, user)
|
|
|
|
}
|
|
|
|
|
|
|
|
// inviteNewUser Invites a USer to a given account and creates reference in datastore
|
|
|
|
func (am *DefaultAccountManager) inviteNewUser(accountID, userID string, invite *UserInfo) (*UserInfo, error) {
|
2022-11-07 17:52:23 +01:00
|
|
|
unlock := am.Store.AcquireAccountLock(accountID)
|
|
|
|
defer unlock()
|
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
|
|
|
}
|
|
|
|
|
|
|
|
if invite == nil {
|
|
|
|
return nil, fmt.Errorf("provided user update is nil")
|
|
|
|
}
|
|
|
|
|
|
|
|
account, err := am.Store.GetAccount(accountID)
|
|
|
|
if err != nil {
|
2022-11-11 20:36:45 +01:00
|
|
|
return nil, status.Errorf(status.NotFound, "account %s doesn't exist", accountID)
|
2022-10-13 18:26:31 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// check if the user is already registered with this email => reject
|
|
|
|
user, err := am.lookupUserInCacheByEmail(invite.Email, accountID)
|
|
|
|
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
|
|
|
}
|
|
|
|
|
|
|
|
users, err := am.idpManager.GetUserByEmail(invite.Email)
|
|
|
|
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
|
|
|
}
|
|
|
|
|
|
|
|
idpUser, err := am.idpManager.CreateUser(invite.Email, invite.Name, accountID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
role := StrRoleToUserRole(invite.Role)
|
|
|
|
newUser := &User{
|
|
|
|
Id: idpUser.ID,
|
|
|
|
Role: role,
|
|
|
|
AutoGroups: invite.AutoGroups,
|
|
|
|
}
|
|
|
|
account.Users[idpUser.ID] = newUser
|
|
|
|
|
|
|
|
err = am.Store.SaveAccount(account)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
_, err = am.refreshCache(account.Id)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2023-01-24 10:17:24 +01:00
|
|
|
am.storeEvent(userID, newUser.Id, accountID, activity.UserInvited, nil)
|
2023-01-02 15:11:32 +01:00
|
|
|
|
2023-05-11 18:09:36 +02:00
|
|
|
return newUser.ToUserInfo(idpUser)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetUser looks up a user by provided authorization claims.
|
|
|
|
// It will also create an account if didn't exist for this user before.
|
|
|
|
func (am *DefaultAccountManager) GetUser(claims jwtclaims.AuthorizationClaims) (*User, error) {
|
|
|
|
account, _, err := am.GetAccountFromToken(claims)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("failed to get account with token claims %v", err)
|
|
|
|
}
|
2022-10-13 18:26:31 +02:00
|
|
|
|
2023-05-11 18:09:36 +02:00
|
|
|
user, ok := account.Users[claims.UserId]
|
|
|
|
if !ok {
|
|
|
|
return nil, status.Errorf(status.NotFound, "user not found")
|
|
|
|
}
|
|
|
|
return user, nil
|
2022-10-13 18:26:31 +02:00
|
|
|
}
|
|
|
|
|
2023-04-22 12:57:51 +02:00
|
|
|
// DeleteUser deletes a user from the given account.
|
2023-05-11 18:09:36 +02:00
|
|
|
func (am *DefaultAccountManager) DeleteUser(accountID, initiatorUserID string, targetUserID string) error {
|
2023-04-22 12:57:51 +02:00
|
|
|
unlock := am.Store.AcquireAccountLock(accountID)
|
|
|
|
defer unlock()
|
|
|
|
|
|
|
|
account, err := am.Store.GetAccount(accountID)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
targetUser := account.Users[targetUserID]
|
|
|
|
if targetUser == nil {
|
|
|
|
return status.Errorf(status.NotFound, "user not found")
|
|
|
|
}
|
|
|
|
|
2023-05-11 18:09:36 +02:00
|
|
|
executingUser := account.Users[initiatorUserID]
|
2023-04-22 12:57:51 +02:00
|
|
|
if executingUser == nil {
|
|
|
|
return status.Errorf(status.NotFound, "user not found")
|
|
|
|
}
|
|
|
|
if executingUser.Role != UserRoleAdmin {
|
|
|
|
return status.Errorf(status.PermissionDenied, "only admins can delete service users")
|
|
|
|
}
|
|
|
|
|
|
|
|
if !targetUser.IsServiceUser {
|
|
|
|
return status.Errorf(status.PermissionDenied, "regular users can not be deleted")
|
|
|
|
}
|
|
|
|
|
|
|
|
meta := map[string]any{"name": targetUser.ServiceUserName}
|
2023-05-11 18:09:36 +02:00
|
|
|
am.storeEvent(initiatorUserID, targetUserID, accountID, activity.ServiceUserDeleted, meta)
|
2023-04-22 12:57:51 +02:00
|
|
|
|
|
|
|
delete(account.Users, targetUserID)
|
|
|
|
|
|
|
|
err = am.Store.SaveAccount(account)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-03-30 13:58:44 +02:00
|
|
|
// CreatePAT creates a new PAT for the given user
|
2023-05-11 18:09:36 +02:00
|
|
|
func (am *DefaultAccountManager) CreatePAT(accountID string, initiatorUserID string, targetUserID string, tokenName string, expiresIn int) (*PersonalAccessTokenGenerated, error) {
|
2023-03-16 15:57:44 +01:00
|
|
|
unlock := am.Store.AcquireAccountLock(accountID)
|
|
|
|
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")
|
|
|
|
}
|
|
|
|
|
2023-03-16 15:57:44 +01:00
|
|
|
account, err := am.Store.GetAccount(accountID)
|
|
|
|
if err != nil {
|
2023-03-30 13:58:44 +02:00
|
|
|
return nil, err
|
2023-03-16 15:57:44 +01:00
|
|
|
}
|
|
|
|
|
2023-04-22 12:57:51 +02:00
|
|
|
targetUser := account.Users[targetUserID]
|
2023-03-30 13:58:44 +02:00
|
|
|
if targetUser == nil {
|
|
|
|
return nil, status.Errorf(status.NotFound, "targetUser not found")
|
2023-03-16 15:57:44 +01:00
|
|
|
}
|
|
|
|
|
2023-05-11 18:09:36 +02:00
|
|
|
executingUser := account.Users[initiatorUserID]
|
2023-04-22 12:57:51 +02:00
|
|
|
if targetUser == nil {
|
|
|
|
return nil, status.Errorf(status.NotFound, "user not found")
|
|
|
|
}
|
|
|
|
|
2023-05-11 18:09:36 +02:00
|
|
|
if !(initiatorUserID == targetUserID || (executingUser.IsAdmin() && targetUser.IsServiceUser)) {
|
2023-04-22 12:57:51 +02:00
|
|
|
return nil, status.Errorf(status.PermissionDenied, "no permission to create PAT for this user")
|
|
|
|
}
|
|
|
|
|
|
|
|
pat, err := CreateNewPAT(tokenName, expiresIn, executingUser.Id)
|
2023-03-30 13:58:44 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, status.Errorf(status.Internal, "failed to create PAT: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
targetUser.PATs[pat.ID] = &pat.PersonalAccessToken
|
|
|
|
|
|
|
|
err = am.Store.SaveAccount(account)
|
|
|
|
if err != nil {
|
|
|
|
return nil, status.Errorf(status.Internal, "failed to save account: %v", err)
|
|
|
|
}
|
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}
|
2023-05-11 18:09:36 +02:00
|
|
|
am.storeEvent(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
|
2023-05-11 18:09:36 +02:00
|
|
|
func (am *DefaultAccountManager) DeletePAT(accountID string, initiatorUserID string, targetUserID string, tokenID string) error {
|
2023-03-20 16:14:55 +01:00
|
|
|
unlock := am.Store.AcquireAccountLock(accountID)
|
|
|
|
defer unlock()
|
|
|
|
|
|
|
|
account, err := am.Store.GetAccount(accountID)
|
|
|
|
if err != nil {
|
2023-03-30 13:58:44 +02:00
|
|
|
return status.Errorf(status.NotFound, "account not found: %s", err)
|
2023-03-20 16:14:55 +01:00
|
|
|
}
|
|
|
|
|
2023-04-22 12:57:51 +02:00
|
|
|
targetUser := account.Users[targetUserID]
|
|
|
|
if targetUser == nil {
|
2023-03-20 16:14:55 +01:00
|
|
|
return status.Errorf(status.NotFound, "user not found")
|
|
|
|
}
|
|
|
|
|
2023-05-11 18:09:36 +02:00
|
|
|
executingUser := account.Users[initiatorUserID]
|
2023-04-22 12:57:51 +02:00
|
|
|
if targetUser == nil {
|
|
|
|
return status.Errorf(status.NotFound, "user not found")
|
|
|
|
}
|
|
|
|
|
2023-05-11 18:09:36 +02:00
|
|
|
if !(initiatorUserID == targetUserID || (executingUser.IsAdmin() && targetUser.IsServiceUser)) {
|
2023-04-22 12:57:51 +02:00
|
|
|
return status.Errorf(status.PermissionDenied, "no permission to delete PAT for this user")
|
|
|
|
}
|
|
|
|
|
|
|
|
pat := targetUser.PATs[tokenID]
|
2023-03-20 16:14:55 +01:00
|
|
|
if pat == nil {
|
|
|
|
return status.Errorf(status.NotFound, "PAT not found")
|
|
|
|
}
|
|
|
|
|
|
|
|
err = am.Store.DeleteTokenID2UserIDIndex(pat.ID)
|
|
|
|
if err != nil {
|
2023-03-30 13:58:44 +02:00
|
|
|
return status.Errorf(status.Internal, "Failed to delete token id index: %s", err)
|
2023-03-20 16:14:55 +01:00
|
|
|
}
|
|
|
|
err = am.Store.DeleteHashedPAT2TokenIDIndex(pat.HashedToken)
|
|
|
|
if err != nil {
|
2023-03-30 13:58:44 +02:00
|
|
|
return status.Errorf(status.Internal, "Failed to delete hashed token index: %s", 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}
|
2023-05-11 18:09:36 +02:00
|
|
|
am.storeEvent(initiatorUserID, targetUserID, accountID, activity.PersonalAccessTokenDeleted, meta)
|
2023-03-31 17:41:22 +02:00
|
|
|
|
2023-04-22 12:57:51 +02:00
|
|
|
delete(targetUser.PATs, tokenID)
|
2023-03-16 15:57:44 +01:00
|
|
|
|
2023-03-30 13:58:44 +02:00
|
|
|
err = am.Store.SaveAccount(account)
|
|
|
|
if err != nil {
|
|
|
|
return status.Errorf(status.Internal, "Failed to save account: %s", err)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetPAT returns a specific PAT from a user
|
2023-05-11 18:09:36 +02:00
|
|
|
func (am *DefaultAccountManager) GetPAT(accountID string, initiatorUserID string, targetUserID string, tokenID string) (*PersonalAccessToken, error) {
|
2023-03-30 13:58:44 +02:00
|
|
|
unlock := am.Store.AcquireAccountLock(accountID)
|
|
|
|
defer unlock()
|
|
|
|
|
|
|
|
account, err := am.Store.GetAccount(accountID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, status.Errorf(status.NotFound, "account not found: %s", err)
|
|
|
|
}
|
|
|
|
|
2023-04-22 12:57:51 +02:00
|
|
|
targetUser := account.Users[targetUserID]
|
|
|
|
if targetUser == nil {
|
2023-03-30 13:58:44 +02:00
|
|
|
return nil, status.Errorf(status.NotFound, "user not found")
|
|
|
|
}
|
|
|
|
|
2023-05-11 18:09:36 +02:00
|
|
|
executingUser := account.Users[initiatorUserID]
|
2023-04-22 12:57:51 +02:00
|
|
|
if targetUser == nil {
|
|
|
|
return nil, status.Errorf(status.NotFound, "user not found")
|
|
|
|
}
|
|
|
|
|
2023-05-11 18:09:36 +02:00
|
|
|
if !(initiatorUserID == targetUserID || (executingUser.IsAdmin() && targetUser.IsServiceUser)) {
|
2023-04-22 12:57:51 +02:00
|
|
|
return nil, status.Errorf(status.PermissionDenied, "no permission to get PAT for this userser")
|
|
|
|
}
|
|
|
|
|
|
|
|
pat := targetUser.PATs[tokenID]
|
2023-03-30 13:58:44 +02:00
|
|
|
if pat == nil {
|
|
|
|
return nil, status.Errorf(status.NotFound, "PAT not found")
|
|
|
|
}
|
|
|
|
|
|
|
|
return pat, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetAllPATs returns all PATs for a user
|
2023-05-11 18:09:36 +02:00
|
|
|
func (am *DefaultAccountManager) GetAllPATs(accountID string, initiatorUserID string, targetUserID string) ([]*PersonalAccessToken, error) {
|
2023-03-30 13:58:44 +02:00
|
|
|
unlock := am.Store.AcquireAccountLock(accountID)
|
|
|
|
defer unlock()
|
|
|
|
|
|
|
|
account, err := am.Store.GetAccount(accountID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, status.Errorf(status.NotFound, "account not found: %s", err)
|
|
|
|
}
|
|
|
|
|
2023-04-22 12:57:51 +02:00
|
|
|
targetUser := account.Users[targetUserID]
|
|
|
|
if targetUser == nil {
|
2023-03-30 13:58:44 +02:00
|
|
|
return nil, status.Errorf(status.NotFound, "user not found")
|
|
|
|
}
|
|
|
|
|
2023-05-11 18:09:36 +02:00
|
|
|
executingUser := account.Users[initiatorUserID]
|
2023-04-22 12:57:51 +02:00
|
|
|
if targetUser == nil {
|
|
|
|
return nil, status.Errorf(status.NotFound, "user not found")
|
|
|
|
}
|
|
|
|
|
2023-05-11 18:09:36 +02:00
|
|
|
if !(initiatorUserID == targetUserID || (executingUser.IsAdmin() && targetUser.IsServiceUser)) {
|
2023-04-22 12:57:51 +02:00
|
|
|
return nil, status.Errorf(status.PermissionDenied, "no permission to get PAT for this user")
|
|
|
|
}
|
|
|
|
|
2023-03-30 13:58:44 +02:00
|
|
|
var pats []*PersonalAccessToken
|
2023-04-22 12:57:51 +02:00
|
|
|
for _, pat := range targetUser.PATs {
|
2023-03-30 13:58:44 +02:00
|
|
|
pats = append(pats, pat)
|
|
|
|
}
|
|
|
|
|
|
|
|
return pats, nil
|
2023-03-16 15:57:44 +01:00
|
|
|
}
|
|
|
|
|
2023-05-11 18:09:36 +02:00
|
|
|
// SaveUser saves updates to the given user. If the user doesn't exit it will throw status.NotFound error.
|
|
|
|
// Only User.AutoGroups, User.Role, and User.Blocked fields are allowed to be updated for now.
|
|
|
|
func (am *DefaultAccountManager) SaveUser(accountID, initiatorUserID string, update *User) (*UserInfo, error) {
|
2022-11-07 17:52:23 +01:00
|
|
|
unlock := am.Store.AcquireAccountLock(accountID)
|
|
|
|
defer unlock()
|
2022-09-22 09:06:32 +02:00
|
|
|
|
|
|
|
if update == nil {
|
2022-11-11 20:36:45 +01:00
|
|
|
return nil, status.Errorf(status.InvalidArgument, "provided user update is nil")
|
2022-09-22 09:06:32 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
account, err := am.Store.GetAccount(accountID)
|
|
|
|
if err != nil {
|
2022-11-11 20:36:45 +01:00
|
|
|
return nil, err
|
2022-09-22 09:06:32 +02:00
|
|
|
}
|
|
|
|
|
2023-05-11 18:09:36 +02:00
|
|
|
initiatorUser, err := account.FindUser(initiatorUserID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if !initiatorUser.IsAdmin() || initiatorUser.IsBlocked() {
|
|
|
|
return nil, status.Errorf(status.PermissionDenied, "only admins are authorized to perform user update operations")
|
2022-09-22 09:06:32 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
oldUser := account.Users[update.Id]
|
|
|
|
if oldUser == nil {
|
2023-05-11 18:09:36 +02:00
|
|
|
return nil, status.Errorf(status.NotFound, "user to update doesn't exist")
|
|
|
|
}
|
|
|
|
|
|
|
|
if initiatorUser.IsAdmin() && initiatorUserID == update.Id && oldUser.Blocked != update.Blocked {
|
|
|
|
return nil, status.Errorf(status.PermissionDenied, "admins can't block or unblock themselves")
|
|
|
|
}
|
|
|
|
|
|
|
|
if initiatorUser.IsAdmin() && initiatorUserID == update.Id && update.Role != UserRoleAdmin {
|
|
|
|
return nil, status.Errorf(status.PermissionDenied, "admins can't change their role")
|
2022-09-22 09:06:32 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// only auto groups, revoked status, and name can be updated for now
|
|
|
|
newUser := oldUser.Copy()
|
2022-09-23 14:18:42 +02:00
|
|
|
newUser.Role = update.Role
|
2023-05-11 18:09:36 +02:00
|
|
|
newUser.Blocked = update.Blocked
|
|
|
|
|
|
|
|
for _, newGroupID := range update.AutoGroups {
|
|
|
|
if _, ok := account.Groups[newGroupID]; !ok {
|
|
|
|
return nil, status.Errorf(status.InvalidArgument, "provided group ID %s in the user %s update doesn't exist",
|
|
|
|
newGroupID, update.Id)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
newUser.AutoGroups = update.AutoGroups
|
2022-09-22 09:06:32 +02:00
|
|
|
|
|
|
|
account.Users[newUser.Id] = newUser
|
|
|
|
|
2023-05-11 18:09:36 +02:00
|
|
|
if !oldUser.IsBlocked() && update.IsBlocked() {
|
|
|
|
// expire peers that belong to the user who's getting blocked
|
|
|
|
blockedPeers, err := account.FindUserPeers(update.Id)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
var peerIDs []string
|
|
|
|
for _, peer := range blockedPeers {
|
|
|
|
peerIDs = append(peerIDs, peer.ID)
|
|
|
|
peer.MarkLoginExpired(true)
|
|
|
|
account.UpdatePeer(peer)
|
|
|
|
err = am.Store.SavePeerStatus(account.Id, peer.ID, *peer.Status)
|
|
|
|
if err != nil {
|
|
|
|
log.Errorf("failed saving peer status while expiring peer %s", peer.ID)
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
am.peersUpdateManager.CloseChannels(peerIDs)
|
|
|
|
err = am.updateAccountPeers(account)
|
|
|
|
if err != nil {
|
|
|
|
log.Errorf("failed updating account peers while expiring peers of a blocked user %s", accountID)
|
|
|
|
return nil, err
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-09-22 09:06:32 +02:00
|
|
|
if err = am.Store.SaveAccount(account); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2023-01-02 15:11:32 +01:00
|
|
|
defer func() {
|
2023-05-11 18:09:36 +02:00
|
|
|
// store activity logs
|
2023-01-02 15:11:32 +01:00
|
|
|
if oldUser.Role != newUser.Role {
|
2023-05-11 18:09:36 +02:00
|
|
|
am.storeEvent(initiatorUserID, oldUser.Id, accountID, activity.UserRoleUpdated, map[string]any{"role": newUser.Role})
|
2023-01-02 15:11:32 +01:00
|
|
|
}
|
|
|
|
|
2023-05-11 18:09:36 +02:00
|
|
|
if update.AutoGroups != nil {
|
|
|
|
removedGroups := difference(oldUser.AutoGroups, update.AutoGroups)
|
|
|
|
addedGroups := difference(newUser.AutoGroups, oldUser.AutoGroups)
|
|
|
|
for _, g := range removedGroups {
|
|
|
|
group := account.GetGroup(g)
|
|
|
|
if group != nil {
|
|
|
|
am.storeEvent(initiatorUserID, oldUser.Id, accountID, activity.GroupRemovedFromUser,
|
|
|
|
map[string]any{"group": group.Name, "group_id": group.ID, "is_service_user": newUser.IsServiceUser, "user_name": newUser.ServiceUserName})
|
|
|
|
} else {
|
|
|
|
log.Errorf("group %s not found while saving user activity event of account %s", g, account.Id)
|
|
|
|
}
|
2023-01-02 15:11:32 +01:00
|
|
|
|
2023-05-11 18:09:36 +02:00
|
|
|
}
|
2023-01-02 15:11:32 +01:00
|
|
|
|
2023-05-11 18:09:36 +02:00
|
|
|
for _, g := range addedGroups {
|
|
|
|
group := account.GetGroup(g)
|
|
|
|
if group != nil {
|
|
|
|
am.storeEvent(initiatorUserID, oldUser.Id, accountID, activity.GroupAddedToUser,
|
|
|
|
map[string]any{"group": group.Name, "group_id": group.ID, "is_service_user": newUser.IsServiceUser, "user_name": newUser.ServiceUserName})
|
|
|
|
}
|
2023-01-02 15:11:32 +01:00
|
|
|
}
|
|
|
|
}
|
2023-05-11 18:09:36 +02:00
|
|
|
|
2023-01-02 15:11:32 +01:00
|
|
|
}()
|
|
|
|
|
2023-04-22 12:57:51 +02:00
|
|
|
if !isNil(am.idpManager) && !newUser.IsServiceUser {
|
2022-10-13 18:26:31 +02:00
|
|
|
userData, err := am.lookupUserInCache(newUser.Id, account)
|
2022-09-22 09:06:32 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2022-10-13 18:26:31 +02:00
|
|
|
if userData == nil {
|
2022-11-11 20:36:45 +01:00
|
|
|
return nil, status.Errorf(status.NotFound, "user %s not found in the IdP", newUser.Id)
|
2022-10-13 18:26:31 +02:00
|
|
|
}
|
2023-05-11 18:09:36 +02:00
|
|
|
return newUser.ToUserInfo(userData)
|
2022-09-22 09:06:32 +02:00
|
|
|
}
|
2023-05-11 18:09:36 +02:00
|
|
|
return newUser.ToUserInfo(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
|
2022-11-07 17:52:23 +01:00
|
|
|
func (am *DefaultAccountManager) GetOrCreateAccountByUser(userID, domain string) (*Account, error) {
|
|
|
|
unlock := am.Store.AcquireGlobalLock()
|
|
|
|
defer unlock()
|
2021-12-27 13:17:15 +01:00
|
|
|
|
2022-03-01 15:22:18 +01:00
|
|
|
lowerDomain := strings.ToLower(domain)
|
|
|
|
|
2022-11-07 17:52:23 +01:00
|
|
|
account, err := am.Store.GetAccountByUser(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 {
|
2022-11-07 17:52:23 +01:00
|
|
|
account, err = am.newAccount(userID, lowerDomain)
|
2022-06-20 18:20:43 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2021-12-27 13:17:15 +01:00
|
|
|
err = am.Store.SaveAccount(account)
|
|
|
|
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
|
|
|
|
|
|
|
if account.Domain != lowerDomain && userObj.Role == UserRoleAdmin {
|
2022-03-01 15:22:18 +01:00
|
|
|
account.Domain = lowerDomain
|
2022-02-11 17:18:18 +01:00
|
|
|
err = am.Store.SaveAccount(account)
|
|
|
|
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.
|
|
|
|
func (am *DefaultAccountManager) GetUsersFromAccount(accountID, userID string) ([]*UserInfo, error) {
|
2022-11-07 17:52:23 +01:00
|
|
|
account, err := am.Store.GetAccount(accountID)
|
2022-09-22 09:06:32 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2022-11-05 10:24:50 +01:00
|
|
|
user, err := account.FindUser(userID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2022-09-22 09:06:32 +02:00
|
|
|
queriedUsers := make([]*idp.UserData, 0)
|
|
|
|
if !isNil(am.idpManager) {
|
2022-10-13 18:26:31 +02:00
|
|
|
users := make(map[string]struct{}, len(account.Users))
|
|
|
|
for _, user := range account.Users {
|
2023-04-22 12:57:51 +02:00
|
|
|
if !user.IsServiceUser {
|
|
|
|
users[user.Id] = struct{}{}
|
|
|
|
}
|
2022-10-13 18:26:31 +02:00
|
|
|
}
|
|
|
|
queriedUsers, err = am.lookupCache(users, accountID)
|
2022-09-22 09:06:32 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
userInfos := make([]*UserInfo, 0)
|
|
|
|
|
|
|
|
// in case of self-hosted, or IDP doesn't return anything, we will return the locally stored userInfo
|
|
|
|
if len(queriedUsers) == 0 {
|
2022-11-05 10:24:50 +01:00
|
|
|
for _, accountUser := range account.Users {
|
|
|
|
if !user.IsAdmin() && user.Id != accountUser.Id {
|
|
|
|
// if user is not an admin then show only current user and do not show other users
|
|
|
|
continue
|
|
|
|
}
|
2023-05-11 18:09:36 +02:00
|
|
|
info, err := accountUser.ToUserInfo(nil)
|
2022-09-22 09:06:32 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
userInfos = append(userInfos, info)
|
|
|
|
}
|
|
|
|
return userInfos, nil
|
|
|
|
}
|
|
|
|
|
2023-04-22 12:57:51 +02:00
|
|
|
for _, localUser := range account.Users {
|
|
|
|
if !user.IsAdmin() && 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 {
|
2023-05-11 18:09:36 +02:00
|
|
|
info, err = localUser.ToUserInfo(queriedUser)
|
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
|
|
|
|
}
|
|
|
|
info = &UserInfo{
|
|
|
|
ID: localUser.Id,
|
|
|
|
Email: "",
|
|
|
|
Name: name,
|
|
|
|
Role: string(localUser.Role),
|
|
|
|
AutoGroups: localUser.AutoGroups,
|
|
|
|
Status: string(UserStatusActive),
|
|
|
|
IsServiceUser: localUser.IsServiceUser,
|
|
|
|
}
|
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
|
|
|
|
|
|
|
func findUserInIDPUserdata(userID string, userData []*idp.UserData) (*idp.UserData, bool) {
|
|
|
|
for _, user := range userData {
|
|
|
|
if user.ID == userID {
|
|
|
|
return user, true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil, false
|
|
|
|
}
|