mirror of
https://github.com/netbirdio/netbird.git
synced 2024-11-22 16:13:31 +01:00
Add routing Rest API support (#428)
Routing API will allow us to list, create, update, and delete routes.
This commit is contained in:
parent
4b34a6d6df
commit
000ea72aec
@ -14,6 +14,8 @@ tags:
|
|||||||
description: Interact with and view information about groups.
|
description: Interact with and view information about groups.
|
||||||
- name: Rules
|
- name: Rules
|
||||||
description: Interact with and view information about rules.
|
description: Interact with and view information about rules.
|
||||||
|
- name: Routes
|
||||||
|
description: Interact with and view information about routes.
|
||||||
components:
|
components:
|
||||||
schemas:
|
schemas:
|
||||||
User:
|
User:
|
||||||
@ -191,17 +193,13 @@ components:
|
|||||||
$ref: '#/components/schemas/PeerMinimum'
|
$ref: '#/components/schemas/PeerMinimum'
|
||||||
required:
|
required:
|
||||||
- peers
|
- peers
|
||||||
GroupPatchOperation:
|
PatchMinimum:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
op:
|
op:
|
||||||
description: Patch operation type
|
description: Patch operation type
|
||||||
type: string
|
type: string
|
||||||
enum: [ "replace","add","remove" ]
|
enum: [ "replace","add","remove" ]
|
||||||
path:
|
|
||||||
description: Group field to update in form /<field>
|
|
||||||
type: string
|
|
||||||
enum: [ "name","peers" ]
|
|
||||||
value:
|
value:
|
||||||
description: Values to be applied
|
description: Values to be applied
|
||||||
type: array
|
type: array
|
||||||
@ -209,8 +207,19 @@ components:
|
|||||||
type: string
|
type: string
|
||||||
required:
|
required:
|
||||||
- op
|
- op
|
||||||
- path
|
|
||||||
- value
|
- value
|
||||||
|
GroupPatchOperation:
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/components/schemas/PatchMinimum'
|
||||||
|
- type: object
|
||||||
|
properties:
|
||||||
|
path:
|
||||||
|
description: Group field to update in form /<field>
|
||||||
|
type: string
|
||||||
|
enum: [ "name","peers" ]
|
||||||
|
required:
|
||||||
|
- path
|
||||||
|
|
||||||
RuleMinimum:
|
RuleMinimum:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
@ -257,25 +266,73 @@ components:
|
|||||||
- sources
|
- sources
|
||||||
- destinations
|
- destinations
|
||||||
RulePatchOperation:
|
RulePatchOperation:
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/components/schemas/PatchMinimum'
|
||||||
|
- type: object
|
||||||
|
properties:
|
||||||
|
path:
|
||||||
|
description: Rule field to update in form /<field>
|
||||||
|
type: string
|
||||||
|
enum: [ "name","description","disabled","flow","sources","destinations" ]
|
||||||
|
required:
|
||||||
|
- path
|
||||||
|
RouteRequest:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
op:
|
description:
|
||||||
description: Patch operation type
|
description: Route description
|
||||||
type: string
|
type: string
|
||||||
enum: [ "replace","add","remove" ]
|
enabled:
|
||||||
path:
|
description: Route status
|
||||||
description: Rule field to update in form /<field>
|
type: boolean
|
||||||
|
peer:
|
||||||
|
description: Peer Identifier associated with route
|
||||||
type: string
|
type: string
|
||||||
enum: [ "name","description","disabled","flow","sources","destinations" ]
|
prefix:
|
||||||
value:
|
description: Prefix or network range in CIDR format
|
||||||
description: Values to be applied
|
type: string
|
||||||
type: array
|
metric:
|
||||||
items:
|
description: Route metric number. Lowest number has higher priority
|
||||||
type: string
|
type: integer
|
||||||
|
maximum: 9999
|
||||||
|
minimum: 1
|
||||||
|
masquerade:
|
||||||
|
description: Indicate if peer should masquerade traffic to this route's prefix
|
||||||
|
type: boolean
|
||||||
required:
|
required:
|
||||||
- op
|
- id
|
||||||
- path
|
- description
|
||||||
- value
|
- enabled
|
||||||
|
- peer
|
||||||
|
- prefix
|
||||||
|
- metric
|
||||||
|
- masquerade
|
||||||
|
Route:
|
||||||
|
allOf:
|
||||||
|
- type: object
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
description: Route Id
|
||||||
|
type: string
|
||||||
|
prefix_type:
|
||||||
|
description: Prefix type indicating if it is IPv4 or IPv6
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- id
|
||||||
|
- prefix_type
|
||||||
|
- $ref: '#/components/schemas/RouteRequest'
|
||||||
|
RoutePatchOperation:
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/components/schemas/PatchMinimum'
|
||||||
|
- type: object
|
||||||
|
properties:
|
||||||
|
path:
|
||||||
|
description: Route field to update in form /<field>
|
||||||
|
type: string
|
||||||
|
enum: [ "prefix","description","enabled","peer","metric","masquerade" ]
|
||||||
|
required:
|
||||||
|
- path
|
||||||
|
|
||||||
responses:
|
responses:
|
||||||
not_found:
|
not_found:
|
||||||
description: Resource not found
|
description: Resource not found
|
||||||
@ -947,3 +1004,174 @@ paths:
|
|||||||
"$ref": "#/components/responses/forbidden"
|
"$ref": "#/components/responses/forbidden"
|
||||||
'500':
|
'500':
|
||||||
"$ref": "#/components/responses/internal_error"
|
"$ref": "#/components/responses/internal_error"
|
||||||
|
|
||||||
|
/api/routes:
|
||||||
|
get:
|
||||||
|
summary: Returns a list of all routes
|
||||||
|
tags: [ Routes ]
|
||||||
|
security:
|
||||||
|
- BearerAuth: [ ]
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: A JSON Array of Routes
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/Route'
|
||||||
|
'400':
|
||||||
|
"$ref": "#/components/responses/bad_request"
|
||||||
|
'401':
|
||||||
|
"$ref": "#/components/responses/requires_authentication"
|
||||||
|
'403':
|
||||||
|
"$ref": "#/components/responses/forbidden"
|
||||||
|
'500':
|
||||||
|
"$ref": "#/components/responses/internal_error"
|
||||||
|
post:
|
||||||
|
summary: Creates a Route
|
||||||
|
tags: [ Routes ]
|
||||||
|
security:
|
||||||
|
- BearerAuth: [ ]
|
||||||
|
requestBody:
|
||||||
|
description: New Routes request
|
||||||
|
content:
|
||||||
|
'application/json':
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/RouteRequest'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: A Route Object
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/Route'
|
||||||
|
'400':
|
||||||
|
"$ref": "#/components/responses/bad_request"
|
||||||
|
'401':
|
||||||
|
"$ref": "#/components/responses/requires_authentication"
|
||||||
|
'403':
|
||||||
|
"$ref": "#/components/responses/forbidden"
|
||||||
|
'500':
|
||||||
|
"$ref": "#/components/responses/internal_error"
|
||||||
|
|
||||||
|
/api/routes/{id}:
|
||||||
|
get:
|
||||||
|
summary: Get information about a Routes
|
||||||
|
tags: [ Routes ]
|
||||||
|
security:
|
||||||
|
- BearerAuth: [ ]
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
description: The Route ID
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: A Route object
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/Route'
|
||||||
|
'400':
|
||||||
|
"$ref": "#/components/responses/bad_request"
|
||||||
|
'401':
|
||||||
|
"$ref": "#/components/responses/requires_authentication"
|
||||||
|
'403':
|
||||||
|
"$ref": "#/components/responses/forbidden"
|
||||||
|
'500':
|
||||||
|
"$ref": "#/components/responses/internal_error"
|
||||||
|
put:
|
||||||
|
summary: Update/Replace a Route
|
||||||
|
tags: [ Routes ]
|
||||||
|
security:
|
||||||
|
- BearerAuth: [ ]
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
description: The Route ID
|
||||||
|
requestBody:
|
||||||
|
description: Update Route request
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/RouteRequest'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: A Route object
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/Route'
|
||||||
|
'400':
|
||||||
|
"$ref": "#/components/responses/bad_request"
|
||||||
|
'401':
|
||||||
|
"$ref": "#/components/responses/requires_authentication"
|
||||||
|
'403':
|
||||||
|
"$ref": "#/components/responses/forbidden"
|
||||||
|
'500':
|
||||||
|
"$ref": "#/components/responses/internal_error"
|
||||||
|
patch:
|
||||||
|
summary: Update information about a Route
|
||||||
|
tags: [ Routes ]
|
||||||
|
security:
|
||||||
|
- BearerAuth: [ ]
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
description: The Route ID
|
||||||
|
requestBody:
|
||||||
|
description: Update Route request using a list of json patch objects
|
||||||
|
content:
|
||||||
|
'application/json':
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/RoutePatchOperation'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: A Route object
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/Route'
|
||||||
|
'400':
|
||||||
|
"$ref": "#/components/responses/bad_request"
|
||||||
|
'401':
|
||||||
|
"$ref": "#/components/responses/requires_authentication"
|
||||||
|
'403':
|
||||||
|
"$ref": "#/components/responses/forbidden"
|
||||||
|
'500':
|
||||||
|
"$ref": "#/components/responses/internal_error"
|
||||||
|
delete:
|
||||||
|
summary: Delete a Route
|
||||||
|
tags: [ Routes ]
|
||||||
|
security:
|
||||||
|
- BearerAuth: [ ]
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
description: The Route ID
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Delete status code
|
||||||
|
content: { }
|
||||||
|
'400':
|
||||||
|
"$ref": "#/components/responses/bad_request"
|
||||||
|
'401':
|
||||||
|
"$ref": "#/components/responses/requires_authentication"
|
||||||
|
'403':
|
||||||
|
"$ref": "#/components/responses/forbidden"
|
||||||
|
'500':
|
||||||
|
"$ref": "#/components/responses/internal_error"
|
@ -24,6 +24,30 @@ const (
|
|||||||
GroupPatchOperationPathPeers GroupPatchOperationPath = "peers"
|
GroupPatchOperationPathPeers GroupPatchOperationPath = "peers"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Defines values for PatchMinimumOp.
|
||||||
|
const (
|
||||||
|
PatchMinimumOpAdd PatchMinimumOp = "add"
|
||||||
|
PatchMinimumOpRemove PatchMinimumOp = "remove"
|
||||||
|
PatchMinimumOpReplace PatchMinimumOp = "replace"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Defines values for RoutePatchOperationOp.
|
||||||
|
const (
|
||||||
|
RoutePatchOperationOpAdd RoutePatchOperationOp = "add"
|
||||||
|
RoutePatchOperationOpRemove RoutePatchOperationOp = "remove"
|
||||||
|
RoutePatchOperationOpReplace RoutePatchOperationOp = "replace"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Defines values for RoutePatchOperationPath.
|
||||||
|
const (
|
||||||
|
RoutePatchOperationPathDescription RoutePatchOperationPath = "description"
|
||||||
|
RoutePatchOperationPathEnabled RoutePatchOperationPath = "enabled"
|
||||||
|
RoutePatchOperationPathMasquerade RoutePatchOperationPath = "masquerade"
|
||||||
|
RoutePatchOperationPathMetric RoutePatchOperationPath = "metric"
|
||||||
|
RoutePatchOperationPathPeer RoutePatchOperationPath = "peer"
|
||||||
|
RoutePatchOperationPathPrefix RoutePatchOperationPath = "prefix"
|
||||||
|
)
|
||||||
|
|
||||||
// Defines values for RulePatchOperationOp.
|
// Defines values for RulePatchOperationOp.
|
||||||
const (
|
const (
|
||||||
RulePatchOperationOpAdd RulePatchOperationOp = "add"
|
RulePatchOperationOpAdd RulePatchOperationOp = "add"
|
||||||
@ -86,6 +110,18 @@ type GroupPatchOperationOp string
|
|||||||
// Group field to update in form /<field>
|
// Group field to update in form /<field>
|
||||||
type GroupPatchOperationPath string
|
type GroupPatchOperationPath string
|
||||||
|
|
||||||
|
// PatchMinimum defines model for PatchMinimum.
|
||||||
|
type PatchMinimum struct {
|
||||||
|
// Patch operation type
|
||||||
|
Op PatchMinimumOp `json:"op"`
|
||||||
|
|
||||||
|
// Values to be applied
|
||||||
|
Value []string `json:"value"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Patch operation type
|
||||||
|
type PatchMinimumOp string
|
||||||
|
|
||||||
// Peer defines model for Peer.
|
// Peer defines model for Peer.
|
||||||
type Peer struct {
|
type Peer struct {
|
||||||
// Provides information of who activated the Peer. User or Setup Key
|
// Provides information of who activated the Peer. User or Setup Key
|
||||||
@ -131,6 +167,72 @@ type PeerMinimum struct {
|
|||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Route defines model for Route.
|
||||||
|
type Route struct {
|
||||||
|
// Route description
|
||||||
|
Description string `json:"description"`
|
||||||
|
|
||||||
|
// Route status
|
||||||
|
Enabled bool `json:"enabled"`
|
||||||
|
|
||||||
|
// Route Id
|
||||||
|
Id string `json:"id"`
|
||||||
|
|
||||||
|
// Indicate if peer should masquerade traffic to this route's prefix
|
||||||
|
Masquerade bool `json:"masquerade"`
|
||||||
|
|
||||||
|
// Route metric number. Lowest number has higher priority
|
||||||
|
Metric int `json:"metric"`
|
||||||
|
|
||||||
|
// Peer Identifier associated with route
|
||||||
|
Peer string `json:"peer"`
|
||||||
|
|
||||||
|
// Prefix or network range in CIDR format
|
||||||
|
Prefix string `json:"prefix"`
|
||||||
|
|
||||||
|
// Prefix type indicating if it is IPv4 or IPv6
|
||||||
|
PrefixType string `json:"prefix_type"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// RoutePatchOperation defines model for RoutePatchOperation.
|
||||||
|
type RoutePatchOperation struct {
|
||||||
|
// Patch operation type
|
||||||
|
Op RoutePatchOperationOp `json:"op"`
|
||||||
|
|
||||||
|
// Route field to update in form /<field>
|
||||||
|
Path RoutePatchOperationPath `json:"path"`
|
||||||
|
|
||||||
|
// Values to be applied
|
||||||
|
Value []string `json:"value"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Patch operation type
|
||||||
|
type RoutePatchOperationOp string
|
||||||
|
|
||||||
|
// Route field to update in form /<field>
|
||||||
|
type RoutePatchOperationPath string
|
||||||
|
|
||||||
|
// RouteRequest defines model for RouteRequest.
|
||||||
|
type RouteRequest struct {
|
||||||
|
// Route description
|
||||||
|
Description string `json:"description"`
|
||||||
|
|
||||||
|
// Route status
|
||||||
|
Enabled bool `json:"enabled"`
|
||||||
|
|
||||||
|
// Indicate if peer should masquerade traffic to this route's prefix
|
||||||
|
Masquerade bool `json:"masquerade"`
|
||||||
|
|
||||||
|
// Route metric number. Lowest number has higher priority
|
||||||
|
Metric int `json:"metric"`
|
||||||
|
|
||||||
|
// Peer Identifier associated with route
|
||||||
|
Peer string `json:"peer"`
|
||||||
|
|
||||||
|
// Prefix or network range in CIDR format
|
||||||
|
Prefix string `json:"prefix"`
|
||||||
|
}
|
||||||
|
|
||||||
// Rule defines model for Rule.
|
// Rule defines model for Rule.
|
||||||
type Rule struct {
|
type Rule struct {
|
||||||
// Rule friendly description
|
// Rule friendly description
|
||||||
@ -272,6 +374,15 @@ type PutApiPeersIdJSONBody struct {
|
|||||||
SshEnabled bool `json:"ssh_enabled"`
|
SshEnabled bool `json:"ssh_enabled"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PostApiRoutesJSONBody defines parameters for PostApiRoutes.
|
||||||
|
type PostApiRoutesJSONBody = RouteRequest
|
||||||
|
|
||||||
|
// PatchApiRoutesIdJSONBody defines parameters for PatchApiRoutesId.
|
||||||
|
type PatchApiRoutesIdJSONBody = []RoutePatchOperation
|
||||||
|
|
||||||
|
// PutApiRoutesIdJSONBody defines parameters for PutApiRoutesId.
|
||||||
|
type PutApiRoutesIdJSONBody = RouteRequest
|
||||||
|
|
||||||
// PostApiRulesJSONBody defines parameters for PostApiRules.
|
// PostApiRulesJSONBody defines parameters for PostApiRules.
|
||||||
type PostApiRulesJSONBody struct {
|
type PostApiRulesJSONBody struct {
|
||||||
// Rule friendly description
|
// Rule friendly description
|
||||||
@ -327,6 +438,15 @@ type PutApiGroupsIdJSONRequestBody PutApiGroupsIdJSONBody
|
|||||||
// PutApiPeersIdJSONRequestBody defines body for PutApiPeersId for application/json ContentType.
|
// PutApiPeersIdJSONRequestBody defines body for PutApiPeersId for application/json ContentType.
|
||||||
type PutApiPeersIdJSONRequestBody PutApiPeersIdJSONBody
|
type PutApiPeersIdJSONRequestBody PutApiPeersIdJSONBody
|
||||||
|
|
||||||
|
// PostApiRoutesJSONRequestBody defines body for PostApiRoutes for application/json ContentType.
|
||||||
|
type PostApiRoutesJSONRequestBody = PostApiRoutesJSONBody
|
||||||
|
|
||||||
|
// PatchApiRoutesIdJSONRequestBody defines body for PatchApiRoutesId for application/json ContentType.
|
||||||
|
type PatchApiRoutesIdJSONRequestBody = PatchApiRoutesIdJSONBody
|
||||||
|
|
||||||
|
// PutApiRoutesIdJSONRequestBody defines body for PutApiRoutesId for application/json ContentType.
|
||||||
|
type PutApiRoutesIdJSONRequestBody = PutApiRoutesIdJSONBody
|
||||||
|
|
||||||
// PostApiRulesJSONRequestBody defines body for PostApiRules for application/json ContentType.
|
// PostApiRulesJSONRequestBody defines body for PostApiRules for application/json ContentType.
|
||||||
type PostApiRulesJSONRequestBody PostApiRulesJSONBody
|
type PostApiRulesJSONRequestBody PostApiRulesJSONBody
|
||||||
|
|
||||||
|
@ -33,6 +33,7 @@ func APIHandler(accountManager s.AccountManager, authIssuer string, authAudience
|
|||||||
peersHandler := NewPeers(accountManager, authAudience)
|
peersHandler := NewPeers(accountManager, authAudience)
|
||||||
keysHandler := NewSetupKeysHandler(accountManager, authAudience)
|
keysHandler := NewSetupKeysHandler(accountManager, authAudience)
|
||||||
userHandler := NewUserHandler(accountManager, authAudience)
|
userHandler := NewUserHandler(accountManager, authAudience)
|
||||||
|
routesHandler := NewRoutes(accountManager, authAudience)
|
||||||
|
|
||||||
apiHandler.HandleFunc("/api/peers", peersHandler.GetPeers).Methods("GET", "OPTIONS")
|
apiHandler.HandleFunc("/api/peers", peersHandler.GetPeers).Methods("GET", "OPTIONS")
|
||||||
apiHandler.HandleFunc("/api/peers/{id}", peersHandler.HandlePeer).
|
apiHandler.HandleFunc("/api/peers/{id}", peersHandler.HandlePeer).
|
||||||
@ -59,6 +60,13 @@ func APIHandler(accountManager s.AccountManager, authIssuer string, authAudience
|
|||||||
apiHandler.HandleFunc("/api/groups/{id}", groupsHandler.GetGroupHandler).Methods("GET", "OPTIONS")
|
apiHandler.HandleFunc("/api/groups/{id}", groupsHandler.GetGroupHandler).Methods("GET", "OPTIONS")
|
||||||
apiHandler.HandleFunc("/api/groups/{id}", groupsHandler.DeleteGroupHandler).Methods("DELETE", "OPTIONS")
|
apiHandler.HandleFunc("/api/groups/{id}", groupsHandler.DeleteGroupHandler).Methods("DELETE", "OPTIONS")
|
||||||
|
|
||||||
|
apiHandler.HandleFunc("/api/routes", routesHandler.GetAllRoutesHandler).Methods("GET", "OPTIONS")
|
||||||
|
apiHandler.HandleFunc("/api/routes", routesHandler.CreateRouteHandler).Methods("POST", "OPTIONS")
|
||||||
|
apiHandler.HandleFunc("/api/routes/{id}", routesHandler.UpdateRouteHandler).Methods("PUT", "OPTIONS")
|
||||||
|
apiHandler.HandleFunc("/api/routes/{id}", routesHandler.PatchRouteHandler).Methods("PATCH", "OPTIONS")
|
||||||
|
apiHandler.HandleFunc("/api/routes/{id}", routesHandler.GetRouteHandler).Methods("GET", "OPTIONS")
|
||||||
|
apiHandler.HandleFunc("/api/routes/{id}", routesHandler.DeleteRouteHandler).Methods("DELETE", "OPTIONS")
|
||||||
|
|
||||||
return apiHandler, nil
|
return apiHandler, nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
380
management/server/http/routes.go
Normal file
380
management/server/http/routes.go
Normal file
@ -0,0 +1,380 @@
|
|||||||
|
package http
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/netbirdio/netbird/management/server"
|
||||||
|
"github.com/netbirdio/netbird/management/server/http/api"
|
||||||
|
"github.com/netbirdio/netbird/management/server/jwtclaims"
|
||||||
|
"github.com/netbirdio/netbird/route"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"google.golang.org/grpc/codes"
|
||||||
|
"google.golang.org/grpc/status"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Routes is the routes handler of the account
|
||||||
|
type Routes struct {
|
||||||
|
jwtExtractor jwtclaims.ClaimsExtractor
|
||||||
|
accountManager server.AccountManager
|
||||||
|
authAudience string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRoutes returns a new instance of Routes handler
|
||||||
|
func NewRoutes(accountManager server.AccountManager, authAudience string) *Routes {
|
||||||
|
return &Routes{
|
||||||
|
accountManager: accountManager,
|
||||||
|
authAudience: authAudience,
|
||||||
|
jwtExtractor: *jwtclaims.NewClaimsExtractor(nil),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAllRoutesHandler returns the list of routes for the account
|
||||||
|
func (h *Routes) GetAllRoutesHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
account, err := getJWTAccount(h.accountManager, h.jwtExtractor, h.authAudience, r)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
routes, err := h.accountManager.ListRoutes(account.Id)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
apiRoutes := make([]*api.Route, 0)
|
||||||
|
for _, r := range routes {
|
||||||
|
apiRoutes = append(apiRoutes, toRouteResponse(account, r))
|
||||||
|
}
|
||||||
|
|
||||||
|
writeJSONObject(w, apiRoutes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateRouteHandler handles route creation request
|
||||||
|
func (h *Routes) CreateRouteHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
account, err := getJWTAccount(h.accountManager, h.jwtExtractor, h.authAudience, r)
|
||||||
|
if err != nil {
|
||||||
|
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var req api.PostApiRoutesJSONRequestBody
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
peerKey := req.Peer
|
||||||
|
if req.Peer != "" {
|
||||||
|
peer, err := h.accountManager.GetPeerByIP(account.Id, req.Peer)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
http.Redirect(w, r, "/", http.StatusUnprocessableEntity)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
peerKey = peer.Key
|
||||||
|
}
|
||||||
|
|
||||||
|
_, newPrefix, err := route.ParsePrefix(req.Prefix)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, fmt.Sprintf("couldn't parse update prefix %s", req.Prefix), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
newRoute, err := h.accountManager.CreateRoute(account.Id, newPrefix.String(), peerKey, req.Description, req.Masquerade, req.Metric, req.Enabled)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := toRouteResponse(account, newRoute)
|
||||||
|
|
||||||
|
writeJSONObject(w, &resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateRouteHandler handles update to a route identified by a given ID
|
||||||
|
func (h *Routes) UpdateRouteHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
account, err := getJWTAccount(h.accountManager, h.jwtExtractor, h.authAudience, r)
|
||||||
|
if err != nil {
|
||||||
|
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
routeID := vars["id"]
|
||||||
|
if len(routeID) == 0 {
|
||||||
|
http.Error(w, "invalid route Id", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = h.accountManager.GetRoute(account.Id, routeID)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, fmt.Sprintf("couldn't find route for ID %s", routeID), http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var req api.PutApiRoutesIdJSONBody
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
prefixType, newPrefix, err := route.ParsePrefix(req.Prefix)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, fmt.Sprintf("couldn't parse update prefix %s for route ID %s", req.Prefix, routeID), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
peerKey := req.Peer
|
||||||
|
if req.Peer != "" {
|
||||||
|
peer, err := h.accountManager.GetPeerByIP(account.Id, req.Peer)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
http.Redirect(w, r, "/", http.StatusUnprocessableEntity)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
peerKey = peer.Key
|
||||||
|
}
|
||||||
|
|
||||||
|
newRoute := &route.Route{
|
||||||
|
ID: routeID,
|
||||||
|
Prefix: newPrefix,
|
||||||
|
PrefixType: prefixType,
|
||||||
|
Masquerade: req.Masquerade,
|
||||||
|
Peer: peerKey,
|
||||||
|
Metric: req.Metric,
|
||||||
|
Description: req.Description,
|
||||||
|
Enabled: req.Enabled,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = h.accountManager.SaveRoute(account.Id, newRoute)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed updating route \"%s\" under account %s %v", routeID, account.Id, err)
|
||||||
|
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := toRouteResponse(account, newRoute)
|
||||||
|
|
||||||
|
writeJSONObject(w, &resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PatchRouteHandler handles patch updates to a route identified by a given ID
|
||||||
|
func (h *Routes) PatchRouteHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
account, err := getJWTAccount(h.accountManager, h.jwtExtractor, h.authAudience, r)
|
||||||
|
if err != nil {
|
||||||
|
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
routeID := vars["id"]
|
||||||
|
if len(routeID) == 0 {
|
||||||
|
http.Error(w, "invalid route ID", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = h.accountManager.GetRoute(account.Id, routeID)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
http.Error(w, fmt.Sprintf("couldn't find route ID %s", routeID), http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var req api.PatchApiRoutesIdJSONRequestBody
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(req) == 0 {
|
||||||
|
http.Error(w, "no patch instruction received", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var operations []server.RouteUpdateOperation
|
||||||
|
|
||||||
|
for _, patch := range req {
|
||||||
|
switch patch.Path {
|
||||||
|
case api.RoutePatchOperationPathPrefix:
|
||||||
|
if patch.Op != api.RoutePatchOperationOpReplace {
|
||||||
|
http.Error(w, fmt.Sprintf("Prefix field only accepts replace operation, got %s", patch.Op),
|
||||||
|
http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
operations = append(operations, server.RouteUpdateOperation{
|
||||||
|
Type: server.UpdateRoutePrefix,
|
||||||
|
Values: patch.Value,
|
||||||
|
})
|
||||||
|
case api.RoutePatchOperationPathDescription:
|
||||||
|
if patch.Op != api.RoutePatchOperationOpReplace {
|
||||||
|
http.Error(w, fmt.Sprintf("Description field only accepts replace operation, got %s", patch.Op),
|
||||||
|
http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
operations = append(operations, server.RouteUpdateOperation{
|
||||||
|
Type: server.UpdateRouteDescription,
|
||||||
|
Values: patch.Value,
|
||||||
|
})
|
||||||
|
case api.RoutePatchOperationPathPeer:
|
||||||
|
if patch.Op != api.RoutePatchOperationOpReplace {
|
||||||
|
http.Error(w, fmt.Sprintf("Peer field only accepts replace operation, got %s", patch.Op),
|
||||||
|
http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(patch.Value) > 1 {
|
||||||
|
http.Error(w, fmt.Sprintf("Value field only accepts 1 value, got %d", len(patch.Value)),
|
||||||
|
http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
peerValue := patch.Value
|
||||||
|
if patch.Value[0] != "" {
|
||||||
|
peer, err := h.accountManager.GetPeerByIP(account.Id, patch.Value[0])
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
http.Redirect(w, r, "/", http.StatusUnprocessableEntity)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
peerValue = []string{peer.Key}
|
||||||
|
}
|
||||||
|
operations = append(operations, server.RouteUpdateOperation{
|
||||||
|
Type: server.UpdateRoutePeer,
|
||||||
|
Values: peerValue,
|
||||||
|
})
|
||||||
|
case api.RoutePatchOperationPathMetric:
|
||||||
|
if patch.Op != api.RoutePatchOperationOpReplace {
|
||||||
|
http.Error(w, fmt.Sprintf("Metric field only accepts replace operation, got %s", patch.Op),
|
||||||
|
http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
operations = append(operations, server.RouteUpdateOperation{
|
||||||
|
Type: server.UpdateRouteMetric,
|
||||||
|
Values: patch.Value,
|
||||||
|
})
|
||||||
|
case api.RoutePatchOperationPathMasquerade:
|
||||||
|
if patch.Op != api.RoutePatchOperationOpReplace {
|
||||||
|
http.Error(w, fmt.Sprintf("Masquerade field only accepts replace operation, got %s", patch.Op),
|
||||||
|
http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
operations = append(operations, server.RouteUpdateOperation{
|
||||||
|
Type: server.UpdateRouteMasquerade,
|
||||||
|
Values: patch.Value,
|
||||||
|
})
|
||||||
|
case api.RoutePatchOperationPathEnabled:
|
||||||
|
if patch.Op != api.RoutePatchOperationOpReplace {
|
||||||
|
http.Error(w, fmt.Sprintf("Enabled field only accepts replace operation, got %s", patch.Op),
|
||||||
|
http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
operations = append(operations, server.RouteUpdateOperation{
|
||||||
|
Type: server.UpdateRouteEnabled,
|
||||||
|
Values: patch.Value,
|
||||||
|
})
|
||||||
|
default:
|
||||||
|
http.Error(w, "invalid patch path", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
route, err := h.accountManager.UpdateRoute(account.Id, routeID, operations)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
errStatus, ok := status.FromError(err)
|
||||||
|
if ok && errStatus.Code() == codes.Internal {
|
||||||
|
http.Error(w, errStatus.String(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if ok && errStatus.Code() == codes.NotFound {
|
||||||
|
http.Error(w, errStatus.String(), http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if ok && errStatus.Code() == codes.InvalidArgument {
|
||||||
|
http.Error(w, errStatus.String(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Errorf("failed updating route %s under account %s %v", routeID, account.Id, err)
|
||||||
|
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := toRouteResponse(account, route)
|
||||||
|
|
||||||
|
writeJSONObject(w, &resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteRouteHandler handles route deletion request
|
||||||
|
func (h *Routes) DeleteRouteHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
account, err := getJWTAccount(h.accountManager, h.jwtExtractor, h.authAudience, r)
|
||||||
|
if err != nil {
|
||||||
|
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
routeID := mux.Vars(r)["id"]
|
||||||
|
if len(routeID) == 0 {
|
||||||
|
http.Error(w, "invalid route ID", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = h.accountManager.DeleteRoute(account.Id, routeID)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed delete route %s under account %s %v", routeID, account.Id, err)
|
||||||
|
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
writeJSONObject(w, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRouteHandler handles a route Get request identified by ID
|
||||||
|
func (h *Routes) GetRouteHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
account, err := getJWTAccount(h.accountManager, h.jwtExtractor, h.authAudience, r)
|
||||||
|
if err != nil {
|
||||||
|
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
routeID := mux.Vars(r)["id"]
|
||||||
|
if len(routeID) == 0 {
|
||||||
|
http.Error(w, "invalid route ID", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
foundRoute, err := h.accountManager.GetRoute(account.Id, routeID)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "route not found", http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
writeJSONObject(w, toRouteResponse(account, foundRoute))
|
||||||
|
}
|
||||||
|
|
||||||
|
func toRouteResponse(account *server.Account, serverRoute *route.Route) *api.Route {
|
||||||
|
var peerIP string
|
||||||
|
if serverRoute.Peer != "" {
|
||||||
|
peer, found := account.Peers[serverRoute.Peer]
|
||||||
|
if !found {
|
||||||
|
panic("peer ID not found")
|
||||||
|
}
|
||||||
|
peerIP = peer.IP.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
return &api.Route{
|
||||||
|
Id: serverRoute.ID,
|
||||||
|
Description: serverRoute.Description,
|
||||||
|
Enabled: serverRoute.Enabled,
|
||||||
|
Peer: peerIP,
|
||||||
|
Prefix: serverRoute.Prefix.String(),
|
||||||
|
PrefixType: serverRoute.PrefixType.String(),
|
||||||
|
Masquerade: serverRoute.Masquerade,
|
||||||
|
Metric: serverRoute.Metric,
|
||||||
|
}
|
||||||
|
}
|
338
management/server/http/routes_test.go
Normal file
338
management/server/http/routes_test.go
Normal file
@ -0,0 +1,338 @@
|
|||||||
|
package http
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"github.com/netbirdio/netbird/management/server/http/api"
|
||||||
|
"github.com/netbirdio/netbird/route"
|
||||||
|
"google.golang.org/grpc/codes"
|
||||||
|
"google.golang.org/grpc/status"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"net/netip"
|
||||||
|
"strconv"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/magiconair/properties/assert"
|
||||||
|
"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"
|
||||||
|
existingPeerID = "100.64.0.100"
|
||||||
|
notFoundPeerID = "100.64.0.200"
|
||||||
|
existingPeerKey = "existingPeerKey"
|
||||||
|
testAccountID = "test_id"
|
||||||
|
)
|
||||||
|
|
||||||
|
var baseExistingRoute = &route.Route{
|
||||||
|
ID: existingRouteID,
|
||||||
|
Description: "base route",
|
||||||
|
Prefix: netip.MustParsePrefix("192.168.0.0/24"),
|
||||||
|
PrefixType: route.IPv4Prefix,
|
||||||
|
Metric: 9999,
|
||||||
|
Masquerade: false,
|
||||||
|
Enabled: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
var testingAccount = &server.Account{
|
||||||
|
Id: testAccountID,
|
||||||
|
Domain: "hotmail.com",
|
||||||
|
Peers: map[string]*server.Peer{
|
||||||
|
existingPeerKey: {
|
||||||
|
Key: existingPeerID,
|
||||||
|
IP: netip.MustParseAddr(existingPeerID).AsSlice(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func initRoutesTestData() *Routes {
|
||||||
|
return &Routes{
|
||||||
|
accountManager: &mock_server.MockAccountManager{
|
||||||
|
GetRouteFunc: func(_, routeID string) (*route.Route, error) {
|
||||||
|
if routeID == existingRouteID {
|
||||||
|
return baseExistingRoute, nil
|
||||||
|
}
|
||||||
|
return nil, status.Errorf(codes.NotFound, "route with ID %s not found", routeID)
|
||||||
|
},
|
||||||
|
CreateRouteFunc: func(accountID string, prefix, peer, description string, masquerade bool, metric int, enabled bool) (*route.Route, error) {
|
||||||
|
prefixType, p, _ := route.ParsePrefix(prefix)
|
||||||
|
return &route.Route{
|
||||||
|
ID: existingRouteID,
|
||||||
|
Peer: peer,
|
||||||
|
Prefix: p,
|
||||||
|
PrefixType: prefixType,
|
||||||
|
Description: description,
|
||||||
|
Masquerade: masquerade,
|
||||||
|
Enabled: enabled,
|
||||||
|
}, nil
|
||||||
|
},
|
||||||
|
SaveRouteFunc: func(_ string, _ *route.Route) error {
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
DeleteRouteFunc: func(_ string, _ string) error {
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
GetPeerByIPFunc: func(_ string, peerIP string) (*server.Peer, error) {
|
||||||
|
if peerIP != existingPeerID {
|
||||||
|
return nil, status.Errorf(codes.NotFound, "Peer with ID %s not found", peerIP)
|
||||||
|
}
|
||||||
|
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 {
|
||||||
|
return nil, status.Errorf(codes.NotFound, "route %s no longer exists", routeID)
|
||||||
|
}
|
||||||
|
for _, operation := range operations {
|
||||||
|
switch operation.Type {
|
||||||
|
case server.UpdateRoutePrefix:
|
||||||
|
routeToUpdate.PrefixType, routeToUpdate.Prefix, _ = route.ParsePrefix(operation.Values[0])
|
||||||
|
case server.UpdateRouteDescription:
|
||||||
|
routeToUpdate.Description = operation.Values[0]
|
||||||
|
case server.UpdateRoutePeer:
|
||||||
|
routeToUpdate.Peer = operation.Values[0]
|
||||||
|
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])
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("no operation")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return routeToUpdate, nil
|
||||||
|
},
|
||||||
|
GetAccountWithAuthorizationClaimsFunc: func(_ jwtclaims.AuthorizationClaims) (*server.Account, error) {
|
||||||
|
return testingAccount, nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
authAudience: "",
|
||||||
|
jwtExtractor: jwtclaims.ClaimsExtractor{
|
||||||
|
ExtractClaimsFromRequestContext: func(r *http.Request, authAudiance string) jwtclaims.AuthorizationClaims {
|
||||||
|
return jwtclaims.AuthorizationClaims{
|
||||||
|
UserId: "test_user",
|
||||||
|
Domain: "hotmail.com",
|
||||||
|
AccountId: testAccountID,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
expectedRoute: toRouteResponse(testingAccount, baseExistingRoute),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Get Not Existing Route",
|
||||||
|
requestType: http.MethodGet,
|
||||||
|
requestPath: "/api/rules/" + notFoundRouteID,
|
||||||
|
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,
|
||||||
|
requestPath: "/api/rules/" + notFoundRouteID,
|
||||||
|
expectedStatus: http.StatusNotFound,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "POST OK",
|
||||||
|
requestType: http.MethodPost,
|
||||||
|
requestPath: "/api/routes",
|
||||||
|
requestBody: bytes.NewBuffer(
|
||||||
|
[]byte(fmt.Sprintf("{\"Description\":\"Post\",\"Prefix\":\"192.168.0.0/16\",\"Peer\":\"%s\"}", existingPeerID))),
|
||||||
|
expectedStatus: http.StatusOK,
|
||||||
|
expectedBody: true,
|
||||||
|
expectedRoute: &api.Route{
|
||||||
|
Id: existingRouteID,
|
||||||
|
Description: "Post",
|
||||||
|
Prefix: "192.168.0.0/16",
|
||||||
|
Peer: existingPeerID,
|
||||||
|
PrefixType: route.IPv4PrefixString,
|
||||||
|
Masquerade: false,
|
||||||
|
Enabled: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "POST Not Found Peer",
|
||||||
|
requestType: http.MethodPost,
|
||||||
|
requestPath: "/api/routes",
|
||||||
|
requestBody: bytes.NewBufferString(fmt.Sprintf("{\"Description\":\"Post\",\"Prefix\":\"192.168.0.0/16\",\"Peer\":\"%s\"}", notFoundPeerID)),
|
||||||
|
expectedStatus: http.StatusUnprocessableEntity,
|
||||||
|
expectedBody: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "POST Invalid Prefix",
|
||||||
|
requestType: http.MethodPost,
|
||||||
|
requestPath: "/api/routes",
|
||||||
|
requestBody: bytes.NewBufferString(fmt.Sprintf("{\"Description\":\"Post\",\"Prefix\":\"192.168.0.0/34\",\"Peer\":\"%s\"}", existingPeerID)),
|
||||||
|
expectedStatus: http.StatusBadRequest,
|
||||||
|
expectedBody: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "PUT OK",
|
||||||
|
requestType: http.MethodPut,
|
||||||
|
requestPath: "/api/routes/" + existingRouteID,
|
||||||
|
requestBody: bytes.NewBufferString(fmt.Sprintf("{\"Description\":\"Post\",\"Prefix\":\"192.168.0.0/16\",\"Peer\":\"%s\"}", existingPeerID)),
|
||||||
|
expectedStatus: http.StatusOK,
|
||||||
|
expectedBody: true,
|
||||||
|
expectedRoute: &api.Route{
|
||||||
|
Id: existingRouteID,
|
||||||
|
Description: "Post",
|
||||||
|
Prefix: "192.168.0.0/16",
|
||||||
|
Peer: existingPeerID,
|
||||||
|
PrefixType: route.IPv4PrefixString,
|
||||||
|
Masquerade: false,
|
||||||
|
Enabled: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "PUT Not Found Route",
|
||||||
|
requestType: http.MethodPut,
|
||||||
|
requestPath: "/api/routes/" + notFoundRouteID,
|
||||||
|
requestBody: bytes.NewBufferString(fmt.Sprintf("{\"Description\":\"Post\",\"Prefix\":\"192.168.0.0/16\",\"Peer\":\"%s\"}", existingPeerID)),
|
||||||
|
expectedStatus: http.StatusNotFound,
|
||||||
|
expectedBody: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "PUT Not Found Peer",
|
||||||
|
requestType: http.MethodPut,
|
||||||
|
requestPath: "/api/routes/" + existingRouteID,
|
||||||
|
requestBody: bytes.NewBufferString(fmt.Sprintf("{\"Description\":\"Post\",\"Prefix\":\"192.168.0.0/16\",\"Peer\":\"%s\"}", notFoundPeerID)),
|
||||||
|
expectedStatus: http.StatusUnprocessableEntity,
|
||||||
|
expectedBody: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "PUT Invalid Prefix",
|
||||||
|
requestType: http.MethodPut,
|
||||||
|
requestPath: "/api/routes/" + existingRouteID,
|
||||||
|
requestBody: bytes.NewBufferString(fmt.Sprintf("{\"Description\":\"Post\",\"Prefix\":\"192.168.0.0/34\",\"Peer\":\"%s\"}", existingPeerID)),
|
||||||
|
expectedStatus: http.StatusBadRequest,
|
||||||
|
expectedBody: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "PATCH Description OK",
|
||||||
|
requestType: http.MethodPatch,
|
||||||
|
requestPath: "/api/routes/" + existingRouteID,
|
||||||
|
requestBody: bytes.NewBufferString("[{\"op\":\"replace\",\"path\":\"description\",\"value\":[\"NewDesc\"]}]"),
|
||||||
|
expectedStatus: http.StatusOK,
|
||||||
|
expectedBody: true,
|
||||||
|
expectedRoute: &api.Route{
|
||||||
|
Id: existingRouteID,
|
||||||
|
Description: "NewDesc",
|
||||||
|
Prefix: baseExistingRoute.Prefix.String(),
|
||||||
|
PrefixType: route.IPv4PrefixString,
|
||||||
|
Masquerade: baseExistingRoute.Masquerade,
|
||||||
|
Enabled: baseExistingRoute.Enabled,
|
||||||
|
Metric: baseExistingRoute.Metric,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "PATCH Peer OK",
|
||||||
|
requestType: http.MethodPatch,
|
||||||
|
requestPath: "/api/routes/" + existingRouteID,
|
||||||
|
requestBody: bytes.NewBufferString(fmt.Sprintf("[{\"op\":\"replace\",\"path\":\"peer\",\"value\":[\"%s\"]}]", existingPeerID)),
|
||||||
|
expectedStatus: http.StatusOK,
|
||||||
|
expectedBody: true,
|
||||||
|
expectedRoute: &api.Route{
|
||||||
|
Id: existingRouteID,
|
||||||
|
Description: "NewDesc",
|
||||||
|
Prefix: baseExistingRoute.Prefix.String(),
|
||||||
|
PrefixType: route.IPv4PrefixString,
|
||||||
|
Peer: existingPeerID,
|
||||||
|
Masquerade: baseExistingRoute.Masquerade,
|
||||||
|
Enabled: baseExistingRoute.Enabled,
|
||||||
|
Metric: baseExistingRoute.Metric,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "PATCH Not Found Peer",
|
||||||
|
requestType: http.MethodPatch,
|
||||||
|
requestPath: "/api/routes/" + existingRouteID,
|
||||||
|
requestBody: bytes.NewBufferString(fmt.Sprintf("[{\"op\":\"replace\",\"path\":\"peer\",\"value\":[\"%s\"]}]", notFoundPeerID)),
|
||||||
|
expectedStatus: http.StatusUnprocessableEntity,
|
||||||
|
expectedBody: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "PATCH Not Found Route",
|
||||||
|
requestType: http.MethodPatch,
|
||||||
|
requestPath: "/api/routes/" + notFoundRouteID,
|
||||||
|
requestBody: bytes.NewBufferString("[{\"op\":\"replace\",\"path\":\"prefix\",\"value\":[\"192.168.0.0/34\"]}]"),
|
||||||
|
expectedStatus: http.StatusNotFound,
|
||||||
|
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()
|
||||||
|
router.HandleFunc("/api/routes/{id}", p.GetRouteHandler).Methods("GET")
|
||||||
|
router.HandleFunc("/api/routes/{id}", p.DeleteRouteHandler).Methods("DELETE")
|
||||||
|
router.HandleFunc("/api/routes", p.CreateRouteHandler).Methods("POST")
|
||||||
|
router.HandleFunc("/api/routes/{id}", p.UpdateRouteHandler).Methods("PUT")
|
||||||
|
router.HandleFunc("/api/routes/{id}", p.PatchRouteHandler).Methods("PATCH")
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user