diff --git a/management/server/account.go b/management/server/account.go index ef5e9c7d7c2..79f9b3422d2 100644 --- a/management/server/account.go +++ b/management/server/account.go @@ -35,11 +35,14 @@ import ( "github.com/netbirdio/netbird/management/server/jwtclaims" "github.com/netbirdio/netbird/management/server/networks" nbpeer "github.com/netbirdio/netbird/management/server/peer" + "github.com/netbirdio/netbird/management/server/permissions" "github.com/netbirdio/netbird/management/server/posture" + "github.com/netbirdio/netbird/management/server/settings" "github.com/netbirdio/netbird/management/server/status" "github.com/netbirdio/netbird/management/server/store" "github.com/netbirdio/netbird/management/server/telemetry" "github.com/netbirdio/netbird/management/server/types" + "github.com/netbirdio/netbird/management/server/users" "github.com/netbirdio/netbird/management/server/util" "github.com/netbirdio/netbird/route" ) @@ -149,6 +152,7 @@ type AccountManager interface { GetAccountSettings(ctx context.Context, accountID string, userID string) (*types.Settings, error) DeleteSetupKey(ctx context.Context, accountID, userID, keyID string) error GetNetworksManager() networks.Manager + GetUserManager() users.Manager } type DefaultAccountManager struct { @@ -186,7 +190,10 @@ type DefaultAccountManager struct { metrics telemetry.AppMetrics - networksManager networks.Manager + networksManager networks.Manager + userManager users.Manager + settingsManager settings.Manager + permissionsManager permissions.Manager } // getJWTGroupsChanges calculates the changes needed to sync a user's JWT groups. @@ -253,12 +260,18 @@ func BuildManager( integratedPeerValidator integrated_validator.IntegratedValidator, metrics telemetry.AppMetrics, ) (*DefaultAccountManager, error) { + userManager := users.NewManager(store) + settingsManager := settings.NewManager(store) + permissionsManager := permissions.NewManager(userManager, settingsManager) am := &DefaultAccountManager{ Store: store, geo: geo, peersUpdateManager: peersUpdateManager, idpManager: idpManager, - networksManager: networks.NewManager(store), + networksManager: networks.NewManager(store, permissionsManager), + userManager: userManager, + settingsManager: settingsManager, + permissionsManager: permissionsManager, ctx: context.Background(), cacheMux: sync.Mutex{}, cacheLoading: map[string]chan struct{}{}, @@ -1721,6 +1734,10 @@ func (am *DefaultAccountManager) GetNetworksManager() networks.Manager { return am.networksManager } +func (am *DefaultAccountManager) GetUserManager() users.Manager { + return am.userManager +} + // addAllGroup to account object if it doesn't exist func addAllGroup(account *types.Account) error { if len(account.Groups) == 0 { diff --git a/management/server/http/api/openapi.yml b/management/server/http/api/openapi.yml index 98b2c7a89e1..8b15ae48eaf 100644 --- a/management/server/http/api/openapi.yml +++ b/management/server/http/api/openapi.yml @@ -1197,8 +1197,22 @@ components: description: Network ID type: string example: chacdk86lnnboviihd7g + routers: + description: List of router IDs associated with the network + type: array + items: + type: string + example: ch8i4ug6lnn4g9hqv7m0 + resources: + description: List of network resource IDs associated with the network + type: array + items: + type: string + example: ch8i4ug6lnn4g9hqv7m1 required: - id + - routers + - resources - $ref: '#/components/schemas/NetworkRequest' NetworkResourceRequest: type: object diff --git a/management/server/http/api/types.gen.go b/management/server/http/api/types.gen.go index 835a6ccff36..c70161f0165 100644 --- a/management/server/http/api/types.gen.go +++ b/management/server/http/api/types.gen.go @@ -511,6 +511,12 @@ type Network struct { // Name Network name Name string `json:"name"` + + // Resources List of network resource IDs associated with the network + Resources []string `json:"resources"` + + // Routers List of router IDs associated with the network + Routers []string `json:"routers"` } // NetworkRequest defines model for NetworkRequest. diff --git a/management/server/http/handlers/networks/handler.go b/management/server/http/handlers/networks/handler.go index 1b49b10b3ec..1ce856118f0 100644 --- a/management/server/http/handlers/networks/handler.go +++ b/management/server/http/handlers/networks/handler.go @@ -3,6 +3,7 @@ package networks import ( "context" "encoding/json" + "fmt" "net/http" "github.com/gorilla/mux" @@ -59,9 +60,21 @@ func (h *handler) getAllNetworks(w http.ResponseWriter, r *http.Request) { return } + routers, err := h.networksManager.GetRouterManager().GetAllRouterIDsInAccount(r.Context(), accountID, userID) + if err != nil { + util.WriteError(r.Context(), err, w) + return + } + + resources, err := h.networksManager.GetResourceManager().GetAllResourceIDsInAccount(r.Context(), accountID, userID) + if err != nil { + util.WriteError(r.Context(), err, w) + return + } + var networkResponse []*api.Network for _, network := range networks { - networkResponse = append(networkResponse, network.ToAPIResponse()) + networkResponse = append(networkResponse, network.ToAPIResponse(routers[network.ID], resources[network.ID])) } util.WriteJSONObject(r.Context(), w, networkResponse) @@ -92,7 +105,7 @@ func (h *handler) createNetwork(w http.ResponseWriter, r *http.Request) { return } - util.WriteJSONObject(r.Context(), w, network.ToAPIResponse()) + util.WriteJSONObject(r.Context(), w, network.ToAPIResponse([]string{}, []string{})) } func (h *handler) getNetwork(w http.ResponseWriter, r *http.Request) { @@ -116,7 +129,13 @@ func (h *handler) getNetwork(w http.ResponseWriter, r *http.Request) { return } - util.WriteJSONObject(r.Context(), w, network.ToAPIResponse()) + routerIDs, resourceIDs, err := h.collectIDsInNetwork(r.Context(), accountID, userID, networkID) + if err != nil { + util.WriteError(r.Context(), err, w) + return + } + + util.WriteJSONObject(r.Context(), w, network.ToAPIResponse(routerIDs, resourceIDs)) } func (h *handler) updateNetwork(w http.ResponseWriter, r *http.Request) { @@ -152,7 +171,13 @@ func (h *handler) updateNetwork(w http.ResponseWriter, r *http.Request) { return } - util.WriteJSONObject(r.Context(), w, network.ToAPIResponse()) + routerIDs, resourceIDs, err := h.collectIDsInNetwork(r.Context(), accountID, userID, networkID) + if err != nil { + util.WriteError(r.Context(), err, w) + return + } + + util.WriteJSONObject(r.Context(), w, network.ToAPIResponse(routerIDs, resourceIDs)) } func (h *handler) deleteNetwork(w http.ResponseWriter, r *http.Request) { @@ -178,3 +203,27 @@ func (h *handler) deleteNetwork(w http.ResponseWriter, r *http.Request) { util.WriteJSONObject(r.Context(), w, util.EmptyObject{}) } + +func (h *handler) collectIDsInNetwork(ctx context.Context, accountID, userID, networkID string) ([]string, []string, error) { + resources, err := h.networksManager.GetResourceManager().GetAllResourcesInNetwork(ctx, accountID, userID, networkID) + if err != nil { + return nil, nil, fmt.Errorf("failed to get resources in network: %w", err) + } + + var resourceIDs []string + for _, resource := range resources { + resourceIDs = append(resourceIDs, resource.ID) + } + + routers, err := h.networksManager.GetRouterManager().GetAllRoutersInNetwork(ctx, accountID, userID, networkID) + if err != nil { + return nil, nil, fmt.Errorf("failed to get routers in network: %w", err) + } + + var routerIDs []string + for _, router := range routers { + routerIDs = append(routerIDs, router.ID) + } + + return routerIDs, resourceIDs, nil +} diff --git a/management/server/http/handlers/networks/resources_handler.go b/management/server/http/handlers/networks/resources_handler.go index c6a5cc211cf..9221cefaf7a 100644 --- a/management/server/http/handlers/networks/resources_handler.go +++ b/management/server/http/handlers/networks/resources_handler.go @@ -23,11 +23,12 @@ type resourceHandler struct { func addResourceEndpoints(resourcesManager resources.Manager, extractFromToken func(ctx context.Context, claims jwtclaims.AuthorizationClaims) (string, string, error), authCfg configs.AuthCfg, router *mux.Router) { resourceHandler := newResourceHandler(resourcesManager, extractFromToken, authCfg) - router.HandleFunc("/networks/{networkId}/resources", resourceHandler.getAllResources).Methods("GET", "OPTIONS") + router.HandleFunc("/networks/{networkId}/resources", resourceHandler.getAllResourcesInNetwork).Methods("GET", "OPTIONS") router.HandleFunc("/networks/{networkId}/resources", resourceHandler.createResource).Methods("POST", "OPTIONS") router.HandleFunc("/networks/{networkId}/resources/{resourceId}", resourceHandler.getResource).Methods("GET", "OPTIONS") router.HandleFunc("/networks/{networkId}/resources/{resourceId}", resourceHandler.updateResource).Methods("PUT", "OPTIONS") router.HandleFunc("/networks/{networkId}/resources/{resourceId}", resourceHandler.deleteResource).Methods("DELETE", "OPTIONS") + router.HandleFunc("/networks/resources", resourceHandler.getAllResourcesInAccount).Methods("GET", "OPTIONS") } func newResourceHandler(resourceManager resources.Manager, extractFromToken func(ctx context.Context, claims jwtclaims.AuthorizationClaims) (string, string, error), authCfg configs.AuthCfg) *resourceHandler { @@ -41,7 +42,7 @@ func newResourceHandler(resourceManager resources.Manager, extractFromToken func } } -func (h *resourceHandler) getAllResources(w http.ResponseWriter, r *http.Request) { +func (h *resourceHandler) getAllResourcesInNetwork(w http.ResponseWriter, r *http.Request) { claims := h.claimsExtractor.FromRequestContext(r) accountID, userID, err := h.extractFromToken(r.Context(), claims) if err != nil { @@ -50,7 +51,28 @@ func (h *resourceHandler) getAllResources(w http.ResponseWriter, r *http.Request } networkID := mux.Vars(r)["networkId"] - resources, err := h.resourceManager.GetAllResources(r.Context(), accountID, userID, networkID) + resources, err := h.resourceManager.GetAllResourcesInNetwork(r.Context(), accountID, userID, networkID) + if err != nil { + util.WriteError(r.Context(), err, w) + return + } + + var resourcesResponse []*api.NetworkResource + for _, resource := range resources { + resourcesResponse = append(resourcesResponse, resource.ToAPIResponse()) + } + + util.WriteJSONObject(r.Context(), w, resourcesResponse) +} +func (h *resourceHandler) getAllResourcesInAccount(w http.ResponseWriter, r *http.Request) { + claims := h.claimsExtractor.FromRequestContext(r) + accountID, userID, err := h.extractFromToken(r.Context(), claims) + if err != nil { + util.WriteError(r.Context(), err, w) + return + } + + resources, err := h.resourceManager.GetAllResourcesInAccount(r.Context(), accountID, userID) if err != nil { util.WriteError(r.Context(), err, w) return diff --git a/management/server/http/handlers/networks/routers_handler.go b/management/server/http/handlers/networks/routers_handler.go index 20f67a17751..2cf39a1329a 100644 --- a/management/server/http/handlers/networks/routers_handler.go +++ b/management/server/http/handlers/networks/routers_handler.go @@ -50,7 +50,7 @@ func (h *routersHandler) getAllRouters(w http.ResponseWriter, r *http.Request) { } networkID := mux.Vars(r)["networkId"] - routers, err := h.routersManager.GetAllRouters(r.Context(), accountID, userID, networkID) + routers, err := h.routersManager.GetAllRoutersInNetwork(r.Context(), accountID, userID, networkID) if err != nil { util.WriteError(r.Context(), err, w) return diff --git a/management/server/mock_server/account_mock.go b/management/server/mock_server/account_mock.go index 30fb28bf4c1..37a392c23d5 100644 --- a/management/server/mock_server/account_mock.go +++ b/management/server/mock_server/account_mock.go @@ -20,6 +20,7 @@ import ( nbpeer "github.com/netbirdio/netbird/management/server/peer" "github.com/netbirdio/netbird/management/server/posture" "github.com/netbirdio/netbird/management/server/types" + "github.com/netbirdio/netbird/management/server/users" "github.com/netbirdio/netbird/route" ) @@ -113,6 +114,11 @@ type MockAccountManager struct { DeleteSetupKeyFunc func(ctx context.Context, accountID, userID, keyID string) error } +func (am *MockAccountManager) GetUserManager() users.Manager { + // TODO implement me + panic("implement me") +} + func (am *MockAccountManager) GetNetworksManager() networks.Manager { // TODO implement me panic("implement me") diff --git a/management/server/networks/manager.go b/management/server/networks/manager.go index 651da70a6ee..61dd59cb8f4 100644 --- a/management/server/networks/manager.go +++ b/management/server/networks/manager.go @@ -2,11 +2,14 @@ package networks import ( "context" - "errors" + + "github.com/rs/xid" "github.com/netbirdio/netbird/management/server/networks/resources" "github.com/netbirdio/netbird/management/server/networks/routers" "github.com/netbirdio/netbird/management/server/networks/types" + "github.com/netbirdio/netbird/management/server/permissions" + "github.com/netbirdio/netbird/management/server/status" "github.com/netbirdio/netbird/management/server/store" ) @@ -21,37 +24,81 @@ type Manager interface { } type managerImpl struct { - store store.Store - routersManager routers.Manager - resourcesManager resources.Manager + store store.Store + permissionsManager permissions.Manager + routersManager routers.Manager + resourcesManager resources.Manager } -func NewManager(store store.Store) Manager { +func NewManager(store store.Store, permissionsManager permissions.Manager) Manager { return &managerImpl{ - store: store, - routersManager: routers.NewManager(store), - resourcesManager: resources.NewManager(store), + store: store, + permissionsManager: permissionsManager, + routersManager: routers.NewManager(store, permissionsManager), + resourcesManager: resources.NewManager(store, permissionsManager), } } func (m *managerImpl) GetAllNetworks(ctx context.Context, accountID, userID string) ([]*types.Network, error) { - return nil, errors.New("not implemented") + ok, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, permissions.Networks, permissions.Read) + if err != nil { + return nil, status.NewPermissionValidationError(err) + } + if !ok { + return nil, status.NewPermissionDeniedError() + } + + return m.store.GetAccountNetworks(ctx, store.LockingStrengthShare, accountID) } func (m *managerImpl) CreateNetwork(ctx context.Context, userID string, network *types.Network) (*types.Network, error) { - return nil, errors.New("not implemented") + ok, err := m.permissionsManager.ValidateUserPermissions(ctx, network.AccountID, userID, permissions.Networks, permissions.Write) + if err != nil { + return nil, status.NewPermissionValidationError(err) + } + if !ok { + return nil, status.NewPermissionDeniedError() + } + + network.ID = xid.New().String() + + return network, m.store.SaveNetwork(ctx, store.LockingStrengthUpdate, network) } func (m *managerImpl) GetNetwork(ctx context.Context, accountID, userID, networkID string) (*types.Network, error) { - return nil, errors.New("not implemented") + ok, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, permissions.Networks, permissions.Read) + if err != nil { + return nil, status.NewPermissionValidationError(err) + } + if !ok { + return nil, status.NewPermissionDeniedError() + } + + return m.store.GetNetworkByID(ctx, store.LockingStrengthShare, accountID, networkID) } func (m *managerImpl) UpdateNetwork(ctx context.Context, userID string, network *types.Network) (*types.Network, error) { - return nil, errors.New("not implemented") + ok, err := m.permissionsManager.ValidateUserPermissions(ctx, network.AccountID, userID, permissions.Networks, permissions.Write) + if err != nil { + return nil, status.NewPermissionValidationError(err) + } + if !ok { + return nil, status.NewPermissionDeniedError() + } + + return network, m.store.SaveNetwork(ctx, store.LockingStrengthUpdate, network) } func (m *managerImpl) DeleteNetwork(ctx context.Context, accountID, userID, networkID string) error { - return errors.New("not implemented") + ok, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, permissions.Networks, permissions.Write) + if err != nil { + return status.NewPermissionValidationError(err) + } + if !ok { + return status.NewPermissionDeniedError() + } + + return m.store.DeleteNetwork(ctx, store.LockingStrengthUpdate, accountID, networkID) } func (m *managerImpl) GetResourceManager() resources.Manager { diff --git a/management/server/networks/resources/manager.go b/management/server/networks/resources/manager.go index cfed34e517c..ad62f7b03f6 100644 --- a/management/server/networks/resources/manager.go +++ b/management/server/networks/resources/manager.go @@ -3,45 +3,147 @@ package resources import ( "context" "errors" + "fmt" "github.com/netbirdio/netbird/management/server/networks/resources/types" + "github.com/netbirdio/netbird/management/server/permissions" + "github.com/netbirdio/netbird/management/server/status" "github.com/netbirdio/netbird/management/server/store" ) type Manager interface { - GetAllResources(ctx context.Context, accountID, userID, networkID string) ([]*types.NetworkResource, error) - CreateResource(ctx context.Context, accountID string, resource *types.NetworkResource) (*types.NetworkResource, error) + GetAllResourcesInNetwork(ctx context.Context, accountID, userID, networkID string) ([]*types.NetworkResource, error) + GetAllResourcesInAccount(ctx context.Context, accountID, userID string) ([]*types.NetworkResource, error) + GetAllResourceIDsInAccount(ctx context.Context, accountID, userID string) (map[string][]string, error) + CreateResource(ctx context.Context, userID string, resource *types.NetworkResource) (*types.NetworkResource, error) GetResource(ctx context.Context, accountID, userID, networkID, resourceID string) (*types.NetworkResource, error) UpdateResource(ctx context.Context, userID string, resource *types.NetworkResource) (*types.NetworkResource, error) DeleteResource(ctx context.Context, accountID, userID, networkID, resourceID string) error } type managerImpl struct { - store store.Store + store store.Store + permissionsManager permissions.Manager } -func NewManager(store store.Store) Manager { +func NewManager(store store.Store, permissionsManager permissions.Manager) Manager { return &managerImpl{ - store: store, + store: store, + permissionsManager: permissionsManager, } } -func (m *managerImpl) GetAllResources(ctx context.Context, accountID, userID, networkID string) ([]*types.NetworkResource, error) { - return nil, errors.New("not implemented") +func (m *managerImpl) GetAllResourcesInNetwork(ctx context.Context, accountID, userID, networkID string) ([]*types.NetworkResource, error) { + ok, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, permissions.Networks, permissions.Read) + if err != nil { + return nil, status.NewPermissionValidationError(err) + } + if !ok { + return nil, status.NewPermissionDeniedError() + } + + return m.store.GetNetworkResourcesByNetID(ctx, store.LockingStrengthShare, accountID, networkID) +} + +func (m *managerImpl) GetAllResourcesInAccount(ctx context.Context, accountID, userID string) ([]*types.NetworkResource, error) { + ok, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, permissions.Networks, permissions.Read) + if err != nil { + return nil, status.NewPermissionValidationError(err) + } + if !ok { + return nil, status.NewPermissionDeniedError() + } + + return m.store.GetNetworkResourcesByAccountID(ctx, store.LockingStrengthShare, accountID) } -func (m *managerImpl) CreateResource(ctx context.Context, accountID string, resource *types.NetworkResource) (*types.NetworkResource, error) { - return nil, errors.New("not implemented") +func (m *managerImpl) GetAllResourceIDsInAccount(ctx context.Context, accountID, userID string) (map[string][]string, error) { + ok, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, permissions.Networks, permissions.Read) + if err != nil { + return nil, status.NewPermissionValidationError(err) + } + if !ok { + return nil, status.NewPermissionDeniedError() + } + + resources, err := m.store.GetNetworkResourcesByAccountID(ctx, store.LockingStrengthShare, accountID) + if err != nil { + return nil, fmt.Errorf("failed to get network resources: %w", err) + } + + resourceMap := make(map[string][]string) + for _, resource := range resources { + resourceMap[resource.NetworkID] = append(resourceMap[resource.NetworkID], resource.ID) + } + + return resourceMap, nil +} + +func (m *managerImpl) CreateResource(ctx context.Context, userID string, resource *types.NetworkResource) (*types.NetworkResource, error) { + ok, err := m.permissionsManager.ValidateUserPermissions(ctx, resource.AccountID, userID, permissions.Networks, permissions.Write) + if err != nil { + return nil, status.NewPermissionValidationError(err) + } + if !ok { + return nil, status.NewPermissionDeniedError() + } + + resource, err = types.NewNetworkResource(resource.AccountID, resource.NetworkID, resource.Name, resource.Description, resource.Address) + if err != nil { + return nil, fmt.Errorf("failed to create new network resource: %w", err) + } + + return resource, m.store.SaveNetworkResource(ctx, store.LockingStrengthUpdate, resource) } func (m *managerImpl) GetResource(ctx context.Context, accountID, userID, networkID, resourceID string) (*types.NetworkResource, error) { - return nil, errors.New("not implemented") + ok, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, permissions.Networks, permissions.Read) + if err != nil { + return nil, status.NewPermissionValidationError(err) + } + if !ok { + return nil, status.NewPermissionDeniedError() + } + + resource, err := m.store.GetNetworkResourceByID(ctx, store.LockingStrengthShare, accountID, resourceID) + if err != nil { + return nil, fmt.Errorf("failed to get network resource: %w", err) + } + + if resource.NetworkID != networkID { + return nil, errors.New("resource not part of network") + } + + return resource, nil } func (m *managerImpl) UpdateResource(ctx context.Context, userID string, resource *types.NetworkResource) (*types.NetworkResource, error) { - return nil, errors.New("not implemented") + ok, err := m.permissionsManager.ValidateUserPermissions(ctx, resource.AccountID, userID, permissions.Networks, permissions.Write) + if err != nil { + return nil, status.NewPermissionValidationError(err) + } + if !ok { + return nil, status.NewPermissionDeniedError() + } + + resourceType, err := types.GetResourceType(resource.Address) + if err != nil { + return nil, fmt.Errorf("failed to get resource type: %w", err) + } + + resource.Type = resourceType + + return resource, m.store.SaveNetworkResource(ctx, store.LockingStrengthUpdate, resource) } func (m *managerImpl) DeleteResource(ctx context.Context, accountID, userID, networkID, resourceID string) error { - return errors.New("not implemented") + ok, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, permissions.Networks, permissions.Write) + if err != nil { + return status.NewPermissionValidationError(err) + } + if !ok { + return status.NewPermissionDeniedError() + } + + return m.store.DeleteNetworkResource(ctx, store.LockingStrengthUpdate, accountID, resourceID) } diff --git a/management/server/networks/resources/types/resource.go b/management/server/networks/resources/types/resource.go index dd2bdd6b7d8..dd2c10fa5b8 100644 --- a/management/server/networks/resources/types/resource.go +++ b/management/server/networks/resources/types/resource.go @@ -35,7 +35,7 @@ type NetworkResource struct { } func NewNetworkResource(accountID, networkID, name, description, address string) (*NetworkResource, error) { - resourceType, err := getResourceType(address) + resourceType, err := GetResourceType(address) if err != nil { return nil, fmt.Errorf("invalid address: %w", err) } @@ -63,7 +63,7 @@ func (n *NetworkResource) ToAPIResponse() *api.NetworkResource { func (n *NetworkResource) FromAPIRequest(req *api.NetworkResourceRequest) { n.Name = req.Name - n.Description = "" + if req.Description != nil { n.Description = *req.Description } @@ -82,8 +82,8 @@ func (n *NetworkResource) Copy() *NetworkResource { } } -// getResourceType returns the type of the resource based on the address -func getResourceType(address string) (NetworkResourceType, error) { +// GetResourceType returns the type of the resource based on the address +func GetResourceType(address string) (NetworkResourceType, error) { if ip, cidr, err := net.ParseCIDR(address); err == nil { ones, _ := cidr.Mask.Size() if strings.HasSuffix(address, "/32") || (ip != nil && ones == 32) { diff --git a/management/server/networks/resources/types/resource_test.go b/management/server/networks/resources/types/resource_test.go index 3282ccfb64c..6b12ca0fc24 100644 --- a/management/server/networks/resources/types/resource_test.go +++ b/management/server/networks/resources/types/resource_test.go @@ -28,7 +28,7 @@ func TestGetResourceType(t *testing.T) { for _, tt := range tests { t.Run(tt.input, func(t *testing.T) { - result, err := getResourceType(tt.input) + result, err := GetResourceType(tt.input) if result != tt.expectedType { t.Errorf("Expected type %v, got %v", tt.expectedType, result) } diff --git a/management/server/networks/routers/manager.go b/management/server/networks/routers/manager.go index 12ad4e66712..0ced5ac9b7a 100644 --- a/management/server/networks/routers/manager.go +++ b/management/server/networks/routers/manager.go @@ -3,13 +3,19 @@ package routers import ( "context" "errors" + "fmt" + + "github.com/rs/xid" "github.com/netbirdio/netbird/management/server/networks/routers/types" + "github.com/netbirdio/netbird/management/server/permissions" + "github.com/netbirdio/netbird/management/server/status" "github.com/netbirdio/netbird/management/server/store" ) type Manager interface { - GetAllRouters(ctx context.Context, accountID, userID, networkID string) ([]*types.NetworkRouter, error) + GetAllRoutersInNetwork(ctx context.Context, accountID, userID, networkID string) ([]*types.NetworkRouter, error) + GetAllRouterIDsInAccount(ctx context.Context, accountID, userID string) (map[string][]string, error) CreateRouter(ctx context.Context, userID string, router *types.NetworkRouter) (*types.NetworkRouter, error) GetRouter(ctx context.Context, accountID, userID, networkID, routerID string) (*types.NetworkRouter, error) UpdateRouter(ctx context.Context, userID string, router *types.NetworkRouter) (*types.NetworkRouter, error) @@ -17,31 +23,106 @@ type Manager interface { } type managerImpl struct { - store store.Store + store store.Store + permissionsManager permissions.Manager } -func NewManager(store store.Store) Manager { +func NewManager(store store.Store, permissionsManager permissions.Manager) Manager { return &managerImpl{ - store: store, + store: store, + permissionsManager: permissionsManager, } } -func (m *managerImpl) GetAllRouters(ctx context.Context, accountID, userID, networkID string) ([]*types.NetworkRouter, error) { - return nil, errors.New("not implemented") +func (m *managerImpl) GetAllRoutersInNetwork(ctx context.Context, accountID, userID, networkID string) ([]*types.NetworkRouter, error) { + ok, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, permissions.Networks, permissions.Read) + if err != nil { + return nil, status.NewPermissionValidationError(err) + } + if !ok { + return nil, status.NewPermissionDeniedError() + } + + return m.store.GetNetworkRoutersByNetID(ctx, store.LockingStrengthShare, accountID, networkID) +} + +func (m *managerImpl) GetAllRouterIDsInAccount(ctx context.Context, accountID, userID string) (map[string][]string, error) { + ok, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, permissions.Networks, permissions.Read) + if err != nil { + return nil, status.NewPermissionValidationError(err) + } + if !ok { + return nil, status.NewPermissionDeniedError() + } + + routers, err := m.store.GetNetworkRoutersByAccountID(ctx, store.LockingStrengthShare, accountID) + if err != nil { + return nil, fmt.Errorf("failed to get network routers: %w", err) + } + + routersMap := make(map[string][]string) + for _, router := range routers { + routersMap[router.NetworkID] = append(routersMap[router.NetworkID], router.ID) + } + + return routersMap, nil } func (m *managerImpl) CreateRouter(ctx context.Context, userID string, router *types.NetworkRouter) (*types.NetworkRouter, error) { - return nil, errors.New("not implemented") + ok, err := m.permissionsManager.ValidateUserPermissions(ctx, router.AccountID, userID, permissions.Networks, permissions.Write) + if err != nil { + return nil, status.NewPermissionValidationError(err) + } + if !ok { + return nil, status.NewPermissionDeniedError() + } + + router.ID = xid.New().String() + + return router, m.store.SaveNetworkRouter(ctx, store.LockingStrengthUpdate, router) } func (m *managerImpl) GetRouter(ctx context.Context, accountID, userID, networkID, routerID string) (*types.NetworkRouter, error) { - return nil, errors.New("not implemented") + ok, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, permissions.Networks, permissions.Read) + if err != nil { + return nil, status.NewPermissionValidationError(err) + } + if !ok { + return nil, status.NewPermissionDeniedError() + } + + router, err := m.store.GetNetworkRouterByID(ctx, store.LockingStrengthShare, accountID, routerID) + if err != nil { + return nil, fmt.Errorf("failed to get network router: %w", err) + } + + if router.NetworkID != networkID { + return nil, errors.New("router not part of network") + } + + return router, nil } func (m *managerImpl) UpdateRouter(ctx context.Context, userID string, router *types.NetworkRouter) (*types.NetworkRouter, error) { - return nil, errors.New("not implemented") + ok, err := m.permissionsManager.ValidateUserPermissions(ctx, router.AccountID, userID, permissions.Networks, permissions.Write) + if err != nil { + return nil, status.NewPermissionValidationError(err) + } + if !ok { + return nil, status.NewPermissionDeniedError() + } + + return router, m.store.SaveNetworkRouter(ctx, store.LockingStrengthUpdate, router) } func (m *managerImpl) DeleteRouter(ctx context.Context, accountID, userID, networkID, routerID string) error { - return errors.New("not implemented") + ok, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, permissions.Networks, permissions.Write) + if err != nil { + return status.NewPermissionValidationError(err) + } + if !ok { + return status.NewPermissionDeniedError() + } + + return m.store.DeleteNetworkRouter(ctx, store.LockingStrengthUpdate, accountID, routerID) } diff --git a/management/server/networks/routers/types/router.go b/management/server/networks/routers/types/router.go index 72971eda8bb..b1491d2d17a 100644 --- a/management/server/networks/routers/types/router.go +++ b/management/server/networks/routers/types/router.go @@ -45,8 +45,14 @@ func (n *NetworkRouter) ToAPIResponse() *api.NetworkRouter { } func (n *NetworkRouter) FromAPIRequest(req *api.NetworkRouterRequest) { - n.Peer = *req.Peer - n.PeerGroups = *req.PeerGroups + if req.Peer != nil { + n.Peer = *req.Peer + } + + if req.PeerGroups != nil { + n.PeerGroups = *req.PeerGroups + } + n.Masquerade = req.Masquerade n.Metric = req.Metric } diff --git a/management/server/networks/types/network.go b/management/server/networks/types/network.go index 70f0aefce1b..b884690d5ef 100644 --- a/management/server/networks/types/network.go +++ b/management/server/networks/types/network.go @@ -22,17 +22,21 @@ func NewNetwork(accountId, name, description string) *Network { } } -func (n *Network) ToAPIResponse() *api.Network { +func (n *Network) ToAPIResponse(routerIDs []string, resourceIDs []string) *api.Network { return &api.Network{ Id: n.ID, Name: n.Name, Description: &n.Description, + Routers: routerIDs, + Resources: resourceIDs, } } func (n *Network) FromAPIRequest(req *api.NetworkRequest) { n.Name = req.Name - n.Description = *req.Description + if req.Description != nil { + n.Description = *req.Description + } } // Copy returns a copy of a posture checks. diff --git a/management/server/permissions/manager.go b/management/server/permissions/manager.go new file mode 100644 index 00000000000..5d1ba232070 --- /dev/null +++ b/management/server/permissions/manager.go @@ -0,0 +1,87 @@ +package permissions + +import ( + "context" + "errors" + "fmt" + + "github.com/netbirdio/netbird/management/server/settings" + "github.com/netbirdio/netbird/management/server/types" + "github.com/netbirdio/netbird/management/server/users" +) + +type Module string + +const ( + Networks Module = "networks" + Peers Module = "peers" +) + +type Operation string + +const ( + Read Operation = "read" + Write Operation = "write" +) + +type Manager interface { + ValidateUserPermissions(ctx context.Context, accountID, userID string, module Module, operation Operation) (bool, error) +} + +type managerImpl struct { + userManager users.Manager + settingsManager settings.Manager +} + +func NewManager(userManager users.Manager, settingsManager settings.Manager) Manager { + return &managerImpl{ + userManager: userManager, + settingsManager: settingsManager, + } +} + +func (m *managerImpl) ValidateUserPermissions(ctx context.Context, accountID, userID string, module Module, operation Operation) (bool, error) { + user, err := m.userManager.GetUser(ctx, userID) + if err != nil { + return false, err + } + + if user == nil { + return false, errors.New("user not found") + } + + if user.AccountID != accountID { + return false, errors.New("user does not belong to account") + } + + switch user.Role { + case types.UserRoleAdmin, types.UserRoleOwner: + return true, nil + case types.UserRoleUser: + return m.validateRegularUserPermissions(ctx, accountID, userID, module, operation) + case types.UserRoleBillingAdmin: + return false, nil + default: + return false, errors.New("invalid role") + } +} + +func (m *managerImpl) validateRegularUserPermissions(ctx context.Context, accountID, userID string, module Module, operation Operation) (bool, error) { + settings, err := m.settingsManager.GetSettings(ctx, accountID, userID) + if err != nil { + return false, fmt.Errorf("failed to get settings: %w", err) + } + if settings.RegularUsersViewBlocked { + return false, nil + } + + if operation == Write { + return false, nil + } + + if module == Peers { + return true, nil + } + + return false, nil +} diff --git a/management/server/settings/manager.go b/management/server/settings/manager.go new file mode 100644 index 00000000000..7d564a02ee6 --- /dev/null +++ b/management/server/settings/manager.go @@ -0,0 +1,26 @@ +package settings + +import ( + "context" + + "github.com/netbirdio/netbird/management/server/store" + "github.com/netbirdio/netbird/management/server/types" +) + +type Manager interface { + GetSettings(ctx context.Context, accountID string, userID string) (*types.Settings, error) +} + +type managerImpl struct { + store store.Store +} + +func NewManager(store store.Store) Manager { + return &managerImpl{ + store: store, + } +} + +func (m *managerImpl) GetSettings(ctx context.Context, accountID string, userID string) (*types.Settings, error) { + return m.store.GetAccountSettings(ctx, store.LockingStrengthShare, accountID) +} diff --git a/management/server/status/error.go b/management/server/status/error.go index 70209b7c84f..d65931b5a6f 100644 --- a/management/server/status/error.go +++ b/management/server/status/error.go @@ -169,3 +169,12 @@ func NewNetworkRouterNotFoundError(routerID string) error { func NewNetworkResourceNotFoundError(resourceID string) error { return Errorf(NotFound, "network resource: %s not found", resourceID) } + +// NewPermissionDeniedError creates a new Error with PermissionDenied type for a permission denied error. +func NewPermissionDeniedError() error { + return Errorf(PermissionDenied, "permission denied") +} + +func NewPermissionValidationError(err error) error { + return Errorf(PermissionDenied, "failed to vlidate user permissions: %s", err) +} diff --git a/management/server/store/sql_store.go b/management/server/store/sql_store.go index 3c5040ec083..4ea1f5e4c5e 100644 --- a/management/server/store/sql_store.go +++ b/management/server/store/sql_store.go @@ -1667,6 +1667,18 @@ func (s *SqlStore) GetNetworkRoutersByNetID(ctx context.Context, lockStrength Lo return netRouters, nil } +func (s *SqlStore) GetNetworkRoutersByAccountID(ctx context.Context, lockStrength LockingStrength, accountID string) ([]*routerTypes.NetworkRouter, error) { + var netRouters []*routerTypes.NetworkRouter + result := s.db.Clauses(clause.Locking{Strength: string(lockStrength)}). + Find(&netRouters, accountIDCondition, accountID) + if result.Error != nil { + log.WithContext(ctx).Errorf("failed to get network routers from store: %v", result.Error) + return nil, status.Errorf(status.Internal, "failed to get network routers from store") + } + + return netRouters, nil +} + func (s *SqlStore) GetNetworkRouterByID(ctx context.Context, lockStrength LockingStrength, accountID, routerID string) (*routerTypes.NetworkRouter, error) { var netRouter *routerTypes.NetworkRouter result := s.db.Clauses(clause.Locking{Strength: string(lockStrength)}). @@ -1719,6 +1731,18 @@ func (s *SqlStore) GetNetworkResourcesByNetID(ctx context.Context, lockStrength return netResources, nil } +func (s *SqlStore) GetNetworkResourcesByAccountID(ctx context.Context, lockStrength LockingStrength, accountID string) ([]*resourceTypes.NetworkResource, error) { + var netResources []*resourceTypes.NetworkResource + result := s.db.Clauses(clause.Locking{Strength: string(lockStrength)}). + Find(&netResources, accountIDCondition, accountID) + if result.Error != nil { + log.WithContext(ctx).Errorf("failed to get network resources from store: %v", result.Error) + return nil, status.Errorf(status.Internal, "failed to get network resources from store") + } + + return netResources, nil +} + func (s *SqlStore) GetNetworkResourceByID(ctx context.Context, lockStrength LockingStrength, accountID, resourceID string) (*resourceTypes.NetworkResource, error) { var netResources *resourceTypes.NetworkResource result := s.db.Clauses(clause.Locking{Strength: string(lockStrength)}). diff --git a/management/server/store/store.go b/management/server/store/store.go index e61ecf092d7..b244d186bf2 100644 --- a/management/server/store/store.go +++ b/management/server/store/store.go @@ -151,11 +151,13 @@ type Store interface { DeleteNetwork(ctx context.Context, lockStrength LockingStrength, accountID, networkID string) error GetNetworkRoutersByNetID(ctx context.Context, lockStrength LockingStrength, accountID, netID string) ([]*routerTypes.NetworkRouter, error) + GetNetworkRoutersByAccountID(ctx context.Context, lockStrength LockingStrength, accountID string) ([]*routerTypes.NetworkRouter, error) GetNetworkRouterByID(ctx context.Context, lockStrength LockingStrength, accountID, routerID string) (*routerTypes.NetworkRouter, error) SaveNetworkRouter(ctx context.Context, lockStrength LockingStrength, router *routerTypes.NetworkRouter) error DeleteNetworkRouter(ctx context.Context, lockStrength LockingStrength, accountID, routerID string) error GetNetworkResourcesByNetID(ctx context.Context, lockStrength LockingStrength, accountID, netID string) ([]*resourceTypes.NetworkResource, error) + GetNetworkResourcesByAccountID(ctx context.Context, lockStrength LockingStrength, accountID string) ([]*resourceTypes.NetworkResource, error) GetNetworkResourceByID(ctx context.Context, lockStrength LockingStrength, accountID, resourceID string) (*resourceTypes.NetworkResource, error) SaveNetworkResource(ctx context.Context, lockStrength LockingStrength, resource *resourceTypes.NetworkResource) error DeleteNetworkResource(ctx context.Context, lockStrength LockingStrength, accountID, resourceID string) error diff --git a/management/server/users/manager.go b/management/server/users/manager.go new file mode 100644 index 00000000000..76291a6785f --- /dev/null +++ b/management/server/users/manager.go @@ -0,0 +1,26 @@ +package users + +import ( + "context" + + "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) +} + +type managerImpl struct { + store store.Store +} + +func NewManager(store store.Store) Manager { + return &managerImpl{ + store: store, + } +} + +func (m *managerImpl) GetUser(ctx context.Context, userID string) (*types.User, error) { + return m.store.GetUserByUserID(ctx, store.LockingStrengthShare, userID) +}