Feature/add nameservers API endpoint (#491)

Add nameservers endpoint and Open API definition

updated open api generator cli
This commit is contained in:
Maycon Santos 2022-10-10 11:06:54 +02:00 committed by GitHub
parent 369a7ef345
commit b4e03f4616
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 1061 additions and 120 deletions

View File

@ -127,6 +127,7 @@ func (g *NameServerGroup) Copy() *NameServerGroup {
Description: g.Description, Description: g.Description,
NameServers: g.NameServers, NameServers: g.NameServers,
Groups: g.Groups, Groups: g.Groups,
Enabled: g.Enabled,
} }
} }

View File

@ -3,3 +3,5 @@ generate:
models: true models: true
embedded-spec: false embedded-spec: false
output: types.gen.go output: types.gen.go
compatibility:
always-prefix-enum-values: true

View File

@ -11,6 +11,6 @@ fi
old_pwd=$(pwd) old_pwd=$(pwd)
script_path=$(dirname $(realpath "$0")) script_path=$(dirname $(realpath "$0"))
cd "$script_path" cd "$script_path"
go install github.com/deepmap/oapi-codegen/cmd/oapi-codegen@v1.11.0 go install github.com/deepmap/oapi-codegen/cmd/oapi-codegen@4a1477f6a8ba6ca8115cc23bb2fb67f0b9fca18e
oapi-codegen --config cfg.yaml openapi.yml oapi-codegen --config cfg.yaml openapi.yml
cd "$old_pwd" cd "$old_pwd"

View File

@ -16,6 +16,8 @@ tags:
description: Interact with and view information about rules. description: Interact with and view information about rules.
- name: Routes - name: Routes
description: Interact with and view information about routes. description: Interact with and view information about routes.
- name: DNS
description: Interact with and view information about DNS configuration.
components: components:
schemas: schemas:
User: User:
@ -373,6 +375,76 @@ components:
enum: [ "network","network_id","description","enabled","peer","metric","masquerade" ] enum: [ "network","network_id","description","enabled","peer","metric","masquerade" ]
required: required:
- path - path
Nameserver:
type: object
properties:
ip:
description: Nameserver IP
type: string
ns_type:
description: Nameserver Type
type: string
enum: ["udp"]
port:
description: Nameserver Port
type: integer
required:
- ip
- ns_type
- port
NameserverGroupRequest:
type: object
properties:
name:
description: Nameserver group name
type: string
maxLength: 40
minLength: 1
description:
description: Nameserver group description
type: string
nameservers:
description: Nameserver group
minLength: 1
maxLength: 2
type: array
items:
$ref: '#/components/schemas/Nameserver'
enabled:
description: Nameserver group status
type: boolean
groups:
description: Nameserver group tag groups
type: array
items:
type: string
required:
- name
- description
- nameservers
- enabled
- groups
NameserverGroup:
allOf:
- type: object
properties:
id:
description: Nameserver group ID
type: string
required:
- id
- $ref: '#/components/schemas/NameserverGroupRequest'
NameserverGroupPatchOperation:
allOf:
- $ref: '#/components/schemas/PatchMinimum'
- type: object
properties:
path:
description: Nameserver group field to update in form /<field>
type: string
enum: [ "name","description","enabled","groups","nameservers" ]
required:
- path
responses: responses:
not_found: not_found:
@ -1238,6 +1310,176 @@ paths:
schema: schema:
type: string type: string
description: The Route ID 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"
/api/dns/nameservers:
get:
summary: Returns a list of all Nameserver Groups
tags: [ DNS ]
security:
- BearerAuth: [ ]
responses:
'200':
description: A JSON Array of Nameserver Groups
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/NameserverGroup'
'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 Nameserver Group
tags: [ DNS ]
security:
- BearerAuth: [ ]
requestBody:
description: New Nameserver Groups request
content:
'application/json':
schema:
$ref: '#/components/schemas/NameserverGroupRequest'
responses:
'200':
description: A Nameserver Groups Object
content:
application/json:
schema:
$ref: '#/components/schemas/NameserverGroup'
'400':
"$ref": "#/components/responses/bad_request"
'401':
"$ref": "#/components/responses/requires_authentication"
'403':
"$ref": "#/components/responses/forbidden"
'500':
"$ref": "#/components/responses/internal_error"
/api/dns/nameservers/{id}:
get:
summary: Get information about a Nameserver Groups
tags: [ DNS ]
security:
- BearerAuth: [ ]
parameters:
- in: path
name: id
required: true
schema:
type: string
description: The Nameserver Group ID
responses:
'200':
description: A Nameserver Group object
content:
application/json:
schema:
$ref: '#/components/schemas/NameserverGroup'
'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 Nameserver Group
tags: [ DNS ]
security:
- BearerAuth: [ ]
parameters:
- in: path
name: id
required: true
schema:
type: string
description: The Nameserver Group ID
requestBody:
description: Update Nameserver Group request
content:
application/json:
schema:
$ref: '#/components/schemas/NameserverGroupRequest'
responses:
'200':
description: A Nameserver Group object
content:
application/json:
schema:
$ref: '#/components/schemas/NameserverGroup'
'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 Nameserver Group
tags: [ DNS ]
security:
- BearerAuth: [ ]
parameters:
- in: path
name: id
required: true
schema:
type: string
description: The Nameserver Group ID
requestBody:
description: Update Nameserver Group request using a list of json patch objects
content:
'application/json':
schema:
type: array
items:
$ref: '#/components/schemas/NameserverGroupPatchOperation'
responses:
'200':
description: A Nameserver Group object
content:
application/json:
schema:
$ref: '#/components/schemas/NameserverGroup'
'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 Nameserver Group
tags: [ DNS ]
security:
- BearerAuth: [ ]
parameters:
- in: path
name: id
required: true
schema:
type: string
description: The Nameserver Group ID
responses: responses:
'200': '200':
description: Delete status code description: Delete status code

View File

@ -1,6 +1,6 @@
// Package api provides primitives to interact with the openapi HTTP API. // Package api provides primitives to interact with the openapi HTTP API.
// //
// Code generated by github.com/deepmap/oapi-codegen version v1.11.0 DO NOT EDIT. // Code generated by github.com/deepmap/oapi-codegen version v1.11.1-0.20220912230023-4a1477f6a8ba DO NOT EDIT.
package api package api
import ( import (
@ -24,6 +24,27 @@ const (
GroupPatchOperationPathPeers GroupPatchOperationPath = "peers" GroupPatchOperationPathPeers GroupPatchOperationPath = "peers"
) )
// Defines values for NameserverNsType.
const (
NameserverNsTypeUdp NameserverNsType = "udp"
)
// Defines values for NameserverGroupPatchOperationOp.
const (
NameserverGroupPatchOperationOpAdd NameserverGroupPatchOperationOp = "add"
NameserverGroupPatchOperationOpRemove NameserverGroupPatchOperationOp = "remove"
NameserverGroupPatchOperationOpReplace NameserverGroupPatchOperationOp = "replace"
)
// Defines values for NameserverGroupPatchOperationPath.
const (
NameserverGroupPatchOperationPathDescription NameserverGroupPatchOperationPath = "description"
NameserverGroupPatchOperationPathEnabled NameserverGroupPatchOperationPath = "enabled"
NameserverGroupPatchOperationPathGroups NameserverGroupPatchOperationPath = "groups"
NameserverGroupPatchOperationPathName NameserverGroupPatchOperationPath = "name"
NameserverGroupPatchOperationPathNameservers NameserverGroupPatchOperationPath = "nameservers"
)
// Defines values for PatchMinimumOp. // Defines values for PatchMinimumOp.
const ( const (
PatchMinimumOpAdd PatchMinimumOp = "add" PatchMinimumOpAdd PatchMinimumOp = "add"
@ -68,322 +89,397 @@ const (
// Group defines model for Group. // Group defines model for Group.
type Group struct { type Group struct {
// Group ID // Id Group ID
Id string `json:"id"` Id string `json:"id"`
// Group Name identifier // Name Group Name identifier
Name string `json:"name"` Name string `json:"name"`
// List of peers object // Peers List of peers object
Peers []PeerMinimum `json:"peers"` Peers []PeerMinimum `json:"peers"`
// Count of peers associated to the group // PeersCount Count of peers associated to the group
PeersCount int `json:"peers_count"` PeersCount int `json:"peers_count"`
} }
// GroupMinimum defines model for GroupMinimum. // GroupMinimum defines model for GroupMinimum.
type GroupMinimum struct { type GroupMinimum struct {
// Group ID // Id Group ID
Id string `json:"id"` Id string `json:"id"`
// Group Name identifier // Name Group Name identifier
Name string `json:"name"` Name string `json:"name"`
// Count of peers associated to the group // PeersCount Count of peers associated to the group
PeersCount int `json:"peers_count"` PeersCount int `json:"peers_count"`
} }
// GroupPatchOperation defines model for GroupPatchOperation. // GroupPatchOperation defines model for GroupPatchOperation.
type GroupPatchOperation struct { type GroupPatchOperation struct {
// Patch operation type // Op Patch operation type
Op GroupPatchOperationOp `json:"op"` Op GroupPatchOperationOp `json:"op"`
// Group field to update in form /<field> // Path Group field to update in form /<field>
Path GroupPatchOperationPath `json:"path"` Path GroupPatchOperationPath `json:"path"`
// Values to be applied // Value Values to be applied
Value []string `json:"value"` Value []string `json:"value"`
} }
// Patch operation type // GroupPatchOperationOp Patch operation type
type GroupPatchOperationOp string type GroupPatchOperationOp string
// Group field to update in form /<field> // GroupPatchOperationPath Group field to update in form /<field>
type GroupPatchOperationPath string type GroupPatchOperationPath string
// Nameserver defines model for Nameserver.
type Nameserver struct {
// Ip Nameserver IP
Ip string `json:"ip"`
// NsType Nameserver Type
NsType NameserverNsType `json:"ns_type"`
// Port Nameserver Port
Port int `json:"port"`
}
// NameserverNsType Nameserver Type
type NameserverNsType string
// NameserverGroup defines model for NameserverGroup.
type NameserverGroup struct {
// Description Nameserver group description
Description string `json:"description"`
// Enabled Nameserver group status
Enabled bool `json:"enabled"`
// Groups Nameserver group tag groups
Groups []string `json:"groups"`
// Id Nameserver group ID
Id string `json:"id"`
// Name Nameserver group name
Name string `json:"name"`
// Nameservers Nameserver group
Nameservers []Nameserver `json:"nameservers"`
}
// NameserverGroupPatchOperation defines model for NameserverGroupPatchOperation.
type NameserverGroupPatchOperation struct {
// Op Patch operation type
Op NameserverGroupPatchOperationOp `json:"op"`
// Path Nameserver group field to update in form /<field>
Path NameserverGroupPatchOperationPath `json:"path"`
// Value Values to be applied
Value []string `json:"value"`
}
// NameserverGroupPatchOperationOp Patch operation type
type NameserverGroupPatchOperationOp string
// NameserverGroupPatchOperationPath Nameserver group field to update in form /<field>
type NameserverGroupPatchOperationPath string
// NameserverGroupRequest defines model for NameserverGroupRequest.
type NameserverGroupRequest struct {
// Description Nameserver group description
Description string `json:"description"`
// Enabled Nameserver group status
Enabled bool `json:"enabled"`
// Groups Nameserver group tag groups
Groups []string `json:"groups"`
// Name Nameserver group name
Name string `json:"name"`
// Nameservers Nameserver group
Nameservers []Nameserver `json:"nameservers"`
}
// PatchMinimum defines model for PatchMinimum. // PatchMinimum defines model for PatchMinimum.
type PatchMinimum struct { type PatchMinimum struct {
// Patch operation type // Op Patch operation type
Op PatchMinimumOp `json:"op"` Op PatchMinimumOp `json:"op"`
// Values to be applied // Value Values to be applied
Value []string `json:"value"` Value []string `json:"value"`
} }
// Patch operation type // PatchMinimumOp Patch operation type
type PatchMinimumOp string type PatchMinimumOp string
// Peer defines model for Peer. // Peer defines model for Peer.
type Peer struct { type Peer struct {
// Peer to Management connection status // Connected Peer to Management connection status
Connected bool `json:"connected"` Connected bool `json:"connected"`
// Groups that the peer belongs to // Groups Groups that the peer belongs to
Groups []GroupMinimum `json:"groups"` Groups []GroupMinimum `json:"groups"`
// Hostname of the machine // Hostname Hostname of the machine
Hostname string `json:"hostname"` Hostname string `json:"hostname"`
// Peer ID // Id Peer ID
Id string `json:"id"` Id string `json:"id"`
// Peer's IP address // Ip Peer's IP address
Ip string `json:"ip"` Ip string `json:"ip"`
// Last time peer connected to Netbird's management service // LastSeen Last time peer connected to Netbird's management service
LastSeen time.Time `json:"last_seen"` LastSeen time.Time `json:"last_seen"`
// Peer's hostname // Name Peer's hostname
Name string `json:"name"` Name string `json:"name"`
// Peer's operating system and version // Os Peer's operating system and version
Os string `json:"os"` Os string `json:"os"`
// Indicates whether SSH server is enabled on this peer // SshEnabled Indicates whether SSH server is enabled on this peer
SshEnabled bool `json:"ssh_enabled"` SshEnabled bool `json:"ssh_enabled"`
// Peer's desktop UI version // UiVersion Peer's desktop UI version
UiVersion *string `json:"ui_version,omitempty"` UiVersion *string `json:"ui_version,omitempty"`
// User ID of the user that enrolled this peer // UserId User ID of the user that enrolled this peer
UserId *string `json:"user_id,omitempty"` UserId *string `json:"user_id,omitempty"`
// Peer's daemon or cli version // Version Peer's daemon or cli version
Version string `json:"version"` Version string `json:"version"`
} }
// PeerMinimum defines model for PeerMinimum. // PeerMinimum defines model for PeerMinimum.
type PeerMinimum struct { type PeerMinimum struct {
// Peer ID // Id Peer ID
Id string `json:"id"` Id string `json:"id"`
// Peer's hostname // Name Peer's hostname
Name string `json:"name"` Name string `json:"name"`
} }
// Route defines model for Route. // Route defines model for Route.
type Route struct { type Route struct {
// Route description // Description Route description
Description string `json:"description"` Description string `json:"description"`
// Route status // Enabled Route status
Enabled bool `json:"enabled"` Enabled bool `json:"enabled"`
// Route Id // Id Route Id
Id string `json:"id"` Id string `json:"id"`
// Indicate if peer should masquerade traffic to this route's prefix // Masquerade Indicate if peer should masquerade traffic to this route's prefix
Masquerade bool `json:"masquerade"` Masquerade bool `json:"masquerade"`
// Route metric number. Lowest number has higher priority // Metric Route metric number. Lowest number has higher priority
Metric int `json:"metric"` Metric int `json:"metric"`
// Network range in CIDR format // Network Network range in CIDR format
Network string `json:"network"` Network string `json:"network"`
// Route network identifier, to group HA routes // NetworkId Route network identifier, to group HA routes
NetworkId string `json:"network_id"` NetworkId string `json:"network_id"`
// Network type indicating if it is IPv4 or IPv6 // NetworkType Network type indicating if it is IPv4 or IPv6
NetworkType string `json:"network_type"` NetworkType string `json:"network_type"`
// Peer Identifier associated with route // Peer Peer Identifier associated with route
Peer string `json:"peer"` Peer string `json:"peer"`
} }
// RoutePatchOperation defines model for RoutePatchOperation. // RoutePatchOperation defines model for RoutePatchOperation.
type RoutePatchOperation struct { type RoutePatchOperation struct {
// Patch operation type // Op Patch operation type
Op RoutePatchOperationOp `json:"op"` Op RoutePatchOperationOp `json:"op"`
// Route field to update in form /<field> // Path Route field to update in form /<field>
Path RoutePatchOperationPath `json:"path"` Path RoutePatchOperationPath `json:"path"`
// Values to be applied // Value Values to be applied
Value []string `json:"value"` Value []string `json:"value"`
} }
// Patch operation type // RoutePatchOperationOp Patch operation type
type RoutePatchOperationOp string type RoutePatchOperationOp string
// Route field to update in form /<field> // RoutePatchOperationPath Route field to update in form /<field>
type RoutePatchOperationPath string type RoutePatchOperationPath string
// RouteRequest defines model for RouteRequest. // RouteRequest defines model for RouteRequest.
type RouteRequest struct { type RouteRequest struct {
// Route description // Description Route description
Description string `json:"description"` Description string `json:"description"`
// Route status // Enabled Route status
Enabled bool `json:"enabled"` Enabled bool `json:"enabled"`
// Indicate if peer should masquerade traffic to this route's prefix // Masquerade Indicate if peer should masquerade traffic to this route's prefix
Masquerade bool `json:"masquerade"` Masquerade bool `json:"masquerade"`
// Route metric number. Lowest number has higher priority // Metric Route metric number. Lowest number has higher priority
Metric int `json:"metric"` Metric int `json:"metric"`
// Network range in CIDR format // Network Network range in CIDR format
Network string `json:"network"` Network string `json:"network"`
// Route network identifier, to group HA routes // NetworkId Route network identifier, to group HA routes
NetworkId string `json:"network_id"` NetworkId string `json:"network_id"`
// Peer Identifier associated with route // Peer Peer Identifier associated with route
Peer string `json:"peer"` Peer string `json:"peer"`
} }
// Rule defines model for Rule. // Rule defines model for Rule.
type Rule struct { type Rule struct {
// Rule friendly description // Description Rule friendly description
Description string `json:"description"` Description string `json:"description"`
// Rule destination groups // Destinations Rule destination groups
Destinations []GroupMinimum `json:"destinations"` Destinations []GroupMinimum `json:"destinations"`
// Rules status // Disabled Rules status
Disabled bool `json:"disabled"` Disabled bool `json:"disabled"`
// Rule flow, currently, only "bidirect" for bi-directional traffic is accepted // Flow Rule flow, currently, only "bidirect" for bi-directional traffic is accepted
Flow string `json:"flow"` Flow string `json:"flow"`
// Rule ID // Id Rule ID
Id string `json:"id"` Id string `json:"id"`
// Rule name identifier // Name Rule name identifier
Name string `json:"name"` Name string `json:"name"`
// Rule source groups // Sources Rule source groups
Sources []GroupMinimum `json:"sources"` Sources []GroupMinimum `json:"sources"`
} }
// RuleMinimum defines model for RuleMinimum. // RuleMinimum defines model for RuleMinimum.
type RuleMinimum struct { type RuleMinimum struct {
// Rule friendly description // Description Rule friendly description
Description string `json:"description"` Description string `json:"description"`
// Rules status // Disabled Rules status
Disabled bool `json:"disabled"` Disabled bool `json:"disabled"`
// Rule flow, currently, only "bidirect" for bi-directional traffic is accepted // Flow Rule flow, currently, only "bidirect" for bi-directional traffic is accepted
Flow string `json:"flow"` Flow string `json:"flow"`
// Rule name identifier // Name Rule name identifier
Name string `json:"name"` Name string `json:"name"`
} }
// RulePatchOperation defines model for RulePatchOperation. // RulePatchOperation defines model for RulePatchOperation.
type RulePatchOperation struct { type RulePatchOperation struct {
// Patch operation type // Op Patch operation type
Op RulePatchOperationOp `json:"op"` Op RulePatchOperationOp `json:"op"`
// Rule field to update in form /<field> // Path Rule field to update in form /<field>
Path RulePatchOperationPath `json:"path"` Path RulePatchOperationPath `json:"path"`
// Values to be applied // Value Values to be applied
Value []string `json:"value"` Value []string `json:"value"`
} }
// Patch operation type // RulePatchOperationOp Patch operation type
type RulePatchOperationOp string type RulePatchOperationOp string
// Rule field to update in form /<field> // RulePatchOperationPath Rule field to update in form /<field>
type RulePatchOperationPath string type RulePatchOperationPath string
// SetupKey defines model for SetupKey. // SetupKey defines model for SetupKey.
type SetupKey struct { type SetupKey struct {
// Setup key groups to auto-assign to peers registered with this key // AutoGroups Setup key groups to auto-assign to peers registered with this key
AutoGroups []string `json:"auto_groups"` AutoGroups []string `json:"auto_groups"`
// Setup Key expiration date // Expires Setup Key expiration date
Expires time.Time `json:"expires"` Expires time.Time `json:"expires"`
// Setup Key ID // Id Setup Key ID
Id string `json:"id"` Id string `json:"id"`
// Setup Key value // Key Setup Key value
Key string `json:"key"` Key string `json:"key"`
// Setup key last usage date // LastUsed Setup key last usage date
LastUsed time.Time `json:"last_used"` LastUsed time.Time `json:"last_used"`
// Setup key name identifier // Name Setup key name identifier
Name string `json:"name"` Name string `json:"name"`
// Setup key revocation status // Revoked Setup key revocation status
Revoked bool `json:"revoked"` Revoked bool `json:"revoked"`
// Setup key status, "valid", "overused","expired" or "revoked" // State Setup key status, "valid", "overused","expired" or "revoked"
State string `json:"state"` State string `json:"state"`
// Setup key type, one-off for single time usage and reusable // Type Setup key type, one-off for single time usage and reusable
Type string `json:"type"` Type string `json:"type"`
// Setup key last update date // UpdatedAt Setup key last update date
UpdatedAt time.Time `json:"updated_at"` UpdatedAt time.Time `json:"updated_at"`
// Usage count of setup key // UsedTimes Usage count of setup key
UsedTimes int `json:"used_times"` UsedTimes int `json:"used_times"`
// Setup key validity status // Valid Setup key validity status
Valid bool `json:"valid"` Valid bool `json:"valid"`
} }
// SetupKeyRequest defines model for SetupKeyRequest. // SetupKeyRequest defines model for SetupKeyRequest.
type SetupKeyRequest struct { type SetupKeyRequest struct {
// Setup key groups to auto-assign to peers registered with this key // AutoGroups Setup key groups to auto-assign to peers registered with this key
AutoGroups []string `json:"auto_groups"` AutoGroups []string `json:"auto_groups"`
// Expiration time in seconds // ExpiresIn Expiration time in seconds
ExpiresIn int `json:"expires_in"` ExpiresIn int `json:"expires_in"`
// Setup Key name // Name Setup Key name
Name string `json:"name"` Name string `json:"name"`
// Setup key revocation status // Revoked Setup key revocation status
Revoked bool `json:"revoked"` Revoked bool `json:"revoked"`
// Setup key type, one-off for single time usage and reusable // Type Setup key type, one-off for single time usage and reusable
Type string `json:"type"` Type string `json:"type"`
} }
// User defines model for User. // User defines model for User.
type User struct { type User struct {
// Groups to auto-assign to peers registered by this user // AutoGroups Groups to auto-assign to peers registered by this user
AutoGroups []string `json:"auto_groups"` AutoGroups []string `json:"auto_groups"`
// User's email address // Email User's email address
Email string `json:"email"` Email string `json:"email"`
// User ID // Id User ID
Id string `json:"id"` Id string `json:"id"`
// User's name from idp provider // Name User's name from idp provider
Name string `json:"name"` Name string `json:"name"`
// User's NetBird account role // Role User's NetBird account role
Role string `json:"role"` Role string `json:"role"`
} }
// UserRequest defines model for UserRequest. // UserRequest defines model for UserRequest.
type UserRequest struct { type UserRequest struct {
// Groups to auto-assign to peers registered by this user // AutoGroups Groups to auto-assign to peers registered by this user
AutoGroups []string `json:"auto_groups"` AutoGroups []string `json:"auto_groups"`
// User's NetBird account role // Role User's NetBird account role
Role string `json:"role"` Role string `json:"role"`
} }
// PatchApiDnsNameserversIdJSONBody defines parameters for PatchApiDnsNameserversId.
type PatchApiDnsNameserversIdJSONBody = []NameserverGroupPatchOperation
// PostApiGroupsJSONBody defines parameters for PostApiGroups. // PostApiGroupsJSONBody defines parameters for PostApiGroups.
type PostApiGroupsJSONBody struct { type PostApiGroupsJSONBody struct {
Name string `json:"name"` Name string `json:"name"`
@ -405,28 +501,22 @@ 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. // PatchApiRoutesIdJSONBody defines parameters for PatchApiRoutesId.
type PatchApiRoutesIdJSONBody = []RoutePatchOperation 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 // Description Rule friendly description
Description string `json:"description"` Description string `json:"description"`
Destinations *[]string `json:"destinations,omitempty"` Destinations *[]string `json:"destinations,omitempty"`
// Rules status // Disabled Rules status
Disabled bool `json:"disabled"` Disabled bool `json:"disabled"`
// Rule flow, currently, only "bidirect" for bi-directional traffic is accepted // Flow Rule flow, currently, only "bidirect" for bi-directional traffic is accepted
Flow string `json:"flow"` Flow string `json:"flow"`
// Rule name identifier // Name Rule name identifier
Name string `json:"name"` Name string `json:"name"`
Sources *[]string `json:"sources,omitempty"` Sources *[]string `json:"sources,omitempty"`
} }
@ -436,29 +526,29 @@ type PatchApiRulesIdJSONBody = []RulePatchOperation
// PutApiRulesIdJSONBody defines parameters for PutApiRulesId. // PutApiRulesIdJSONBody defines parameters for PutApiRulesId.
type PutApiRulesIdJSONBody struct { type PutApiRulesIdJSONBody struct {
// Rule friendly description // Description Rule friendly description
Description string `json:"description"` Description string `json:"description"`
Destinations *[]string `json:"destinations,omitempty"` Destinations *[]string `json:"destinations,omitempty"`
// Rules status // Disabled Rules status
Disabled bool `json:"disabled"` Disabled bool `json:"disabled"`
// Rule flow, currently, only "bidirect" for bi-directional traffic is accepted // Flow Rule flow, currently, only "bidirect" for bi-directional traffic is accepted
Flow string `json:"flow"` Flow string `json:"flow"`
// Rule name identifier // Name Rule name identifier
Name string `json:"name"` Name string `json:"name"`
Sources *[]string `json:"sources,omitempty"` Sources *[]string `json:"sources,omitempty"`
} }
// PostApiSetupKeysJSONBody defines parameters for PostApiSetupKeys. // PostApiDnsNameserversJSONRequestBody defines body for PostApiDnsNameservers for application/json ContentType.
type PostApiSetupKeysJSONBody = SetupKeyRequest type PostApiDnsNameserversJSONRequestBody = NameserverGroupRequest
// PutApiSetupKeysIdJSONBody defines parameters for PutApiSetupKeysId. // PatchApiDnsNameserversIdJSONRequestBody defines body for PatchApiDnsNameserversId for application/json ContentType.
type PutApiSetupKeysIdJSONBody = SetupKeyRequest type PatchApiDnsNameserversIdJSONRequestBody = PatchApiDnsNameserversIdJSONBody
// PutApiUsersIdJSONBody defines parameters for PutApiUsersId. // PutApiDnsNameserversIdJSONRequestBody defines body for PutApiDnsNameserversId for application/json ContentType.
type PutApiUsersIdJSONBody = UserRequest type PutApiDnsNameserversIdJSONRequestBody = NameserverGroupRequest
// PostApiGroupsJSONRequestBody defines body for PostApiGroups for application/json ContentType. // PostApiGroupsJSONRequestBody defines body for PostApiGroups for application/json ContentType.
type PostApiGroupsJSONRequestBody PostApiGroupsJSONBody type PostApiGroupsJSONRequestBody PostApiGroupsJSONBody
@ -473,13 +563,13 @@ type PutApiGroupsIdJSONRequestBody PutApiGroupsIdJSONBody
type PutApiPeersIdJSONRequestBody PutApiPeersIdJSONBody type PutApiPeersIdJSONRequestBody PutApiPeersIdJSONBody
// PostApiRoutesJSONRequestBody defines body for PostApiRoutes for application/json ContentType. // PostApiRoutesJSONRequestBody defines body for PostApiRoutes for application/json ContentType.
type PostApiRoutesJSONRequestBody = PostApiRoutesJSONBody type PostApiRoutesJSONRequestBody = RouteRequest
// PatchApiRoutesIdJSONRequestBody defines body for PatchApiRoutesId for application/json ContentType. // PatchApiRoutesIdJSONRequestBody defines body for PatchApiRoutesId for application/json ContentType.
type PatchApiRoutesIdJSONRequestBody = PatchApiRoutesIdJSONBody type PatchApiRoutesIdJSONRequestBody = PatchApiRoutesIdJSONBody
// PutApiRoutesIdJSONRequestBody defines body for PutApiRoutesId for application/json ContentType. // PutApiRoutesIdJSONRequestBody defines body for PutApiRoutesId for application/json ContentType.
type PutApiRoutesIdJSONRequestBody = PutApiRoutesIdJSONBody type PutApiRoutesIdJSONRequestBody = RouteRequest
// PostApiRulesJSONRequestBody defines body for PostApiRules for application/json ContentType. // PostApiRulesJSONRequestBody defines body for PostApiRules for application/json ContentType.
type PostApiRulesJSONRequestBody PostApiRulesJSONBody type PostApiRulesJSONRequestBody PostApiRulesJSONBody
@ -491,10 +581,10 @@ type PatchApiRulesIdJSONRequestBody = PatchApiRulesIdJSONBody
type PutApiRulesIdJSONRequestBody PutApiRulesIdJSONBody type PutApiRulesIdJSONRequestBody PutApiRulesIdJSONBody
// PostApiSetupKeysJSONRequestBody defines body for PostApiSetupKeys for application/json ContentType. // PostApiSetupKeysJSONRequestBody defines body for PostApiSetupKeys for application/json ContentType.
type PostApiSetupKeysJSONRequestBody = PostApiSetupKeysJSONBody type PostApiSetupKeysJSONRequestBody = SetupKeyRequest
// PutApiSetupKeysIdJSONRequestBody defines body for PutApiSetupKeysId for application/json ContentType. // PutApiSetupKeysIdJSONRequestBody defines body for PutApiSetupKeysId for application/json ContentType.
type PutApiSetupKeysIdJSONRequestBody = PutApiSetupKeysIdJSONBody type PutApiSetupKeysIdJSONRequestBody = SetupKeyRequest
// PutApiUsersIdJSONRequestBody defines body for PutApiUsersId for application/json ContentType. // PutApiUsersIdJSONRequestBody defines body for PutApiUsersId for application/json ContentType.
type PutApiUsersIdJSONRequestBody = PutApiUsersIdJSONBody type PutApiUsersIdJSONRequestBody = UserRequest

View File

@ -34,6 +34,7 @@ func APIHandler(accountManager s.AccountManager, authIssuer string, authAudience
keysHandler := NewSetupKeysHandler(accountManager, authAudience) keysHandler := NewSetupKeysHandler(accountManager, authAudience)
userHandler := NewUserHandler(accountManager, authAudience) userHandler := NewUserHandler(accountManager, authAudience)
routesHandler := NewRoutes(accountManager, authAudience) routesHandler := NewRoutes(accountManager, authAudience)
nameserversHandler := NewNameservers(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).
@ -67,6 +68,13 @@ func APIHandler(accountManager s.AccountManager, authIssuer string, authAudience
apiHandler.HandleFunc("/api/routes/{id}", routesHandler.GetRouteHandler).Methods("GET", "OPTIONS") apiHandler.HandleFunc("/api/routes/{id}", routesHandler.GetRouteHandler).Methods("GET", "OPTIONS")
apiHandler.HandleFunc("/api/routes/{id}", routesHandler.DeleteRouteHandler).Methods("DELETE", "OPTIONS") apiHandler.HandleFunc("/api/routes/{id}", routesHandler.DeleteRouteHandler).Methods("DELETE", "OPTIONS")
apiHandler.HandleFunc("/api/dns/nameservers", nameserversHandler.GetAllNameserversHandler).Methods("GET", "OPTIONS")
apiHandler.HandleFunc("/api/dns/nameservers", nameserversHandler.CreateNameserverGroupHandler).Methods("POST", "OPTIONS")
apiHandler.HandleFunc("/api/dns/nameservers/{id}", nameserversHandler.UpdateNameserverGroupHandler).Methods("PUT", "OPTIONS")
apiHandler.HandleFunc("/api/dns/nameservers/{id}", nameserversHandler.PatchNameserverGroupHandler).Methods("PATCH", "OPTIONS")
apiHandler.HandleFunc("/api/dns/nameservers/{id}", nameserversHandler.GetNameserverGroupHandler).Methods("GET", "OPTIONS")
apiHandler.HandleFunc("/api/dns/nameservers/{id}", nameserversHandler.DeleteNameserverGroupHandler).Methods("DELETE", "OPTIONS")
return apiHandler, nil return apiHandler, nil
} }

View File

@ -0,0 +1,286 @@
package http
import (
"encoding/json"
"fmt"
"github.com/gorilla/mux"
nbdns "github.com/netbirdio/netbird/dns"
"github.com/netbirdio/netbird/management/server"
"github.com/netbirdio/netbird/management/server/http/api"
"github.com/netbirdio/netbird/management/server/jwtclaims"
log "github.com/sirupsen/logrus"
"net/http"
)
// Nameservers is the nameserver group handler of the account
type Nameservers struct {
jwtExtractor jwtclaims.ClaimsExtractor
accountManager server.AccountManager
authAudience string
}
// NewNameservers returns a new instance of Nameservers handler
func NewNameservers(accountManager server.AccountManager, authAudience string) *Nameservers {
return &Nameservers{
accountManager: accountManager,
authAudience: authAudience,
jwtExtractor: *jwtclaims.NewClaimsExtractor(nil),
}
}
// GetAllNameserversHandler returns the list of nameserver groups for the account
func (h *Nameservers) GetAllNameserversHandler(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
}
nsGroups, err := h.accountManager.ListNameServerGroups(account.Id)
if err != nil {
toHTTPError(err, w)
return
}
apiNameservers := make([]*api.NameserverGroup, 0)
for _, r := range nsGroups {
apiNameservers = append(apiNameservers, toNameserverGroupResponse(r))
}
writeJSONObject(w, apiNameservers)
}
// CreateNameserverGroupHandler handles nameserver group creation request
func (h *Nameservers) CreateNameserverGroupHandler(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
}
var req api.PostApiDnsNameserversJSONRequestBody
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
nsList, err := toServerNSList(req.Nameservers)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
nsGroup, err := h.accountManager.CreateNameServerGroup(account.Id, req.Name, req.Description, nsList, req.Groups, req.Enabled)
if err != nil {
toHTTPError(err, w)
return
}
resp := toNameserverGroupResponse(nsGroup)
writeJSONObject(w, &resp)
}
// UpdateNameserverGroupHandler handles update to a nameserver group identified by a given ID
func (h *Nameservers) UpdateNameserverGroupHandler(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
}
nsGroupID := mux.Vars(r)["id"]
if len(nsGroupID) == 0 {
http.Error(w, "invalid nameserver group ID", http.StatusBadRequest)
return
}
var req api.PutApiDnsNameserversIdJSONRequestBody
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
nsList, err := toServerNSList(req.Nameservers)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
updatedNSGroup := &nbdns.NameServerGroup{
ID: nsGroupID,
Name: req.Name,
Description: req.Description,
NameServers: nsList,
Groups: req.Groups,
Enabled: req.Enabled,
}
err = h.accountManager.SaveNameServerGroup(account.Id, updatedNSGroup)
if err != nil {
toHTTPError(err, w)
return
}
resp := toNameserverGroupResponse(updatedNSGroup)
writeJSONObject(w, &resp)
}
// PatchNameserverGroupHandler handles patch updates to a nameserver group identified by a given ID
func (h *Nameservers) PatchNameserverGroupHandler(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
}
nsGroupID := mux.Vars(r)["id"]
if len(nsGroupID) == 0 {
http.Error(w, "invalid nameserver group ID", http.StatusBadRequest)
return
}
var req api.PatchApiDnsNameserversIdJSONRequestBody
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
var operations []server.NameServerGroupUpdateOperation
for _, patch := range req {
if patch.Op != api.NameserverGroupPatchOperationOpReplace {
http.Error(w, fmt.Sprintf("nameserver groups only accepts replace operations, got %s", patch.Op),
http.StatusBadRequest)
return
}
switch patch.Path {
case api.NameserverGroupPatchOperationPathName:
operations = append(operations, server.NameServerGroupUpdateOperation{
Type: server.UpdateNameServerGroupName,
Values: patch.Value,
})
case api.NameserverGroupPatchOperationPathDescription:
operations = append(operations, server.NameServerGroupUpdateOperation{
Type: server.UpdateNameServerGroupDescription,
Values: patch.Value,
})
case api.NameserverGroupPatchOperationPathNameservers:
operations = append(operations, server.NameServerGroupUpdateOperation{
Type: server.UpdateNameServerGroupNameServers,
Values: patch.Value,
})
case api.NameserverGroupPatchOperationPathGroups:
operations = append(operations, server.NameServerGroupUpdateOperation{
Type: server.UpdateNameServerGroupGroups,
Values: patch.Value,
})
case api.NameserverGroupPatchOperationPathEnabled:
operations = append(operations, server.NameServerGroupUpdateOperation{
Type: server.UpdateNameServerGroupEnabled,
Values: patch.Value,
})
default:
http.Error(w, "invalid patch path", http.StatusBadRequest)
return
}
}
updatedNSGroup, err := h.accountManager.UpdateNameServerGroup(account.Id, nsGroupID, operations)
if err != nil {
toHTTPError(err, w)
return
}
resp := toNameserverGroupResponse(updatedNSGroup)
writeJSONObject(w, &resp)
}
// DeleteNameserverGroupHandler handles nameserver group deletion request
func (h *Nameservers) DeleteNameserverGroupHandler(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
}
nsGroupID := mux.Vars(r)["id"]
if len(nsGroupID) == 0 {
http.Error(w, "invalid nameserver group ID", http.StatusBadRequest)
return
}
err = h.accountManager.DeleteNameServerGroup(account.Id, nsGroupID)
if err != nil {
toHTTPError(err, w)
return
}
writeJSONObject(w, "")
}
// GetNameserverGroupHandler handles a nameserver group Get request identified by ID
func (h *Nameservers) GetNameserverGroupHandler(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
}
nsGroupID := mux.Vars(r)["id"]
if len(nsGroupID) == 0 {
http.Error(w, "invalid nameserver group ID", http.StatusBadRequest)
return
}
nsGroup, err := h.accountManager.GetNameServerGroup(account.Id, nsGroupID)
if err != nil {
toHTTPError(err, w)
return
}
resp := toNameserverGroupResponse(nsGroup)
writeJSONObject(w, &resp)
}
func toServerNSList(apiNSList []api.Nameserver) ([]nbdns.NameServer, error) {
var nsList []nbdns.NameServer
for _, apiNS := range apiNSList {
parsed, err := nbdns.ParseNameServerURL(fmt.Sprintf("%s://%s:%d", apiNS.NsType, apiNS.Ip, apiNS.Port))
if err != nil {
return nil, err
}
nsList = append(nsList, parsed)
}
return nsList, nil
}
func toNameserverGroupResponse(serverNSGroup *nbdns.NameServerGroup) *api.NameserverGroup {
var nsList []api.Nameserver
for _, ns := range serverNSGroup.NameServers {
apiNS := api.Nameserver{
Ip: ns.IP.String(),
NsType: api.NameserverNsType(ns.NSType.String()),
Port: ns.Port,
}
nsList = append(nsList, apiNS)
}
return &api.NameserverGroup{
Id: serverNSGroup.ID,
Name: serverNSGroup.Name,
Description: serverNSGroup.Description,
Groups: serverNSGroup.Groups,
Nameservers: nsList,
Enabled: serverNSGroup.Enabled,
}
}

View File

@ -0,0 +1,287 @@
package http
import (
"bytes"
"encoding/json"
nbdns "github.com/netbirdio/netbird/dns"
"github.com/netbirdio/netbird/management/server/http/api"
"github.com/stretchr/testify/assert"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"io"
"net/http"
"net/http/httptest"
"net/netip"
"testing"
"github.com/gorilla/mux"
"github.com/netbirdio/netbird/management/server"
"github.com/netbirdio/netbird/management/server/jwtclaims"
"github.com/netbirdio/netbird/management/server/mock_server"
)
const (
existingNSGroupID = "existingNSGroupID"
notFoundNSGroupID = "notFoundNSGroupID"
testNSGroupAccountID = "test_id"
)
var testingNSAccount = &server.Account{
Id: testNSGroupAccountID,
Domain: "hotmail.com",
}
var baseExistingNSGroup = &nbdns.NameServerGroup{
ID: existingNSGroupID,
Name: "super",
Description: "super",
NameServers: []nbdns.NameServer{
{
IP: netip.MustParseAddr("1.1.1.1"),
NSType: nbdns.UDPNameServerType,
Port: nbdns.DefaultDNSPort,
},
{
IP: netip.MustParseAddr("1.1.2.2"),
NSType: nbdns.UDPNameServerType,
Port: nbdns.DefaultDNSPort,
},
},
Groups: []string{"testing"},
Enabled: true,
}
func initNameserversTestData() *Nameservers {
return &Nameservers{
accountManager: &mock_server.MockAccountManager{
GetNameServerGroupFunc: func(accountID, nsGroupID string) (*nbdns.NameServerGroup, error) {
if nsGroupID == existingNSGroupID {
return baseExistingNSGroup.Copy(), nil
}
return nil, status.Errorf(codes.NotFound, "nameserver group with ID %s not found", nsGroupID)
},
CreateNameServerGroupFunc: func(accountID string, name, description string, nameServerList []nbdns.NameServer, groups []string, enabled bool) (*nbdns.NameServerGroup, error) {
return &nbdns.NameServerGroup{
ID: existingNSGroupID,
Name: name,
Description: description,
NameServers: nameServerList,
Groups: groups,
Enabled: enabled,
}, nil
},
DeleteNameServerGroupFunc: func(accountID, nsGroupID string) error {
return nil
},
SaveNameServerGroupFunc: func(accountID string, nsGroupToSave *nbdns.NameServerGroup) error {
if nsGroupToSave.ID == existingNSGroupID {
return nil
}
return status.Errorf(codes.NotFound, "nameserver group with ID %s was not found", nsGroupToSave.ID)
},
UpdateNameServerGroupFunc: func(accountID, nsGroupID string, operations []server.NameServerGroupUpdateOperation) (*nbdns.NameServerGroup, error) {
nsGroupToUpdate := baseExistingNSGroup.Copy()
if nsGroupID != nsGroupToUpdate.ID {
return nil, status.Errorf(codes.NotFound, "nameserver group ID %s no longer exists", nsGroupID)
}
for _, operation := range operations {
switch operation.Type {
case server.UpdateNameServerGroupName:
nsGroupToUpdate.Name = operation.Values[0]
case server.UpdateNameServerGroupDescription:
nsGroupToUpdate.Description = operation.Values[0]
case server.UpdateNameServerGroupNameServers:
var parsedNSList []nbdns.NameServer
for _, nsURL := range operation.Values {
parsed, err := nbdns.ParseNameServerURL(nsURL)
if err != nil {
return nil, err
}
parsedNSList = append(parsedNSList, parsed)
}
nsGroupToUpdate.NameServers = parsedNSList
}
}
return nsGroupToUpdate, nil
},
GetAccountWithAuthorizationClaimsFunc: func(_ jwtclaims.AuthorizationClaims) (*server.Account, error) {
return testingNSAccount, nil
},
},
authAudience: "",
jwtExtractor: jwtclaims.ClaimsExtractor{
ExtractClaimsFromRequestContext: func(r *http.Request, authAudiance string) jwtclaims.AuthorizationClaims {
return jwtclaims.AuthorizationClaims{
UserId: "test_user",
Domain: "hotmail.com",
AccountId: testNSGroupAccountID,
}
},
},
}
}
func TestNameserversHandlers(t *testing.T) {
tt := []struct {
name string
expectedStatus int
expectedBody bool
expectedNSGroup *api.NameserverGroup
requestType string
requestPath string
requestBody io.Reader
}{
{
name: "Get Existing Nameserver Group",
requestType: http.MethodGet,
requestPath: "/api/dns/nameservers/" + existingNSGroupID,
expectedStatus: http.StatusOK,
expectedBody: true,
expectedNSGroup: toNameserverGroupResponse(baseExistingNSGroup),
},
{
name: "Get Not Existing Nameserver Group",
requestType: http.MethodGet,
requestPath: "/api/dns/nameservers/" + notFoundNSGroupID,
expectedStatus: http.StatusNotFound,
},
{
name: "POST OK",
requestType: http.MethodPost,
requestPath: "/api/dns/nameservers",
requestBody: bytes.NewBuffer(
[]byte("{\"name\":\"name\",\"Description\":\"Post\",\"nameservers\":[{\"ip\":\"1.1.1.1\",\"ns_type\":\"udp\",\"port\":53}],\"groups\":[\"group\"],\"enabled\":true}")),
expectedStatus: http.StatusOK,
expectedBody: true,
expectedNSGroup: &api.NameserverGroup{
Id: existingNSGroupID,
Name: "name",
Description: "Post",
Nameservers: []api.Nameserver{
{
Ip: "1.1.1.1",
NsType: "udp",
Port: 53,
},
},
Groups: []string{"group"},
Enabled: true,
},
},
{
name: "POST Invalid Nameserver",
requestType: http.MethodPost,
requestPath: "/api/dns/nameservers",
requestBody: bytes.NewBuffer(
[]byte("{\"name\":\"name\",\"Description\":\"Post\",\"nameservers\":[{\"ip\":\"1000\",\"ns_type\":\"udp\",\"port\":53}],\"groups\":[\"group\"],\"enabled\":true}")),
expectedStatus: http.StatusBadRequest,
expectedBody: false,
},
{
name: "PUT OK",
requestType: http.MethodPut,
requestPath: "/api/dns/nameservers/" + existingNSGroupID,
requestBody: bytes.NewBuffer(
[]byte("{\"name\":\"name\",\"Description\":\"Post\",\"nameservers\":[{\"ip\":\"1.1.1.1\",\"ns_type\":\"udp\",\"port\":53}],\"groups\":[\"group\"],\"enabled\":true}")),
expectedStatus: http.StatusOK,
expectedBody: true,
expectedNSGroup: &api.NameserverGroup{
Id: existingNSGroupID,
Name: "name",
Description: "Post",
Nameservers: []api.Nameserver{
{
Ip: "1.1.1.1",
NsType: "udp",
Port: 53,
},
},
Groups: []string{"group"},
Enabled: true,
},
},
{
name: "PUT Not Existing Nameserver Group",
requestType: http.MethodPut,
requestPath: "/api/dns/nameservers/" + notFoundNSGroupID,
requestBody: bytes.NewBuffer(
[]byte("{\"name\":\"name\",\"Description\":\"Post\",\"nameservers\":[{\"ip\":\"1.1.1.1\",\"ns_type\":\"udp\",\"port\":53}],\"groups\":[\"group\"],\"enabled\":true}")),
expectedStatus: http.StatusNotFound,
expectedBody: false,
},
{
name: "PUT Invalid Nameserver",
requestType: http.MethodPut,
requestPath: "/api/dns/nameservers/" + notFoundNSGroupID,
requestBody: bytes.NewBuffer(
[]byte("{\"name\":\"name\",\"Description\":\"Post\",\"nameservers\":[{\"ip\":\"100\",\"ns_type\":\"udp\",\"port\":53}],\"groups\":[\"group\"],\"enabled\":true}")),
expectedStatus: http.StatusBadRequest,
expectedBody: false,
},
{
name: "PATCH OK",
requestType: http.MethodPatch,
requestPath: "/api/dns/nameservers/" + existingNSGroupID,
requestBody: bytes.NewBufferString("[{\"op\":\"replace\",\"path\":\"description\",\"value\":[\"NewDesc\"]}]"),
expectedStatus: http.StatusOK,
expectedBody: true,
expectedNSGroup: &api.NameserverGroup{
Id: existingNSGroupID,
Name: baseExistingNSGroup.Name,
Description: "NewDesc",
Nameservers: toNameserverGroupResponse(baseExistingNSGroup).Nameservers,
Groups: baseExistingNSGroup.Groups,
Enabled: baseExistingNSGroup.Enabled,
},
},
{
name: "PATCH Invalid Nameserver Group OK",
requestType: http.MethodPatch,
requestPath: "/api/dns/nameservers/" + notFoundRouteID,
requestBody: bytes.NewBufferString("[{\"op\":\"replace\",\"path\":\"description\",\"value\":[\"NewDesc\"]}]"),
expectedStatus: http.StatusNotFound,
expectedBody: false,
},
}
p := initNameserversTestData()
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/dns/nameservers/{id}", p.GetNameserverGroupHandler).Methods("GET")
router.HandleFunc("/api/dns/nameservers", p.CreateNameserverGroupHandler).Methods("POST")
router.HandleFunc("/api/dns/nameservers/{id}", p.DeleteNameserverGroupHandler).Methods("DELETE")
router.HandleFunc("/api/dns/nameservers/{id}", p.UpdateNameserverGroupHandler).Methods("PUT")
router.HandleFunc("/api/dns/nameservers/{id}", p.PatchNameserverGroupHandler).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.NameserverGroup{}
if err = json.Unmarshal(content, &got); err != nil {
t.Fatalf("Sent content is not in correct json format; %v", err)
}
assert.Equal(t, tc.expectedNSGroup, got)
})
}
}

View File

@ -123,7 +123,7 @@ func (h *Routes) UpdateRouteHandler(w http.ResponseWriter, r *http.Request) {
return return
} }
var req api.PutApiRoutesIdJSONBody var req api.PutApiRoutesIdJSONRequestBody
if err := json.NewDecoder(r.Body).Decode(&req); err != nil { if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest) http.Error(w, err.Error(), http.StatusBadRequest)
return return

View File

@ -6,11 +6,14 @@ import (
"fmt" "fmt"
"github.com/netbirdio/netbird/management/server" "github.com/netbirdio/netbird/management/server"
"github.com/netbirdio/netbird/management/server/jwtclaims" "github.com/netbirdio/netbird/management/server/jwtclaims"
log "github.com/sirupsen/logrus"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"net/http" "net/http"
"time" "time"
) )
//writeJSONObject simply writes object to the HTTP reponse in JSON format // writeJSONObject simply writes object to the HTTP reponse in JSON format
func writeJSONObject(w http.ResponseWriter, obj interface{}) { func writeJSONObject(w http.ResponseWriter, obj interface{}) {
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
w.Header().Set("Content-Type", "application/json; charset=UTF-8") w.Header().Set("Content-Type", "application/json; charset=UTF-8")
@ -21,7 +24,7 @@ func writeJSONObject(w http.ResponseWriter, obj interface{}) {
} }
} }
//Duration is used strictly for JSON requests/responses due to duration marshalling issues // Duration is used strictly for JSON requests/responses due to duration marshalling issues
type Duration struct { type Duration struct {
time.Duration time.Duration
} }
@ -64,3 +67,25 @@ func getJWTAccount(accountManager server.AccountManager,
return account, nil return account, nil
} }
func toHTTPError(err error, w http.ResponseWriter) {
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
}
unhandledMSG := fmt.Sprintf("got unhandled error code, error: %s", errStatus.String())
log.Error(unhandledMSG)
http.Error(w, unhandledMSG, http.StatusInternalServerError)
}