netbird/management/server/http/groups_handler.go
Yury Gargay bb40325977
Update GitHub Actions and Enhance golangci-lint (#1075)
This PR showcases the implementation of additional linter rules. I've updated the golangci-lint GitHub Actions to the latest available version. This update makes sure that the tool works the same way locally - assuming being updated regularly - and with the GitHub Actions.

I've also taken care of keeping all the GitHub Actions up to date, which helps our code stay current. But there's one part, goreleaser that's a bit tricky to test on our computers. So, it's important to take a close look at that.

To make it easier to understand what I've done, I've made separate changes for each thing that the new linters found. This should help the people reviewing the changes see what's going on more clearly. Some of the changes might not be obvious at first glance.

Things to consider for the future
CI runs on Ubuntu so the static analysis only happens for Linux. Consider running it for the rest: Darwin, Windows
2023-09-04 17:03:44 +02:00

265 lines
6.5 KiB
Go

package http
import (
"encoding/json"
"net/http"
"github.com/netbirdio/netbird/management/server/http/api"
"github.com/netbirdio/netbird/management/server/http/util"
"github.com/netbirdio/netbird/management/server/status"
"github.com/rs/xid"
"github.com/netbirdio/netbird/management/server"
"github.com/netbirdio/netbird/management/server/jwtclaims"
"github.com/gorilla/mux"
log "github.com/sirupsen/logrus"
)
// GroupsHandler is a handler that returns groups of the account
type GroupsHandler struct {
accountManager server.AccountManager
claimsExtractor *jwtclaims.ClaimsExtractor
}
// NewGroupsHandler creates a new GroupsHandler HTTP handler
func NewGroupsHandler(accountManager server.AccountManager, authCfg AuthCfg) *GroupsHandler {
return &GroupsHandler{
accountManager: accountManager,
claimsExtractor: jwtclaims.NewClaimsExtractor(
jwtclaims.WithAudience(authCfg.Audience),
jwtclaims.WithUserIDClaim(authCfg.UserIDClaim),
),
}
}
// GetAllGroups list for the account
func (h *GroupsHandler) GetAllGroups(w http.ResponseWriter, r *http.Request) {
claims := h.claimsExtractor.FromRequestContext(r)
account, _, err := h.accountManager.GetAccountFromToken(claims)
if err != nil {
log.Error(err)
http.Redirect(w, r, "/", http.StatusInternalServerError)
return
}
var groups []*api.Group
for _, g := range account.Groups {
groups = append(groups, toGroupResponse(account, g))
}
util.WriteJSONObject(w, groups)
}
// UpdateGroup handles update to a group identified by a given ID
func (h *GroupsHandler) UpdateGroup(w http.ResponseWriter, r *http.Request) {
claims := h.claimsExtractor.FromRequestContext(r)
account, user, err := h.accountManager.GetAccountFromToken(claims)
if err != nil {
util.WriteError(err, w)
return
}
vars := mux.Vars(r)
groupID, ok := vars["groupId"]
if !ok {
util.WriteError(status.Errorf(status.InvalidArgument, "group ID field is missing"), w)
return
}
if len(groupID) == 0 {
util.WriteError(status.Errorf(status.InvalidArgument, "group ID can't be empty"), w)
return
}
eg, ok := account.Groups[groupID]
if !ok {
util.WriteError(status.Errorf(status.NotFound, "couldn't find group with ID %s", groupID), w)
return
}
allGroup, err := account.GetGroupAll()
if err != nil {
util.WriteError(err, w)
return
}
if allGroup.ID == groupID {
util.WriteError(status.Errorf(status.InvalidArgument, "updating group ALL is not allowed"), w)
return
}
var req api.PutApiGroupsGroupIdJSONRequestBody
err = json.NewDecoder(r.Body).Decode(&req)
if err != nil {
util.WriteErrorResponse("couldn't parse JSON request", http.StatusBadRequest, w)
return
}
if req.Name == "" {
util.WriteError(status.Errorf(status.InvalidArgument, "group name shouldn't be empty"), w)
return
}
var peers []string
if req.Peers == nil {
peers = make([]string, 0)
} else {
peers = *req.Peers
}
group := server.Group{
ID: groupID,
Name: req.Name,
Peers: peers,
Issued: eg.Issued,
}
if err := h.accountManager.SaveGroup(account.Id, user.Id, &group); err != nil {
log.Errorf("failed updating group %s under account %s %v", groupID, account.Id, err)
util.WriteError(err, w)
return
}
util.WriteJSONObject(w, toGroupResponse(account, &group))
}
// CreateGroup handles group creation request
func (h *GroupsHandler) CreateGroup(w http.ResponseWriter, r *http.Request) {
claims := h.claimsExtractor.FromRequestContext(r)
account, user, err := h.accountManager.GetAccountFromToken(claims)
if err != nil {
util.WriteError(err, w)
return
}
var req api.PostApiGroupsJSONRequestBody
err = json.NewDecoder(r.Body).Decode(&req)
if err != nil {
util.WriteErrorResponse("couldn't parse JSON request", http.StatusBadRequest, w)
return
}
if req.Name == "" {
util.WriteError(status.Errorf(status.InvalidArgument, "group name shouldn't be empty"), w)
return
}
var peers []string
if req.Peers == nil {
peers = make([]string, 0)
} else {
peers = *req.Peers
}
group := server.Group{
ID: xid.New().String(),
Name: req.Name,
Peers: peers,
Issued: server.GroupIssuedAPI,
}
err = h.accountManager.SaveGroup(account.Id, user.Id, &group)
if err != nil {
util.WriteError(err, w)
return
}
util.WriteJSONObject(w, toGroupResponse(account, &group))
}
// DeleteGroup handles group deletion request
func (h *GroupsHandler) DeleteGroup(w http.ResponseWriter, r *http.Request) {
claims := h.claimsExtractor.FromRequestContext(r)
account, user, err := h.accountManager.GetAccountFromToken(claims)
if err != nil {
util.WriteError(err, w)
return
}
aID := account.Id
groupID := mux.Vars(r)["groupId"]
if len(groupID) == 0 {
util.WriteError(status.Errorf(status.InvalidArgument, "invalid group ID"), w)
return
}
allGroup, err := account.GetGroupAll()
if err != nil {
util.WriteError(err, w)
return
}
if allGroup.ID == groupID {
util.WriteError(status.Errorf(status.InvalidArgument, "deleting group ALL is not allowed"), w)
return
}
err = h.accountManager.DeleteGroup(aID, user.Id, groupID)
if err != nil {
_, ok := err.(*server.GroupLinkError)
if ok {
util.WriteErrorResponse(err.Error(), http.StatusBadRequest, w)
return
}
util.WriteError(err, w)
return
}
util.WriteJSONObject(w, emptyObject{})
}
// GetGroup returns a group
func (h *GroupsHandler) GetGroup(w http.ResponseWriter, r *http.Request) {
claims := h.claimsExtractor.FromRequestContext(r)
account, _, err := h.accountManager.GetAccountFromToken(claims)
if err != nil {
util.WriteError(err, w)
return
}
switch r.Method {
case http.MethodGet:
groupID := mux.Vars(r)["groupId"]
if len(groupID) == 0 {
util.WriteError(status.Errorf(status.InvalidArgument, "invalid group ID"), w)
return
}
group, err := h.accountManager.GetGroup(account.Id, groupID)
if err != nil {
util.WriteError(err, w)
return
}
util.WriteJSONObject(w, toGroupResponse(account, group))
default:
util.WriteError(status.Errorf(status.NotFound, "HTTP method not found"), w)
return
}
}
func toGroupResponse(account *server.Account, group *server.Group) *api.Group {
cache := make(map[string]api.PeerMinimum)
gr := api.Group{
Id: group.ID,
Name: group.Name,
PeersCount: len(group.Peers),
Issued: &group.Issued,
}
for _, pid := range group.Peers {
_, ok := cache[pid]
if !ok {
peer, ok := account.Peers[pid]
if !ok {
continue
}
peerResp := api.PeerMinimum{
Id: peer.ID,
Name: peer.Name,
}
cache[pid] = peerResp
gr.Peers = append(gr.Peers, peerResp)
}
}
return &gr
}