mirror of
https://github.com/netbirdio/netbird.git
synced 2026-10-23 05:49:07 +02:00
Merge branch 'feature/user-info-with-role-permissions' into feature/users-roles-endpoint
# Conflicts: # management/server/http/handlers/users/users_handler.go # management/server/permissions/manager.go
This commit is contained in:
@@ -30,14 +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",
|
||||||
Default: map[string]bool{
|
Status: api.UserStatusActive,
|
||||||
"read": false,
|
|
||||||
"write": false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Role: "user",
|
|
||||||
Status: api.UserStatusActive,
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -211,17 +211,8 @@ components:
|
|||||||
- read
|
- read
|
||||||
- write
|
- write
|
||||||
example: {"networks": { "read": true, "write": false}, "peers": { "read": false, "write": false} }
|
example: {"networks": { "read": true, "write": false}, "peers": { "read": false, "write": false} }
|
||||||
default:
|
|
||||||
type: object
|
|
||||||
additionalProperties:
|
|
||||||
type: boolean
|
|
||||||
propertyNames:
|
|
||||||
type: string
|
|
||||||
enum:
|
|
||||||
- read
|
|
||||||
- write
|
|
||||||
required:
|
required:
|
||||||
- default
|
- modules
|
||||||
- is_restricted
|
- is_restricted
|
||||||
UserRequest:
|
UserRequest:
|
||||||
type: object
|
type: object
|
||||||
|
|||||||
@@ -1710,11 +1710,9 @@ type UserCreateRequest struct {
|
|||||||
|
|
||||||
// UserPermissions defines model for UserPermissions.
|
// UserPermissions defines model for UserPermissions.
|
||||||
type UserPermissions struct {
|
type UserPermissions struct {
|
||||||
Default map[string]bool `json:"default"`
|
|
||||||
|
|
||||||
// IsRestricted Indicates whether this User's Peers view is restricted
|
// IsRestricted Indicates whether this User's Peers view is restricted
|
||||||
IsRestricted bool `json:"is_restricted"`
|
IsRestricted bool `json:"is_restricted"`
|
||||||
Modules *map[string]map[string]bool `json:"modules,omitempty"`
|
Modules map[string]map[string]bool `json:"modules"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// UserRequest defines model for UserRequest.
|
// UserRequest defines model for UserRequest.
|
||||||
|
|||||||
@@ -328,45 +328,21 @@ func toRolesResponse(roles map[types.UserRole]roles.RolePermissions) []api.RoleP
|
|||||||
func toUserWithPermissionsResponse(user *users.UserInfoWithPermissions, userID string) *api.User {
|
func toUserWithPermissionsResponse(user *users.UserInfoWithPermissions, userID string) *api.User {
|
||||||
response := toUserResponse(user.UserInfo, userID)
|
response := toUserResponse(user.UserInfo, userID)
|
||||||
|
|
||||||
response.Permissions = &api.UserPermissions{
|
// stringify modules and operations keys
|
||||||
IsRestricted: user.Restricted,
|
|
||||||
Default: toOperationsMapResponse(user.Permissions.AutoAllowNew),
|
|
||||||
Modules: toModulesMapResponse(user.Permissions.Permissions),
|
|
||||||
}
|
|
||||||
|
|
||||||
return response
|
|
||||||
}
|
|
||||||
|
|
||||||
func toOperationsMapResponse[K ~string, V any](input map[K]V) map[string]V {
|
|
||||||
if len(input) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
result := make(map[string]V, len(input))
|
|
||||||
for k, v := range input {
|
|
||||||
result[string(k)] = v
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
func toModulesMapResponse[K, L ~string](input map[K]map[L]bool) *map[string]map[string]bool {
|
|
||||||
if len(input) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
modules := make(map[string]map[string]bool)
|
modules := make(map[string]map[string]bool)
|
||||||
for module, operations := range input {
|
for module, operations := range user.Permissions {
|
||||||
if converted := toOperationsMapResponse(operations); len(converted) > 0 {
|
modules[string(module)] = make(map[string]bool)
|
||||||
modules[string(module)] = converted
|
for op, val := range operations {
|
||||||
|
modules[string(module)][string(op)] = val
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(modules) == 0 {
|
response.Permissions = &api.UserPermissions{
|
||||||
return nil
|
IsRestricted: user.Restricted,
|
||||||
|
Modules: modules,
|
||||||
}
|
}
|
||||||
|
|
||||||
return &modules
|
return response
|
||||||
}
|
}
|
||||||
|
|
||||||
func toUserResponse(user *types.UserInfo, currenUserID string) *api.User {
|
func toUserResponse(user *types.UserInfo, currenUserID string) *api.User {
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import (
|
|||||||
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/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"
|
||||||
@@ -149,7 +150,7 @@ func initUsersTestData() *handler {
|
|||||||
NonDeletable: false,
|
NonDeletable: false,
|
||||||
Issued: "api",
|
Issued: "api",
|
||||||
},
|
},
|
||||||
Permissions: roles.Owner,
|
Permissions: mergeRolePermissions(roles.Owner),
|
||||||
}, nil
|
}, nil
|
||||||
case "regular-user":
|
case "regular-user":
|
||||||
return &users.UserInfoWithPermissions{
|
return &users.UserInfoWithPermissions{
|
||||||
@@ -163,7 +164,7 @@ func initUsersTestData() *handler {
|
|||||||
NonDeletable: false,
|
NonDeletable: false,
|
||||||
Issued: "api",
|
Issued: "api",
|
||||||
},
|
},
|
||||||
Permissions: roles.User,
|
Permissions: mergeRolePermissions(roles.User),
|
||||||
}, nil
|
}, nil
|
||||||
|
|
||||||
case "admin-user":
|
case "admin-user":
|
||||||
@@ -179,7 +180,7 @@ func initUsersTestData() *handler {
|
|||||||
LastLogin: time.Time{},
|
LastLogin: time.Time{},
|
||||||
Issued: "api",
|
Issued: "api",
|
||||||
},
|
},
|
||||||
Permissions: roles.Admin,
|
Permissions: mergeRolePermissions(roles.Admin),
|
||||||
}, nil
|
}, nil
|
||||||
case "restricted-user":
|
case "restricted-user":
|
||||||
return &users.UserInfoWithPermissions{
|
return &users.UserInfoWithPermissions{
|
||||||
@@ -194,7 +195,7 @@ func initUsersTestData() *handler {
|
|||||||
LastLogin: time.Time{},
|
LastLogin: time.Time{},
|
||||||
Issued: "api",
|
Issued: "api",
|
||||||
},
|
},
|
||||||
Permissions: roles.User,
|
Permissions: mergeRolePermissions(roles.User),
|
||||||
Restricted: true,
|
Restricted: true,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
@@ -606,13 +607,7 @@ func TestCurrentUser(t *testing.T) {
|
|||||||
Issued: ptr("api"),
|
Issued: ptr("api"),
|
||||||
LastLogin: ptr(time.Time{}),
|
LastLogin: ptr(time.Time{}),
|
||||||
Permissions: &api.UserPermissions{
|
Permissions: &api.UserPermissions{
|
||||||
IsRestricted: false,
|
Modules: stringifyPermissionsKeys(mergeRolePermissions(roles.Owner)),
|
||||||
Default: map[string]bool{
|
|
||||||
"read": true,
|
|
||||||
"create": true,
|
|
||||||
"update": true,
|
|
||||||
"delete": true,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -631,12 +626,7 @@ func TestCurrentUser(t *testing.T) {
|
|||||||
Issued: ptr("api"),
|
Issued: ptr("api"),
|
||||||
LastLogin: ptr(time.Time{}),
|
LastLogin: ptr(time.Time{}),
|
||||||
Permissions: &api.UserPermissions{
|
Permissions: &api.UserPermissions{
|
||||||
Default: map[string]bool{
|
Modules: stringifyPermissionsKeys(mergeRolePermissions(roles.User)),
|
||||||
"read": false,
|
|
||||||
"create": false,
|
|
||||||
"update": false,
|
|
||||||
"delete": false,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -655,21 +645,7 @@ func TestCurrentUser(t *testing.T) {
|
|||||||
Issued: ptr("api"),
|
Issued: ptr("api"),
|
||||||
LastLogin: ptr(time.Time{}),
|
LastLogin: ptr(time.Time{}),
|
||||||
Permissions: &api.UserPermissions{
|
Permissions: &api.UserPermissions{
|
||||||
IsRestricted: false,
|
Modules: stringifyPermissionsKeys(mergeRolePermissions(roles.Admin)),
|
||||||
Default: map[string]bool{
|
|
||||||
"read": true,
|
|
||||||
"create": true,
|
|
||||||
"update": true,
|
|
||||||
"delete": true,
|
|
||||||
},
|
|
||||||
Modules: ptr(map[string]map[string]bool{
|
|
||||||
"accounts": {
|
|
||||||
"read": true,
|
|
||||||
"create": false,
|
|
||||||
"update": false,
|
|
||||||
"delete": false,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -689,12 +665,7 @@ func TestCurrentUser(t *testing.T) {
|
|||||||
LastLogin: ptr(time.Time{}),
|
LastLogin: ptr(time.Time{}),
|
||||||
Permissions: &api.UserPermissions{
|
Permissions: &api.UserPermissions{
|
||||||
IsRestricted: true,
|
IsRestricted: true,
|
||||||
Default: map[string]bool{
|
Modules: stringifyPermissionsKeys(mergeRolePermissions(roles.User)),
|
||||||
"read": false,
|
|
||||||
"create": false,
|
|
||||||
"update": false,
|
|
||||||
"delete": false,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -729,3 +700,28 @@ func TestCurrentUser(t *testing.T) {
|
|||||||
func ptr[T any, PT *T](x T) PT {
|
func ptr[T any, PT *T](x T) PT {
|
||||||
return &x
|
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
|
||||||
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ type Manager interface {
|
|||||||
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)
|
GetRolePermissions(ctx context.Context, role types.UserRole) (roles.Permissions, error)
|
||||||
GetPermissions(ctx context.Context) map[types.UserRole]roles.RolePermissions
|
GetPermissions(ctx context.Context) map[types.UserRole]roles.RolePermissions
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,9 +67,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, err := m.GetRolePermissions(ctx, user.Role)
|
role, ok := roles.RolesMap[user.Role]
|
||||||
if err != nil {
|
if !ok {
|
||||||
return false, err
|
return false, status.NewUserRoleNotFoundError(string(user.Role))
|
||||||
}
|
}
|
||||||
|
|
||||||
return m.ValidateRoleModuleAccess(ctx, accountID, role, module, operation), nil
|
return m.ValidateRoleModuleAccess(ctx, accountID, role, module, operation), nil
|
||||||
@@ -100,10 +100,20 @@ 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) {
|
func (m *managerImpl) GetRolePermissions(ctx context.Context, role types.UserRole) (roles.Permissions, error) {
|
||||||
permissions, ok := roles.RolesMap[role]
|
roleMap, ok := roles.RolesMap[role]
|
||||||
if !ok {
|
if !ok {
|
||||||
return roles.RolePermissions{}, status.NewUserRoleNotFoundError(string(role))
|
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
|
return permissions, nil
|
||||||
|
|||||||
@@ -53,10 +53,10 @@ func (mr *MockManagerMockRecorder) GetPermissions(ctx interface{}) *gomock.Call
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetRolePermissions mocks base method.
|
// GetRolePermissions mocks base method.
|
||||||
func (m *MockManager) GetRolePermissions(ctx context.Context, role types.UserRole) (roles.RolePermissions, error) {
|
func (m *MockManager) GetRolePermissions(ctx context.Context, role types.UserRole) (roles.Permissions, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "GetRolePermissions", ctx, role)
|
ret := m.ctrl.Call(m, "GetRolePermissions", ctx, role)
|
||||||
ret0, _ := ret[0].(roles.RolePermissions)
|
ret0, _ := ret[0].(roles.Permissions)
|
||||||
ret1, _ := ret[1].(error)
|
ret1, _ := ret[1].(error)
|
||||||
return ret0, ret1
|
return ret0, ret1
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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: {},
|
||||||
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ 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/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/users"
|
||||||
@@ -1619,7 +1620,7 @@ func TestDefaultAccountManager_GetCurrentUserInfo(t *testing.T) {
|
|||||||
Issued: "api",
|
Issued: "api",
|
||||||
IntegrationReference: integration_reference.IntegrationReference{},
|
IntegrationReference: integration_reference.IntegrationReference{},
|
||||||
},
|
},
|
||||||
Permissions: roles.Owner,
|
Permissions: mergeRolePermissions(roles.Owner),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -1639,7 +1640,7 @@ func TestDefaultAccountManager_GetCurrentUserInfo(t *testing.T) {
|
|||||||
Issued: "api",
|
Issued: "api",
|
||||||
IntegrationReference: integration_reference.IntegrationReference{},
|
IntegrationReference: integration_reference.IntegrationReference{},
|
||||||
},
|
},
|
||||||
Permissions: roles.User,
|
Permissions: mergeRolePermissions(roles.User),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -1659,7 +1660,7 @@ func TestDefaultAccountManager_GetCurrentUserInfo(t *testing.T) {
|
|||||||
Issued: "api",
|
Issued: "api",
|
||||||
IntegrationReference: integration_reference.IntegrationReference{},
|
IntegrationReference: integration_reference.IntegrationReference{},
|
||||||
},
|
},
|
||||||
Permissions: roles.Admin,
|
Permissions: mergeRolePermissions(roles.Admin),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -1679,7 +1680,7 @@ func TestDefaultAccountManager_GetCurrentUserInfo(t *testing.T) {
|
|||||||
Issued: "api",
|
Issued: "api",
|
||||||
IntegrationReference: integration_reference.IntegrationReference{},
|
IntegrationReference: integration_reference.IntegrationReference{},
|
||||||
},
|
},
|
||||||
Permissions: roles.User,
|
Permissions: mergeRolePermissions(roles.User),
|
||||||
Restricted: true,
|
Restricted: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -1701,7 +1702,7 @@ func TestDefaultAccountManager_GetCurrentUserInfo(t *testing.T) {
|
|||||||
Issued: "api",
|
Issued: "api",
|
||||||
IntegrationReference: integration_reference.IntegrationReference{},
|
IntegrationReference: integration_reference.IntegrationReference{},
|
||||||
},
|
},
|
||||||
Permissions: roles.Owner,
|
Permissions: mergeRolePermissions(roles.Owner),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -1720,3 +1721,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
|
||||||
|
}
|
||||||
|
|||||||
@@ -9,6 +9,6 @@ import (
|
|||||||
type UserInfoWithPermissions struct {
|
type UserInfoWithPermissions struct {
|
||||||
*types.UserInfo
|
*types.UserInfo
|
||||||
|
|
||||||
Permissions roles.RolePermissions
|
Permissions roles.Permissions
|
||||||
Restricted bool
|
Restricted bool
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user