2022-08-20 19:11:54 +02:00
|
|
|
package http
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"net/http"
|
|
|
|
"net/http/httptest"
|
|
|
|
"net/netip"
|
|
|
|
"strconv"
|
|
|
|
"testing"
|
|
|
|
|
2023-02-03 21:47:20 +01:00
|
|
|
"github.com/netbirdio/netbird/management/server/http/api"
|
|
|
|
"github.com/netbirdio/netbird/management/server/status"
|
|
|
|
"github.com/netbirdio/netbird/route"
|
|
|
|
|
2022-08-20 19:11:54 +02:00
|
|
|
"github.com/gorilla/mux"
|
|
|
|
"github.com/magiconair/properties/assert"
|
2023-02-28 15:01:24 +01:00
|
|
|
|
2022-08-20 19:11:54 +02:00
|
|
|
"github.com/netbirdio/netbird/management/server"
|
|
|
|
"github.com/netbirdio/netbird/management/server/jwtclaims"
|
|
|
|
"github.com/netbirdio/netbird/management/server/mock_server"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
existingRouteID = "existingRouteID"
|
|
|
|
notFoundRouteID = "notFoundRouteID"
|
2023-02-03 10:33:28 +01:00
|
|
|
existingPeerIP = "100.64.0.100"
|
|
|
|
existingPeerID = "peer-id"
|
|
|
|
notFoundPeerID = "nonExistingPeer"
|
2022-08-20 19:11:54 +02:00
|
|
|
existingPeerKey = "existingPeerKey"
|
|
|
|
testAccountID = "test_id"
|
2022-12-06 10:11:57 +01:00
|
|
|
existingGroupID = "testGroup"
|
2022-08-20 19:11:54 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
var baseExistingRoute = &route.Route{
|
|
|
|
ID: existingRouteID,
|
|
|
|
Description: "base route",
|
2022-08-22 14:10:24 +02:00
|
|
|
NetID: "awesomeNet",
|
|
|
|
Network: netip.MustParsePrefix("192.168.0.0/24"),
|
|
|
|
NetworkType: route.IPv4Network,
|
2022-08-20 19:11:54 +02:00
|
|
|
Metric: 9999,
|
|
|
|
Masquerade: false,
|
|
|
|
Enabled: true,
|
2022-12-06 10:11:57 +01:00
|
|
|
Groups: []string{existingGroupID},
|
2022-08-20 19:11:54 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
var testingAccount = &server.Account{
|
|
|
|
Id: testAccountID,
|
|
|
|
Domain: "hotmail.com",
|
|
|
|
Peers: map[string]*server.Peer{
|
2023-02-03 10:33:28 +01:00
|
|
|
existingPeerID: {
|
2023-01-25 16:29:59 +01:00
|
|
|
Key: existingPeerKey,
|
2023-02-03 10:33:28 +01:00
|
|
|
IP: netip.MustParseAddr(existingPeerIP).AsSlice(),
|
|
|
|
ID: existingPeerID,
|
2022-08-20 19:11:54 +02:00
|
|
|
},
|
|
|
|
},
|
2022-11-05 10:24:50 +01:00
|
|
|
Users: map[string]*server.User{
|
|
|
|
"test_user": server.NewAdminUser("test_user"),
|
|
|
|
},
|
2022-08-20 19:11:54 +02:00
|
|
|
}
|
|
|
|
|
2023-02-28 15:46:08 +01:00
|
|
|
func initRoutesTestData() *RoutesHandler {
|
|
|
|
return &RoutesHandler{
|
2022-08-20 19:11:54 +02:00
|
|
|
accountManager: &mock_server.MockAccountManager{
|
2022-11-05 10:24:50 +01:00
|
|
|
GetRouteFunc: func(_, routeID, _ string) (*route.Route, error) {
|
2022-08-20 19:11:54 +02:00
|
|
|
if routeID == existingRouteID {
|
|
|
|
return baseExistingRoute, nil
|
|
|
|
}
|
2022-11-11 20:36:45 +01:00
|
|
|
return nil, status.Errorf(status.NotFound, "route with ID %s not found", routeID)
|
2022-08-20 19:11:54 +02:00
|
|
|
},
|
2023-02-03 10:33:28 +01:00
|
|
|
CreateRouteFunc: func(accountID string, network, peerID, description, netID string, masquerade bool, metric int, groups []string, enabled bool, _ string) (*route.Route, error) {
|
|
|
|
if peerID == notFoundPeerID {
|
|
|
|
return nil, status.Errorf(status.InvalidArgument, "peer with ID %s not found", peerID)
|
2023-01-25 16:29:59 +01:00
|
|
|
}
|
2022-08-22 14:10:24 +02:00
|
|
|
networkType, p, _ := route.ParseNetwork(network)
|
2022-08-20 19:11:54 +02:00
|
|
|
return &route.Route{
|
|
|
|
ID: existingRouteID,
|
2022-08-22 14:10:24 +02:00
|
|
|
NetID: netID,
|
2023-02-03 10:33:28 +01:00
|
|
|
Peer: peerID,
|
2022-08-22 14:10:24 +02:00
|
|
|
Network: p,
|
|
|
|
NetworkType: networkType,
|
2022-08-20 19:11:54 +02:00
|
|
|
Description: description,
|
|
|
|
Masquerade: masquerade,
|
|
|
|
Enabled: enabled,
|
2022-12-06 10:11:57 +01:00
|
|
|
Groups: groups,
|
2022-08-20 19:11:54 +02:00
|
|
|
}, nil
|
|
|
|
},
|
2023-02-03 10:33:28 +01:00
|
|
|
SaveRouteFunc: func(_, _ string, r *route.Route) error {
|
|
|
|
if r.Peer == notFoundPeerID {
|
|
|
|
return status.Errorf(status.InvalidArgument, "peer with ID %s not found", r.Peer)
|
|
|
|
}
|
2022-08-20 19:11:54 +02:00
|
|
|
return nil
|
|
|
|
},
|
2023-01-25 16:29:59 +01:00
|
|
|
DeleteRouteFunc: func(_ string, routeID string, _ string) error {
|
|
|
|
if routeID != existingRouteID {
|
|
|
|
return status.Errorf(status.NotFound, "Peer with ID %s not found", routeID)
|
2022-09-11 23:16:40 +02:00
|
|
|
}
|
2022-08-20 19:11:54 +02:00
|
|
|
return nil
|
|
|
|
},
|
|
|
|
GetPeerByIPFunc: func(_ string, peerIP string) (*server.Peer, error) {
|
|
|
|
if peerIP != existingPeerID {
|
2022-11-11 20:36:45 +01:00
|
|
|
return nil, status.Errorf(status.NotFound, "Peer with ID %s not found", peerIP)
|
2022-08-20 19:11:54 +02:00
|
|
|
}
|
|
|
|
return &server.Peer{
|
|
|
|
Key: existingPeerKey,
|
|
|
|
IP: netip.MustParseAddr(existingPeerID).AsSlice(),
|
|
|
|
}, nil
|
|
|
|
},
|
|
|
|
UpdateRouteFunc: func(_ string, routeID string, operations []server.RouteUpdateOperation) (*route.Route, error) {
|
|
|
|
routeToUpdate := baseExistingRoute
|
|
|
|
if routeID != routeToUpdate.ID {
|
2022-11-11 20:36:45 +01:00
|
|
|
return nil, status.Errorf(status.NotFound, "route %s no longer exists", routeID)
|
2022-08-20 19:11:54 +02:00
|
|
|
}
|
|
|
|
for _, operation := range operations {
|
|
|
|
switch operation.Type {
|
2022-08-22 14:10:24 +02:00
|
|
|
case server.UpdateRouteNetwork:
|
|
|
|
routeToUpdate.NetworkType, routeToUpdate.Network, _ = route.ParseNetwork(operation.Values[0])
|
2022-08-20 19:11:54 +02:00
|
|
|
case server.UpdateRouteDescription:
|
|
|
|
routeToUpdate.Description = operation.Values[0]
|
2022-08-22 14:10:24 +02:00
|
|
|
case server.UpdateRouteNetworkIdentifier:
|
|
|
|
routeToUpdate.NetID = operation.Values[0]
|
2022-08-20 19:11:54 +02:00
|
|
|
case server.UpdateRoutePeer:
|
|
|
|
routeToUpdate.Peer = operation.Values[0]
|
2023-02-03 10:33:28 +01:00
|
|
|
if routeToUpdate.Peer == notFoundPeerID {
|
|
|
|
return nil, status.Errorf(status.InvalidArgument, "peer with ID %s not found", routeToUpdate.Peer)
|
|
|
|
}
|
2022-08-20 19:11:54 +02:00
|
|
|
case server.UpdateRouteMetric:
|
|
|
|
routeToUpdate.Metric, _ = strconv.Atoi(operation.Values[0])
|
|
|
|
case server.UpdateRouteMasquerade:
|
|
|
|
routeToUpdate.Masquerade, _ = strconv.ParseBool(operation.Values[0])
|
|
|
|
case server.UpdateRouteEnabled:
|
|
|
|
routeToUpdate.Enabled, _ = strconv.ParseBool(operation.Values[0])
|
2022-12-06 10:11:57 +01:00
|
|
|
case server.UpdateRouteGroups:
|
|
|
|
routeToUpdate.Groups = operation.Values
|
2022-08-20 19:11:54 +02:00
|
|
|
default:
|
|
|
|
return nil, fmt.Errorf("no operation")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return routeToUpdate, nil
|
|
|
|
},
|
2022-11-11 20:36:45 +01:00
|
|
|
GetAccountFromTokenFunc: func(_ jwtclaims.AuthorizationClaims) (*server.Account, *server.User, error) {
|
|
|
|
return testingAccount, testingAccount.Users["test_user"], nil
|
2022-08-20 19:11:54 +02:00
|
|
|
},
|
|
|
|
},
|
2023-02-03 21:47:20 +01:00
|
|
|
claimsExtractor: jwtclaims.NewClaimsExtractor(
|
|
|
|
jwtclaims.WithFromRequestContext(func(r *http.Request) jwtclaims.AuthorizationClaims {
|
2022-08-20 19:11:54 +02:00
|
|
|
return jwtclaims.AuthorizationClaims{
|
|
|
|
UserId: "test_user",
|
|
|
|
Domain: "hotmail.com",
|
|
|
|
AccountId: testAccountID,
|
|
|
|
}
|
2023-02-03 21:47:20 +01:00
|
|
|
}),
|
|
|
|
),
|
2022-08-20 19:11:54 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestRoutesHandlers(t *testing.T) {
|
|
|
|
tt := []struct {
|
|
|
|
name string
|
|
|
|
expectedStatus int
|
|
|
|
expectedBody bool
|
|
|
|
expectedRoute *api.Route
|
|
|
|
requestType string
|
|
|
|
requestPath string
|
|
|
|
requestBody io.Reader
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
name: "Get Existing Route",
|
|
|
|
requestType: http.MethodGet,
|
|
|
|
requestPath: "/api/routes/" + existingRouteID,
|
|
|
|
expectedStatus: http.StatusOK,
|
|
|
|
expectedBody: true,
|
2023-02-03 10:33:28 +01:00
|
|
|
expectedRoute: toRouteResponse(baseExistingRoute),
|
2022-08-20 19:11:54 +02:00
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "Get Not Existing Route",
|
|
|
|
requestType: http.MethodGet,
|
2022-09-11 23:16:40 +02:00
|
|
|
requestPath: "/api/routes/" + notFoundRouteID,
|
2022-08-20 19:11:54 +02:00
|
|
|
expectedStatus: http.StatusNotFound,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "Delete Existing Route",
|
|
|
|
requestType: http.MethodDelete,
|
|
|
|
requestPath: "/api/routes/" + existingRouteID,
|
|
|
|
expectedStatus: http.StatusOK,
|
|
|
|
expectedBody: false,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "Delete Not Existing Route",
|
|
|
|
requestType: http.MethodDelete,
|
2022-09-11 23:16:40 +02:00
|
|
|
requestPath: "/api/routes/" + notFoundRouteID,
|
2022-08-20 19:11:54 +02:00
|
|
|
expectedStatus: http.StatusNotFound,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "POST OK",
|
|
|
|
requestType: http.MethodPost,
|
|
|
|
requestPath: "/api/routes",
|
|
|
|
requestBody: bytes.NewBuffer(
|
2022-12-06 10:11:57 +01:00
|
|
|
[]byte(fmt.Sprintf("{\"Description\":\"Post\",\"Network\":\"192.168.0.0/16\",\"network_id\":\"awesomeNet\",\"Peer\":\"%s\",\"groups\":[\"%s\"]}", existingPeerID, existingGroupID))),
|
2022-08-20 19:11:54 +02:00
|
|
|
expectedStatus: http.StatusOK,
|
|
|
|
expectedBody: true,
|
|
|
|
expectedRoute: &api.Route{
|
|
|
|
Id: existingRouteID,
|
|
|
|
Description: "Post",
|
2022-08-22 14:10:24 +02:00
|
|
|
NetworkId: "awesomeNet",
|
|
|
|
Network: "192.168.0.0/16",
|
2022-08-20 19:11:54 +02:00
|
|
|
Peer: existingPeerID,
|
2022-08-22 14:10:24 +02:00
|
|
|
NetworkType: route.IPv4NetworkString,
|
2022-08-20 19:11:54 +02:00
|
|
|
Masquerade: false,
|
|
|
|
Enabled: false,
|
2022-12-06 10:11:57 +01:00
|
|
|
Groups: []string{existingGroupID},
|
2022-08-20 19:11:54 +02:00
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "POST Not Found Peer",
|
|
|
|
requestType: http.MethodPost,
|
|
|
|
requestPath: "/api/routes",
|
2022-12-06 10:11:57 +01:00
|
|
|
requestBody: bytes.NewBufferString(fmt.Sprintf("{\"Description\":\"Post\",\"Network\":\"192.168.0.0/16\",\"network_id\":\"awesomeNet\",\"Peer\":\"%s\",\"groups\":[\"%s\"]}", notFoundPeerID, existingGroupID)),
|
2023-02-03 10:33:28 +01:00
|
|
|
expectedStatus: http.StatusUnprocessableEntity,
|
2022-08-20 19:11:54 +02:00
|
|
|
expectedBody: false,
|
|
|
|
},
|
|
|
|
{
|
2022-11-11 20:36:45 +01:00
|
|
|
name: "POST Invalid Network Identifier",
|
2022-08-20 19:11:54 +02:00
|
|
|
requestType: http.MethodPost,
|
|
|
|
requestPath: "/api/routes",
|
2022-12-06 10:11:57 +01:00
|
|
|
requestBody: bytes.NewBufferString(fmt.Sprintf("{\"Description\":\"Post\",\"Network\":\"192.168.0.0/16\",\"network_id\":\"12345678901234567890qwertyuiopqwertyuiop1\",\"Peer\":\"%s\",\"groups\":[\"%s\"]}", existingPeerID, existingGroupID)),
|
2022-11-11 20:36:45 +01:00
|
|
|
expectedStatus: http.StatusUnprocessableEntity,
|
2022-08-22 14:10:24 +02:00
|
|
|
expectedBody: false,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "POST Invalid Network",
|
|
|
|
requestType: http.MethodPost,
|
|
|
|
requestPath: "/api/routes",
|
2022-12-06 10:11:57 +01:00
|
|
|
requestBody: bytes.NewBufferString(fmt.Sprintf("{\"Description\":\"Post\",\"Network\":\"192.168.0.0/34\",\"network_id\":\"awesomeNet\",\"Peer\":\"%s\",\"groups\":[\"%s\"]}", existingPeerID, existingGroupID)),
|
2022-11-11 20:36:45 +01:00
|
|
|
expectedStatus: http.StatusUnprocessableEntity,
|
2022-08-20 19:11:54 +02:00
|
|
|
expectedBody: false,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "PUT OK",
|
|
|
|
requestType: http.MethodPut,
|
|
|
|
requestPath: "/api/routes/" + existingRouteID,
|
2022-12-06 10:11:57 +01:00
|
|
|
requestBody: bytes.NewBufferString(fmt.Sprintf("{\"Description\":\"Post\",\"Network\":\"192.168.0.0/16\",\"network_id\":\"awesomeNet\",\"Peer\":\"%s\",\"groups\":[\"%s\"]}", existingPeerID, existingGroupID)),
|
2022-08-20 19:11:54 +02:00
|
|
|
expectedStatus: http.StatusOK,
|
|
|
|
expectedBody: true,
|
|
|
|
expectedRoute: &api.Route{
|
|
|
|
Id: existingRouteID,
|
|
|
|
Description: "Post",
|
2022-08-22 14:10:24 +02:00
|
|
|
NetworkId: "awesomeNet",
|
|
|
|
Network: "192.168.0.0/16",
|
2022-08-20 19:11:54 +02:00
|
|
|
Peer: existingPeerID,
|
2022-08-22 14:10:24 +02:00
|
|
|
NetworkType: route.IPv4NetworkString,
|
2022-08-20 19:11:54 +02:00
|
|
|
Masquerade: false,
|
|
|
|
Enabled: false,
|
2022-12-06 10:11:57 +01:00
|
|
|
Groups: []string{existingGroupID},
|
2022-08-20 19:11:54 +02:00
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "PUT Not Found Route",
|
|
|
|
requestType: http.MethodPut,
|
|
|
|
requestPath: "/api/routes/" + notFoundRouteID,
|
2022-12-06 10:11:57 +01:00
|
|
|
requestBody: bytes.NewBufferString(fmt.Sprintf("{\"Description\":\"Post\",\"Network\":\"192.168.0.0/16\",\"network_id\":\"awesomeNet\",\"Peer\":\"%s\",\"groups\":[\"%s\"]}", existingPeerID, existingGroupID)),
|
2022-08-20 19:11:54 +02:00
|
|
|
expectedStatus: http.StatusNotFound,
|
|
|
|
expectedBody: false,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "PUT Not Found Peer",
|
|
|
|
requestType: http.MethodPut,
|
|
|
|
requestPath: "/api/routes/" + existingRouteID,
|
2022-12-06 10:11:57 +01:00
|
|
|
requestBody: bytes.NewBufferString(fmt.Sprintf("{\"Description\":\"Post\",\"Network\":\"192.168.0.0/16\",\"network_id\":\"awesomeNet\",\"Peer\":\"%s\",\"groups\":[\"%s\"]}", notFoundPeerID, existingGroupID)),
|
2023-02-03 10:33:28 +01:00
|
|
|
expectedStatus: http.StatusUnprocessableEntity,
|
2022-08-20 19:11:54 +02:00
|
|
|
expectedBody: false,
|
|
|
|
},
|
|
|
|
{
|
2022-08-22 14:10:24 +02:00
|
|
|
name: "PUT Invalid Network Identifier",
|
|
|
|
requestType: http.MethodPut,
|
|
|
|
requestPath: "/api/routes/" + existingRouteID,
|
2022-12-06 10:11:57 +01:00
|
|
|
requestBody: bytes.NewBufferString(fmt.Sprintf("{\"Description\":\"Post\",\"Network\":\"192.168.0.0/16\",\"network_id\":\"12345678901234567890qwertyuiopqwertyuiop1\",\"Peer\":\"%s\",\"groups\":[\"%s\"]}", existingPeerID, existingGroupID)),
|
2022-11-11 20:36:45 +01:00
|
|
|
expectedStatus: http.StatusUnprocessableEntity,
|
2022-08-22 14:10:24 +02:00
|
|
|
expectedBody: false,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "PUT Invalid Network",
|
2022-08-20 19:11:54 +02:00
|
|
|
requestType: http.MethodPut,
|
|
|
|
requestPath: "/api/routes/" + existingRouteID,
|
2022-12-06 10:11:57 +01:00
|
|
|
requestBody: bytes.NewBufferString(fmt.Sprintf("{\"Description\":\"Post\",\"Network\":\"192.168.0.0/34\",\"network_id\":\"awesomeNet\",\"Peer\":\"%s\",\"groups\":[\"%s\"]}", existingPeerID, existingGroupID)),
|
2022-11-11 20:36:45 +01:00
|
|
|
expectedStatus: http.StatusUnprocessableEntity,
|
2022-08-20 19:11:54 +02:00
|
|
|
expectedBody: false,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
p := initRoutesTestData()
|
|
|
|
|
|
|
|
for _, tc := range tt {
|
|
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
|
|
recorder := httptest.NewRecorder()
|
|
|
|
req := httptest.NewRequest(tc.requestType, tc.requestPath, tc.requestBody)
|
|
|
|
|
|
|
|
router := mux.NewRouter()
|
2023-05-03 00:15:25 +02:00
|
|
|
router.HandleFunc("/api/routes/{routeId}", p.GetRoute).Methods("GET")
|
|
|
|
router.HandleFunc("/api/routes/{routeId}", p.DeleteRoute).Methods("DELETE")
|
2023-02-28 15:01:24 +01:00
|
|
|
router.HandleFunc("/api/routes", p.CreateRoute).Methods("POST")
|
2023-05-03 00:15:25 +02:00
|
|
|
router.HandleFunc("/api/routes/{routeId}", p.UpdateRoute).Methods("PUT")
|
2022-08-20 19:11:54 +02:00
|
|
|
router.ServeHTTP(recorder, req)
|
|
|
|
|
|
|
|
res := recorder.Result()
|
|
|
|
defer res.Body.Close()
|
|
|
|
|
|
|
|
content, err := io.ReadAll(res.Body)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("I don't know what I expected; %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if status := recorder.Code; status != tc.expectedStatus {
|
|
|
|
t.Errorf("handler returned wrong status code: got %v want %v, content: %s",
|
|
|
|
status, tc.expectedStatus, string(content))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if !tc.expectedBody {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
got := &api.Route{}
|
|
|
|
if err = json.Unmarshal(content, &got); err != nil {
|
|
|
|
t.Fatalf("Sent content is not in correct json format; %v", err)
|
|
|
|
}
|
|
|
|
assert.Equal(t, got, tc.expectedRoute)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|