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:
Pedro Costa
2025-04-17 18:58:58 +01:00
10 changed files with 104 additions and 108 deletions
+2 -8
View File
@@ -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,
} }
) )
+1 -10
View File
@@ -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
+2 -4
View File
@@ -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
}
+17 -7
View File
@@ -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: {},
}
+20 -5
View File
@@ -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
}
+1 -1
View File
@@ -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
} }