[management] user info with role permissions

This commit is contained in:
Pedro Costa
2025-04-15 22:26:41 +01:00
parent a354004564
commit 5e989dff4f
13 changed files with 256 additions and 254 deletions
+4 -1
View File
@@ -31,7 +31,10 @@ var (
LastLogin: &time.Time{}, LastLogin: &time.Time{},
Name: "M. Essam", Name: "M. Essam",
Permissions: &api.UserPermissions{ Permissions: &api.UserPermissions{
DashboardView: ptr(api.UserPermissionsDashboardViewFull), AutoAllowNew: map[string]bool{
"read": true,
"write": true,
},
}, },
Role: "user", Role: "user",
Status: api.UserStatusActive, Status: api.UserStatusActive,
+2 -1
View File
@@ -16,6 +16,7 @@ import (
"github.com/netbirdio/netbird/management/server/posture" "github.com/netbirdio/netbird/management/server/posture"
"github.com/netbirdio/netbird/management/server/store" "github.com/netbirdio/netbird/management/server/store"
"github.com/netbirdio/netbird/management/server/types" "github.com/netbirdio/netbird/management/server/types"
"github.com/netbirdio/netbird/management/server/users"
"github.com/netbirdio/netbird/route" "github.com/netbirdio/netbird/route"
) )
@@ -114,5 +115,5 @@ type Manager interface {
CreateAccountByPrivateDomain(ctx context.Context, initiatorId, domain string) (*types.Account, error) CreateAccountByPrivateDomain(ctx context.Context, initiatorId, domain string) (*types.Account, error)
UpdateToPrimaryAccount(ctx context.Context, accountId string) (*types.Account, error) UpdateToPrimaryAccount(ctx context.Context, accountId string) (*types.Account, error)
GetOwnerInfo(ctx context.Context, accountId string) (*types.UserInfo, error) GetOwnerInfo(ctx context.Context, accountId string) (*types.UserInfo, error)
GetCurrentUserInfo(ctx context.Context, accountID, userID string) (*types.UserInfo, error) GetCurrentUserInfo(ctx context.Context, accountID, userID string) (*users.UserInfoWithPermissions, error)
} }
+29 -5
View File
@@ -191,11 +191,35 @@ components:
UserPermissions: UserPermissions:
type: object type: object
properties: properties:
dashboard_view: permissions:
description: User's permission to view the dashboard type: object
type: string additionalProperties:
enum: [ "limited", "blocked", "full" ] type: object
example: limited additionalProperties:
type: boolean
propertyNames:
type: string
enum:
- read
- write
propertyNames:
type: string
enum:
- read
- write
example: {"networks": { "read": true, "write": false}, "peers": { "read": false, "write": false} }
auto_allow_new:
type: object
additionalProperties:
type: boolean
propertyNames:
type: string
enum:
- read
- write
required:
- permissions
- auto_allow_new
UserRequest: UserRequest:
type: object type: object
properties: properties:
+2 -12
View File
@@ -178,13 +178,6 @@ const (
UserStatusInvited UserStatus = "invited" UserStatusInvited UserStatus = "invited"
) )
// Defines values for UserPermissionsDashboardView.
const (
UserPermissionsDashboardViewBlocked UserPermissionsDashboardView = "blocked"
UserPermissionsDashboardViewFull UserPermissionsDashboardView = "full"
UserPermissionsDashboardViewLimited UserPermissionsDashboardView = "limited"
)
// AccessiblePeer defines model for AccessiblePeer. // AccessiblePeer defines model for AccessiblePeer.
type AccessiblePeer struct { type AccessiblePeer struct {
// CityName Commonly used English name of the city // CityName Commonly used English name of the city
@@ -1710,13 +1703,10 @@ type UserCreateRequest struct {
// UserPermissions defines model for UserPermissions. // UserPermissions defines model for UserPermissions.
type UserPermissions struct { type UserPermissions struct {
// DashboardView User's permission to view the dashboard AutoAllowNew map[string]bool `json:"auto_allow_new"`
DashboardView *UserPermissionsDashboardView `json:"dashboard_view,omitempty"` Permissions map[string]map[string]bool `json:"permissions"`
} }
// UserPermissionsDashboardView User's permission to view the dashboard
type UserPermissionsDashboardView string
// UserRequest defines model for UserRequest. // UserRequest defines model for UserRequest.
type UserRequest struct { type UserRequest struct {
// AutoGroups Group IDs to auto-assign to peers registered by this user // AutoGroups Group IDs to auto-assign to peers registered by this user
@@ -13,6 +13,7 @@ import (
"github.com/netbirdio/netbird/management/server/http/util" "github.com/netbirdio/netbird/management/server/http/util"
"github.com/netbirdio/netbird/management/server/status" "github.com/netbirdio/netbird/management/server/status"
"github.com/netbirdio/netbird/management/server/types" "github.com/netbirdio/netbird/management/server/types"
"github.com/netbirdio/netbird/management/server/users"
nbcontext "github.com/netbirdio/netbird/management/server/context" nbcontext "github.com/netbirdio/netbird/management/server/context"
) )
@@ -280,7 +281,41 @@ func (h *handler) getCurrentUser(w http.ResponseWriter, r *http.Request) {
return return
} }
util.WriteJSONObject(r.Context(), w, toUserResponse(user, userID)) util.WriteJSONObject(r.Context(), w, toUserWithPermissionsResponse(user, userID))
}
func toUserWithPermissionsResponse(user *users.UserInfoWithPermissions, userID string) *api.User {
response := toUserResponse(user.UserInfo, userID)
if user.Permissions == nil {
return response
}
permissions := &api.UserPermissions{}
if len(user.Permissions.AutoAllowNew) > 0 {
permissions.AutoAllowNew = make(map[string]bool)
for k, v := range user.Permissions.AutoAllowNew {
permissions.AutoAllowNew[string(k)] = v
}
}
if len(user.Permissions.Permissions) > 0 {
permissions.Permissions = make(map[string]map[string]bool)
for module, operations := range user.Permissions.Permissions {
if len(operations) == 0 {
continue
}
access := make(map[string]bool)
for k, v := range operations {
access[string(k)] = v
}
permissions.Permissions[string(module)] = access
}
}
response.Permissions = permissions
return response
} }
func toUserResponse(user *types.UserInfo, currenUserID string) *api.User { func toUserResponse(user *types.UserInfo, currenUserID string) *api.User {
@@ -316,8 +351,5 @@ func toUserResponse(user *types.UserInfo, currenUserID string) *api.User {
IsBlocked: user.IsBlocked, IsBlocked: user.IsBlocked,
LastLogin: &user.LastLogin, LastLogin: &user.LastLogin,
Issued: &user.Issued, Issued: &user.Issued,
Permissions: &api.UserPermissions{
DashboardView: (*api.UserPermissionsDashboardView)(&user.Permissions.DashboardView),
},
} }
} }
@@ -19,6 +19,7 @@ import (
"github.com/netbirdio/netbird/management/server/mock_server" "github.com/netbirdio/netbird/management/server/mock_server"
"github.com/netbirdio/netbird/management/server/status" "github.com/netbirdio/netbird/management/server/status"
"github.com/netbirdio/netbird/management/server/types" "github.com/netbirdio/netbird/management/server/types"
"github.com/netbirdio/netbird/management/server/users"
) )
const ( const (
@@ -107,7 +108,7 @@ func initUsersTestData() *handler {
return nil, status.Errorf(status.NotFound, "user with ID %s does not exists", userID) return nil, status.Errorf(status.NotFound, "user with ID %s does not exists", userID)
} }
info, err := update.Copy().ToUserInfo(nil, &types.Settings{RegularUsersViewBlocked: false}) info, err := update.Copy().ToUserInfo(nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -124,7 +125,7 @@ func initUsersTestData() *handler {
return nil return nil
}, },
GetCurrentUserInfoFunc: func(ctx context.Context, accountID, userID string) (*types.UserInfo, error) { GetCurrentUserInfoFunc: func(ctx context.Context, accountID, userID string) (*users.UserInfoWithPermissions, error) {
switch userID { switch userID {
case "not-found": case "not-found":
return nil, status.NewUserNotFoundError("not-found") return nil, status.NewUserNotFoundError("not-found")
@@ -135,47 +136,44 @@ func initUsersTestData() *handler {
case "service-user": case "service-user":
return nil, status.NewPermissionDeniedError() return nil, status.NewPermissionDeniedError()
case "owner": case "owner":
return &types.UserInfo{ return &users.UserInfoWithPermissions{
ID: "owner", UserInfo: &types.UserInfo{
Name: "", ID: "owner",
Role: "owner", Name: "",
Status: "active", Role: "owner",
IsServiceUser: false, Status: "active",
IsBlocked: false, IsServiceUser: false,
NonDeletable: false, IsBlocked: false,
Issued: "api", NonDeletable: false,
Permissions: types.UserPermissions{ Issued: "api",
DashboardView: "full",
}, },
}, nil }, nil
case "regular-user": case "regular-user":
return &types.UserInfo{ return &users.UserInfoWithPermissions{
ID: "regular-user", UserInfo: &types.UserInfo{
Name: "", ID: "regular-user",
Role: "user", Name: "",
Status: "active", Role: "user",
IsServiceUser: false, Status: "active",
IsBlocked: false, IsServiceUser: false,
NonDeletable: false, IsBlocked: false,
Issued: "api", NonDeletable: false,
Permissions: types.UserPermissions{ Issued: "api",
DashboardView: "limited",
}, },
}, nil }, nil
case "admin-user": case "admin-user":
return &types.UserInfo{ return &users.UserInfoWithPermissions{
ID: "admin-user", UserInfo: &types.UserInfo{
Name: "", ID: "admin-user",
Role: "admin", Name: "",
Status: "active", Role: "admin",
IsServiceUser: false, Status: "active",
IsBlocked: false, IsServiceUser: false,
NonDeletable: false, IsBlocked: false,
LastLogin: time.Time{}, NonDeletable: false,
Issued: "api", LastLogin: time.Time{},
Permissions: types.UserPermissions{ Issued: "api",
DashboardView: "full",
}, },
}, nil }, nil
} }
@@ -19,6 +19,7 @@ import (
"github.com/netbirdio/netbird/management/server/posture" "github.com/netbirdio/netbird/management/server/posture"
"github.com/netbirdio/netbird/management/server/store" "github.com/netbirdio/netbird/management/server/store"
"github.com/netbirdio/netbird/management/server/types" "github.com/netbirdio/netbird/management/server/types"
"github.com/netbirdio/netbird/management/server/users"
"github.com/netbirdio/netbird/route" "github.com/netbirdio/netbird/route"
) )
@@ -115,7 +116,7 @@ type MockAccountManager struct {
CreateAccountByPrivateDomainFunc func(ctx context.Context, initiatorId, domain string) (*types.Account, error) CreateAccountByPrivateDomainFunc func(ctx context.Context, initiatorId, domain string) (*types.Account, error)
UpdateToPrimaryAccountFunc func(ctx context.Context, accountId string) (*types.Account, error) UpdateToPrimaryAccountFunc func(ctx context.Context, accountId string) (*types.Account, error)
GetOwnerInfoFunc func(ctx context.Context, accountID string) (*types.UserInfo, error) GetOwnerInfoFunc func(ctx context.Context, accountID string) (*types.UserInfo, error)
GetCurrentUserInfoFunc func(ctx context.Context, accountID, userID string) (*types.UserInfo, error) GetCurrentUserInfoFunc func(ctx context.Context, accountID, userID string) (*users.UserInfoWithPermissions, error)
} }
func (am *MockAccountManager) UpdateAccountPeers(ctx context.Context, accountID string) { func (am *MockAccountManager) UpdateAccountPeers(ctx context.Context, accountID string) {
@@ -873,7 +874,7 @@ func (am *MockAccountManager) GetOwnerInfo(ctx context.Context, accountId string
return nil, status.Errorf(codes.Unimplemented, "method GetOwnerInfo is not implemented") return nil, status.Errorf(codes.Unimplemented, "method GetOwnerInfo is not implemented")
} }
func (am *MockAccountManager) GetCurrentUserInfo(ctx context.Context, accountID, userID string) (*types.UserInfo, error) { func (am *MockAccountManager) GetCurrentUserInfo(ctx context.Context, accountID, userID string) (*users.UserInfoWithPermissions, error) {
if am.GetCurrentUserInfoFunc != nil { if am.GetCurrentUserInfoFunc != nil {
return am.GetCurrentUserInfoFunc(ctx, accountID, userID) return am.GetCurrentUserInfoFunc(ctx, accountID, userID)
} }
+14 -3
View File
@@ -20,6 +20,8 @@ type Manager interface {
ValidateUserPermissions(ctx context.Context, accountID, userID string, module modules.Module, operation operations.Operation) (bool, error) ValidateUserPermissions(ctx context.Context, accountID, userID string, module modules.Module, operation operations.Operation) (bool, error)
ValidateRoleModuleAccess(ctx context.Context, accountID string, role roles.RolePermissions, module modules.Module, operation operations.Operation) bool ValidateRoleModuleAccess(ctx context.Context, accountID string, role roles.RolePermissions, module modules.Module, operation operations.Operation) bool
ValidateAccountAccess(ctx context.Context, accountID string, user *types.User, allowOwnerAndAdmin bool) error ValidateAccountAccess(ctx context.Context, accountID string, user *types.User, allowOwnerAndAdmin bool) error
GetRolePermissions(ctx context.Context, role types.UserRole) (roles.RolePermissions, error)
} }
type managerImpl struct { type managerImpl struct {
@@ -64,9 +66,9 @@ func (m *managerImpl) ValidateUserPermissions(
return true, nil // this should be replaced by proper granular access role return true, nil // this should be replaced by proper granular access role
} }
role, ok := roles.RolesMap[user.Role] role, err := m.GetRolePermissions(ctx, user.Role)
if !ok { if err != nil {
return false, status.NewUserRoleNotFoundError(string(user.Role)) return false, err
} }
return m.ValidateRoleModuleAccess(ctx, accountID, role, module, operation), nil return m.ValidateRoleModuleAccess(ctx, accountID, role, module, operation), nil
@@ -96,3 +98,12 @@ func (m *managerImpl) ValidateAccountAccess(ctx context.Context, accountID strin
} }
return nil return nil
} }
func (m *managerImpl) GetRolePermissions(ctx context.Context, role types.UserRole) (roles.RolePermissions, error) {
permissions, ok := roles.RolesMap[role]
if !ok {
return roles.RolePermissions{}, status.NewUserRoleNotFoundError(string(role))
}
return permissions, nil
}
@@ -38,6 +38,21 @@ func (m *MockManager) EXPECT() *MockManagerMockRecorder {
return m.recorder return m.recorder
} }
// GetRolePermissions mocks base method.
func (m *MockManager) GetRolePermissions(ctx context.Context, role types.UserRole) (roles.RolePermissions, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetRolePermissions", ctx, role)
ret0, _ := ret[0].(roles.RolePermissions)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetRolePermissions indicates an expected call of GetRolePermissions.
func (mr *MockManagerMockRecorder) GetRolePermissions(ctx, role interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRolePermissions", reflect.TypeOf((*MockManager)(nil).GetRolePermissions), ctx, role)
}
// ValidateAccountAccess mocks base method. // ValidateAccountAccess mocks base method.
func (m *MockManager) ValidateAccountAccess(ctx context.Context, accountID string, user *types.User, allowOwnerAndAdmin bool) error { func (m *MockManager) ValidateAccountAccess(ctx context.Context, accountID string, user *types.User, allowOwnerAndAdmin bool) error {
m.ctrl.T.Helper() m.ctrl.T.Helper()
+1 -20
View File
@@ -59,11 +59,6 @@ type UserInfo struct {
LastLogin time.Time `json:"last_login"` LastLogin time.Time `json:"last_login"`
Issued string `json:"issued"` Issued string `json:"issued"`
IntegrationReference integration_reference.IntegrationReference `json:"-"` IntegrationReference integration_reference.IntegrationReference `json:"-"`
Permissions UserPermissions `json:"permissions"`
}
type UserPermissions struct {
DashboardView string `json:"dashboard_view"`
} }
// User represents a user of the system // User represents a user of the system
@@ -127,20 +122,12 @@ func (u *User) IsRegularUser() bool {
} }
// ToUserInfo converts a User object to a UserInfo object. // ToUserInfo converts a User object to a UserInfo object.
func (u *User) ToUserInfo(userData *idp.UserData, settings *Settings) (*UserInfo, error) { func (u *User) ToUserInfo(userData *idp.UserData) (*UserInfo, error) {
autoGroups := u.AutoGroups autoGroups := u.AutoGroups
if autoGroups == nil { if autoGroups == nil {
autoGroups = []string{} autoGroups = []string{}
} }
dashboardViewPermissions := "full"
if !u.HasAdminPower() {
dashboardViewPermissions = "limited"
if settings.RegularUsersViewBlocked {
dashboardViewPermissions = "blocked"
}
}
if userData == nil { if userData == nil {
return &UserInfo{ return &UserInfo{
ID: u.Id, ID: u.Id,
@@ -153,9 +140,6 @@ func (u *User) ToUserInfo(userData *idp.UserData, settings *Settings) (*UserInfo
IsBlocked: u.Blocked, IsBlocked: u.Blocked,
LastLogin: u.GetLastLogin(), LastLogin: u.GetLastLogin(),
Issued: u.Issued, Issued: u.Issued,
Permissions: UserPermissions{
DashboardView: dashboardViewPermissions,
},
}, nil }, nil
} }
if userData.ID != u.Id { if userData.ID != u.Id {
@@ -178,9 +162,6 @@ func (u *User) ToUserInfo(userData *idp.UserData, settings *Settings) (*UserInfo
IsBlocked: u.Blocked, IsBlocked: u.Blocked,
LastLogin: u.GetLastLogin(), LastLogin: u.GetLastLogin(),
Issued: u.Issued, Issued: u.Issued,
Permissions: UserPermissions{
DashboardView: dashboardViewPermissions,
},
}, nil }, nil
} }
+26 -32
View File
@@ -19,6 +19,7 @@ import (
"github.com/netbirdio/netbird/management/server/status" "github.com/netbirdio/netbird/management/server/status"
"github.com/netbirdio/netbird/management/server/store" "github.com/netbirdio/netbird/management/server/store"
"github.com/netbirdio/netbird/management/server/types" "github.com/netbirdio/netbird/management/server/types"
"github.com/netbirdio/netbird/management/server/users"
"github.com/netbirdio/netbird/management/server/util" "github.com/netbirdio/netbird/management/server/util"
) )
@@ -122,11 +123,6 @@ func (am *DefaultAccountManager) inviteNewUser(ctx context.Context, accountID, u
CreatedAt: time.Now().UTC(), CreatedAt: time.Now().UTC(),
} }
settings, err := am.Store.GetAccountSettings(ctx, store.LockingStrengthShare, accountID)
if err != nil {
return nil, err
}
if err = am.Store.SaveUser(ctx, store.LockingStrengthUpdate, newUser); err != nil { if err = am.Store.SaveUser(ctx, store.LockingStrengthUpdate, newUser); err != nil {
return nil, err return nil, err
} }
@@ -138,7 +134,7 @@ func (am *DefaultAccountManager) inviteNewUser(ctx context.Context, accountID, u
am.StoreEvent(ctx, userID, newUser.Id, accountID, activity.UserInvited, nil) am.StoreEvent(ctx, userID, newUser.Id, accountID, activity.UserInvited, nil)
return newUser.ToUserInfo(idpUser, settings) return newUser.ToUserInfo(idpUser)
} }
// createNewIdpUser validates the invite and creates a new user in the IdP // createNewIdpUser validates the invite and creates a new user in the IdP
@@ -727,19 +723,14 @@ func handleOwnerRoleTransfer(ctx context.Context, transaction store.Store, initi
// If the AccountManager has a non-nil idpManager and the User is not a service user, // 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. // it will attempt to look up the UserData from the cache.
func (am *DefaultAccountManager) getUserInfo(ctx context.Context, user *types.User, accountID string) (*types.UserInfo, error) { func (am *DefaultAccountManager) getUserInfo(ctx context.Context, user *types.User, accountID string) (*types.UserInfo, error) {
settings, err := am.Store.GetAccountSettings(ctx, store.LockingStrengthShare, accountID)
if err != nil {
return nil, err
}
if !isNil(am.idpManager) && !user.IsServiceUser { if !isNil(am.idpManager) && !user.IsServiceUser {
userData, err := am.lookupUserInCache(ctx, user.Id, accountID) userData, err := am.lookupUserInCache(ctx, user.Id, accountID)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return user.ToUserInfo(userData, settings) return user.ToUserInfo(userData)
} }
return user.ToUserInfo(nil, settings) return user.ToUserInfo(nil)
} }
// validateUserUpdate validates the update operation for a user. // validateUserUpdate validates the update operation for a user.
@@ -879,17 +870,12 @@ func (am *DefaultAccountManager) BuildUserInfosForAccount(ctx context.Context, a
queriedUsers = append(queriedUsers, usersFromIntegration...) queriedUsers = append(queriedUsers, usersFromIntegration...)
} }
settings, err := am.Store.GetAccountSettings(ctx, store.LockingStrengthShare, accountID)
if err != nil {
return nil, err
}
userInfosMap := make(map[string]*types.UserInfo) userInfosMap := make(map[string]*types.UserInfo)
// in case of self-hosted, or IDP doesn't return anything, we will return the locally stored userInfo // in case of self-hosted, or IDP doesn't return anything, we will return the locally stored userInfo
if len(queriedUsers) == 0 { if len(queriedUsers) == 0 {
for _, accountUser := range accountUsers { for _, accountUser := range accountUsers {
info, err := accountUser.ToUserInfo(nil, settings) info, err := accountUser.ToUserInfo(nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -902,7 +888,7 @@ func (am *DefaultAccountManager) BuildUserInfosForAccount(ctx context.Context, a
for _, localUser := range accountUsers { for _, localUser := range accountUsers {
var info *types.UserInfo var info *types.UserInfo
if queriedUser, contains := findUserInIDPUserdata(localUser.Id, queriedUsers); contains { if queriedUser, contains := findUserInIDPUserdata(localUser.Id, queriedUsers); contains {
info, err = localUser.ToUserInfo(queriedUser, settings) info, err = localUser.ToUserInfo(queriedUser)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -912,14 +898,6 @@ func (am *DefaultAccountManager) BuildUserInfosForAccount(ctx context.Context, a
name = localUser.ServiceUserName name = localUser.ServiceUserName
} }
dashboardViewPermissions := "full"
if !localUser.HasAdminPower() {
dashboardViewPermissions = "limited"
if settings.RegularUsersViewBlocked {
dashboardViewPermissions = "blocked"
}
}
info = &types.UserInfo{ info = &types.UserInfo{
ID: localUser.Id, ID: localUser.Id,
Email: "", Email: "",
@@ -929,7 +907,6 @@ func (am *DefaultAccountManager) BuildUserInfosForAccount(ctx context.Context, a
Status: string(types.UserStatusActive), Status: string(types.UserStatusActive),
IsServiceUser: localUser.IsServiceUser, IsServiceUser: localUser.IsServiceUser,
NonDeletable: localUser.NonDeletable, NonDeletable: localUser.NonDeletable,
Permissions: types.UserPermissions{DashboardView: dashboardViewPermissions},
} }
} }
userInfosMap[info.ID] = info userInfosMap[info.ID] = info
@@ -1233,8 +1210,8 @@ func validateUserInvite(invite *types.UserInfo) error {
return nil return nil
} }
// GetCurrentUserInfo retrieves the account's current user info // GetCurrentUserInfo retrieves the account's current user info and permissions
func (am *DefaultAccountManager) GetCurrentUserInfo(ctx context.Context, accountID, userID string) (*types.UserInfo, error) { func (am *DefaultAccountManager) GetCurrentUserInfo(ctx context.Context, accountID, userID string) (*users.UserInfoWithPermissions, error) {
user, err := am.Store.GetUserByUserID(ctx, store.LockingStrengthShare, userID) user, err := am.Store.GetUserByUserID(ctx, store.LockingStrengthShare, userID)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -1252,10 +1229,27 @@ func (am *DefaultAccountManager) GetCurrentUserInfo(ctx context.Context, account
return nil, err return nil, err
} }
settings, err := am.Store.GetAccountSettings(ctx, store.LockingStrengthShare, accountID)
if err != nil {
return nil, err
}
userInfo, err := am.getUserInfo(ctx, user, accountID) userInfo, err := am.getUserInfo(ctx, user, accountID)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return userInfo, nil userWithPermissions := &users.UserInfoWithPermissions{
UserInfo: userInfo,
}
if user.Role == types.UserRoleUser && settings.RegularUsersViewBlocked {
return userWithPermissions, nil
}
permissions, err := am.permissionsManager.GetRolePermissions(ctx, user.Role)
if err == nil {
userWithPermissions.Permissions = &permissions
}
return userWithPermissions, nil
} }
+77 -138
View File
@@ -13,7 +13,9 @@ import (
nbcache "github.com/netbirdio/netbird/management/server/cache" nbcache "github.com/netbirdio/netbird/management/server/cache"
nbcontext "github.com/netbirdio/netbird/management/server/context" nbcontext "github.com/netbirdio/netbird/management/server/context"
"github.com/netbirdio/netbird/management/server/permissions" "github.com/netbirdio/netbird/management/server/permissions"
"github.com/netbirdio/netbird/management/server/permissions/roles"
"github.com/netbirdio/netbird/management/server/status" "github.com/netbirdio/netbird/management/server/status"
"github.com/netbirdio/netbird/management/server/users"
"github.com/netbirdio/netbird/management/server/util" "github.com/netbirdio/netbird/management/server/util"
nbpeer "github.com/netbirdio/netbird/management/server/peer" nbpeer "github.com/netbirdio/netbird/management/server/peer"
@@ -1020,90 +1022,6 @@ func TestDefaultAccountManager_ListUsers(t *testing.T) {
assert.Equal(t, 2, regular) assert.Equal(t, 2, regular)
} }
func TestDefaultAccountManager_ListUsers_DashboardPermissions(t *testing.T) {
testCases := []struct {
name string
role types.UserRole
limitedViewSettings bool
expectedDashboardPermissions string
}{
{
name: "Regular user, no limited view settings",
role: types.UserRoleUser,
limitedViewSettings: false,
expectedDashboardPermissions: "limited",
},
{
name: "Admin user, no limited view settings",
role: types.UserRoleAdmin,
limitedViewSettings: false,
expectedDashboardPermissions: "full",
},
{
name: "Owner, no limited view settings",
role: types.UserRoleOwner,
limitedViewSettings: false,
expectedDashboardPermissions: "full",
},
{
name: "Regular user, limited view settings",
role: types.UserRoleUser,
limitedViewSettings: true,
expectedDashboardPermissions: "blocked",
},
{
name: "Admin user, limited view settings",
role: types.UserRoleAdmin,
limitedViewSettings: true,
expectedDashboardPermissions: "full",
},
{
name: "Owner, limited view settings",
role: types.UserRoleOwner,
limitedViewSettings: true,
expectedDashboardPermissions: "full",
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
store, cleanup, err := store.NewTestStoreFromSQL(context.Background(), "", t.TempDir())
if err != nil {
t.Fatalf("Error when creating store: %s", err)
}
t.Cleanup(cleanup)
account := newAccountWithId(context.Background(), mockAccountID, mockUserID, "")
account.Users["normal_user1"] = types.NewUser("normal_user1", testCase.role, false, false, "", []string{}, types.UserIssuedAPI)
account.Settings.RegularUsersViewBlocked = testCase.limitedViewSettings
delete(account.Users, mockUserID)
err = store.SaveAccount(context.Background(), account)
if err != nil {
t.Fatalf("Error when saving account: %s", err)
}
permissionsManager := permissions.NewManager(store)
am := DefaultAccountManager{
Store: store,
eventStore: &activity.InMemoryEventStore{},
permissionsManager: permissionsManager,
}
users, err := am.ListUsers(context.Background(), mockAccountID)
if err != nil {
t.Fatalf("Error when checking user role: %s", err)
}
assert.Equal(t, 1, len(users))
userInfo, _ := users[0].ToUserInfo(nil, account.Settings)
assert.Equal(t, testCase.expectedDashboardPermissions, userInfo.Permissions.DashboardView)
})
}
}
func TestDefaultAccountManager_ExternalCache(t *testing.T) { func TestDefaultAccountManager_ExternalCache(t *testing.T) {
store, cleanup, err := store.NewTestStoreFromSQL(context.Background(), "", t.TempDir()) store, cleanup, err := store.NewTestStoreFromSQL(context.Background(), "", t.TempDir())
if err != nil { if err != nil {
@@ -1657,7 +1575,7 @@ func TestDefaultAccountManager_GetCurrentUserInfo(t *testing.T) {
accountId string accountId string
userId string userId string
expectedErr error expectedErr error
expectedResult *types.UserInfo expectedResult *users.UserInfoWithPermissions
}{ }{
{ {
name: "not found", name: "not found",
@@ -1687,81 +1605,102 @@ func TestDefaultAccountManager_GetCurrentUserInfo(t *testing.T) {
name: "owner user", name: "owner user",
accountId: account1.Id, accountId: account1.Id,
userId: "account1Owner", userId: "account1Owner",
expectedResult: &types.UserInfo{ expectedResult: &users.UserInfoWithPermissions{
ID: "account1Owner", UserInfo: &types.UserInfo{
Name: "", ID: "account1Owner",
Role: "owner", Name: "",
AutoGroups: []string{}, Role: "owner",
Status: "active", AutoGroups: []string{},
IsServiceUser: false, Status: "active",
IsBlocked: false, IsServiceUser: false,
NonDeletable: false, IsBlocked: false,
LastLogin: time.Time{}, NonDeletable: false,
Issued: "api", LastLogin: time.Time{},
IntegrationReference: integration_reference.IntegrationReference{}, Issued: "api",
Permissions: types.UserPermissions{ IntegrationReference: integration_reference.IntegrationReference{},
DashboardView: "full",
}, },
Permissions: &roles.Owner,
}, },
}, },
{ {
name: "regular user", name: "regular user",
accountId: account1.Id, accountId: account1.Id,
userId: "regular-user", userId: "regular-user",
expectedResult: &types.UserInfo{ expectedResult: &users.UserInfoWithPermissions{
ID: "regular-user", UserInfo: &types.UserInfo{
Name: "", ID: "regular-user",
Role: "user", Name: "",
Status: "active", Role: "user",
IsServiceUser: false, Status: "active",
IsBlocked: false, IsServiceUser: false,
NonDeletable: false, IsBlocked: false,
LastLogin: time.Time{}, NonDeletable: false,
Issued: "api", LastLogin: time.Time{},
IntegrationReference: integration_reference.IntegrationReference{}, Issued: "api",
Permissions: types.UserPermissions{ IntegrationReference: integration_reference.IntegrationReference{},
DashboardView: "limited",
}, },
Permissions: &roles.User,
}, },
}, },
{ {
name: "admin user", name: "admin user",
accountId: account1.Id, accountId: account1.Id,
userId: "admin-user", userId: "admin-user",
expectedResult: &types.UserInfo{ expectedResult: &users.UserInfoWithPermissions{
ID: "admin-user", UserInfo: &types.UserInfo{
Name: "", ID: "admin-user",
Role: "admin", Name: "",
Status: "active", Role: "admin",
IsServiceUser: false, Status: "active",
IsBlocked: false, IsServiceUser: false,
NonDeletable: false, IsBlocked: false,
LastLogin: time.Time{}, NonDeletable: false,
Issued: "api", LastLogin: time.Time{},
IntegrationReference: integration_reference.IntegrationReference{}, Issued: "api",
Permissions: types.UserPermissions{ IntegrationReference: integration_reference.IntegrationReference{},
DashboardView: "full",
}, },
Permissions: &roles.Admin,
}, },
}, },
{ {
name: "settings blocked regular user", name: "settings blocked regular user",
accountId: account2.Id, accountId: account2.Id,
userId: "settings-blocked-user", userId: "settings-blocked-user",
expectedResult: &types.UserInfo{ expectedResult: &users.UserInfoWithPermissions{
ID: "settings-blocked-user", UserInfo: &types.UserInfo{
Name: "", ID: "settings-blocked-user",
Role: "user", Name: "",
Status: "active", Role: "user",
IsServiceUser: false, Status: "active",
IsBlocked: false, IsServiceUser: false,
NonDeletable: false, IsBlocked: false,
LastLogin: time.Time{}, NonDeletable: false,
Issued: "api", LastLogin: time.Time{},
IntegrationReference: integration_reference.IntegrationReference{}, Issued: "api",
Permissions: types.UserPermissions{ IntegrationReference: integration_reference.IntegrationReference{},
DashboardView: "blocked",
}, },
Permissions: nil,
},
},
{
name: "settings blocked owner user",
accountId: account2.Id,
userId: "account2Owner",
expectedResult: &users.UserInfoWithPermissions{
UserInfo: &types.UserInfo{
ID: "account2Owner",
Name: "",
Role: "owner",
AutoGroups: []string{},
Status: "active",
IsServiceUser: false,
IsBlocked: false,
NonDeletable: false,
LastLogin: time.Time{},
Issued: "api",
IntegrationReference: integration_reference.IntegrationReference{},
},
Permissions: &roles.Owner,
}, },
}, },
} }
+13
View File
@@ -0,0 +1,13 @@
package users
import (
"github.com/netbirdio/netbird/management/server/permissions/roles"
"github.com/netbirdio/netbird/management/server/types"
)
// Wrapped UserInfo with Role Permissions
type UserInfoWithPermissions struct {
*types.UserInfo
Permissions *roles.RolePermissions
}