mirror of
https://github.com/netbirdio/netbird.git
synced 2025-06-20 09:47:49 +02:00
[management] user info with role permissions (#3728)
This commit is contained in:
parent
9bc7d788f0
commit
7b64953eed
@ -30,11 +30,8 @@ var (
|
|||||||
Issued: ptr("api"),
|
Issued: ptr("api"),
|
||||||
LastLogin: &time.Time{},
|
LastLogin: &time.Time{},
|
||||||
Name: "M. Essam",
|
Name: "M. Essam",
|
||||||
Permissions: &api.UserPermissions{
|
Role: "user",
|
||||||
DashboardView: ptr(api.UserPermissionsDashboardViewFull),
|
Status: api.UserStatusActive,
|
||||||
},
|
|
||||||
Role: "user",
|
|
||||||
Status: api.UserStatusActive,
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -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"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -115,5 +116,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, userAuth nbcontext.UserAuth) (*users.UserInfoWithPermissions, error)
|
||||||
}
|
}
|
||||||
|
@ -216,11 +216,25 @@ components:
|
|||||||
UserPermissions:
|
UserPermissions:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
dashboard_view:
|
is_restricted:
|
||||||
description: User's permission to view the dashboard
|
type: boolean
|
||||||
type: string
|
description: Indicates whether this User's Peers view is restricted
|
||||||
enum: [ "limited", "blocked", "full" ]
|
modules:
|
||||||
example: limited
|
type: object
|
||||||
|
additionalProperties:
|
||||||
|
type: object
|
||||||
|
additionalProperties:
|
||||||
|
type: boolean
|
||||||
|
propertyNames:
|
||||||
|
type: string
|
||||||
|
description: The operation type
|
||||||
|
propertyNames:
|
||||||
|
type: string
|
||||||
|
description: The module name
|
||||||
|
example: {"networks": { "read": true, "create": false, "update": false, "delete": false}, "peers": { "read": false, "create": false, "update": false, "delete": false} }
|
||||||
|
required:
|
||||||
|
- modules
|
||||||
|
- is_restricted
|
||||||
UserRequest:
|
UserRequest:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
@ -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"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Defines values for GetApiEventsNetworkTrafficParamsType.
|
// Defines values for GetApiEventsNetworkTrafficParamsType.
|
||||||
const (
|
const (
|
||||||
GetApiEventsNetworkTrafficParamsTypeTYPEDROP GetApiEventsNetworkTrafficParamsType = "TYPE_DROP"
|
GetApiEventsNetworkTrafficParamsTypeTYPEDROP GetApiEventsNetworkTrafficParamsType = "TYPE_DROP"
|
||||||
@ -1757,13 +1750,11 @@ 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
|
// IsRestricted Indicates whether this User's Peers view is restricted
|
||||||
DashboardView *UserPermissionsDashboardView `json:"dashboard_view,omitempty"`
|
IsRestricted bool `json:"is_restricted"`
|
||||||
|
Modules map[string]map[string]bool `json:"modules"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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"
|
||||||
)
|
)
|
||||||
@ -272,15 +273,33 @@ func (h *handler) getCurrentUser(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
accountID, userID := userAuth.AccountId, userAuth.UserId
|
user, err := h.accountManager.GetCurrentUserInfo(ctx, userAuth)
|
||||||
|
|
||||||
user, err := h.accountManager.GetCurrentUserInfo(ctx, accountID, userID)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.WriteError(r.Context(), err, w)
|
util.WriteError(r.Context(), err, w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
util.WriteJSONObject(r.Context(), w, toUserResponse(user, userID))
|
util.WriteJSONObject(r.Context(), w, toUserWithPermissionsResponse(user, userAuth.UserId))
|
||||||
|
}
|
||||||
|
|
||||||
|
func toUserWithPermissionsResponse(user *users.UserInfoWithPermissions, userID string) *api.User {
|
||||||
|
response := toUserResponse(user.UserInfo, userID)
|
||||||
|
|
||||||
|
// stringify modules and operations keys
|
||||||
|
modules := make(map[string]map[string]bool)
|
||||||
|
for module, operations := range user.Permissions {
|
||||||
|
modules[string(module)] = make(map[string]bool)
|
||||||
|
for op, val := range operations {
|
||||||
|
modules[string(module)][string(op)] = val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
response.Permissions = &api.UserPermissions{
|
||||||
|
IsRestricted: user.Restricted,
|
||||||
|
Modules: modules,
|
||||||
|
}
|
||||||
|
|
||||||
|
return response
|
||||||
}
|
}
|
||||||
|
|
||||||
func toUserResponse(user *types.UserInfo, currenUserID string) *api.User {
|
func toUserResponse(user *types.UserInfo, currenUserID string) *api.User {
|
||||||
@ -316,8 +335,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),
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,12 +13,16 @@ import (
|
|||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
nbcontext "github.com/netbirdio/netbird/management/server/context"
|
nbcontext "github.com/netbirdio/netbird/management/server/context"
|
||||||
"github.com/netbirdio/netbird/management/server/http/api"
|
"github.com/netbirdio/netbird/management/server/http/api"
|
||||||
"github.com/netbirdio/netbird/management/server/mock_server"
|
"github.com/netbirdio/netbird/management/server/mock_server"
|
||||||
|
"github.com/netbirdio/netbird/management/server/permissions/modules"
|
||||||
|
"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/types"
|
"github.com/netbirdio/netbird/management/server/types"
|
||||||
|
"github.com/netbirdio/netbird/management/server/users"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -107,7 +111,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,8 +128,8 @@ func initUsersTestData() *handler {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
GetCurrentUserInfoFunc: func(ctx context.Context, accountID, userID string) (*types.UserInfo, error) {
|
GetCurrentUserInfoFunc: func(ctx context.Context, userAuth nbcontext.UserAuth) (*users.UserInfoWithPermissions, error) {
|
||||||
switch userID {
|
switch userAuth.UserId {
|
||||||
case "not-found":
|
case "not-found":
|
||||||
return nil, status.NewUserNotFoundError("not-found")
|
return nil, status.NewUserNotFoundError("not-found")
|
||||||
case "not-of-account":
|
case "not-of-account":
|
||||||
@ -135,52 +139,68 @@ 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",
|
|
||||||
},
|
},
|
||||||
|
Permissions: mergeRolePermissions(roles.Owner),
|
||||||
}, 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",
|
|
||||||
},
|
},
|
||||||
|
Permissions: mergeRolePermissions(roles.User),
|
||||||
}, 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",
|
|
||||||
},
|
},
|
||||||
|
Permissions: mergeRolePermissions(roles.Admin),
|
||||||
|
}, nil
|
||||||
|
case "restricted-user":
|
||||||
|
return &users.UserInfoWithPermissions{
|
||||||
|
UserInfo: &types.UserInfo{
|
||||||
|
ID: "restricted-user",
|
||||||
|
Name: "",
|
||||||
|
Role: "user",
|
||||||
|
Status: "active",
|
||||||
|
IsServiceUser: false,
|
||||||
|
IsBlocked: false,
|
||||||
|
NonDeletable: false,
|
||||||
|
LastLogin: time.Time{},
|
||||||
|
Issued: "api",
|
||||||
|
},
|
||||||
|
Permissions: mergeRolePermissions(roles.User),
|
||||||
|
Restricted: true,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, fmt.Errorf("user id %s not handled", userID)
|
return nil, fmt.Errorf("user id %s not handled", userAuth.UserId)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -546,6 +566,7 @@ func TestCurrentUser(t *testing.T) {
|
|||||||
name string
|
name string
|
||||||
expectedStatus int
|
expectedStatus int
|
||||||
requestAuth nbcontext.UserAuth
|
requestAuth nbcontext.UserAuth
|
||||||
|
expectedResult *api.User
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "without auth",
|
name: "without auth",
|
||||||
@ -575,16 +596,78 @@ func TestCurrentUser(t *testing.T) {
|
|||||||
name: "owner",
|
name: "owner",
|
||||||
requestAuth: nbcontext.UserAuth{UserId: "owner"},
|
requestAuth: nbcontext.UserAuth{UserId: "owner"},
|
||||||
expectedStatus: http.StatusOK,
|
expectedStatus: http.StatusOK,
|
||||||
|
expectedResult: &api.User{
|
||||||
|
Id: "owner",
|
||||||
|
Role: "owner",
|
||||||
|
Status: "active",
|
||||||
|
IsBlocked: false,
|
||||||
|
IsCurrent: ptr(true),
|
||||||
|
IsServiceUser: ptr(false),
|
||||||
|
AutoGroups: []string{},
|
||||||
|
Issued: ptr("api"),
|
||||||
|
LastLogin: ptr(time.Time{}),
|
||||||
|
Permissions: &api.UserPermissions{
|
||||||
|
Modules: stringifyPermissionsKeys(mergeRolePermissions(roles.Owner)),
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "regular user",
|
name: "regular user",
|
||||||
requestAuth: nbcontext.UserAuth{UserId: "regular-user"},
|
requestAuth: nbcontext.UserAuth{UserId: "regular-user"},
|
||||||
expectedStatus: http.StatusOK,
|
expectedStatus: http.StatusOK,
|
||||||
|
expectedResult: &api.User{
|
||||||
|
Id: "regular-user",
|
||||||
|
Role: "user",
|
||||||
|
Status: "active",
|
||||||
|
IsBlocked: false,
|
||||||
|
IsCurrent: ptr(true),
|
||||||
|
IsServiceUser: ptr(false),
|
||||||
|
AutoGroups: []string{},
|
||||||
|
Issued: ptr("api"),
|
||||||
|
LastLogin: ptr(time.Time{}),
|
||||||
|
Permissions: &api.UserPermissions{
|
||||||
|
Modules: stringifyPermissionsKeys(mergeRolePermissions(roles.User)),
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "admin user",
|
name: "admin user",
|
||||||
requestAuth: nbcontext.UserAuth{UserId: "admin-user"},
|
requestAuth: nbcontext.UserAuth{UserId: "admin-user"},
|
||||||
expectedStatus: http.StatusOK,
|
expectedStatus: http.StatusOK,
|
||||||
|
expectedResult: &api.User{
|
||||||
|
Id: "admin-user",
|
||||||
|
Role: "admin",
|
||||||
|
Status: "active",
|
||||||
|
IsBlocked: false,
|
||||||
|
IsCurrent: ptr(true),
|
||||||
|
IsServiceUser: ptr(false),
|
||||||
|
AutoGroups: []string{},
|
||||||
|
Issued: ptr("api"),
|
||||||
|
LastLogin: ptr(time.Time{}),
|
||||||
|
Permissions: &api.UserPermissions{
|
||||||
|
Modules: stringifyPermissionsKeys(mergeRolePermissions(roles.Admin)),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "restricted user",
|
||||||
|
requestAuth: nbcontext.UserAuth{UserId: "restricted-user"},
|
||||||
|
expectedStatus: http.StatusOK,
|
||||||
|
expectedResult: &api.User{
|
||||||
|
Id: "restricted-user",
|
||||||
|
Role: "user",
|
||||||
|
Status: "active",
|
||||||
|
IsBlocked: false,
|
||||||
|
IsCurrent: ptr(true),
|
||||||
|
IsServiceUser: ptr(false),
|
||||||
|
AutoGroups: []string{},
|
||||||
|
Issued: ptr("api"),
|
||||||
|
LastLogin: ptr(time.Time{}),
|
||||||
|
Permissions: &api.UserPermissions{
|
||||||
|
IsRestricted: true,
|
||||||
|
Modules: stringifyPermissionsKeys(mergeRolePermissions(roles.User)),
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -603,10 +686,42 @@ func TestCurrentUser(t *testing.T) {
|
|||||||
res := rr.Result()
|
res := rr.Result()
|
||||||
defer res.Body.Close()
|
defer res.Body.Close()
|
||||||
|
|
||||||
if status := rr.Code; status != tc.expectedStatus {
|
assert.Equal(t, tc.expectedStatus, rr.Code, "handler returned wrong status code")
|
||||||
t.Fatalf("handler returned wrong status code: got %v want %v",
|
|
||||||
status, tc.expectedStatus)
|
if tc.expectedResult != nil {
|
||||||
|
var result api.User
|
||||||
|
require.NoError(t, json.NewDecoder(res.Body).Decode(&result))
|
||||||
|
assert.EqualValues(t, *tc.expectedResult, result)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ptr[T any, PT *T](x T) PT {
|
||||||
|
return &x
|
||||||
|
}
|
||||||
|
|
||||||
|
func mergeRolePermissions(role roles.RolePermissions) roles.Permissions {
|
||||||
|
permissions := roles.Permissions{}
|
||||||
|
|
||||||
|
for k := range modules.All {
|
||||||
|
if rolePermissions, ok := role.Permissions[k]; ok {
|
||||||
|
permissions[k] = rolePermissions
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
permissions[k] = role.AutoAllowNew
|
||||||
|
}
|
||||||
|
|
||||||
|
return permissions
|
||||||
|
}
|
||||||
|
|
||||||
|
func stringifyPermissionsKeys(permissions roles.Permissions) map[string]map[string]bool {
|
||||||
|
modules := make(map[string]map[string]bool)
|
||||||
|
for module, operations := range permissions {
|
||||||
|
modules[string(module)] = make(map[string]bool)
|
||||||
|
for op, val := range operations {
|
||||||
|
modules[string(module)][string(op)] = val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return modules
|
||||||
|
}
|
||||||
|
@ -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, userAuth nbcontext.UserAuth) (*users.UserInfoWithPermissions, error)
|
||||||
GetAccountMetaFunc func(ctx context.Context, accountID, userID string) (*types.AccountMeta, error)
|
GetAccountMetaFunc func(ctx context.Context, accountID, userID string) (*types.AccountMeta, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -882,9 +883,9 @@ 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, userAuth nbcontext.UserAuth) (*users.UserInfoWithPermissions, error) {
|
||||||
if am.GetCurrentUserInfoFunc != nil {
|
if am.GetCurrentUserInfoFunc != nil {
|
||||||
return am.GetCurrentUserInfoFunc(ctx, accountID, userID)
|
return am.GetCurrentUserInfoFunc(ctx, userAuth)
|
||||||
}
|
}
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method GetCurrentUserInfo is not implemented")
|
return nil, status.Errorf(codes.Unimplemented, "method GetCurrentUserInfo is not implemented")
|
||||||
}
|
}
|
||||||
|
@ -59,7 +59,7 @@ func (am *DefaultAccountManager) GetPeers(ctx context.Context, accountID, userID
|
|||||||
return nil, fmt.Errorf("failed to get account settings: %w", err)
|
return nil, fmt.Errorf("failed to get account settings: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if settings.RegularUsersViewBlocked {
|
if user.IsRestrictable() && settings.RegularUsersViewBlocked {
|
||||||
return []*nbpeer.Peer{}, nil
|
return []*nbpeer.Peer{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
GetPermissionsByRole(ctx context.Context, role types.UserRole) (roles.Permissions, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type managerImpl struct {
|
type managerImpl struct {
|
||||||
@ -96,3 +98,22 @@ func (m *managerImpl) ValidateAccountAccess(ctx context.Context, accountID strin
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *managerImpl) GetPermissionsByRole(ctx context.Context, role types.UserRole) (roles.Permissions, error) {
|
||||||
|
roleMap, ok := roles.RolesMap[role]
|
||||||
|
if !ok {
|
||||||
|
return roles.Permissions{}, status.NewUserRoleNotFoundError(string(role))
|
||||||
|
}
|
||||||
|
|
||||||
|
permissions := roles.Permissions{}
|
||||||
|
|
||||||
|
for k := range modules.All {
|
||||||
|
if rolePermissions, ok := roleMap.Permissions[k]; ok {
|
||||||
|
permissions[k] = rolePermissions
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
permissions[k] = roleMap.AutoAllowNew
|
||||||
|
}
|
||||||
|
|
||||||
|
return permissions, nil
|
||||||
|
}
|
||||||
|
@ -38,6 +38,21 @@ func (m *MockManager) EXPECT() *MockManagerMockRecorder {
|
|||||||
return m.recorder
|
return m.recorder
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetPermissionsByRole mocks base method.
|
||||||
|
func (m *MockManager) GetPermissionsByRole(ctx context.Context, role types.UserRole) (roles.Permissions, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "GetPermissionsByRole", ctx, role)
|
||||||
|
ret0, _ := ret[0].(roles.Permissions)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPermissionsByRole indicates an expected call of GetPermissionsByRole.
|
||||||
|
func (mr *MockManagerMockRecorder) GetPermissionsByRole(ctx, role interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPermissionsByRole", reflect.TypeOf((*MockManager)(nil).GetPermissionsByRole), 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()
|
||||||
|
@ -17,3 +17,19 @@ const (
|
|||||||
SetupKeys Module = "setup_keys"
|
SetupKeys Module = "setup_keys"
|
||||||
Pats Module = "pats"
|
Pats Module = "pats"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var All = map[Module]struct{}{
|
||||||
|
Networks: {},
|
||||||
|
Peers: {},
|
||||||
|
Groups: {},
|
||||||
|
Settings: {},
|
||||||
|
Accounts: {},
|
||||||
|
Dns: {},
|
||||||
|
Nameservers: {},
|
||||||
|
Events: {},
|
||||||
|
Policies: {},
|
||||||
|
Routes: {},
|
||||||
|
Users: {},
|
||||||
|
SetupKeys: {},
|
||||||
|
Pats: {},
|
||||||
|
}
|
||||||
|
@ -23,9 +23,9 @@ var NetworkAdmin = RolePermissions{
|
|||||||
},
|
},
|
||||||
modules.Groups: {
|
modules.Groups: {
|
||||||
operations.Read: true,
|
operations.Read: true,
|
||||||
operations.Create: false,
|
operations.Create: true,
|
||||||
operations.Update: false,
|
operations.Update: true,
|
||||||
operations.Delete: false,
|
operations.Delete: true,
|
||||||
},
|
},
|
||||||
modules.Settings: {
|
modules.Settings: {
|
||||||
operations.Read: true,
|
operations.Read: true,
|
||||||
@ -87,5 +87,11 @@ var NetworkAdmin = RolePermissions{
|
|||||||
operations.Update: true,
|
operations.Update: true,
|
||||||
operations.Delete: true,
|
operations.Delete: true,
|
||||||
},
|
},
|
||||||
|
modules.Peers: {
|
||||||
|
operations.Read: true,
|
||||||
|
operations.Create: false,
|
||||||
|
operations.Update: false,
|
||||||
|
operations.Delete: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -65,11 +65,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
|
||||||
@ -132,21 +127,18 @@ func (u *User) IsRegularUser() bool {
|
|||||||
return !u.HasAdminPower() && !u.IsServiceUser
|
return !u.HasAdminPower() && !u.IsServiceUser
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsRestrictable checks whether a user is in a restrictable role.
|
||||||
|
func (u *User) IsRestrictable() bool {
|
||||||
|
return u.Role == UserRoleUser || u.Role == UserRoleBillingAdmin
|
||||||
|
}
|
||||||
|
|
||||||
// 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,
|
||||||
@ -159,9 +151,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 {
|
||||||
@ -184,9 +173,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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,6 +12,7 @@ import (
|
|||||||
|
|
||||||
"github.com/netbirdio/netbird/management/server/activity"
|
"github.com/netbirdio/netbird/management/server/activity"
|
||||||
nbContext "github.com/netbirdio/netbird/management/server/context"
|
nbContext "github.com/netbirdio/netbird/management/server/context"
|
||||||
|
nbcontext "github.com/netbirdio/netbird/management/server/context"
|
||||||
"github.com/netbirdio/netbird/management/server/idp"
|
"github.com/netbirdio/netbird/management/server/idp"
|
||||||
nbpeer "github.com/netbirdio/netbird/management/server/peer"
|
nbpeer "github.com/netbirdio/netbird/management/server/peer"
|
||||||
"github.com/netbirdio/netbird/management/server/permissions/modules"
|
"github.com/netbirdio/netbird/management/server/permissions/modules"
|
||||||
@ -19,6 +20,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 +124,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 +135,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
|
||||||
@ -360,6 +357,7 @@ func (am *DefaultAccountManager) CreatePAT(ctx context.Context, accountID string
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @note this is essential to prevent non admin users with Pats create permission frpm creating one for a service user
|
||||||
if initiatorUserID != targetUserID && !(initiatorUser.HasAdminPower() && targetUser.IsServiceUser) {
|
if initiatorUserID != targetUserID && !(initiatorUser.HasAdminPower() && targetUser.IsServiceUser) {
|
||||||
return nil, status.NewAdminPermissionError()
|
return nil, status.NewAdminPermissionError()
|
||||||
}
|
}
|
||||||
@ -727,19 +725,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 +872,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 +890,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 +900,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 +909,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
|
||||||
@ -1239,8 +1218,10 @@ 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, userAuth nbcontext.UserAuth) (*users.UserInfoWithPermissions, error) {
|
||||||
|
accountID, userID := userAuth.AccountId, userAuth.UserId
|
||||||
|
|
||||||
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
|
||||||
@ -1258,10 +1239,25 @@ 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,
|
||||||
|
Restricted: !userAuth.IsChild && user.IsRestrictable() && settings.RegularUsersViewBlocked,
|
||||||
|
}
|
||||||
|
|
||||||
|
permissions, err := am.permissionsManager.GetPermissionsByRole(ctx, user.Role)
|
||||||
|
if err == nil {
|
||||||
|
userWithPermissions.Permissions = permissions
|
||||||
|
}
|
||||||
|
|
||||||
|
return userWithPermissions, nil
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,10 @@ 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/modules"
|
||||||
|
"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 +1023,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 {
|
||||||
@ -1654,121 +1573,154 @@ func TestDefaultAccountManager_GetCurrentUserInfo(t *testing.T) {
|
|||||||
|
|
||||||
tt := []struct {
|
tt := []struct {
|
||||||
name string
|
name string
|
||||||
accountId string
|
userAuth nbcontext.UserAuth
|
||||||
userId string
|
|
||||||
expectedErr error
|
expectedErr error
|
||||||
expectedResult *types.UserInfo
|
expectedResult *users.UserInfoWithPermissions
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "not found",
|
name: "not found",
|
||||||
accountId: account1.Id,
|
userAuth: nbcontext.UserAuth{AccountId: account1.Id, UserId: "not-found"},
|
||||||
userId: "not-found",
|
|
||||||
expectedErr: status.NewUserNotFoundError("not-found"),
|
expectedErr: status.NewUserNotFoundError("not-found"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "not part of account",
|
name: "not part of account",
|
||||||
accountId: account1.Id,
|
userAuth: nbcontext.UserAuth{AccountId: account1.Id, UserId: "account2Owner"},
|
||||||
userId: "account2Owner",
|
|
||||||
expectedErr: status.NewUserNotPartOfAccountError(),
|
expectedErr: status.NewUserNotPartOfAccountError(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "blocked",
|
name: "blocked",
|
||||||
accountId: account1.Id,
|
userAuth: nbcontext.UserAuth{AccountId: account1.Id, UserId: "blocked-user"},
|
||||||
userId: "blocked-user",
|
|
||||||
expectedErr: status.NewUserBlockedError(),
|
expectedErr: status.NewUserBlockedError(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "service user",
|
name: "service user",
|
||||||
accountId: account1.Id,
|
userAuth: nbcontext.UserAuth{AccountId: account1.Id, UserId: "service-user"},
|
||||||
userId: "service-user",
|
|
||||||
expectedErr: status.NewPermissionDeniedError(),
|
expectedErr: status.NewPermissionDeniedError(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "owner user",
|
name: "owner user",
|
||||||
accountId: account1.Id,
|
userAuth: nbcontext.UserAuth{AccountId: account1.Id, UserId: "account1Owner"},
|
||||||
userId: "account1Owner",
|
expectedResult: &users.UserInfoWithPermissions{
|
||||||
expectedResult: &types.UserInfo{
|
UserInfo: &types.UserInfo{
|
||||||
ID: "account1Owner",
|
ID: "account1Owner",
|
||||||
Name: "",
|
Name: "",
|
||||||
Role: "owner",
|
Role: "owner",
|
||||||
AutoGroups: []string{},
|
AutoGroups: []string{},
|
||||||
Status: "active",
|
Status: "active",
|
||||||
IsServiceUser: false,
|
IsServiceUser: false,
|
||||||
IsBlocked: false,
|
IsBlocked: false,
|
||||||
NonDeletable: false,
|
NonDeletable: false,
|
||||||
LastLogin: time.Time{},
|
LastLogin: time.Time{},
|
||||||
Issued: "api",
|
Issued: "api",
|
||||||
IntegrationReference: integration_reference.IntegrationReference{},
|
IntegrationReference: integration_reference.IntegrationReference{},
|
||||||
Permissions: types.UserPermissions{
|
|
||||||
DashboardView: "full",
|
|
||||||
},
|
},
|
||||||
|
Permissions: mergeRolePermissions(roles.Owner),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "regular user",
|
name: "regular user",
|
||||||
accountId: account1.Id,
|
userAuth: nbcontext.UserAuth{AccountId: account1.Id, UserId: "regular-user"},
|
||||||
userId: "regular-user",
|
expectedResult: &users.UserInfoWithPermissions{
|
||||||
expectedResult: &types.UserInfo{
|
UserInfo: &types.UserInfo{
|
||||||
ID: "regular-user",
|
ID: "regular-user",
|
||||||
Name: "",
|
Name: "",
|
||||||
Role: "user",
|
Role: "user",
|
||||||
Status: "active",
|
Status: "active",
|
||||||
IsServiceUser: false,
|
IsServiceUser: false,
|
||||||
IsBlocked: false,
|
IsBlocked: false,
|
||||||
NonDeletable: false,
|
NonDeletable: false,
|
||||||
LastLogin: time.Time{},
|
LastLogin: time.Time{},
|
||||||
Issued: "api",
|
Issued: "api",
|
||||||
IntegrationReference: integration_reference.IntegrationReference{},
|
IntegrationReference: integration_reference.IntegrationReference{},
|
||||||
Permissions: types.UserPermissions{
|
|
||||||
DashboardView: "limited",
|
|
||||||
},
|
},
|
||||||
|
Permissions: mergeRolePermissions(roles.User),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "admin user",
|
name: "admin user",
|
||||||
accountId: account1.Id,
|
userAuth: nbcontext.UserAuth{AccountId: account1.Id, UserId: "admin-user"},
|
||||||
userId: "admin-user",
|
expectedResult: &users.UserInfoWithPermissions{
|
||||||
expectedResult: &types.UserInfo{
|
UserInfo: &types.UserInfo{
|
||||||
ID: "admin-user",
|
ID: "admin-user",
|
||||||
Name: "",
|
Name: "",
|
||||||
Role: "admin",
|
Role: "admin",
|
||||||
Status: "active",
|
Status: "active",
|
||||||
IsServiceUser: false,
|
IsServiceUser: false,
|
||||||
IsBlocked: false,
|
IsBlocked: false,
|
||||||
NonDeletable: false,
|
NonDeletable: false,
|
||||||
LastLogin: time.Time{},
|
LastLogin: time.Time{},
|
||||||
Issued: "api",
|
Issued: "api",
|
||||||
IntegrationReference: integration_reference.IntegrationReference{},
|
IntegrationReference: integration_reference.IntegrationReference{},
|
||||||
Permissions: types.UserPermissions{
|
|
||||||
DashboardView: "full",
|
|
||||||
},
|
},
|
||||||
|
Permissions: mergeRolePermissions(roles.Admin),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "settings blocked regular user",
|
name: "settings blocked regular user",
|
||||||
accountId: account2.Id,
|
userAuth: nbcontext.UserAuth{AccountId: account2.Id, UserId: "settings-blocked-user"},
|
||||||
userId: "settings-blocked-user",
|
expectedResult: &users.UserInfoWithPermissions{
|
||||||
expectedResult: &types.UserInfo{
|
UserInfo: &types.UserInfo{
|
||||||
ID: "settings-blocked-user",
|
ID: "settings-blocked-user",
|
||||||
Name: "",
|
Name: "",
|
||||||
Role: "user",
|
Role: "user",
|
||||||
Status: "active",
|
Status: "active",
|
||||||
IsServiceUser: false,
|
IsServiceUser: false,
|
||||||
IsBlocked: false,
|
IsBlocked: false,
|
||||||
NonDeletable: false,
|
NonDeletable: false,
|
||||||
LastLogin: time.Time{},
|
LastLogin: time.Time{},
|
||||||
Issued: "api",
|
Issued: "api",
|
||||||
IntegrationReference: integration_reference.IntegrationReference{},
|
IntegrationReference: integration_reference.IntegrationReference{},
|
||||||
Permissions: types.UserPermissions{
|
|
||||||
DashboardView: "blocked",
|
|
||||||
},
|
},
|
||||||
|
Permissions: mergeRolePermissions(roles.User),
|
||||||
|
Restricted: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "settings blocked regular user child account",
|
||||||
|
userAuth: nbcontext.UserAuth{AccountId: account2.Id, UserId: "settings-blocked-user", IsChild: true},
|
||||||
|
expectedResult: &users.UserInfoWithPermissions{
|
||||||
|
UserInfo: &types.UserInfo{
|
||||||
|
ID: "settings-blocked-user",
|
||||||
|
Name: "",
|
||||||
|
Role: "user",
|
||||||
|
Status: "active",
|
||||||
|
IsServiceUser: false,
|
||||||
|
IsBlocked: false,
|
||||||
|
NonDeletable: false,
|
||||||
|
LastLogin: time.Time{},
|
||||||
|
Issued: "api",
|
||||||
|
IntegrationReference: integration_reference.IntegrationReference{},
|
||||||
|
},
|
||||||
|
Permissions: mergeRolePermissions(roles.User),
|
||||||
|
Restricted: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "settings blocked owner user",
|
||||||
|
userAuth: nbcontext.UserAuth{AccountId: account2.Id, UserId: "account2Owner"},
|
||||||
|
expectedResult: &users.UserInfoWithPermissions{
|
||||||
|
UserInfo: &types.UserInfo{
|
||||||
|
ID: "account2Owner",
|
||||||
|
Name: "",
|
||||||
|
Role: "owner",
|
||||||
|
AutoGroups: []string{},
|
||||||
|
Status: "active",
|
||||||
|
IsServiceUser: false,
|
||||||
|
IsBlocked: false,
|
||||||
|
NonDeletable: false,
|
||||||
|
LastLogin: time.Time{},
|
||||||
|
Issued: "api",
|
||||||
|
IntegrationReference: integration_reference.IntegrationReference{},
|
||||||
|
},
|
||||||
|
Permissions: mergeRolePermissions(roles.Owner),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range tt {
|
for _, tc := range tt {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
result, err := am.GetCurrentUserInfo(context.Background(), tc.accountId, tc.userId)
|
result, err := am.GetCurrentUserInfo(context.Background(), tc.userAuth)
|
||||||
|
|
||||||
if tc.expectedErr != nil {
|
if tc.expectedErr != nil {
|
||||||
assert.Equal(t, err, tc.expectedErr)
|
assert.Equal(t, err, tc.expectedErr)
|
||||||
@ -1780,3 +1732,17 @@ func TestDefaultAccountManager_GetCurrentUserInfo(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func mergeRolePermissions(role roles.RolePermissions) roles.Permissions {
|
||||||
|
permissions := roles.Permissions{}
|
||||||
|
|
||||||
|
for k := range modules.All {
|
||||||
|
if rolePermissions, ok := role.Permissions[k]; ok {
|
||||||
|
permissions[k] = rolePermissions
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
permissions[k] = role.AutoAllowNew
|
||||||
|
}
|
||||||
|
|
||||||
|
return permissions
|
||||||
|
}
|
||||||
|
14
management/server/users/user.go
Normal file
14
management/server/users/user.go
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
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.Permissions
|
||||||
|
Restricted bool
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user