Files
netmaker/models/api_node.go
T
Abhishek Kondur edda2868fc NM-163: Users, Groups, Roles, Networks and Hosts Table Migration (#3910)
* feat(go): add user schema;

* feat(go): migrate to user schema;

* feat(go): add audit fields;

* feat(go): remove unused fields from the network model;

* feat(go): add network schema;

* feat(go): migrate to network schema;

* refactor(go): add comment to clarify migration logic;

* fix(go): test failures;

* fix(go): test failures;

* feat(go): change membership table to store memberships at all scopes;

* feat(go): add schema for access grants;

* feat(go): remove nameservers from new networks table; ensure db passed for schema functions;

* feat(go): set max conns for sqlite to 1;

* fix(go): issues updating user account status;

* refactor(go): remove converters and access grants;

* refactor(go): add json tags in schema models;

* refactor(go): rename file to migrate_v1_6_0.go;

* refactor(go): add user groups and user roles tables; use schema tables;

* refactor(go): inline get and list from schema package;

* refactor(go): inline get network and list users from schema package;

* fix(go): staticcheck issues;

* fix(go): remove test not in use; fix test case;

* fix(go): validate network;

* fix(go): resolve static checks;

* fix(go): new models errors;

* fix(go): test errors;

* fix(go): handle no records;

* fix(go): add validations for user object;

* fix(go): set correct extclient status;

* fix(go): test error;

* feat(go): make schema the base package;

* feat(go): add host schema;

* feat(go): use schema host everywhere;

* feat(go): inline get host, list hosts and delete host;

* feat(go): use non-ptr value;

* feat(go): use save to upsert all fields;

* feat(go): use save to upsert all fields;

* feat(go): save turn endpoint as string;

* feat(go): check for gorm error record not found;

* fix(go): test failures;

* fix(go): update all network fields;

* fix(go): update all network fields;

* feat(go): add paginated list networks api;

* feat(go): add paginated list users api;

* feat(go): add paginated list hosts api;

* feat(go): add pagination to list groups api;

* fix(go): comment;

* fix(go): implement marshal and unmarshal text for custom types;

* fix(go): implement marshal and unmarshal json for custom types;

* fix(go): just use the old model for unmarshalling;

* fix(go): implement marshal and unmarshal json for custom types;

* feat(go): remove paginated list networks api;

* feat(go): use custom paginated response object;

* fix(go): ensure default values for page and per_page are used when not passed;

* fix(go): rename v1.6.0 to v1.5.1;

* fix(go): check for gorm.ErrRecordNotFound instead of database.IsEmptyRecord;

* fix(go): use host id, not pending host id;

* feat(go): add filters to paginated apis;

* feat(go): add filters to paginated apis;

* feat(go): remove check for max username length;

* feat(go): add filters to count as well;

* feat(go): use library to check email address validity;

* feat(go): ignore pagination if params not passed;

* fix(go): pagination issues;

* fix(go): check exists before using;

* fix(go): remove debug log;

* fix(go): use gorm err record not found;

* fix(go): use gorm err record not found;

* fix(go): use user principal name when creating pending user;

* fix(go): use schema package for consts;

* fix(go): prevent disabling superadmin user;

Co-authored-by: tenki-reviewer[bot] <262613592+tenki-reviewer[bot]@users.noreply.github.com>

* fix(go): swap is admin and is superadmin;

Co-authored-by: tenki-reviewer[bot] <262613592+tenki-reviewer[bot]@users.noreply.github.com>

* fix(go): remove dead code block;

https://github.com/gravitl/netmaker/pull/3910#discussion_r2928837937

* fix(go): incorrect message when trying to disable self;

https://github.com/gravitl/netmaker/pull/3910#discussion_r2928837934

* fix(go): use correct header;

Co-authored-by: tenki-reviewer[bot] <262613592+tenki-reviewer[bot]@users.noreply.github.com>

* fix(go): return after error response;

Co-authored-by: tenki-reviewer[bot] <262613592+tenki-reviewer[bot]@users.noreply.github.com>

* fix(go): use correct order of params;

https://github.com/gravitl/netmaker/pull/3910#discussion_r2929593036

* fix(go): set default values for page and page size; use v2 instead of /list;

* Update logic/auth.go

Co-authored-by: tenki-reviewer[bot] <262613592+tenki-reviewer[bot]@users.noreply.github.com>

* Update schema/user_roles.go

Co-authored-by: tenki-reviewer[bot] <262613592+tenki-reviewer[bot]@users.noreply.github.com>

* fix(go): syntax error;

* fix(go): set default values when page and per_page are not passed or 0;

* fix(go): use uuid.parse instead of uuid.must parse;

* fix(go): review errors;

* fix(go): review errors;

* Update controllers/user.go

Co-authored-by: tenki-reviewer[bot] <262613592+tenki-reviewer[bot]@users.noreply.github.com>

* Update controllers/user.go

Co-authored-by: tenki-reviewer[bot] <262613592+tenki-reviewer[bot]@users.noreply.github.com>

* NM-163: fix errors:

* Update db/types/options.go

Co-authored-by: tenki-reviewer[bot] <262613592+tenki-reviewer[bot]@users.noreply.github.com>

* fix(go): persist return user in event;

* Update db/types/options.go

Co-authored-by: tenki-reviewer[bot] <262613592+tenki-reviewer[bot]@users.noreply.github.com>

* NM-163: duplicate lines of code

* NM-163: fix(go): fix missing return and filter parsing in user controller

- Add missing return after error response in updateUserAccountStatus
  to prevent double-response and spurious ext-client side-effects
- Use switch statements in listUsers to skip unrecognized
  account_status and mfa_status filter values

* fix(go): check for both min and max page size;

* fix(go): enclose transfer superadmin in transaction;

* fix(go): review errors;

* fix(go): remove free tier checks;

* fix(go): review fixes;

---------

Co-authored-by: VishalDalwadi <dalwadivishal26@gmail.com>
Co-authored-by: Vishal Dalwadi <51291657+VishalDalwadi@users.noreply.github.com>
Co-authored-by: tenki-reviewer[bot] <262613592+tenki-reviewer[bot]@users.noreply.github.com>
2026-03-17 19:36:52 +05:30

246 lines
10 KiB
Go

package models
import (
"net"
"time"
"github.com/google/uuid"
"github.com/gravitl/netmaker/schema"
"golang.org/x/exp/slog"
)
type ApiNodeStatus struct {
ID string `json:"id"`
IsStatic bool `json:"is_static"`
IsUserNode bool `json:"is_user_node"`
Status NodeStatus `json:"status"`
}
// ApiNode is a stripped down Node DTO that exposes only required fields to external systems
type ApiNode struct {
ID string `json:"id,omitempty" validate:"required,min=5,id_unique"`
HostID string `json:"hostid,omitempty" validate:"required,min=5,id_unique"`
Address string `json:"address" validate:"omitempty,cidrv4"`
Address6 string `json:"address6" validate:"omitempty,cidrv6"`
LocalAddress string `json:"localaddress" validate:"omitempty,cidr"`
AllowedIPs []string `json:"allowedips"`
LastModified int64 `json:"lastmodified" swaggertype:"primitive,integer" format:"int64"`
ExpirationDateTime int64 `json:"expdatetime" swaggertype:"primitive,integer" format:"int64"`
LastCheckIn int64 `json:"lastcheckin" swaggertype:"primitive,integer" format:"int64"`
LastPeerUpdate int64 `json:"lastpeerupdate" swaggertype:"primitive,integer" format:"int64"`
Network string `json:"network"`
NetworkRange string `json:"networkrange"`
NetworkRange6 string `json:"networkrange6"`
IsRelayed bool `json:"isrelayed"`
IsRelay bool `json:"isrelay"`
IsGw bool `json:"is_gw"`
IsAutoRelay bool `json:"is_auto_relay"`
AutoRelayedPeers map[string]string `json:"auto_relayed_peers"`
AutoAssignGateway bool `json:"auto_assign_gw"`
//AutoRelayedBy uuid.UUID `json:"auto_relayed_by"`
RelayedBy string `json:"relayedby" bson:"relayedby" yaml:"relayedby"`
RelayedNodes []string `json:"relaynodes" yaml:"relayedNodes"`
IsEgressGateway bool `json:"isegressgateway"`
IsIngressGateway bool `json:"isingressgateway"`
EgressGatewayRanges []string `json:"egressgatewayranges"`
EgressGatewayNatEnabled bool `json:"egressgatewaynatenabled"`
EgressGatewayRangesWithMetric []EgressRangeMetric `json:"egressgatewayranges_with_metric"`
DNSOn bool `json:"dnson"`
IngressDns string `json:"ingressdns"`
IngressPersistentKeepalive int32 `json:"ingresspersistentkeepalive"`
IngressMTU int32 `json:"ingressmtu"`
Server string `json:"server"`
Connected bool `json:"connected"`
PendingDelete bool `json:"pendingdelete"`
Metadata string `json:"metadata"`
// == PRO ==
DefaultACL string `json:"defaultacl,omitempty" validate:"checkyesornoorunset"`
IsFailOver bool `json:"is_fail_over"`
FailOverPeers map[string]struct{} `json:"fail_over_peers" yaml:"fail_over_peers"`
FailedOverBy uuid.UUID `json:"failed_over_by" yaml:"failed_over_by"`
IsInternetGateway bool `json:"isinternetgateway" yaml:"isinternetgateway"`
InetNodeReq InetNodeReq `json:"inet_node_req" yaml:"inet_node_req"`
InternetGwID string `json:"internetgw_node_id" yaml:"internetgw_node_id"`
AdditionalRagIps []string `json:"additional_rag_ips" yaml:"additional_rag_ips"`
Tags map[TagID]struct{} `json:"tags" yaml:"tags"`
IsStatic bool `json:"is_static"`
IsUserNode bool `json:"is_user_node"`
StaticNode ExtClient `json:"static_node"`
Status NodeStatus `json:"status"`
Location string `json:"location"`
Country string `json:"country"`
PostureChecksViolations []Violation `json:"posture_check_violations"`
PostureCheckVolationSeverityLevel schema.Severity `json:"posture_check_violation_severity_level"`
LastEvaluatedAt time.Time `json:"last_evaluated_at"`
}
// ApiNode.ConvertToServerNode - converts an api node to a server node
func (a *ApiNode) ConvertToServerNode(currentNode *Node) *Node {
convertedNode := Node{}
convertedNode.Network = a.Network
convertedNode.Server = a.Server
convertedNode.Action = currentNode.Action
convertedNode.Connected = a.Connected
convertedNode.ID, _ = uuid.Parse(a.ID)
convertedNode.HostID, _ = uuid.Parse(a.HostID)
//convertedNode.IsRelay = a.IsRelay
if a.RelayedBy != "" && !a.IsRelayed {
a.IsRelayed = true
}
convertedNode.IsRelayed = a.IsRelayed
convertedNode.RelayedBy = a.RelayedBy
convertedNode.RelayedNodes = a.RelayedNodes
convertedNode.PendingDelete = a.PendingDelete
convertedNode.FailedOverBy = uuid.Nil
convertedNode.FailOverPeers = make(map[string]struct{})
convertedNode.IsFailOver = false
//convertedNode.IsIngressGateway = a.IsIngressGateway
convertedNode.IngressGatewayRange = currentNode.IngressGatewayRange
convertedNode.IngressGatewayRange6 = currentNode.IngressGatewayRange6
convertedNode.IngressDNS = a.IngressDns
convertedNode.IngressPersistentKeepalive = a.IngressPersistentKeepalive
convertedNode.IngressMTU = a.IngressMTU
convertedNode.IsInternetGateway = a.IsInternetGateway
convertedNode.InternetGwID = currentNode.InternetGwID
convertedNode.InetNodeReq = currentNode.InetNodeReq
convertedNode.RelayedNodes = a.RelayedNodes
convertedNode.DefaultACL = a.DefaultACL
convertedNode.OwnerID = currentNode.OwnerID
_, networkRange, err := net.ParseCIDR(a.NetworkRange)
if err == nil {
convertedNode.NetworkRange = *networkRange
}
_, networkRange6, err := net.ParseCIDR(a.NetworkRange6)
if err == nil {
convertedNode.NetworkRange6 = *networkRange6
}
if len(a.LocalAddress) > 0 {
_, localAddr, err := net.ParseCIDR(a.LocalAddress)
if err == nil {
convertedNode.LocalAddress = *localAddr
}
} else if !isEmptyAddr(currentNode.LocalAddress.String()) {
convertedNode.LocalAddress = currentNode.LocalAddress
}
ip, addr, err := net.ParseCIDR(a.Address)
if err == nil {
convertedNode.Address = *addr
convertedNode.Address.IP = ip
}
ip6, addr6, err := net.ParseCIDR(a.Address6)
if err == nil {
convertedNode.Address6 = *addr6
convertedNode.Address6.IP = ip6
}
convertedNode.LastModified = time.Unix(a.LastModified, 0)
convertedNode.LastCheckIn = time.Unix(a.LastCheckIn, 0)
convertedNode.LastPeerUpdate = time.Unix(a.LastPeerUpdate, 0)
convertedNode.ExpirationDateTime = time.Unix(a.ExpirationDateTime, 0)
convertedNode.Metadata = a.Metadata
for _, ip := range a.AdditionalRagIps {
ragIp := net.ParseIP(ip)
if ragIp == nil {
slog.Error("error parsing additional rag ip", "error", err, "ip", ip)
return nil
}
convertedNode.AdditionalRagIps = append(convertedNode.AdditionalRagIps, ragIp)
}
convertedNode.Tags = a.Tags
convertedNode.IsGw = a.IsGw
convertedNode.IsAutoRelay = a.IsAutoRelay
if convertedNode.IsGw {
convertedNode.IsRelay = true
convertedNode.IsIngressGateway = true
}
convertedNode.AutoAssignGateway = a.AutoAssignGateway
return &convertedNode
}
func (nm *Node) ConvertToStatusNode() *ApiNodeStatus {
apiNode := ApiNodeStatus{}
if nm.IsStatic {
apiNode.ID = nm.StaticNode.ClientID
} else {
apiNode.ID = nm.ID.String()
}
apiNode.IsStatic = nm.IsStatic
apiNode.IsUserNode = nm.IsUserNode
apiNode.Status = nm.Status
return &apiNode
}
// Node.ConvertToAPINode - converts a node to an API node
func (nm *Node) ConvertToAPINode() *ApiNode {
apiNode := ApiNode{}
apiNode.ID = nm.ID.String()
apiNode.HostID = nm.HostID.String()
apiNode.Address = nm.Address.String()
if isEmptyAddr(apiNode.Address) {
apiNode.Address = ""
}
apiNode.Address6 = nm.Address6.String()
if isEmptyAddr(apiNode.Address6) {
apiNode.Address6 = ""
}
apiNode.LocalAddress = nm.LocalAddress.String()
if isEmptyAddr(apiNode.LocalAddress) {
apiNode.LocalAddress = ""
}
apiNode.LastModified = nm.LastModified.Unix()
apiNode.LastCheckIn = nm.LastCheckIn.Unix()
apiNode.LastPeerUpdate = nm.LastPeerUpdate.Unix()
apiNode.ExpirationDateTime = nm.ExpirationDateTime.Unix()
apiNode.Network = nm.Network
apiNode.NetworkRange = nm.NetworkRange.String()
if isEmptyAddr(apiNode.NetworkRange) {
apiNode.NetworkRange = ""
}
apiNode.NetworkRange6 = nm.NetworkRange6.String()
if isEmptyAddr(apiNode.NetworkRange6) {
apiNode.NetworkRange6 = ""
}
apiNode.IsRelayed = nm.IsRelayed
apiNode.IsRelay = nm.IsRelay
apiNode.IsGw = nm.IsGw
apiNode.RelayedBy = nm.RelayedBy
apiNode.RelayedNodes = nm.RelayedNodes
apiNode.IsAutoRelay = nm.IsAutoRelay
//apiNode.AutoRelayedBy = nm.AutoRelayedBy
apiNode.AutoRelayedPeers = nm.AutoRelayedPeers
apiNode.AutoAssignGateway = nm.AutoAssignGateway
apiNode.IsIngressGateway = nm.IsIngressGateway
apiNode.IngressDns = nm.IngressDNS
apiNode.IngressPersistentKeepalive = nm.IngressPersistentKeepalive
apiNode.IngressMTU = nm.IngressMTU
apiNode.Server = nm.Server
apiNode.Connected = nm.Connected
apiNode.PendingDelete = nm.PendingDelete
apiNode.DefaultACL = nm.DefaultACL
apiNode.IsInternetGateway = nm.IsInternetGateway
apiNode.InternetGwID = nm.InternetGwID
apiNode.InetNodeReq = nm.InetNodeReq
apiNode.IsFailOver = false
apiNode.FailOverPeers = nm.FailOverPeers
apiNode.FailedOverBy = nm.FailedOverBy
apiNode.Metadata = nm.Metadata
apiNode.AdditionalRagIps = []string{}
apiNode.Tags = nm.Tags
for _, ip := range nm.AdditionalRagIps {
apiNode.AdditionalRagIps = append(apiNode.AdditionalRagIps, ip.String())
}
apiNode.IsStatic = nm.IsStatic
apiNode.IsUserNode = nm.IsUserNode
apiNode.StaticNode = nm.StaticNode
apiNode.Status = nm.Status
apiNode.PostureChecksViolations = nm.PostureChecksViolations
apiNode.PostureCheckVolationSeverityLevel = nm.PostureCheckVolationSeverityLevel
apiNode.LastEvaluatedAt = nm.LastEvaluatedAt
apiNode.Location = nm.Location
apiNode.Country = nm.CountryCode
return &apiNode
}
func isEmptyAddr(addr string) bool {
return addr == "<nil>" || addr == ":0"
}