diff --git a/management/server/http/api/openapi.yml b/management/server/http/api/openapi.yml index 1e3b0c443..455bbfd79 100644 --- a/management/server/http/api/openapi.yml +++ b/management/server/http/api/openapi.yml @@ -188,9 +188,13 @@ components: - auto_groups - status - is_blocked + - permissions UserPermissions: type: object properties: + is_restricted: + type: boolean + description: Indicates whether this User's Peers view is restricted modules: type: object additionalProperties: @@ -219,6 +223,7 @@ components: - write required: - default + - is_restricted UserRequest: type: object properties: diff --git a/management/server/http/api/types.gen.go b/management/server/http/api/types.gen.go index 9a0264582..087fb5eef 100644 --- a/management/server/http/api/types.gen.go +++ b/management/server/http/api/types.gen.go @@ -1677,8 +1677,8 @@ type User struct { LastLogin *time.Time `json:"last_login,omitempty"` // Name User's name from idp provider - Name string `json:"name"` - Permissions *UserPermissions `json:"permissions,omitempty"` + Name string `json:"name"` + Permissions UserPermissions `json:"permissions"` // Role User's NetBird account role Role string `json:"role"` @@ -1710,8 +1710,11 @@ type UserCreateRequest struct { // UserPermissions defines model for UserPermissions. type UserPermissions struct { - Default map[string]bool `json:"default"` - Modules *map[string]map[string]bool `json:"modules,omitempty"` + Default map[string]bool `json:"default"` + + // 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. diff --git a/management/server/http/handlers/users/users_handler.go b/management/server/http/handlers/users/users_handler.go index 0d58d1ab5..307fdd75d 100644 --- a/management/server/http/handlers/users/users_handler.go +++ b/management/server/http/handlers/users/users_handler.go @@ -347,11 +347,11 @@ func toRolesResponse(roles map[types.UserRole]roles.RolePermissions) []api.RoleP func toUserWithPermissionsResponse(user *users.UserInfoWithPermissions, userID string) *api.User { 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 { permissions.Default = make(map[string]bool) for k, v := range user.Permissions.AutoAllowNew { diff --git a/management/server/http/handlers/users/users_handler_test.go b/management/server/http/handlers/users/users_handler_test.go index e15d030e5..0321068c2 100644 --- a/management/server/http/handlers/users/users_handler_test.go +++ b/management/server/http/handlers/users/users_handler_test.go @@ -13,10 +13,12 @@ import ( "github.com/gorilla/mux" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" nbcontext "github.com/netbirdio/netbird/management/server/context" "github.com/netbirdio/netbird/management/server/http/api" "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/types" "github.com/netbirdio/netbird/management/server/users" @@ -147,6 +149,7 @@ func initUsersTestData() *handler { NonDeletable: false, Issued: "api", }, + Permissions: roles.Owner, }, nil case "regular-user": return &users.UserInfoWithPermissions{ @@ -160,6 +163,7 @@ func initUsersTestData() *handler { NonDeletable: false, Issued: "api", }, + Permissions: roles.User, }, nil case "admin-user": @@ -175,6 +179,23 @@ func initUsersTestData() *handler { LastLogin: time.Time{}, 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 } @@ -544,6 +565,7 @@ func TestCurrentUser(t *testing.T) { name string expectedStatus int requestAuth nbcontext.UserAuth + expectedResult *api.User }{ { name: "without auth", @@ -573,16 +595,108 @@ func TestCurrentUser(t *testing.T) { name: "owner", requestAuth: nbcontext.UserAuth{UserId: "owner"}, 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", requestAuth: nbcontext.UserAuth{UserId: "regular-user"}, 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", requestAuth: nbcontext.UserAuth{UserId: "admin-user"}, 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() defer res.Body.Close() - if status := rr.Code; status != tc.expectedStatus { - t.Fatalf("handler returned wrong status code: got %v want %v", - status, tc.expectedStatus) + assert.Equal(t, tc.expectedStatus, rr.Code, "handler returned wrong status code") + + 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 +} diff --git a/management/server/types/user.go b/management/server/types/user.go index 16d99e1ff..f1d00f548 100644 --- a/management/server/types/user.go +++ b/management/server/types/user.go @@ -121,6 +121,11 @@ func (u *User) IsRegularUser() bool { 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. func (u *User) ToUserInfo(userData *idp.UserData) (*UserInfo, error) { autoGroups := u.AutoGroups diff --git a/management/server/user.go b/management/server/user.go index bffdfdb80..75889b421 100644 --- a/management/server/user.go +++ b/management/server/user.go @@ -1240,16 +1240,14 @@ func (am *DefaultAccountManager) GetCurrentUserInfo(ctx context.Context, account } userWithPermissions := &users.UserInfoWithPermissions{ - UserInfo: userInfo, - } - - if user.Role == types.UserRoleUser && settings.RegularUsersViewBlocked { - return userWithPermissions, nil + UserInfo: userInfo, + Restricted: user.IsRestrictable() && settings.RegularUsersViewBlocked, } permissions, err := am.permissionsManager.GetRolePermissions(ctx, user.Role) if err == nil { - userWithPermissions.Permissions = &permissions + userWithPermissions.Permissions = permissions } + return userWithPermissions, nil } diff --git a/management/server/user_test.go b/management/server/user_test.go index 4684d192a..b74b76fd3 100644 --- a/management/server/user_test.go +++ b/management/server/user_test.go @@ -1619,7 +1619,7 @@ func TestDefaultAccountManager_GetCurrentUserInfo(t *testing.T) { Issued: "api", IntegrationReference: integration_reference.IntegrationReference{}, }, - Permissions: &roles.Owner, + Permissions: roles.Owner, }, }, { @@ -1639,7 +1639,7 @@ func TestDefaultAccountManager_GetCurrentUserInfo(t *testing.T) { Issued: "api", IntegrationReference: integration_reference.IntegrationReference{}, }, - Permissions: &roles.User, + Permissions: roles.User, }, }, { @@ -1659,7 +1659,7 @@ func TestDefaultAccountManager_GetCurrentUserInfo(t *testing.T) { Issued: "api", IntegrationReference: integration_reference.IntegrationReference{}, }, - Permissions: &roles.Admin, + Permissions: roles.Admin, }, }, { @@ -1679,7 +1679,8 @@ func TestDefaultAccountManager_GetCurrentUserInfo(t *testing.T) { Issued: "api", IntegrationReference: integration_reference.IntegrationReference{}, }, - Permissions: nil, + Permissions: roles.User, + Restricted: true, }, }, { @@ -1700,7 +1701,7 @@ func TestDefaultAccountManager_GetCurrentUserInfo(t *testing.T) { Issued: "api", IntegrationReference: integration_reference.IntegrationReference{}, }, - Permissions: &roles.Owner, + Permissions: roles.Owner, }, }, } diff --git a/management/server/users/user.go b/management/server/users/user.go index 8408e4637..a0d6144ba 100644 --- a/management/server/users/user.go +++ b/management/server/users/user.go @@ -9,5 +9,6 @@ import ( type UserInfoWithPermissions struct { *types.UserInfo - Permissions *roles.RolePermissions + Permissions roles.RolePermissions + Restricted bool }