mirror of
https://github.com/netbirdio/netbird.git
synced 2026-07-18 20:49:56 +00:00
[management] users roles endpoint
This commit is contained in:
@@ -203,7 +203,7 @@ var (
|
||||
}
|
||||
|
||||
permissionsManager := integrations.InitPermissionsManager(store)
|
||||
userManager := users.NewManager(store)
|
||||
userManager := users.NewManager(store, permissionsManager)
|
||||
extraSettingsManager := integrations.NewManager(eventStore)
|
||||
settingsManager := settings.NewManager(store, userManager, extraSettingsManager, permissionsManager)
|
||||
peersManager := peers.NewManager(store, permissionsManager)
|
||||
@@ -275,8 +275,9 @@ var (
|
||||
resourcesManager := resources.NewManager(store, permissionsManager, groupsManager, accountManager)
|
||||
routersManager := routers.NewManager(store, permissionsManager, accountManager)
|
||||
networksManager := networks.NewManager(store, permissionsManager, resourcesManager, routersManager, accountManager)
|
||||
usersManager := users.NewManager(store, permissionsManager)
|
||||
|
||||
httpAPIHandler, err := nbhttp.NewAPIHandler(ctx, accountManager, networksManager, resourcesManager, routersManager, groupsManager, geo, authManager, appMetrics, integratedPeerValidator, proxyController, permissionsManager, peersManager, settingsManager)
|
||||
httpAPIHandler, err := nbhttp.NewAPIHandler(ctx, accountManager, networksManager, resourcesManager, routersManager, groupsManager, geo, authManager, appMetrics, integratedPeerValidator, proxyController, permissionsManager, peersManager, settingsManager, usersManager)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed creating HTTP API handler: %v", err)
|
||||
|
||||
@@ -2013,6 +2013,41 @@ components:
|
||||
- policy_name
|
||||
- icmp_type
|
||||
- icmp_code
|
||||
RolePermissions:
|
||||
type: object
|
||||
properties:
|
||||
role:
|
||||
type: string
|
||||
example: admin
|
||||
modules:
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: boolean
|
||||
propertyNames:
|
||||
type: string
|
||||
enum:
|
||||
- read
|
||||
- write
|
||||
propertyNames:
|
||||
type: string
|
||||
enum:
|
||||
- read
|
||||
- write
|
||||
example: {"networks": { "read": true, "write": false}, "peers": { "read": false, "write": false} }
|
||||
default:
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: boolean
|
||||
propertyNames:
|
||||
type: string
|
||||
enum:
|
||||
- read
|
||||
- write
|
||||
required:
|
||||
- default
|
||||
- role
|
||||
responses:
|
||||
not_found:
|
||||
description: Resource not found
|
||||
@@ -2443,6 +2478,31 @@ paths:
|
||||
"$ref": "#/components/responses/forbidden"
|
||||
'500':
|
||||
"$ref": "#/components/responses/internal_error"
|
||||
/api/users/roles:
|
||||
get:
|
||||
summary: Retrieves user roles and permissions
|
||||
description: Get permissions for user roles
|
||||
tags: [ Users ]
|
||||
security:
|
||||
- BearerAuth: [ ]
|
||||
- TokenAuth: [ ]
|
||||
responses:
|
||||
'200':
|
||||
description: A JSON Array of RolePermissions objects
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/RolePermissions'
|
||||
'400':
|
||||
"$ref": "#/components/responses/bad_request"
|
||||
'401':
|
||||
"$ref": "#/components/responses/requires_authentication"
|
||||
'403':
|
||||
"$ref": "#/components/responses/forbidden"
|
||||
'500':
|
||||
"$ref": "#/components/responses/internal_error"
|
||||
/api/peers:
|
||||
get:
|
||||
summary: List all Peers
|
||||
|
||||
@@ -1400,6 +1400,13 @@ type Resource struct {
|
||||
// ResourceType defines model for ResourceType.
|
||||
type ResourceType string
|
||||
|
||||
// RolePermissions defines model for RolePermissions.
|
||||
type RolePermissions struct {
|
||||
Default map[string]bool `json:"default"`
|
||||
Modules *map[string]map[string]bool `json:"modules,omitempty"`
|
||||
Role string `json:"role"`
|
||||
}
|
||||
|
||||
// Route defines model for Route.
|
||||
type Route struct {
|
||||
// AccessControlGroups Access control group identifier associated with route.
|
||||
|
||||
@@ -36,6 +36,7 @@ import (
|
||||
"github.com/netbirdio/netbird/management/server/networks/routers"
|
||||
nbpeers "github.com/netbirdio/netbird/management/server/peers"
|
||||
"github.com/netbirdio/netbird/management/server/telemetry"
|
||||
musers "github.com/netbirdio/netbird/management/server/users"
|
||||
)
|
||||
|
||||
const apiPrefix = "/api"
|
||||
@@ -56,6 +57,7 @@ func NewAPIHandler(
|
||||
permissionsManager permissions.Manager,
|
||||
peersManager nbpeers.Manager,
|
||||
settingsManager settings.Manager,
|
||||
usersManager musers.Manager,
|
||||
) (http.Handler, error) {
|
||||
|
||||
authMiddleware := middleware.NewAuthMiddleware(
|
||||
@@ -80,7 +82,7 @@ func NewAPIHandler(
|
||||
|
||||
accounts.AddEndpoints(accountManager, settingsManager, router)
|
||||
peers.AddEndpoints(accountManager, router)
|
||||
users.AddEndpoints(accountManager, router)
|
||||
users.AddEndpoints(accountManager, usersManager, router)
|
||||
setup_keys.AddEndpoints(accountManager, router)
|
||||
policies.AddEndpoints(accountManager, LocationManager, router)
|
||||
policies.AddPostureCheckEndpoints(accountManager, LocationManager, router)
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"github.com/netbirdio/netbird/management/server/account"
|
||||
"github.com/netbirdio/netbird/management/server/http/api"
|
||||
"github.com/netbirdio/netbird/management/server/http/util"
|
||||
"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"
|
||||
@@ -21,9 +22,10 @@ import (
|
||||
// handler is a handler that returns users of the account
|
||||
type handler struct {
|
||||
accountManager account.Manager
|
||||
usersManager users.Manager
|
||||
}
|
||||
|
||||
func AddEndpoints(accountManager account.Manager, router *mux.Router) {
|
||||
func AddEndpoints(accountManager account.Manager, usersManager users.Manager, router *mux.Router) {
|
||||
userHandler := newHandler(accountManager)
|
||||
router.HandleFunc("/users", userHandler.getAllUsers).Methods("GET", "OPTIONS")
|
||||
router.HandleFunc("/users/current", userHandler.getCurrentUser).Methods("GET", "OPTIONS")
|
||||
@@ -31,6 +33,7 @@ func AddEndpoints(accountManager account.Manager, router *mux.Router) {
|
||||
router.HandleFunc("/users/{userId}", userHandler.deleteUser).Methods("DELETE", "OPTIONS")
|
||||
router.HandleFunc("/users", userHandler.createUser).Methods("POST", "OPTIONS")
|
||||
router.HandleFunc("/users/{userId}/invite", userHandler.inviteUser).Methods("POST", "OPTIONS")
|
||||
router.HandleFunc("/users/roles", userHandler.getRoles).Methods("GET", "OPTIONS")
|
||||
addUsersTokensEndpoint(accountManager, router)
|
||||
}
|
||||
|
||||
@@ -284,6 +287,64 @@ func (h *handler) getCurrentUser(w http.ResponseWriter, r *http.Request) {
|
||||
util.WriteJSONObject(r.Context(), w, toUserWithPermissionsResponse(user, userID))
|
||||
}
|
||||
|
||||
func (h *handler) getRoles(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
util.WriteErrorResponse("wrong HTTP method", http.StatusMethodNotAllowed, w)
|
||||
return
|
||||
}
|
||||
ctx := r.Context()
|
||||
userAuth, err := nbcontext.GetUserAuthFromContext(ctx)
|
||||
if err != nil {
|
||||
util.WriteError(r.Context(), err, w)
|
||||
return
|
||||
}
|
||||
|
||||
accountID, userID := userAuth.AccountId, userAuth.UserId
|
||||
|
||||
roles, err := h.usersManager.GetRoles(ctx, accountID, userID)
|
||||
if err != nil {
|
||||
util.WriteError(r.Context(), err, w)
|
||||
return
|
||||
}
|
||||
|
||||
util.WriteJSONObject(r.Context(), w, toRolesResponse(roles))
|
||||
}
|
||||
|
||||
func toRolesResponse(roles map[types.UserRole]roles.RolePermissions) []api.RolePermissions {
|
||||
result := make([]api.RolePermissions, 0, len(roles))
|
||||
for _, permissions := range roles {
|
||||
rolePermissions := api.RolePermissions{
|
||||
Role: string(permissions.Role),
|
||||
}
|
||||
|
||||
if len(permissions.AutoAllowNew) > 0 {
|
||||
rolePermissions.Default = make(map[string]bool)
|
||||
for k, v := range permissions.AutoAllowNew {
|
||||
rolePermissions.Default[string(k)] = v
|
||||
}
|
||||
}
|
||||
|
||||
if len(permissions.Permissions) > 0 {
|
||||
modules := make(map[string]map[string]bool)
|
||||
for module, operations := range permissions.Permissions {
|
||||
if len(operations) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
access := make(map[string]bool)
|
||||
for k, v := range operations {
|
||||
access[string(k)] = v
|
||||
}
|
||||
modules[string(module)] = access
|
||||
}
|
||||
rolePermissions.Modules = &modules
|
||||
}
|
||||
|
||||
result = append(result, rolePermissions)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func toUserWithPermissionsResponse(user *users.UserInfoWithPermissions, userID string) *api.User {
|
||||
response := toUserResponse(user.UserInfo, userID)
|
||||
if user.Permissions == nil {
|
||||
|
||||
@@ -121,9 +121,9 @@ func BuildApiBlackBoxWithDBState(t TB, sqlFile string, expectedPeerUpdate *serve
|
||||
geoMock := &geolocation.Mock{}
|
||||
validatorMock := server.MocIntegratedValidator{}
|
||||
proxyController := integrations.NewController(store)
|
||||
userManager := users.NewManager(store)
|
||||
permissionsManager := permissions.NewManager(store)
|
||||
settingsManager := settings.NewManager(store, userManager, integrations.NewManager(&activity.InMemoryEventStore{}), permissionsManager)
|
||||
usersManager := users.NewManager(store, permissionsManager)
|
||||
settingsManager := settings.NewManager(store, usersManager, integrations.NewManager(&activity.InMemoryEventStore{}), permissionsManager)
|
||||
am, err := server.BuildManager(context.Background(), store, peersUpdateManager, nil, "", "", &activity.InMemoryEventStore{}, geoMock, false, validatorMock, metrics, proxyController, settingsManager, permissionsManager)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create manager: %v", err)
|
||||
@@ -144,7 +144,7 @@ func BuildApiBlackBoxWithDBState(t TB, sqlFile string, expectedPeerUpdate *serve
|
||||
groupsManagerMock := groups.NewManagerMock()
|
||||
peersManager := peers.NewManager(store, permissionsManager)
|
||||
|
||||
apiHandler, err := nbhttp.NewAPIHandler(context.Background(), am, networksManagerMock, resourcesManagerMock, routersManagerMock, groupsManagerMock, geoMock, authManagerMock, metrics, validatorMock, proxyController, permissionsManager, peersManager, settingsManager)
|
||||
apiHandler, err := nbhttp.NewAPIHandler(context.Background(), am, networksManagerMock, resourcesManagerMock, routersManagerMock, groupsManagerMock, geoMock, authManagerMock, metrics, validatorMock, proxyController, permissionsManager, peersManager, settingsManager, usersManager)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create API handler: %v", err)
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ type Manager interface {
|
||||
ValidateAccountAccess(ctx context.Context, accountID string, user *types.User, allowOwnerAndAdmin bool) error
|
||||
|
||||
GetRolePermissions(ctx context.Context, role types.UserRole) (roles.RolePermissions, error)
|
||||
GetPermissions(ctx context.Context) map[types.UserRole]roles.RolePermissions
|
||||
}
|
||||
|
||||
type managerImpl struct {
|
||||
@@ -107,3 +108,7 @@ func (m *managerImpl) GetRolePermissions(ctx context.Context, role types.UserRol
|
||||
|
||||
return permissions, nil
|
||||
}
|
||||
|
||||
func (m *managerImpl) GetPermissions(ctx context.Context) map[types.UserRole]roles.RolePermissions {
|
||||
return roles.RolesMap
|
||||
}
|
||||
|
||||
@@ -38,6 +38,20 @@ func (m *MockManager) EXPECT() *MockManagerMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// GetPermissions mocks base method.
|
||||
func (m *MockManager) GetPermissions(ctx context.Context) map[types.UserRole]roles.RolePermissions {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetPermissions", ctx)
|
||||
ret0, _ := ret[0].(map[types.UserRole]roles.RolePermissions)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// GetPermissions indicates an expected call of GetPermissions.
|
||||
func (mr *MockManagerMockRecorder) GetPermissions(ctx interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPermissions", reflect.TypeOf((*MockManager)(nil).GetPermissions), ctx)
|
||||
}
|
||||
|
||||
// GetRolePermissions mocks base method.
|
||||
func (m *MockManager) GetRolePermissions(ctx context.Context, role types.UserRole) (roles.RolePermissions, error) {
|
||||
m.ctrl.T.Helper()
|
||||
|
||||
@@ -1,27 +1,31 @@
|
||||
package users
|
||||
|
||||
//go:generate go run github.com/golang/mock/mockgen -package users -destination=manager_mock.go -source=./manager.go -build_flags=-mod=mod
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"github.com/netbirdio/netbird/management/server/permissions"
|
||||
"github.com/netbirdio/netbird/management/server/permissions/roles"
|
||||
"github.com/netbirdio/netbird/management/server/status"
|
||||
"github.com/netbirdio/netbird/management/server/store"
|
||||
"github.com/netbirdio/netbird/management/server/types"
|
||||
)
|
||||
|
||||
type Manager interface {
|
||||
GetUser(ctx context.Context, userID string) (*types.User, error)
|
||||
GetRoles(ctx context.Context, accountId, userId string) (map[types.UserRole]roles.RolePermissions, error)
|
||||
}
|
||||
|
||||
type managerImpl struct {
|
||||
store store.Store
|
||||
store store.Store
|
||||
permissionsManager permissions.Manager
|
||||
}
|
||||
|
||||
type managerMock struct {
|
||||
}
|
||||
|
||||
func NewManager(store store.Store) Manager {
|
||||
func NewManager(store store.Store, permissionsManager permissions.Manager) Manager {
|
||||
return &managerImpl{
|
||||
store: store,
|
||||
store: store,
|
||||
permissionsManager: permissionsManager,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,21 +33,23 @@ func (m *managerImpl) GetUser(ctx context.Context, userID string) (*types.User,
|
||||
return m.store.GetUserByUserID(ctx, store.LockingStrengthShare, userID)
|
||||
}
|
||||
|
||||
func NewManagerMock() Manager {
|
||||
return &managerMock{}
|
||||
}
|
||||
|
||||
func (m *managerMock) GetUser(ctx context.Context, userID string) (*types.User, error) {
|
||||
switch userID {
|
||||
case "adminUser":
|
||||
return &types.User{Id: userID, Role: types.UserRoleAdmin}, nil
|
||||
case "regularUser":
|
||||
return &types.User{Id: userID, Role: types.UserRoleUser}, nil
|
||||
case "ownerUser":
|
||||
return &types.User{Id: userID, Role: types.UserRoleOwner}, nil
|
||||
case "billingUser":
|
||||
return &types.User{Id: userID, Role: types.UserRoleBillingAdmin}, nil
|
||||
default:
|
||||
return nil, errors.New("user not found")
|
||||
func (m *managerImpl) GetRoles(ctx context.Context, accountId, userId string) (map[types.UserRole]roles.RolePermissions, error) {
|
||||
user, err := m.store.GetUserByUserID(ctx, store.LockingStrengthShare, userId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if user.IsBlocked() {
|
||||
return nil, status.NewUserBlockedError()
|
||||
}
|
||||
|
||||
if user.IsServiceUser {
|
||||
return nil, status.NewPermissionDeniedError()
|
||||
}
|
||||
|
||||
if err := m.permissionsManager.ValidateAccountAccess(ctx, accountId, user, false); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return m.permissionsManager.GetPermissions(ctx), nil
|
||||
}
|
||||
|
||||
51
management/server/users/manager_mock.go
Normal file
51
management/server/users/manager_mock.go
Normal file
@@ -0,0 +1,51 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: ./manager.go
|
||||
|
||||
// Package users is a generated GoMock package.
|
||||
package users
|
||||
|
||||
import (
|
||||
context "context"
|
||||
reflect "reflect"
|
||||
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
types "github.com/netbirdio/netbird/management/server/types"
|
||||
)
|
||||
|
||||
// MockManager is a mock of Manager interface.
|
||||
type MockManager struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockManagerMockRecorder
|
||||
}
|
||||
|
||||
// MockManagerMockRecorder is the mock recorder for MockManager.
|
||||
type MockManagerMockRecorder struct {
|
||||
mock *MockManager
|
||||
}
|
||||
|
||||
// NewMockManager creates a new mock instance.
|
||||
func NewMockManager(ctrl *gomock.Controller) *MockManager {
|
||||
mock := &MockManager{ctrl: ctrl}
|
||||
mock.recorder = &MockManagerMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||
func (m *MockManager) EXPECT() *MockManagerMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// GetUser mocks base method.
|
||||
func (m *MockManager) GetUser(ctx context.Context, userID string) (*types.User, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetUser", ctx, userID)
|
||||
ret0, _ := ret[0].(*types.User)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetUser indicates an expected call of GetUser.
|
||||
func (mr *MockManagerMockRecorder) GetUser(ctx, userID interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUser", reflect.TypeOf((*MockManager)(nil).GetUser), ctx, userID)
|
||||
}
|
||||
Reference in New Issue
Block a user