From fd223e3d4a6219d9d1bb602c694bb85f8b0201a6 Mon Sep 17 00:00:00 2001 From: afeiszli Date: Sat, 24 Jul 2021 16:13:24 -0400 Subject: [PATCH] refactored nodes model --- controllers/common.go | 165 +-------------------- controllers/networkHttpController.go | 4 +- controllers/nodeGrpcController.go | 12 +- controllers/nodeHttpController.go | 69 ++++----- functions/helpers.go | 20 --- models/network.go | 109 +++++++++----- models/node.go | 213 +++++++++++++++++---------- models/validation.go | 16 ++ test/nodecreate.sh | 4 +- 9 files changed, 269 insertions(+), 343 deletions(-) create mode 100644 models/validation.go diff --git a/controllers/common.go b/controllers/common.go index 20c856a4..b26493ad 100644 --- a/controllers/common.go +++ b/controllers/common.go @@ -5,7 +5,6 @@ import ( "fmt" "time" - "github.com/go-playground/validator/v10" "github.com/gravitl/netmaker/database" "github.com/gravitl/netmaker/functions" "github.com/gravitl/netmaker/models" @@ -70,143 +69,6 @@ func GetExtPeersList(networkName string, macaddress string) ([]models.ExtPeersRe return peers, err } -func ValidateNodeCreate(networkName string, node models.Node) error { - v := validator.New() - _ = v.RegisterValidation("macaddress_unique", func(fl validator.FieldLevel) bool { - isFieldUnique, _ := functions.IsMacAddressUnique(node.MacAddress, networkName) - return isFieldUnique - }) - _ = v.RegisterValidation("network_exists", func(fl validator.FieldLevel) bool { - _, err := node.GetNetwork() - return err == nil - }) - _ = v.RegisterValidation("in_charset", func(fl validator.FieldLevel) bool { - isgood := functions.NameInNodeCharSet(node.Name) - return isgood - }) - err := v.Struct(node) - - if err != nil { - for _, e := range err.(validator.ValidationErrors) { - fmt.Println(e) - } - } - return err -} - -func ValidateNodeUpdate(networkName string, node models.NodeUpdate) error { - v := validator.New() - _ = v.RegisterValidation("network_exists", func(fl validator.FieldLevel) bool { - _, err := functions.GetParentNetwork(networkName) - return err == nil - }) - _ = v.RegisterValidation("in_charset", func(fl validator.FieldLevel) bool { - isgood := functions.NameInNodeCharSet(node.Name) - return isgood - }) - err := v.Struct(node) - if err != nil { - for _, e := range err.(validator.ValidationErrors) { - fmt.Println(e) - } - } - return err -} - -func UpdateNode(nodechange models.NodeUpdate, node models.Node) (models.Node, error) { - //Question: Is there a better way of doing this than a bunch of "if" statements? probably... - //Eventually, lets have a better way to check if any of the fields are filled out... - oldkey, err := functions.GetRecordKey(node.MacAddress, node.Network) - if err != nil { - return node, err - } - - notifynetwork := false - - if nodechange.Address != "" { - node.Address = nodechange.Address - notifynetwork = true - } - if nodechange.Address6 != "" { - node.Address6 = nodechange.Address6 - notifynetwork = true - } - if nodechange.Name != "" { - node.Name = nodechange.Name - } - if nodechange.LocalAddress != "" { - node.LocalAddress = nodechange.LocalAddress - } - if nodechange.ListenPort != 0 { - node.ListenPort = nodechange.ListenPort - } - if nodechange.ExpirationDateTime != 0 { - node.ExpirationDateTime = nodechange.ExpirationDateTime - } - if nodechange.PostDown != "" { - node.PostDown = nodechange.PostDown - } - if nodechange.Interface != "" { - node.Interface = nodechange.Interface - } - if nodechange.PostUp != "" { - node.PostUp = nodechange.PostUp - } - if nodechange.AccessKey != "" { - node.AccessKey = nodechange.AccessKey - } - if nodechange.Endpoint != "" { - node.Endpoint = nodechange.Endpoint - notifynetwork = true - } - if nodechange.SaveConfig != nil { - node.SaveConfig = nodechange.SaveConfig - } - if nodechange.PersistentKeepalive != 0 { - node.PersistentKeepalive = nodechange.PersistentKeepalive - } - if nodechange.Password != "" { - err := bcrypt.CompareHashAndPassword([]byte(nodechange.Password), []byte(node.Password)) - if err != nil && nodechange.Password != node.Password { - hash, err := bcrypt.GenerateFromPassword([]byte(nodechange.Password), 5) - if err != nil { - return node, err - } - nodechange.Password = string(hash) - node.Password = nodechange.Password - } - } - if nodechange.MacAddress != "" { - node.MacAddress = nodechange.MacAddress - } - if nodechange.PublicKey != "" { - node.PublicKey = nodechange.PublicKey - node.KeyUpdateTimeStamp = time.Now().Unix() - notifynetwork = true - } - newkey, err := functions.GetRecordKey(node.MacAddress, node.Network) - if err != nil { - return node, err - } - if oldkey != newkey { - if err := database.DeleteRecord(database.NODES_TABLE_NAME, oldkey); err != nil { - return models.Node{}, err - } - } - value, err := json.Marshal(&node) - if err != nil { - return models.Node{}, err - } - err = database.Insert(newkey, string(value), database.NODES_TABLE_NAME) - if notifynetwork { - err = SetNetworkNodesLastModified(node.Network) - } - if servercfg.IsDNSMode() { - err = SetDNS() - } - return node, err -} - func DeleteNode(macaddress string, network string) error { key, err := functions.GetRecordKey(macaddress, network) @@ -270,7 +132,7 @@ func GetIntClient(clientid string) (models.IntClient, error) { func CreateNode(node models.Node, networkName string) (models.Node, error) { - //encrypt that password so we never see it again + //encrypt that password so we never see it hash, err := bcrypt.GenerateFromPassword([]byte(node.Password), 5) if err != nil { @@ -281,37 +143,17 @@ func CreateNode(node models.Node, networkName string) (models.Node, error) { node.Network = networkName - //node.SetDefaults() - //Umm, why am I doing this again? - //TODO: Why am I using a local function instead of the struct function? I really dont know. - //I think I thought it didn't work but uhhh...idk node.SetDefaults() - - //Another DB call here...Inefficient - //Anyways, this scrolls through all the IP Addresses in the network range and checks against nodes - //until one is open and then returns it node.Address, err = functions.UniqueAddress(networkName) if err != nil { return node, err } - node.Address6, err = functions.UniqueAddress6(networkName) - if err != nil { return node, err } - - //IDK why these aren't a part of "set defaults. Pretty dumb. - //TODO: This is dumb. Consolidate and fix. - node.SetLastModified() - node.SetDefaultName() - node.SetLastCheckIn() - node.SetLastPeerUpdate() - node.KeyUpdateTimeStamp = time.Now().Unix() - //Create a JWT for the node tokenString, _ := functions.CreateJWT(node.MacAddress, networkName) - if tokenString == "" { //returnErrorResponse(w, r, errorResponse) return node, err @@ -324,18 +166,13 @@ func CreateNode(node models.Node, networkName string) (models.Node, error) { if err != nil { return node, err } - err = database.Insert(key, string(nodebytes), database.NODES_TABLE_NAME) if err != nil { return node, err } - //return response for if node is pending if !node.IsPending { - functions.DecrimentKey(node.Network, node.AccessKey) - } - SetNetworkNodesLastModified(node.Network) if servercfg.IsDNSMode() { err = SetDNS() diff --git a/controllers/networkHttpController.go b/controllers/networkHttpController.go index 5f785640..3825ed27 100644 --- a/controllers/networkHttpController.go +++ b/controllers/networkHttpController.go @@ -275,14 +275,12 @@ func updateNetwork(w http.ResponseWriter, r *http.Request) { returnErrorResponse(w, r, formatError(err, "internal")) return } - var newNetwork models.Network err = json.NewDecoder(r.Body).Decode(&newNetwork) if err != nil { returnErrorResponse(w, r, formatError(err, "badrequest")) return } - rangeupdate, localrangeupdate, err := network.Update(&newNetwork) if err != nil { returnErrorResponse(w, r, formatError(err, "badrequest")) @@ -405,7 +403,7 @@ func CreateNetwork(network models.Network) error { network.SetNetworkLastModified() network.KeyUpdateTimeStamp = time.Now().Unix() - err := network.Validate() + err := network.Validate(false) if err != nil { //returnErrorResponse(w, r, formatError(err, "badrequest")) return err diff --git a/controllers/nodeGrpcController.go b/controllers/nodeGrpcController.go index 74600577..dd84afa6 100644 --- a/controllers/nodeGrpcController.go +++ b/controllers/nodeGrpcController.go @@ -139,7 +139,7 @@ func (s *NodeServiceServer) CreateNode(ctx context.Context, req *nodepb.CreateNo ListenPort: data.GetListenport(), } - err := ValidateNodeCreate(node.Network, node) + err := node.Validate(false) if err != nil { // return internal gRPC error to be handled later @@ -257,7 +257,7 @@ func (s *NodeServiceServer) UpdateNode(ctx context.Context, req *nodepb.UpdateNo // Get the node data from the request data := req.GetNode() // Now we have to convert this into a NodeItem type to convert into BSON - nodechange := models.NodeUpdate{ + newnode := models.Node{ // ID: primitive.NilObjectID, MacAddress: data.GetMacaddress(), Name: data.GetName(), @@ -277,11 +277,11 @@ func (s *NodeServiceServer) UpdateNode(ctx context.Context, req *nodepb.UpdateNo } // Convert the Id string to a MongoDB ObjectId - macaddress := nodechange.MacAddress - networkName := nodechange.Network + macaddress := newnode.MacAddress + networkName := newnode.Network network, _ := functions.GetParentNetwork(networkName) - err := ValidateNodeUpdate(networkName, nodechange) + err := newnode.Validate(true) if err != nil { return nil, err } @@ -294,7 +294,7 @@ func (s *NodeServiceServer) UpdateNode(ctx context.Context, req *nodepb.UpdateNo ) } - newnode, err := UpdateNode(nodechange, node) + err = node.Update(&newnode) if err != nil { return nil, status.Errorf( diff --git a/controllers/nodeHttpController.go b/controllers/nodeHttpController.go index 59fb70d5..8bafbbf7 100644 --- a/controllers/nodeHttpController.go +++ b/controllers/nodeHttpController.go @@ -3,6 +3,7 @@ package controller import ( "encoding/json" "errors" + "fmt" "log" "net/http" "strings" @@ -12,6 +13,7 @@ import ( "github.com/gravitl/netmaker/database" "github.com/gravitl/netmaker/functions" "github.com/gravitl/netmaker/models" + "github.com/gravitl/netmaker/servercfg" "golang.org/x/crypto/bcrypt" ) @@ -310,23 +312,34 @@ func GetNetworkNodes(network string) ([]models.Node, error) { //Not quite sure if this is necessary. Probably necessary based on front end but may want to review after iteration 1 if it's being used or not func getAllNodes(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") - nodes, err := getUsersNodes(r.Header.Get("user")) + user, err := functions.GetUser(r.Header.Get("user")) if err != nil { returnErrorResponse(w, r, formatError(err, "internal")) return } + var nodes []models.Node + if user.IsAdmin { + nodes, err = models.GetAllNodes() + if err != nil { + returnErrorResponse(w, r, formatError(err, "internal")) + return + } + } else { + nodes, err = getUsersNodes(user) + if err != nil { + returnErrorResponse(w, r, formatError(err, "internal")) + return + } + } //Return all the nodes in JSON format functions.PrintUserLog(r.Header.Get("user"), "fetched nodes", 2) w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(nodes) } -func getUsersNodes(username string) ([]models.Node, error) { +func getUsersNodes(user models.User) ([]models.Node, error) { var nodes []models.Node - user, err := functions.GetUser(username) - if err != nil { - return nodes, err - } + var err error for _, networkName := range user.Networks { tmpNodes, err := GetNetworkNodes(networkName) if err != nil { @@ -416,9 +429,6 @@ func getLastModified(w http.ResponseWriter, r *http.Request) { json.NewEncoder(w).Encode(network.NodesLastModified) } -//This one's a doozy -//To create a node -//Must have valid key and be unique func createNode(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") @@ -427,12 +437,7 @@ func createNode(w http.ResponseWriter, r *http.Request) { var errorResponse = models.ErrorResponse{ Code: http.StatusInternalServerError, Message: "W1R3: It's not you it's me.", } - networkName := params["network"] - - //Check if network exists first - //TODO: This is inefficient. Let's find a better way. - //Just a few rows down we grab the network anyway networkexists, err := functions.NetworkExists(networkName) if err != nil { @@ -481,7 +486,7 @@ func createNode(w http.ResponseWriter, r *http.Request) { } } - err = ValidateNodeCreate(networkName, node) + err = node.Validate(false) if err != nil { returnErrorResponse(w, r, formatError(err, "badrequest")) return @@ -759,16 +764,11 @@ func DeleteIngressGateway(network, macaddress string) (models.Node, error) { } func updateNode(w http.ResponseWriter, r *http.Request) { - log.Println("update reached.") w.Header().Set("Content-Type", "application/json") var params = mux.Vars(r) - //Get id from parameters - //id, _ := primitive.ObjectIDFromHex(params["id"]) - var node models.Node - log.Println("Called", params["network"], params["macaddress"]) //start here node, err := functions.GetNodeByMacAddress(params["network"], params["macaddress"]) if err != nil { @@ -776,29 +776,32 @@ func updateNode(w http.ResponseWriter, r *http.Request) { return } - var nodechange models.NodeUpdate - + var newNode models.Node // we decode our body request params - _ = json.NewDecoder(r.Body).Decode(&nodechange) - if nodechange.Network == "" { - nodechange.Network = node.Network - } - if nodechange.MacAddress == "" { - nodechange.MacAddress = node.MacAddress - } - err = ValidateNodeUpdate(params["network"], nodechange) + err = json.NewDecoder(r.Body).Decode(&newNode) if err != nil { returnErrorResponse(w, r, formatError(err, "badrequest")) return } - - node, err = UpdateNode(nodechange, node) + err = node.Update(&newNode) if err != nil { returnErrorResponse(w, r, formatError(err, "internal")) return } + + if err = SetNetworkNodesLastModified(node.Network); err != nil { + fmt.Println(err) + } + if servercfg.IsDNSMode() { + err = SetDNS() + } + if err != nil { + returnErrorResponse(w, r, formatError(err, "internal")) + return + } + functions.PrintUserLog(r.Header.Get("user"), "updated node "+node.MacAddress+" on network "+node.Network, 1) w.WriteHeader(http.StatusOK) - json.NewEncoder(w).Encode(node) + json.NewEncoder(w).Encode(newNode) } //Delete a node diff --git a/functions/helpers.go b/functions/helpers.go index b1a85da7..33c0bcb2 100644 --- a/functions/helpers.go +++ b/functions/helpers.go @@ -732,23 +732,3 @@ func Inc(ip net.IP) { } } } - -func GetAllNodes() ([]models.Node, error) { - var nodes []models.Node - - collection, err := database.FetchRecords(database.NODES_TABLE_NAME) - if err != nil { - return []models.Node{}, err - } - - for _, value := range collection { - var node models.Node - if err := json.Unmarshal([]byte(value), &node); err != nil { - return []models.Node{}, err - } - // add node to our array - nodes = append(nodes, node) - } - - return nodes, nil -} diff --git a/models/network.go b/models/network.go index 7036f129..d7764a32 100644 --- a/models/network.go +++ b/models/network.go @@ -3,6 +3,8 @@ package models import ( "encoding/json" "errors" + "fmt" + "reflect" "strings" "time" @@ -26,49 +28,56 @@ type Network struct { DefaultPostDown string `json:"defaultpostdown" bson:"defaultpostdown"` KeyUpdateTimeStamp int64 `json:"keyupdatetimestamp" bson:"keyupdatetimestamp"` DefaultKeepalive int32 `json:"defaultkeepalive" bson:"defaultkeepalive" validate:"omitempty,max=1000"` - DefaultSaveConfig string `json:"defaultsaveconfig" bson:"defaultsaveconfig" validate:"regexp=^(yes|no)$"` + DefaultSaveConfig string `json:"defaultsaveconfig" bson:"defaultsaveconfig" validate:"checkyesorno"` AccessKeys []AccessKey `json:"accesskeys" bson:"accesskeys"` - AllowManualSignUp string `json:"allowmanualsignup" bson:"allowmanualsignup" validate:"regexp=^(yes|no)$"` - IsLocal string `json:"islocal" bson:"islocal" validate:"regexp=^(yes|no)$"` - IsDualStack string `json:"isdualstack" bson:"isdualstack" validate:"regexp=^(yes|no)$"` - IsIPv4 string `json:"isipv4" bson:"isipv4" validate:"regexp=^(yes|no)$"` - IsIPv6 string `json:"isipv6" bson:"isipv6" validate:"regexp=^(yes|no)$"` - IsGRPCHub string `json:"isgrpchub" bson:"isgrpchub" validate:"regexp=^(yes|no)$"` + AllowManualSignUp string `json:"allowmanualsignup" bson:"allowmanualsignup" validate:"checkyesorno"` + IsLocal string `json:"islocal" bson:"islocal" validate:"checkyesorno"` + IsDualStack string `json:"isdualstack" bson:"isdualstack" validate:"checkyesorno"` + IsIPv4 string `json:"isipv4" bson:"isipv4" validate:"checkyesorno"` + IsIPv6 string `json:"isipv6" bson:"isipv6" validate:"checkyesorno"` + IsGRPCHub string `json:"isgrpchub" bson:"isgrpchub" validate:"checkyesorno"` LocalRange string `json:"localrange" bson:"localrange" validate:"omitempty,cidr"` DefaultCheckInInterval int32 `json:"checkininterval,omitempty" bson:"checkininterval,omitempty" validate:"omitempty,numeric,min=2,max=100000"` - DefaultUDPHolePunch string `json:"defaultudpholepunch" bson:"defaultudpholepunch" validate:"regexp=^(yes|no)$"` + DefaultUDPHolePunch string `json:"defaultudpholepunch" bson:"defaultudpholepunch" validate:"checkyesorno"` } type SaveData struct { // put sensitive fields here NetID string `json:"netid" bson:"netid" validate:"required,min=1,max=12,netid_valid"` } +const STRING_FIELD_TYPE = "string" +const INT64_FIELD_TYPE = "int64" +const INT32_FIELD_TYPE = "int32" +const ACCESS_KEY_TYPE = "[]AccessKey" + +var FIELD_TYPES = []string{STRING_FIELD_TYPE, INT64_FIELD_TYPE, INT32_FIELD_TYPE, ACCESS_KEY_TYPE} + var FIELDS = map[string][]string{ // "id": {"ID", "string"}, - "addressrange": {"AddressRange", "string"}, - "addressrange6": {"AddressRange6", "string"}, - "displayname": {"DisplayName", "string"}, - "netid": {"NetID", "string"}, - "nodeslastmodified": {"NodesLastModified", "int64"}, - "networklastmodified": {"NetworkLastModified", "int64"}, - "defaultinterface": {"DefaultInterface", "string"}, - "defaultlistenport": {"DefaultListenPort", "int32"}, - "nodelimit": {"NodeLimit", "int32"}, - "defaultpostup": {"DefaultPostUp", "string"}, - "defaultpostdown": {"DefaultPostDown", "string"}, - "keyupdatetimestamp": {"KeyUpdateTimeStamp", "int64"}, - "defaultkeepalive": {"DefaultKeepalive", "int32"}, - "defaultsaveconfig": {"DefaultSaveConfig", "string"}, - "accesskeys": {"AccessKeys", "[]AccessKey"}, - "allowmanualsignup": {"AllowManualSignUp", "string"}, - "islocal": {"IsLocal", "string"}, - "isdualstack": {"IsDualStack", "string"}, - "isipv4": {"IsIPv4", "string"}, - "isipv6": {"IsIPv6", "string"}, - "isgrpchub": {"IsGRPCHub", "string"}, - "localrange": {"LocalRange", "string"}, - "checkininterval": {"DefaultCheckInInterval", "int32"}, - "defaultudpholepunch": {"DefaultUDPHolePunch", "string"}, + "addressrange": {"AddressRange", STRING_FIELD_TYPE}, + "addressrange6": {"AddressRange6", STRING_FIELD_TYPE}, + "displayname": {"DisplayName", STRING_FIELD_TYPE}, + "netid": {"NetID", STRING_FIELD_TYPE}, + "nodeslastmodified": {"NodesLastModified", INT64_FIELD_TYPE}, + "networklastmodified": {"NetworkLastModified", INT64_FIELD_TYPE}, + "defaultinterface": {"DefaultInterface", STRING_FIELD_TYPE}, + "defaultlistenport": {"DefaultListenPort", INT32_FIELD_TYPE}, + "nodelimit": {"NodeLimit", INT32_FIELD_TYPE}, + "defaultpostup": {"DefaultPostUp", STRING_FIELD_TYPE}, + "defaultpostdown": {"DefaultPostDown", STRING_FIELD_TYPE}, + "keyupdatetimestamp": {"KeyUpdateTimeStamp", INT64_FIELD_TYPE}, + "defaultkeepalive": {"DefaultKeepalive", INT32_FIELD_TYPE}, + "defaultsaveconfig": {"DefaultSaveConfig", STRING_FIELD_TYPE}, + "accesskeys": {"AccessKeys", ACCESS_KEY_TYPE}, + "allowmanualsignup": {"AllowManualSignUp", STRING_FIELD_TYPE}, + "islocal": {"IsLocal", STRING_FIELD_TYPE}, + "isdualstack": {"IsDualStack", STRING_FIELD_TYPE}, + "isipv4": {"IsIPv4", STRING_FIELD_TYPE}, + "isipv6": {"IsIPv6", STRING_FIELD_TYPE}, + "isgrpchub": {"IsGRPCHub", STRING_FIELD_TYPE}, + "localrange": {"LocalRange", STRING_FIELD_TYPE}, + "checkininterval": {"DefaultCheckInInterval", INT32_FIELD_TYPE}, + "defaultudpholepunch": {"DefaultUDPHolePunch", STRING_FIELD_TYPE}, } func (network *Network) FieldExists(field string) bool { @@ -162,21 +171,35 @@ func (network *Network) IsNetworkNameUnique() (bool, error) { return isunique, nil } -func (network *Network) Validate() error { +func (network *Network) Validate(isUpdate bool) error { v := validator.New() _ = v.RegisterValidation("netid_valid", func(fl validator.FieldLevel) bool { - isFieldUnique, _ := network.IsNetworkNameUnique() inCharSet := network.NetIDInNetworkCharSet() + if isUpdate { + return inCharSet + } + isFieldUnique, _ := network.IsNetworkNameUnique() return isFieldUnique && inCharSet }) // _ = v.RegisterValidation("displayname_valid", func(fl validator.FieldLevel) bool { isFieldUnique, _ := network.IsNetworkDisplayNameUnique() inCharSet := network.DisplayNameInNetworkCharSet() + if isUpdate { + return inCharSet + } return isFieldUnique && inCharSet }) - + _ = v.RegisterValidation("checkyesorno", func(fl validator.FieldLevel) bool { + return CheckYesOrNo(fl) + }) err := v.Struct(network) + if err != nil { + for _, e := range err.(validator.ValidationErrors) { + fmt.Println(e) + } + } + return err } @@ -241,8 +264,22 @@ func (network *Network) SetDefaults() { } } +func (network *Network) CopyValues(newNetwork *Network, fieldName string) { + reflection := reflect.ValueOf(newNetwork) + value := reflect.Indirect(reflection).FieldByName(FIELDS[fieldName][0]) + if value.IsValid() && len(FIELDS[fieldName]) == 2 { + fieldData := FIELDS[fieldName] + for _, indexVal := range FIELD_TYPES { + if indexVal == fieldData[1] { + currentReflection := reflect.ValueOf(network) + reflect.Indirect(currentReflection).FieldByName(FIELDS[fieldName][0]).Set(value) + } + } + } +} + func (currentNetwork *Network) Update(newNetwork *Network) (bool, bool, error) { - if err := newNetwork.Validate(); err != nil { + if err := newNetwork.Validate(true); err != nil { return false, false, err } if newNetwork.NetID == currentNetwork.NetID { diff --git a/models/node.go b/models/node.go index 322df7da..211f8303 100644 --- a/models/node.go +++ b/models/node.go @@ -2,12 +2,14 @@ package models import ( "encoding/json" + "errors" "math/rand" "net" + "strings" "time" + "github.com/go-playground/validator/v10" "github.com/gravitl/netmaker/database" - "go.mongodb.org/mongo-driver/bson/primitive" ) const charset = "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" @@ -17,74 +19,39 @@ var seededRand *rand.Rand = rand.New( //node struct type Node struct { - ID primitive.ObjectID `json:"_id,omitempty" bson:"_id,omitempty"` - Address string `json:"address" bson:"address" validate:"omitempty,ipv4"` - Address6 string `json:"address6" bson:"address6" validate:"omitempty,ipv6"` - LocalAddress string `json:"localaddress" bson:"localaddress" validate:"omitempty,ip"` - Name string `json:"name" bson:"name" validate:"omitempty,max=12,in_charset"` - ListenPort int32 `json:"listenport" bson:"listenport" validate:"omitempty,numeric,min=1024,max=65535"` - PublicKey string `json:"publickey" bson:"publickey" validate:"required,base64"` - Endpoint string `json:"endpoint" bson:"endpoint" validate:"required,ip"` - PostUp string `json:"postup" bson:"postup"` - PostDown string `json:"postdown" bson:"postdown"` - AllowedIPs []string `json:"allowedips" bson:"allowedips"` - PersistentKeepalive int32 `json:"persistentkeepalive" bson:"persistentkeepalive" validate:"omitempty,numeric,max=1000"` - SaveConfig *bool `json:"saveconfig" bson:"saveconfig"` - AccessKey string `json:"accesskey" bson:"accesskey"` - Interface string `json:"interface" bson:"interface"` - LastModified int64 `json:"lastmodified" bson:"lastmodified"` - KeyUpdateTimeStamp int64 `json:"keyupdatetimestamp" bson:"keyupdatetimestamp"` - ExpirationDateTime int64 `json:"expdatetime" bson:"expdatetime"` - LastPeerUpdate int64 `json:"lastpeerupdate" bson:"lastpeerupdate"` - LastCheckIn int64 `json:"lastcheckin" bson:"lastcheckin"` - MacAddress string `json:"macaddress" bson:"macaddress" validate:"required,mac,macaddress_unique"` - CheckInInterval int32 `json:"checkininterval" bson:"checkininterval"` - Password string `json:"password" bson:"password" validate:"required,min=6"` - Network string `json:"network" bson:"network" validate:"network_exists"` - IsPending bool `json:"ispending" bson:"ispending"` - IsEgressGateway bool `json:"isegressgateway" bson:"isegressgateway"` - IsIngressGateway bool `json:"isingressgateway" bson:"isingressgateway"` - EgressGatewayRanges []string `json:"egressgatewayranges" bson:"egressgatewayranges"` - IngressGatewayRange string `json:"ingressgatewayrange" bson:"ingressgatewayrange"` - PostChanges string `json:"postchanges" bson:"postchanges"` - StaticIP string `json:"staticip" bson:"staticip"` - StaticPubKey string `json:"staticpubkey" bson:"staticpubkey"` -} - -//node update struct --- only validations are different -type NodeUpdate struct { - ID primitive.ObjectID `json:"_id,omitempty" bson:"_id,omitempty"` - Address string `json:"address" bson:"address" validate:"omitempty,ip"` - Address6 string `json:"address6" bson:"address6" validate:"omitempty,ipv6"` - LocalAddress string `json:"localaddress" bson:"localaddress" validate:"omitempty,ip"` - Name string `json:"name" bson:"name" validate:"omitempty,max=12,in_charset"` - ListenPort int32 `json:"listenport" bson:"listenport" validate:"omitempty,numeric,min=1024,max=65535"` - PublicKey string `json:"publickey" bson:"publickey" validate:"omitempty,base64"` - Endpoint string `json:"endpoint" bson:"endpoint" validate:"omitempty,ip"` - PostUp string `json:"postup" bson:"postup"` - PostDown string `json:"postdown" bson:"postdown"` - AllowedIPs []string `json:"allowedips" bson:"allowedips"` - PersistentKeepalive int32 `json:"persistentkeepalive" bson:"persistentkeepalive" validate:"omitempty,numeric,max=1000"` - SaveConfig *bool `json:"saveconfig" bson:"saveconfig"` - AccessKey string `json:"accesskey" bson:"accesskey"` - Interface string `json:"interface" bson:"interface"` - LastModified int64 `json:"lastmodified" bson:"lastmodified"` - KeyUpdateTimeStamp int64 `json:"keyupdatetimestamp" bson:"keyupdatetimestamp"` - ExpirationDateTime int64 `json:"expdatetime" bson:"expdatetime"` - LastPeerUpdate int64 `json:"lastpeerupdate" bson:"lastpeerupdate"` - LastCheckIn int64 `json:"lastcheckin" bson:"lastcheckin"` - MacAddress string `json:"macaddress" bson:"macaddress" validate:"required,mac"` - CheckInInterval int32 `json:"checkininterval" bson:"checkininterval"` - Password string `json:"password" bson:"password" validate:"omitempty,min=5"` - Network string `json:"network" bson:"network" validate:"network_exists"` - IsPending bool `json:"ispending" bson:"ispending"` - IsIngressGateway bool `json:"isingressgateway" bson:"isingressgateway"` - IsEgressGateway bool `json:"isegressgateway" bson:"isegressgateway"` - IngressGatewayRange string `json:"ingressgatewayrange" bson:"ingressgatewayrange"` - EgressGatewayRanges []string `json:"egressgatewayranges" bson:"egressgatewayranges"` - PostChanges string `json:"postchanges" bson:"postchanges"` - StaticIP string `json:"staticip" bson:"staticip"` - StaticPubKey string `json:"staticpubkey" bson:"staticpubkey"` + ID string `json:"id,omitempty" bson:"id,omitempty"` + Address string `json:"address" bson:"address" validate:"omitempty,ipv4"` + Address6 string `json:"address6" bson:"address6" validate:"omitempty,ipv6"` + LocalAddress string `json:"localaddress" bson:"localaddress" validate:"omitempty,ip"` + Name string `json:"name" bson:"name" validate:"omitempty,max=12,in_charset"` + ListenPort int32 `json:"listenport" bson:"listenport" validate:"omitempty,numeric,min=1024,max=65535"` + PublicKey string `json:"publickey" bson:"publickey" validate:"required,base64"` + Endpoint string `json:"endpoint" bson:"endpoint" validate:"required,ip"` + PostUp string `json:"postup" bson:"postup"` + PostDown string `json:"postdown" bson:"postdown"` + AllowedIPs []string `json:"allowedips" bson:"allowedips"` + PersistentKeepalive int32 `json:"persistentkeepalive" bson:"persistentkeepalive" validate:"omitempty,numeric,max=1000"` + SaveConfig string `json:"saveconfig" bson:"saveconfig" validate:"checkyesorno"` + AccessKey string `json:"accesskey" bson:"accesskey"` + Interface string `json:"interface" bson:"interface"` + LastModified int64 `json:"lastmodified" bson:"lastmodified"` + KeyUpdateTimeStamp int64 `json:"keyupdatetimestamp" bson:"keyupdatetimestamp"` + ExpirationDateTime int64 `json:"expdatetime" bson:"expdatetime"` + LastPeerUpdate int64 `json:"lastpeerupdate" bson:"lastpeerupdate"` + LastCheckIn int64 `json:"lastcheckin" bson:"lastcheckin"` + MacAddress string `json:"macaddress" bson:"macaddress" validate:"required,mac,macaddress_unique"` + CheckInInterval int32 `json:"checkininterval" bson:"checkininterval"` + Password string `json:"password" bson:"password" validate:"required,min=6"` + Network string `json:"network" bson:"network" validate:"network_exists"` + IsPending bool `json:"ispending" bson:"ispending"` + IsEgressGateway bool `json:"isegressgateway" bson:"isegressgateway"` + IsIngressGateway bool `json:"isingressgateway" bson:"isingressgateway"` + EgressGatewayRanges []string `json:"egressgatewayranges" bson:"egressgatewayranges"` + IngressGatewayRange string `json:"ingressgatewayrange" bson:"ingressgatewayrange"` + PostChanges string `json:"postchanges" bson:"postchanges"` + StaticIP string `json:"staticip" bson:"staticip"` + StaticPubKey string `json:"staticpubkey" bson:"staticpubkey"` + UDPHolePunch string `json:"udpholepunch" bson:"udpholepunch" validate:"checkyesorno"` } //TODO: @@ -101,6 +68,10 @@ func (node *Node) SetLastPeerUpdate() { node.LastPeerUpdate = time.Now().Unix() } +func (node *Node) SetID() { + node.ID = node.MacAddress + "###" + node.Network +} + func (node *Node) SetExpirationDateTime() { node.ExpirationDateTime = time.Unix(33174902665, 0).Unix() } @@ -138,16 +109,9 @@ func (node *Node) SetDefaults() { if node.ListenPort == 0 { node.ListenPort = parentNetwork.DefaultListenPort } - if node.PostDown == "" { - //Empty because we dont set it - //may want to set it to something in the future - } - //TODO: This is dumb and doesn't work - //Need to change - if node.SaveConfig == nil { + if node.SaveConfig == "" { if parentNetwork.DefaultSaveConfig != "" { - defaultsave := parentNetwork.DefaultSaveConfig == "yes" - node.SaveConfig = &defaultsave + node.SaveConfig = parentNetwork.DefaultSaveConfig } } if node.Interface == "" { @@ -166,9 +130,35 @@ func (node *Node) SetDefaults() { if node.StaticPubKey == "" { node.StaticPubKey = "no" } - + if node.UDPHolePunch == "" { + node.UDPHolePunch = parentNetwork.DefaultUDPHolePunch + } node.CheckInInterval = parentNetwork.DefaultCheckInInterval + node.SetLastModified() + node.SetDefaultName() + node.SetLastCheckIn() + node.SetLastPeerUpdate() + node.SetID() + node.KeyUpdateTimeStamp = time.Now().Unix() +} + +func (currentNode *Node) Update(newNode *Node) error { + if err := newNode.Validate(true); err != nil { + return err + } + newNode.SetID() + if newNode.ID == currentNode.ID { + if data, err := json.Marshal(newNode); err != nil { + return err + } else { + newNode.SetLastModified() + err = database.Insert(newNode.ID, string(data), database.NODES_TABLE_NAME) + return err + } + } + // copy values + return errors.New("failed to update node " + newNode.MacAddress + ", cannot change macaddress.") } func StringWithCharset(length int, charset string) string { @@ -185,3 +175,68 @@ func StringWithCharset(length int, charset string) string { func IsIpv4Net(host string) bool { return net.ParseIP(host) != nil } + +func (node *Node) Validate(isUpdate bool) error { + v := validator.New() + _ = v.RegisterValidation("macaddress_unique", func(fl validator.FieldLevel) bool { + if isUpdate { + return true + } + isFieldUnique, _ := node.IsIDUnique() + return isFieldUnique + }) + _ = v.RegisterValidation("network_exists", func(fl validator.FieldLevel) bool { + _, err := node.GetNetwork() + return err == nil + }) + _ = v.RegisterValidation("in_charset", func(fl validator.FieldLevel) bool { + isgood := node.NameInNodeCharSet() + return isgood + }) + _ = v.RegisterValidation("checkyesorno", func(fl validator.FieldLevel) bool { + return CheckYesOrNo(fl) + }) + err := v.Struct(node) + + return err +} + +func (node *Node) IsIDUnique() (bool, error) { + record, err := database.FetchRecord(database.NODES_TABLE_NAME, node.ID) + if err != nil { + return false, err + } + return record == "", err +} + +func (node *Node) NameInNodeCharSet() bool { + + charset := "abcdefghijklmnopqrstuvwxyz1234567890-" + + for _, char := range node.Name { + if !strings.Contains(charset, strings.ToLower(string(char))) { + return false + } + } + return true +} + +func GetAllNodes() ([]Node, error) { + var nodes []Node + + collection, err := database.FetchRecords(database.NODES_TABLE_NAME) + if err != nil { + return []Node{}, err + } + + for _, value := range collection { + var node Node + if err := json.Unmarshal([]byte(value), &node); err != nil { + return []Node{}, err + } + // add node to our array + nodes = append(nodes, node) + } + + return nodes, nil +} diff --git a/models/validation.go b/models/validation.go new file mode 100644 index 00000000..458b10b7 --- /dev/null +++ b/models/validation.go @@ -0,0 +1,16 @@ +package models + +import ( + "regexp" + + "github.com/go-playground/validator/v10" +) + +func CheckYesOrNo(fl validator.FieldLevel) bool { + return fl.Field().String() == "yes" || fl.Field().String() == "no" +} + +func CheckRegex(fl validator.FieldLevel) bool { + re := regexp.MustCompile(fl.Param()) + return re.MatchString(fl.Field().String()) +} diff --git a/test/nodecreate.sh b/test/nodecreate.sh index da92a507..7ab6921c 100755 --- a/test/nodecreate.sh +++ b/test/nodecreate.sh @@ -15,12 +15,12 @@ generate_post_json () "macaddress": "$MACADDRESS", "password": "$PASSWORD", "localaddress": "172.123.123.3", - "accesskey": "$ACCESSKEY" + "accesskey": "HskQMAoHVplC3l7E" } EOF } POST_JSON=$(generate_post_json) -curl --max-time 5.0 -d "$POST_JSON" -H 'Content-Type: application/json' -H "authorization: Bearer secretkey" localhost:8081/api/nodes/my-net +curl --max-time 5.0 -d "$POST_JSON" -H 'Content-Type: application/json' -H "authorization: Bearer secretkey" localhost:8081/api/nodes/network