diff --git a/management/server/account.go b/management/server/account.go index 9e91a54b4..27db82986 100644 --- a/management/server/account.go +++ b/management/server/account.go @@ -29,6 +29,7 @@ import ( "github.com/netbirdio/netbird/management/domain" "github.com/netbirdio/netbird/management/server/activity" "github.com/netbirdio/netbird/management/server/geolocation" + "github.com/netbirdio/netbird/management/server/groups" "github.com/netbirdio/netbird/management/server/idp" "github.com/netbirdio/netbird/management/server/integrated_validator" "github.com/netbirdio/netbird/management/server/jwtclaims" @@ -152,6 +153,7 @@ type AccountManager interface { DeleteSetupKey(ctx context.Context, accountID, userID, keyID string) error GetNetworksManager() networks.Manager GetUserManager() users.Manager + GetGroupsManager() groups.Manager } type DefaultAccountManager struct { @@ -189,6 +191,7 @@ type DefaultAccountManager struct { metrics telemetry.AppMetrics + groupsManager groups.Manager networksManager networks.Manager userManager users.Manager settingsManager settings.Manager @@ -268,6 +271,7 @@ func BuildManager( peersUpdateManager: peersUpdateManager, idpManager: idpManager, networksManager: networks.NewManager(store, permissionsManager), + groupsManager: groups.NewManager(store, permissionsManager), userManager: userManager, settingsManager: settingsManager, permissionsManager: permissionsManager, @@ -1737,6 +1741,10 @@ func (am *DefaultAccountManager) GetUserManager() users.Manager { return am.userManager } +func (am *DefaultAccountManager) GetGroupsManager() groups.Manager { + return am.groupsManager +} + // 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/groups/manager.go b/management/server/groups/manager.go new file mode 100644 index 000000000..905277064 --- /dev/null +++ b/management/server/groups/manager.go @@ -0,0 +1,99 @@ +package groups + +import ( + "context" + "fmt" + + "github.com/netbirdio/netbird/management/server/http/api" + "github.com/netbirdio/netbird/management/server/permissions" + "github.com/netbirdio/netbird/management/server/store" + "github.com/netbirdio/netbird/management/server/types" +) + +type Manager interface { + GetAllGroups(ctx context.Context, accountID, userID string) (map[string]*types.Group, error) + AddResourceToGroup(ctx context.Context, accountID, userID, groupID string, resourceID *types.Resource) error +} + +type managerImpl struct { + store store.Store + permissionsManager permissions.Manager +} + +func NewManager(store store.Store, permissionsManager permissions.Manager) Manager { + return &managerImpl{ + store: store, + permissionsManager: permissionsManager, + } +} + +func (m *managerImpl) GetAllGroups(ctx context.Context, accountID, userID string) (map[string]*types.Group, error) { + ok, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, permissions.Groups, permissions.Read) + if err != nil { + return nil, err + } + if !ok { + return nil, err + } + + groups, err := m.store.GetAccountGroups(ctx, store.LockingStrengthShare, accountID) + if err != nil { + return nil, fmt.Errorf("error getting account groups: %w", err) + } + + groupsMap := make(map[string]*types.Group) + for _, group := range groups { + groupsMap[group.ID] = group + } + + return groupsMap, nil +} + +func (m *managerImpl) AddResourceToGroup(ctx context.Context, accountID, userID, groupID string, resource *types.Resource) error { + ok, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, permissions.Groups, permissions.Write) + if err != nil { + return err + } + if !ok { + return err + } + + return m.store.AddResourceToGroup(ctx, accountID, groupID, resource) +} + +func ToGroupsInfo(groups map[string]*types.Group, id string) []api.GroupMinimum { + groupsInfo := []api.GroupMinimum{} + groupsChecked := make(map[string]struct{}) + for _, group := range groups { + _, ok := groupsChecked[group.ID] + if ok { + continue + } + groupsChecked[group.ID] = struct{}{} + for _, pk := range group.Peers { + if pk == id { + info := api.GroupMinimum{ + Id: group.ID, + Name: group.Name, + PeersCount: len(group.Peers), + ResourcesCount: len(group.Resources), + } + groupsInfo = append(groupsInfo, info) + break + } + } + for _, rk := range group.Resources { + if rk.ID == id { + info := api.GroupMinimum{ + Id: group.ID, + Name: group.Name, + PeersCount: len(group.Peers), + ResourcesCount: len(group.Resources), + } + groupsInfo = append(groupsInfo, info) + break + } + } + } + return groupsInfo +} diff --git a/management/server/http/api/openapi.yml b/management/server/http/api/openapi.yml index 6a1088141..0f12ca82c 100644 --- a/management/server/http/api/openapi.yml +++ b/management/server/http/api/openapi.yml @@ -1241,6 +1241,10 @@ components: items: type: string example: ch8i4ug6lnn4g9hqv7m0 + routing_peers_count: + description: Count of routing peers associated with the network + type: integer + example: 2 resources: description: List of network resource IDs associated with the network type: array @@ -1251,8 +1255,9 @@ components: - id - routers - resources + - routing_peers_count - $ref: '#/components/schemas/NetworkRequest' - NetworkResourceRequest: + NetworkResourceMinimum: type: object properties: name: @@ -1270,6 +1275,19 @@ components: required: - name - address + NetworkResourceRequest: + allOf: + - $ref: '#/components/schemas/NetworkResourceMinimum' + - type: object + properties: + groups: + description: Group IDs containing the resource + type: array + items: + type: string + example: "chacdk86lnnboviihd70" + required: + - groups NetworkResource: allOf: - type: object @@ -1280,10 +1298,16 @@ components: example: chacdk86lnnboviihd7g type: $ref: '#/components/schemas/NetworkResourceType' + groups: + description: Groups that the resource belongs to + type: array + items: + $ref: '#/components/schemas/GroupMinimum' required: - id - type - - $ref: '#/components/schemas/NetworkResourceRequest' + - groups + - $ref: '#/components/schemas/NetworkResourceMinimum' NetworkResourceType: description: Network resource type based of the address type: string diff --git a/management/server/http/api/types.gen.go b/management/server/http/api/types.gen.go index 0ffc6eabe..622c7a60e 100644 --- a/management/server/http/api/types.gen.go +++ b/management/server/http/api/types.gen.go @@ -532,6 +532,9 @@ type Network struct { // Routers List of router IDs associated with the network Routers []string `json:"routers"` + + // RoutingPeersCount Count of routing peers associated with the network + RoutingPeersCount int `json:"routing_peers_count"` } // NetworkRequest defines model for NetworkRequest. @@ -551,6 +554,9 @@ type NetworkResource struct { // Description Network resource description Description *string `json:"description,omitempty"` + // Groups Groups that the resource belongs to + Groups []GroupMinimum `json:"groups"` + // Id Network Resource ID Id string `json:"id"` @@ -561,6 +567,18 @@ type NetworkResource struct { Type NetworkResourceType `json:"type"` } +// NetworkResourceMinimum defines model for NetworkResourceMinimum. +type NetworkResourceMinimum struct { + // Address Network resource address (either a direct host like 1.1.1.1 or 1.1.1.1/32, or a subnet like 192.168.178.0/24, or a domain like example.com) + Address string `json:"address"` + + // Description Network resource description + Description *string `json:"description,omitempty"` + + // Name Network resource name + Name string `json:"name"` +} + // NetworkResourceRequest defines model for NetworkResourceRequest. type NetworkResourceRequest struct { // Address Network resource address (either a direct host like 1.1.1.1 or 1.1.1.1/32, or a subnet like 192.168.178.0/24, or a domain like example.com) @@ -569,6 +587,9 @@ type NetworkResourceRequest struct { // Description Network resource description Description *string `json:"description,omitempty"` + // Groups Group IDs containing the resource + Groups []string `json:"groups"` + // Name Network resource name Name string `json:"name"` } diff --git a/management/server/http/handler.go b/management/server/http/handler.go index 1bc11b1e9..824a1cd8e 100644 --- a/management/server/http/handler.go +++ b/management/server/http/handler.go @@ -94,7 +94,7 @@ func APIHandler(ctx context.Context, accountManager s.AccountManager, LocationMa routes.AddEndpoints(api.AccountManager, authCfg, router) dns.AddEndpoints(api.AccountManager, authCfg, router) events.AddEndpoints(api.AccountManager, authCfg, router) - networks.AddEndpoints(api.AccountManager.GetNetworksManager(), api.AccountManager.GetAccountIDFromToken, authCfg, router) + networks.AddEndpoints(api.AccountManager.GetNetworksManager(), api.AccountManager.GetGroupsManager(), api.AccountManager.GetAccountIDFromToken, authCfg, router) return rootRouter, nil } diff --git a/management/server/http/handlers/groups/groups_handler.go b/management/server/http/handlers/groups/groups_handler.go index ee52d8b4c..0ecea7ec2 100644 --- a/management/server/http/handlers/groups/groups_handler.go +++ b/management/server/http/handlers/groups/groups_handler.go @@ -287,13 +287,7 @@ func toGroupResponse(peers []*nbpeer.Peer, group *types.Group) *api.Group { peersMap[peer.ID] = peer } - resMap := make(map[string]types.Resource, len(peers)) - for _, peer := range peers { - peersMap[peer.ID] = peer - } - peerCache := make(map[string]api.PeerMinimum) - resCache := make(map[string]api.Resource) gr := api.Group{ Id: group.ID, Name: group.Name, @@ -319,16 +313,8 @@ func toGroupResponse(peers []*nbpeer.Peer, group *types.Group) *api.Group { gr.PeersCount = len(gr.Peers) for _, res := range group.Resources { - _, ok := resCache[res.ID] - if !ok { - resource, ok := resMap[res.ID] - if !ok { - continue - } - resResp := resource.ToAPIResponse() - resCache[res.ID] = *resResp - gr.Resources = append(gr.Resources, *resResp) - } + resResp := res.ToAPIResponse() + gr.Resources = append(gr.Resources, *resResp) } gr.ResourcesCount = len(gr.Resources) diff --git a/management/server/http/handlers/networks/handler.go b/management/server/http/handlers/networks/handler.go index 1ce856118..ec3a5261e 100644 --- a/management/server/http/handlers/networks/handler.go +++ b/management/server/http/handlers/networks/handler.go @@ -8,36 +8,42 @@ import ( "github.com/gorilla/mux" + "github.com/netbirdio/netbird/management/server/groups" "github.com/netbirdio/netbird/management/server/http/api" "github.com/netbirdio/netbird/management/server/http/configs" "github.com/netbirdio/netbird/management/server/http/util" "github.com/netbirdio/netbird/management/server/jwtclaims" "github.com/netbirdio/netbird/management/server/networks" + routerTypes "github.com/netbirdio/netbird/management/server/networks/routers/types" "github.com/netbirdio/netbird/management/server/networks/types" "github.com/netbirdio/netbird/management/server/status" + nbtypes "github.com/netbirdio/netbird/management/server/types" ) // handler is a handler that returns networks of the account type handler struct { networksManager networks.Manager + groupsManager groups.Manager extractFromToken func(ctx context.Context, claims jwtclaims.AuthorizationClaims) (string, string, error) claimsExtractor *jwtclaims.ClaimsExtractor } -func AddEndpoints(networksManager networks.Manager, extractFromToken func(ctx context.Context, claims jwtclaims.AuthorizationClaims) (string, string, error), authCfg configs.AuthCfg, router *mux.Router) { - networksHandler := newHandler(networksManager, extractFromToken, authCfg) +func AddEndpoints(networksManager networks.Manager, groupsManager groups.Manager, extractFromToken func(ctx context.Context, claims jwtclaims.AuthorizationClaims) (string, string, error), authCfg configs.AuthCfg, router *mux.Router) { + addRouterEndpoints(networksManager.GetRouterManager(), extractFromToken, authCfg, router) + addResourceEndpoints(networksManager.GetResourceManager(), groupsManager, extractFromToken, authCfg, router) + + networksHandler := newHandler(networksManager, groupsManager, extractFromToken, authCfg) router.HandleFunc("/networks", networksHandler.getAllNetworks).Methods("GET", "OPTIONS") router.HandleFunc("/networks", networksHandler.createNetwork).Methods("POST", "OPTIONS") router.HandleFunc("/networks/{networkId}", networksHandler.getNetwork).Methods("GET", "OPTIONS") router.HandleFunc("/networks/{networkId}", networksHandler.updateNetwork).Methods("PUT", "OPTIONS") router.HandleFunc("/networks/{networkId}", networksHandler.deleteNetwork).Methods("DELETE", "OPTIONS") - addRouterEndpoints(networksManager.GetRouterManager(), extractFromToken, authCfg, router) - addResourceEndpoints(networksManager.GetResourceManager(), extractFromToken, authCfg, router) } -func newHandler(networksManager networks.Manager, extractFromToken func(ctx context.Context, claims jwtclaims.AuthorizationClaims) (string, string, error), authCfg configs.AuthCfg) *handler { +func newHandler(networksManager networks.Manager, groupsManager groups.Manager, extractFromToken func(ctx context.Context, claims jwtclaims.AuthorizationClaims) (string, string, error), authCfg configs.AuthCfg) *handler { return &handler{ networksManager: networksManager, + groupsManager: groupsManager, extractFromToken: extractFromToken, claimsExtractor: jwtclaims.NewClaimsExtractor( jwtclaims.WithAudience(authCfg.Audience), @@ -60,24 +66,25 @@ func (h *handler) getAllNetworks(w http.ResponseWriter, r *http.Request) { return } - routers, err := h.networksManager.GetRouterManager().GetAllRouterIDsInAccount(r.Context(), accountID, userID) + resourceIDs, err := h.networksManager.GetResourceManager().GetAllResourceIDsInAccount(r.Context(), accountID, userID) if err != nil { util.WriteError(r.Context(), err, w) return } - resources, err := h.networksManager.GetResourceManager().GetAllResourceIDsInAccount(r.Context(), accountID, userID) + groups, err := h.groupsManager.GetAllGroups(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(routers[network.ID], resources[network.ID])) + routers, err := h.networksManager.GetRouterManager().GetAllRoutersInAccount(r.Context(), accountID, userID) + if err != nil { + util.WriteError(r.Context(), err, w) + return } - util.WriteJSONObject(r.Context(), w, networkResponse) + util.WriteJSONObject(r.Context(), w, h.generateNetworkResponse(networks, routers, resourceIDs, groups)) } func (h *handler) createNetwork(w http.ResponseWriter, r *http.Request) { @@ -105,7 +112,7 @@ func (h *handler) createNetwork(w http.ResponseWriter, r *http.Request) { return } - util.WriteJSONObject(r.Context(), w, network.ToAPIResponse([]string{}, []string{})) + util.WriteJSONObject(r.Context(), w, network.ToAPIResponse([]string{}, []string{}, 0)) } func (h *handler) getNetwork(w http.ResponseWriter, r *http.Request) { @@ -129,13 +136,13 @@ func (h *handler) getNetwork(w http.ResponseWriter, r *http.Request) { return } - routerIDs, resourceIDs, err := h.collectIDsInNetwork(r.Context(), accountID, userID, networkID) + routerIDs, resourceIDs, peerCount, 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)) + util.WriteJSONObject(r.Context(), w, network.ToAPIResponse(routerIDs, resourceIDs, peerCount)) } func (h *handler) updateNetwork(w http.ResponseWriter, r *http.Request) { @@ -171,13 +178,13 @@ func (h *handler) updateNetwork(w http.ResponseWriter, r *http.Request) { return } - routerIDs, resourceIDs, err := h.collectIDsInNetwork(r.Context(), accountID, userID, networkID) + routerIDs, resourceIDs, peerCount, 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)) + util.WriteJSONObject(r.Context(), w, network.ToAPIResponse(routerIDs, resourceIDs, peerCount)) } func (h *handler) deleteNetwork(w http.ResponseWriter, r *http.Request) { @@ -204,10 +211,10 @@ 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) { +func (h *handler) collectIDsInNetwork(ctx context.Context, accountID, userID, networkID string) ([]string, []string, int, 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) + return nil, nil, 0, fmt.Errorf("failed to get resources in network: %w", err) } var resourceIDs []string @@ -217,13 +224,48 @@ func (h *handler) collectIDsInNetwork(ctx context.Context, accountID, userID, ne 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) + return nil, nil, 0, fmt.Errorf("failed to get routers in network: %w", err) } + groups, err := h.groupsManager.GetAllGroups(ctx, accountID, userID) + if err != nil { + return nil, nil, 0, fmt.Errorf("failed to get groups: %w", err) + } + + peerCounter := 0 var routerIDs []string for _, router := range routers { routerIDs = append(routerIDs, router.ID) + if router.Peer != "" { + peerCounter++ + } + if len(router.PeerGroups) > 0 { + for _, groupID := range router.PeerGroups { + peerCounter += len(groups[groupID].Peers) + } + } } - return routerIDs, resourceIDs, nil + return routerIDs, resourceIDs, peerCounter, nil +} + +func (h *handler) generateNetworkResponse(networks []*types.Network, routers map[string][]*routerTypes.NetworkRouter, resourceIDs map[string][]string, groups map[string]*nbtypes.Group) []*api.Network { + var networkResponse []*api.Network + for _, network := range networks { + routerIDs := []string{} + peerCounter := 0 + for _, router := range routers[network.ID] { + routerIDs = append(routerIDs, router.ID) + if router.Peer != "" { + peerCounter++ + } + if len(router.PeerGroups) > 0 { + for _, groupID := range router.PeerGroups { + peerCounter += len(groups[groupID].Peers) + } + } + } + networkResponse = append(networkResponse, network.ToAPIResponse(routerIDs, resourceIDs[network.ID], peerCounter)) + } + return networkResponse } diff --git a/management/server/http/handlers/networks/resources_handler.go b/management/server/http/handlers/networks/resources_handler.go index 9221cefaf..53fbb7b93 100644 --- a/management/server/http/handlers/networks/resources_handler.go +++ b/management/server/http/handlers/networks/resources_handler.go @@ -7,33 +7,37 @@ import ( "github.com/gorilla/mux" + "github.com/netbirdio/netbird/management/server/groups" "github.com/netbirdio/netbird/management/server/http/api" "github.com/netbirdio/netbird/management/server/http/configs" "github.com/netbirdio/netbird/management/server/http/util" "github.com/netbirdio/netbird/management/server/jwtclaims" "github.com/netbirdio/netbird/management/server/networks/resources" "github.com/netbirdio/netbird/management/server/networks/resources/types" + nbtypes "github.com/netbirdio/netbird/management/server/types" ) type resourceHandler struct { resourceManager resources.Manager + groupsManager groups.Manager extractFromToken func(ctx context.Context, claims jwtclaims.AuthorizationClaims) (string, string, error) claimsExtractor *jwtclaims.ClaimsExtractor } -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) +func addResourceEndpoints(resourcesManager resources.Manager, groupsManager groups.Manager, extractFromToken func(ctx context.Context, claims jwtclaims.AuthorizationClaims) (string, string, error), authCfg configs.AuthCfg, router *mux.Router) { + resourceHandler := newResourceHandler(resourcesManager, groupsManager, extractFromToken, authCfg) + router.HandleFunc("/networks/resources", resourceHandler.getAllResourcesInAccount).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 { +func newResourceHandler(resourceManager resources.Manager, groupsManager groups.Manager, extractFromToken func(ctx context.Context, claims jwtclaims.AuthorizationClaims) (string, string, error), authCfg configs.AuthCfg) *resourceHandler { return &resourceHandler{ resourceManager: resourceManager, + groupsManager: groupsManager, extractFromToken: extractFromToken, claimsExtractor: jwtclaims.NewClaimsExtractor( jwtclaims.WithAudience(authCfg.Audience), @@ -57,9 +61,16 @@ func (h *resourceHandler) getAllResourcesInNetwork(w http.ResponseWriter, r *htt return } + grps, err := h.groupsManager.GetAllGroups(r.Context(), accountID, userID) + if err != nil { + util.WriteError(r.Context(), err, w) + return + } + var resourcesResponse []*api.NetworkResource for _, resource := range resources { - resourcesResponse = append(resourcesResponse, resource.ToAPIResponse()) + groupMinimumInfo := groups.ToGroupsInfo(grps, resource.ID) + resourcesResponse = append(resourcesResponse, resource.ToAPIResponse(groupMinimumInfo)) } util.WriteJSONObject(r.Context(), w, resourcesResponse) @@ -78,9 +89,16 @@ func (h *resourceHandler) getAllResourcesInAccount(w http.ResponseWriter, r *htt return } + grps, err := h.groupsManager.GetAllGroups(r.Context(), accountID, userID) + if err != nil { + util.WriteError(r.Context(), err, w) + return + } + var resourcesResponse []*api.NetworkResource for _, resource := range resources { - resourcesResponse = append(resourcesResponse, resource.ToAPIResponse()) + groupMinimumInfo := groups.ToGroupsInfo(grps, resource.ID) + resourcesResponse = append(resourcesResponse, resource.ToAPIResponse(groupMinimumInfo)) } util.WriteJSONObject(r.Context(), w, resourcesResponse) @@ -112,7 +130,26 @@ func (h *resourceHandler) createResource(w http.ResponseWriter, r *http.Request) return } - util.WriteJSONObject(r.Context(), w, resource.ToAPIResponse()) + res := nbtypes.Resource{ + ID: resource.ID, + Type: resource.Type.String(), + } + for _, groupID := range req.Groups { + err = h.groupsManager.AddResourceToGroup(r.Context(), accountID, userID, groupID, &res) + if err != nil { + util.WriteError(r.Context(), err, w) + return + } + } + + grps, err := h.groupsManager.GetAllGroups(r.Context(), accountID, userID) + if err != nil { + util.WriteError(r.Context(), err, w) + return + } + + groupMinimumInfo := groups.ToGroupsInfo(grps, resource.ID) + util.WriteJSONObject(r.Context(), w, resource.ToAPIResponse(groupMinimumInfo)) } func (h *resourceHandler) getResource(w http.ResponseWriter, r *http.Request) { @@ -131,7 +168,14 @@ func (h *resourceHandler) getResource(w http.ResponseWriter, r *http.Request) { return } - util.WriteJSONObject(r.Context(), w, resource.ToAPIResponse()) + grps, err := h.groupsManager.GetAllGroups(r.Context(), accountID, userID) + if err != nil { + util.WriteError(r.Context(), err, w) + return + } + + groupMinimumInfo := groups.ToGroupsInfo(grps, resource.ID) + util.WriteJSONObject(r.Context(), w, resource.ToAPIResponse(groupMinimumInfo)) } func (h *resourceHandler) updateResource(w http.ResponseWriter, r *http.Request) { @@ -161,7 +205,26 @@ func (h *resourceHandler) updateResource(w http.ResponseWriter, r *http.Request) return } - util.WriteJSONObject(r.Context(), w, resource.ToAPIResponse()) + res := nbtypes.Resource{ + ID: resource.ID, + Type: resource.Type.String(), + } + for _, groupID := range req.Groups { + err = h.groupsManager.AddResourceToGroup(r.Context(), accountID, userID, groupID, &res) + if err != nil { + util.WriteError(r.Context(), err, w) + return + } + } + + grps, err := h.groupsManager.GetAllGroups(r.Context(), accountID, userID) + if err != nil { + util.WriteError(r.Context(), err, w) + return + } + + groupMinimumInfo := groups.ToGroupsInfo(grps, resource.ID) + util.WriteJSONObject(r.Context(), w, resource.ToAPIResponse(groupMinimumInfo)) } func (h *resourceHandler) deleteResource(w http.ResponseWriter, r *http.Request) { diff --git a/management/server/http/handlers/peers/peers_handler.go b/management/server/http/handlers/peers/peers_handler.go index 4562766bd..77f1a1a7f 100644 --- a/management/server/http/handlers/peers/peers_handler.go +++ b/management/server/http/handlers/peers/peers_handler.go @@ -10,6 +10,7 @@ import ( log "github.com/sirupsen/logrus" "github.com/netbirdio/netbird/management/server" + "github.com/netbirdio/netbird/management/server/groups" "github.com/netbirdio/netbird/management/server/http/api" "github.com/netbirdio/netbird/management/server/http/configs" "github.com/netbirdio/netbird/management/server/http/util" @@ -71,7 +72,7 @@ func (h *Handler) getPeer(ctx context.Context, account *types.Account, peerID, u } dnsDomain := h.accountManager.GetDNSDomain() - groupsInfo := toGroupsInfo(account.Groups, peer.ID) + groupsInfo := groups.ToGroupsInfo(account.Groups, peer.ID) validPeers, err := h.accountManager.GetValidatedPeers(account) if err != nil { @@ -115,7 +116,7 @@ func (h *Handler) updatePeer(ctx context.Context, account *types.Account, userID } dnsDomain := h.accountManager.GetDNSDomain() - groupMinimumInfo := toGroupsInfo(account.Groups, peer.ID) + groupMinimumInfo := groups.ToGroupsInfo(account.Groups, peer.ID) validPeers, err := h.accountManager.GetValidatedPeers(account) if err != nil { @@ -200,8 +201,8 @@ func (h *Handler) GetAllPeers(w http.ResponseWriter, r *http.Request) { } groupsMap := map[string]*types.Group{} - groups, _ := h.accountManager.GetAllGroups(r.Context(), accountID, userID) - for _, group := range groups { + grps, _ := h.accountManager.GetAllGroups(r.Context(), accountID, userID) + for _, group := range grps { groupsMap[group.ID] = group } @@ -212,7 +213,7 @@ func (h *Handler) GetAllPeers(w http.ResponseWriter, r *http.Request) { util.WriteError(r.Context(), err, w) return } - groupMinimumInfo := toGroupsInfo(groupsMap, peer.ID) + groupMinimumInfo := groups.ToGroupsInfo(groupsMap, peer.ID) respBody = append(respBody, toPeerListItemResponse(peerToReturn, groupMinimumInfo, dnsDomain, 0)) } @@ -324,30 +325,6 @@ func peerToAccessiblePeer(peer *nbpeer.Peer, dnsDomain string) api.AccessiblePee } } -func toGroupsInfo(groups map[string]*types.Group, peerID string) []api.GroupMinimum { - groupsInfo := []api.GroupMinimum{} - groupsChecked := make(map[string]struct{}) - for _, group := range groups { - _, ok := groupsChecked[group.ID] - if ok { - continue - } - groupsChecked[group.ID] = struct{}{} - for _, pk := range group.Peers { - if pk == peerID { - info := api.GroupMinimum{ - Id: group.ID, - Name: group.Name, - PeersCount: len(group.Peers), - } - groupsInfo = append(groupsInfo, info) - break - } - } - } - return groupsInfo -} - func toSinglePeerResponse(peer *nbpeer.Peer, groupsInfo []api.GroupMinimum, dnsDomain string, approved bool) *api.Peer { osVersion := peer.Meta.OSVersion if osVersion == "" { diff --git a/management/server/mock_server/account_mock.go b/management/server/mock_server/account_mock.go index 45d5eceb6..98579c7bf 100644 --- a/management/server/mock_server/account_mock.go +++ b/management/server/mock_server/account_mock.go @@ -13,6 +13,7 @@ import ( "github.com/netbirdio/netbird/management/domain" "github.com/netbirdio/netbird/management/server" "github.com/netbirdio/netbird/management/server/activity" + "github.com/netbirdio/netbird/management/server/groups" "github.com/netbirdio/netbird/management/server/idp" "github.com/netbirdio/netbird/management/server/jwtclaims" "github.com/netbirdio/netbird/management/server/networks" @@ -123,6 +124,11 @@ func (am *MockAccountManager) GetNetworksManager() networks.Manager { panic("implement me") } +func (am *MockAccountManager) GetGroupsManager() groups.Manager { + // TODO implement me + panic("implement me") +} + func (am *MockAccountManager) DeleteSetupKey(ctx context.Context, accountID, userID, keyID string) error { if am.DeleteSetupKeyFunc != nil { return am.DeleteSetupKeyFunc(ctx, accountID, userID, keyID) diff --git a/management/server/networks/resources/types/resource.go b/management/server/networks/resources/types/resource.go index dd2c10fa5..ed142083b 100644 --- a/management/server/networks/resources/types/resource.go +++ b/management/server/networks/resources/types/resource.go @@ -51,13 +51,14 @@ func NewNetworkResource(accountID, networkID, name, description, address string) }, nil } -func (n *NetworkResource) ToAPIResponse() *api.NetworkResource { +func (n *NetworkResource) ToAPIResponse(groups []api.GroupMinimum) *api.NetworkResource { return &api.NetworkResource{ Id: n.ID, Name: n.Name, Description: &n.Description, Type: api.NetworkResourceType(n.Type.String()), Address: n.Address, + Groups: groups, } } diff --git a/management/server/networks/routers/manager.go b/management/server/networks/routers/manager.go index 0ced5ac9b..8e6d03043 100644 --- a/management/server/networks/routers/manager.go +++ b/management/server/networks/routers/manager.go @@ -15,7 +15,7 @@ import ( type Manager interface { GetAllRoutersInNetwork(ctx context.Context, accountID, userID, networkID string) ([]*types.NetworkRouter, error) - GetAllRouterIDsInAccount(ctx context.Context, accountID, userID string) (map[string][]string, error) + GetAllRoutersInAccount(ctx context.Context, accountID, userID string) (map[string][]*types.NetworkRouter, 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) @@ -46,7 +46,7 @@ func (m *managerImpl) GetAllRoutersInNetwork(ctx context.Context, accountID, use return m.store.GetNetworkRoutersByNetID(ctx, store.LockingStrengthShare, accountID, networkID) } -func (m *managerImpl) GetAllRouterIDsInAccount(ctx context.Context, accountID, userID string) (map[string][]string, error) { +func (m *managerImpl) GetAllRoutersInAccount(ctx context.Context, accountID, userID string) (map[string][]*types.NetworkRouter, error) { ok, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, permissions.Networks, permissions.Read) if err != nil { return nil, status.NewPermissionValidationError(err) @@ -60,9 +60,9 @@ func (m *managerImpl) GetAllRouterIDsInAccount(ctx context.Context, accountID, u return nil, fmt.Errorf("failed to get network routers: %w", err) } - routersMap := make(map[string][]string) + routersMap := make(map[string][]*types.NetworkRouter) for _, router := range routers { - routersMap[router.NetworkID] = append(routersMap[router.NetworkID], router.ID) + routersMap[router.NetworkID] = append(routersMap[router.NetworkID], router) } return routersMap, nil diff --git a/management/server/networks/types/network.go b/management/server/networks/types/network.go index b884690d5..d95252382 100644 --- a/management/server/networks/types/network.go +++ b/management/server/networks/types/network.go @@ -22,13 +22,14 @@ func NewNetwork(accountId, name, description string) *Network { } } -func (n *Network) ToAPIResponse(routerIDs []string, resourceIDs []string) *api.Network { +func (n *Network) ToAPIResponse(routerIDs []string, resourceIDs []string, routingPeersCount int) *api.Network { return &api.Network{ - Id: n.ID, - Name: n.Name, - Description: &n.Description, - Routers: routerIDs, - Resources: resourceIDs, + Id: n.ID, + Name: n.Name, + Description: &n.Description, + Routers: routerIDs, + Resources: resourceIDs, + RoutingPeersCount: routingPeersCount, } } diff --git a/management/server/permissions/manager.go b/management/server/permissions/manager.go index 5d1ba2320..f16b41fa0 100644 --- a/management/server/permissions/manager.go +++ b/management/server/permissions/manager.go @@ -15,6 +15,7 @@ type Module string const ( Networks Module = "networks" Peers Module = "peers" + Groups Module = "groups" ) type Operation string diff --git a/management/server/store/sql_store.go b/management/server/store/sql_store.go index 771a32aae..7240a7a09 100644 --- a/management/server/store/sql_store.go +++ b/management/server/store/sql_store.go @@ -1070,6 +1070,32 @@ func (s *SqlStore) AddPeerToGroup(ctx context.Context, accountId string, peerId return nil } +func (s *SqlStore) AddResourceToGroup(ctx context.Context, accountId string, groupID string, resource *types.Resource) error { + var group types.Group + result := s.db.Where(accountAndIDQueryCondition, accountId, groupID).First(&group) + if result.Error != nil { + if errors.Is(result.Error, gorm.ErrRecordNotFound) { + return status.NewGroupNotFoundError(groupID) + } + + return status.Errorf(status.Internal, "issue finding group: %s", result.Error) + } + + for _, res := range group.Resources { + if res.ID == resource.ID { + return nil + } + } + + group.Resources = append(group.Resources, *resource) + + if err := s.db.Save(&group).Error; err != nil { + return status.Errorf(status.Internal, "issue updating group: %s", err) + } + + return nil +} + // GetUserPeers retrieves peers for a user. func (s *SqlStore) GetUserPeers(ctx context.Context, lockStrength LockingStrength, accountID, userID string) ([]*nbpeer.Peer, error) { return getRecords[*nbpeer.Peer](s.db.Where("user_id = ?", userID), lockStrength, accountID) diff --git a/management/server/store/store.go b/management/server/store/store.go index 07fef6cfd..4d77b95e9 100644 --- a/management/server/store/store.go +++ b/management/server/store/store.go @@ -98,6 +98,7 @@ type Store interface { GetPeerLabelsInAccount(ctx context.Context, lockStrength LockingStrength, accountId string) ([]string, error) AddPeerToAllGroup(ctx context.Context, accountID string, peerID string) error AddPeerToGroup(ctx context.Context, accountId string, peerId string, groupID string) error + AddResourceToGroup(ctx context.Context, accountId string, groupID string, resource *types.Resource) error AddPeerToAccount(ctx context.Context, peer *nbpeer.Peer) error GetPeerByPeerPubKey(ctx context.Context, lockStrength LockingStrength, peerKey string) (*nbpeer.Peer, error) GetUserPeers(ctx context.Context, lockStrength LockingStrength, accountID, userID string) ([]*nbpeer.Peer, error)