[management] restrict dashboard only to restrictable roles

This commit is contained in:
Pedro Costa
2025-04-16 19:34:20 +01:00
parent 974f297022
commit ab391ed9fa
8 changed files with 156 additions and 22 deletions
+5
View File
@@ -188,9 +188,13 @@ components:
- auto_groups - auto_groups
- status - status
- is_blocked - is_blocked
- permissions
UserPermissions: UserPermissions:
type: object type: object
properties: properties:
is_restricted:
type: boolean
description: Indicates whether this User's Peers view is restricted
modules: modules:
type: object type: object
additionalProperties: additionalProperties:
@@ -219,6 +223,7 @@ components:
- write - write
required: required:
- default - default
- is_restricted
UserRequest: UserRequest:
type: object type: object
properties: properties:
+7 -4
View File
@@ -1670,8 +1670,8 @@ type User struct {
LastLogin *time.Time `json:"last_login,omitempty"` LastLogin *time.Time `json:"last_login,omitempty"`
// Name User's name from idp provider // Name User's name from idp provider
Name string `json:"name"` Name string `json:"name"`
Permissions *UserPermissions `json:"permissions,omitempty"` Permissions UserPermissions `json:"permissions"`
// Role User's NetBird account role // Role User's NetBird account role
Role string `json:"role"` Role string `json:"role"`
@@ -1703,8 +1703,11 @@ 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"` Default map[string]bool `json:"default"`
Modules *map[string]map[string]bool `json:"modules,omitempty"`
// IsRestricted Indicates whether this User's Peers view is restricted
IsRestricted bool `json:"is_restricted"`
Modules *map[string]map[string]bool `json:"modules,omitempty"`
} }
// UserRequest defines model for UserRequest. // UserRequest defines model for UserRequest.
@@ -286,11 +286,11 @@ func (h *handler) getCurrentUser(w http.ResponseWriter, r *http.Request) {
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)
if user.Permissions == nil {
return response permissions := api.UserPermissions{
IsRestricted: user.Restricted,
} }
permissions := &api.UserPermissions{}
if len(user.Permissions.AutoAllowNew) > 0 { if len(user.Permissions.AutoAllowNew) > 0 {
permissions.Default = make(map[string]bool) permissions.Default = make(map[string]bool)
for k, v := range user.Permissions.AutoAllowNew { for k, v := range user.Permissions.AutoAllowNew {
@@ -13,10 +13,12 @@ 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/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" "github.com/netbirdio/netbird/management/server/users"
@@ -147,6 +149,7 @@ func initUsersTestData() *handler {
NonDeletable: false, NonDeletable: false,
Issued: "api", Issued: "api",
}, },
Permissions: roles.Owner,
}, nil }, nil
case "regular-user": case "regular-user":
return &users.UserInfoWithPermissions{ return &users.UserInfoWithPermissions{
@@ -160,6 +163,7 @@ func initUsersTestData() *handler {
NonDeletable: false, NonDeletable: false,
Issued: "api", Issued: "api",
}, },
Permissions: roles.User,
}, nil }, nil
case "admin-user": case "admin-user":
@@ -175,6 +179,23 @@ func initUsersTestData() *handler {
LastLogin: time.Time{}, LastLogin: time.Time{},
Issued: "api", Issued: "api",
}, },
Permissions: 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: roles.User,
Restricted: true,
}, nil }, nil
} }
@@ -544,6 +565,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",
@@ -573,16 +595,108 @@ 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{
IsRestricted: false,
Default: map[string]bool{
"read": true,
"create": true,
"update": true,
"delete": true,
},
},
},
}, },
{ {
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{
Default: map[string]bool{
"read": false,
"create": false,
"update": false,
"delete": false,
},
},
},
}, },
{ {
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{
IsRestricted: false,
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,
},
}),
},
},
},
{
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,
Default: map[string]bool{
"read": false,
"create": false,
"update": false,
"delete": false,
},
},
},
}, },
} }
@@ -601,10 +715,17 @@ 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
}
+5
View File
@@ -121,6 +121,11 @@ 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) (*UserInfo, error) { func (u *User) ToUserInfo(userData *idp.UserData) (*UserInfo, error) {
autoGroups := u.AutoGroups autoGroups := u.AutoGroups
+4 -6
View File
@@ -1240,16 +1240,14 @@ func (am *DefaultAccountManager) GetCurrentUserInfo(ctx context.Context, account
} }
userWithPermissions := &users.UserInfoWithPermissions{ userWithPermissions := &users.UserInfoWithPermissions{
UserInfo: userInfo, UserInfo: userInfo,
} Restricted: user.IsRestrictable() && settings.RegularUsersViewBlocked,
if user.Role == types.UserRoleUser && settings.RegularUsersViewBlocked {
return userWithPermissions, nil
} }
permissions, err := am.permissionsManager.GetRolePermissions(ctx, user.Role) permissions, err := am.permissionsManager.GetRolePermissions(ctx, user.Role)
if err == nil { if err == nil {
userWithPermissions.Permissions = &permissions userWithPermissions.Permissions = permissions
} }
return userWithPermissions, nil return userWithPermissions, nil
} }
+6 -5
View File
@@ -1619,7 +1619,7 @@ func TestDefaultAccountManager_GetCurrentUserInfo(t *testing.T) {
Issued: "api", Issued: "api",
IntegrationReference: integration_reference.IntegrationReference{}, IntegrationReference: integration_reference.IntegrationReference{},
}, },
Permissions: &roles.Owner, Permissions: roles.Owner,
}, },
}, },
{ {
@@ -1639,7 +1639,7 @@ func TestDefaultAccountManager_GetCurrentUserInfo(t *testing.T) {
Issued: "api", Issued: "api",
IntegrationReference: integration_reference.IntegrationReference{}, IntegrationReference: integration_reference.IntegrationReference{},
}, },
Permissions: &roles.User, Permissions: roles.User,
}, },
}, },
{ {
@@ -1659,7 +1659,7 @@ func TestDefaultAccountManager_GetCurrentUserInfo(t *testing.T) {
Issued: "api", Issued: "api",
IntegrationReference: integration_reference.IntegrationReference{}, IntegrationReference: integration_reference.IntegrationReference{},
}, },
Permissions: &roles.Admin, Permissions: roles.Admin,
}, },
}, },
{ {
@@ -1679,7 +1679,8 @@ func TestDefaultAccountManager_GetCurrentUserInfo(t *testing.T) {
Issued: "api", Issued: "api",
IntegrationReference: integration_reference.IntegrationReference{}, IntegrationReference: integration_reference.IntegrationReference{},
}, },
Permissions: nil, Permissions: roles.User,
Restricted: true,
}, },
}, },
{ {
@@ -1700,7 +1701,7 @@ func TestDefaultAccountManager_GetCurrentUserInfo(t *testing.T) {
Issued: "api", Issued: "api",
IntegrationReference: integration_reference.IntegrationReference{}, IntegrationReference: integration_reference.IntegrationReference{},
}, },
Permissions: &roles.Owner, Permissions: roles.Owner,
}, },
}, },
} }
+2 -1
View File
@@ -9,5 +9,6 @@ import (
type UserInfoWithPermissions struct { type UserInfoWithPermissions struct {
*types.UserInfo *types.UserInfo
Permissions *roles.RolePermissions Permissions roles.RolePermissions
Restricted bool
} }