Compare commits
20 Commits
feature/pe
...
fix/includ
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7649b962c1 | ||
|
|
17f5abc653 | ||
|
|
aa935bdae3 | ||
|
|
452419c4c3 | ||
|
|
17b1099032 | ||
|
|
a4b9e93217 | ||
|
|
63d7957140 | ||
|
|
9a6814deff | ||
|
|
190698bcf2 | ||
|
|
468fa2940b | ||
|
|
79a0647a26 | ||
|
|
17ceb3bde8 | ||
|
|
5a8f1763a6 | ||
|
|
f64e73ca70 | ||
|
|
b085419ab8 | ||
|
|
d78b652ff7 | ||
|
|
7251150c1c | ||
|
|
b65c2f69b0 | ||
|
|
d8ce08d898 | ||
|
|
e1c50248d9 |
@@ -13,7 +13,6 @@ import (
|
||||
|
||||
"google.golang.org/grpc"
|
||||
|
||||
"github.com/netbirdio/management-integrations/integrations"
|
||||
clientProto "github.com/netbirdio/netbird/client/proto"
|
||||
client "github.com/netbirdio/netbird/client/server"
|
||||
mgmtProto "github.com/netbirdio/netbird/management/proto"
|
||||
@@ -76,8 +75,10 @@ func startManagement(t *testing.T, config *mgmt.Config) (*grpc.Server, net.Liste
|
||||
|
||||
peersUpdateManager := mgmt.NewPeersUpdateManager(nil)
|
||||
eventStore := &activity.InMemoryEventStore{}
|
||||
iv, _ := integrations.NewIntegratedApproval(eventStore)
|
||||
accountManager, err := mgmt.BuildManager(store, peersUpdateManager, nil, "", "", eventStore, nil, false, iv)
|
||||
if err != nil {
|
||||
return nil, nil
|
||||
}
|
||||
accountManager, err := mgmt.BuildManager(store, peersUpdateManager, nil, "", "", eventStore, nil, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -21,7 +21,6 @@ import (
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/keepalive"
|
||||
|
||||
"github.com/netbirdio/management-integrations/integrations"
|
||||
"github.com/netbirdio/netbird/client/internal/dns"
|
||||
"github.com/netbirdio/netbird/client/internal/peer"
|
||||
"github.com/netbirdio/netbird/client/internal/routemanager"
|
||||
@@ -71,10 +70,10 @@ func TestEngine_SSH(t *testing.T) {
|
||||
defer cancel()
|
||||
|
||||
engine := NewEngine(ctx, cancel, &signal.MockClient{}, &mgmt.MockClient{}, &EngineConfig{
|
||||
WgIfaceName: "utun101",
|
||||
WgAddr: "100.64.0.1/24",
|
||||
WgPrivateKey: key,
|
||||
WgPort: 33100,
|
||||
WgIfaceName: "utun101",
|
||||
WgAddr: "100.64.0.1/24",
|
||||
WgPrivateKey: key,
|
||||
WgPort: 33100,
|
||||
ServerSSHAllowed: true,
|
||||
}, MobileDependency{}, peer.NewRecorder("https://mgm"))
|
||||
|
||||
@@ -1048,8 +1047,10 @@ func startManagement(dataDir string) (*grpc.Server, string, error) {
|
||||
|
||||
peersUpdateManager := server.NewPeersUpdateManager(nil)
|
||||
eventStore := &activity.InMemoryEventStore{}
|
||||
ia, _ := integrations.NewIntegratedApproval(eventStore)
|
||||
accountManager, err := server.BuildManager(store, peersUpdateManager, nil, "", "", eventStore, nil, false, ia)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
accountManager, err := server.BuildManager(store, peersUpdateManager, nil, "", "", eventStore, nil, false)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
5
go.mod
5
go.mod
@@ -46,7 +46,6 @@ require (
|
||||
github.com/golang/mock v1.6.0
|
||||
github.com/google/go-cmp v0.5.9
|
||||
github.com/google/gopacket v1.1.19
|
||||
github.com/google/martian/v3 v3.0.0
|
||||
github.com/google/nftables v0.0.0-20220808154552-2eca00135732
|
||||
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.0.2-0.20240212192251-757544f21357
|
||||
github.com/hashicorp/go-secure-stdlib/base62 v0.1.2
|
||||
@@ -58,8 +57,8 @@ require (
|
||||
github.com/miekg/dns v1.1.43
|
||||
github.com/mitchellh/hashstructure/v2 v2.0.2
|
||||
github.com/nadoo/ipset v0.5.0
|
||||
github.com/netbirdio/management-integrations/additions v0.0.0-20240226151841-2e4fe2407450
|
||||
github.com/netbirdio/management-integrations/integrations v0.0.0-20240305130559-469a80446ac7
|
||||
github.com/netbirdio/management-integrations/additions v0.0.0-20240212121739-8ea8c89a4552
|
||||
github.com/netbirdio/management-integrations/integrations v0.0.0-20240212121739-8ea8c89a4552
|
||||
github.com/okta/okta-sdk-golang/v2 v2.18.0
|
||||
github.com/oschwald/maxminddb-golang v1.12.0
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||
|
||||
9
go.sum
9
go.sum
@@ -255,7 +255,6 @@ github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/
|
||||
github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
|
||||
github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/martian/v3 v3.0.0 h1:pMen7vLs8nvgEYhywH3KDWJIJTeEr2ULsVWHWYHQyBs=
|
||||
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
github.com/google/nftables v0.0.0-20220808154552-2eca00135732 h1:csc7dT82JiSLvq4aMyQMIQDL7986NH6Wxf/QrvOj55A=
|
||||
github.com/google/nftables v0.0.0-20220808154552-2eca00135732/go.mod h1:b97ulCCFipUC+kSin+zygkvUVpx0vyIAwxXFdY3PlNc=
|
||||
@@ -377,10 +376,10 @@ github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRW
|
||||
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
|
||||
github.com/nadoo/ipset v0.5.0 h1:5GJUAuZ7ITQQQGne5J96AmFjRtI8Avlbk6CabzYWVUc=
|
||||
github.com/nadoo/ipset v0.5.0/go.mod h1:rYF5DQLRGGoQ8ZSWeK+6eX5amAuPqwFkWjhQlEITGJQ=
|
||||
github.com/netbirdio/management-integrations/additions v0.0.0-20240226151841-2e4fe2407450 h1:qA4S5YFt6/s0kQ8wKLjq8faLxuBSte1WzjWfmQmyJTU=
|
||||
github.com/netbirdio/management-integrations/additions v0.0.0-20240226151841-2e4fe2407450/go.mod h1:31FhBNvQ+riHEIu6LSTmqr8IeuSIsGfQffqV4LFmbwA=
|
||||
github.com/netbirdio/management-integrations/integrations v0.0.0-20240305130559-469a80446ac7 h1:YYIQJbRhANmNFClkCmjBa0w33RpTzsF2DpbGAWhul6Y=
|
||||
github.com/netbirdio/management-integrations/integrations v0.0.0-20240305130559-469a80446ac7/go.mod h1:B0nMS3es77gOvPYhc0K91fAzTkQLi/jRq5TffUN3klM=
|
||||
github.com/netbirdio/management-integrations/additions v0.0.0-20240212121739-8ea8c89a4552 h1:yzcQKizAK9YufCHMMCIsr467Dw/OU/4xyHbWizGb1E4=
|
||||
github.com/netbirdio/management-integrations/additions v0.0.0-20240212121739-8ea8c89a4552/go.mod h1:31FhBNvQ+riHEIu6LSTmqr8IeuSIsGfQffqV4LFmbwA=
|
||||
github.com/netbirdio/management-integrations/integrations v0.0.0-20240212121739-8ea8c89a4552 h1:OFlzVZtkXCoJsfDKrMigFpuad8ZXTm8epq6x27K0irA=
|
||||
github.com/netbirdio/management-integrations/integrations v0.0.0-20240212121739-8ea8c89a4552/go.mod h1:B0nMS3es77gOvPYhc0K91fAzTkQLi/jRq5TffUN3klM=
|
||||
github.com/netbirdio/service v0.0.0-20230215170314-b923b89432b0 h1:hirFRfx3grVA/9eEyjME5/z3nxdJlN9kfQpvWWPk32g=
|
||||
github.com/netbirdio/service v0.0.0-20230215170314-b923b89432b0/go.mod h1:CIMRFEJVL+0DS1a3Nx06NaMn4Dz63Ng6O7dl0qH0zVM=
|
||||
github.com/netbirdio/systray v0.0.0-20231030152038-ef1ed2a27949 h1:xbWM9BU6mwZZLHxEjxIX/V8Hv3HurQt4mReIE4mY4DM=
|
||||
|
||||
@@ -15,7 +15,6 @@ import (
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/netbirdio/management-integrations/integrations"
|
||||
"github.com/netbirdio/netbird/encryption"
|
||||
mgmtProto "github.com/netbirdio/netbird/management/proto"
|
||||
mgmt "github.com/netbirdio/netbird/management/server"
|
||||
@@ -61,8 +60,7 @@ func startManagement(t *testing.T) (*grpc.Server, net.Listener) {
|
||||
|
||||
peersUpdateManager := mgmt.NewPeersUpdateManager(nil)
|
||||
eventStore := &activity.InMemoryEventStore{}
|
||||
ia, _ := integrations.NewIntegratedApproval(eventStore)
|
||||
accountManager, err := mgmt.BuildManager(store, peersUpdateManager, nil, "", "", eventStore, nil, false, ia)
|
||||
accountManager, err := mgmt.BuildManager(store, peersUpdateManager, nil, "", "", eventStore, nil, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -172,12 +172,8 @@ var (
|
||||
log.Infof("geo location service has been initialized from %s", config.Datadir)
|
||||
}
|
||||
|
||||
integratedPeerApproval, err := integrations.NewIntegratedApproval(eventStore)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to initialize integrated peer approval: %v", err)
|
||||
}
|
||||
accountManager, err := server.BuildManager(store, peersUpdateManager, idpManager, mgmtSingleAccModeDomain,
|
||||
dnsDomain, eventStore, geo, userDeleteFromIDPEnabled, integratedPeerApproval)
|
||||
dnsDomain, eventStore, geo, userDeleteFromIDPEnabled)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to build default manager: %v", err)
|
||||
}
|
||||
@@ -327,7 +323,6 @@ var (
|
||||
SetupCloseHandler()
|
||||
|
||||
<-stopCh
|
||||
integratedPeerApproval.Stop()
|
||||
if geo != nil {
|
||||
_ = geo.Stop()
|
||||
}
|
||||
|
||||
@@ -22,13 +22,13 @@ import (
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/netbirdio/management-integrations/additions"
|
||||
|
||||
"github.com/netbirdio/netbird/base62"
|
||||
nbdns "github.com/netbirdio/netbird/dns"
|
||||
"github.com/netbirdio/netbird/management/server/account"
|
||||
"github.com/netbirdio/netbird/management/server/activity"
|
||||
"github.com/netbirdio/netbird/management/server/geolocation"
|
||||
"github.com/netbirdio/netbird/management/server/idp"
|
||||
"github.com/netbirdio/netbird/management/server/integrated_approval"
|
||||
"github.com/netbirdio/netbird/management/server/jwtclaims"
|
||||
nbpeer "github.com/netbirdio/netbird/management/server/peer"
|
||||
"github.com/netbirdio/netbird/management/server/posture"
|
||||
@@ -127,9 +127,6 @@ type AccountManager interface {
|
||||
DeletePostureChecks(accountID, postureChecksID, userID string) error
|
||||
ListPostureChecks(accountID, userID string) ([]*posture.Checks, error)
|
||||
GetIdpManager() idp.Manager
|
||||
UpdateIntegratedApprovalGroups(accountID string, userID string, groups []string) error
|
||||
IsRequiresApproval(accountID string, peer *nbpeer.Peer, peersGroup []string, extraSettings *account.ExtraSettings) bool
|
||||
GroupValidation(accountId string, groups []string) (bool, error)
|
||||
}
|
||||
|
||||
type DefaultAccountManager struct {
|
||||
@@ -158,8 +155,6 @@ type DefaultAccountManager struct {
|
||||
|
||||
// userDeleteFromIDPEnabled allows to delete user from IDP when user is deleted from account
|
||||
userDeleteFromIDPEnabled bool
|
||||
|
||||
integratedPeerValidator integrated_approval.IntegratedApproval
|
||||
}
|
||||
|
||||
// Settings represents Account settings structure that can be modified via API and Dashboard
|
||||
@@ -394,14 +389,12 @@ func (a *Account) GetPeerNetworkMap(peerID, dnsDomain string) *NetworkMap {
|
||||
Network: a.Network.Copy(),
|
||||
}
|
||||
}
|
||||
|
||||
validatedPeers := additions.ValidatePeers([]*nbpeer.Peer{peer})
|
||||
if len(validatedPeers) == 0 {
|
||||
return &NetworkMap{
|
||||
Network: a.Network.Copy(),
|
||||
}
|
||||
}
|
||||
|
||||
aclPeers, firewallRules := a.getPeerConnectionResources(peerID)
|
||||
// exclude expired peers
|
||||
var peersToConnect []*nbpeer.Peer
|
||||
@@ -580,20 +573,6 @@ func (a *Account) FindSetupKey(setupKey string) (*SetupKey, error) {
|
||||
return key, nil
|
||||
}
|
||||
|
||||
// GetPeerGroupsList return with the list of groups ID.
|
||||
func (a *Account) GetPeerGroupsList(peerID string) []string {
|
||||
var grps []string
|
||||
for groupID, group := range a.Groups {
|
||||
for _, id := range group.Peers {
|
||||
if id == peerID {
|
||||
grps = append(grps, groupID)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return grps
|
||||
}
|
||||
|
||||
func (a *Account) getUserGroups(userID string) ([]string, error) {
|
||||
user, err := a.FindUser(userID)
|
||||
if err != nil {
|
||||
@@ -848,7 +827,6 @@ func (a *Account) UserGroupsRemoveFromPeers(userID string, groups ...string) {
|
||||
func BuildManager(store Store, peersUpdateManager *PeersUpdateManager, idpManager idp.Manager,
|
||||
singleAccountModeDomain string, dnsDomain string, eventStore activity.Store, geo *geolocation.Geolocation,
|
||||
userDeleteFromIDPEnabled bool,
|
||||
integratedPeerValidator integrated_approval.IntegratedApproval,
|
||||
) (*DefaultAccountManager, error) {
|
||||
am := &DefaultAccountManager{
|
||||
Store: store,
|
||||
@@ -862,7 +840,6 @@ func BuildManager(store Store, peersUpdateManager *PeersUpdateManager, idpManage
|
||||
eventStore: eventStore,
|
||||
peerLoginExpiry: NewDefaultScheduler(),
|
||||
userDeleteFromIDPEnabled: userDeleteFromIDPEnabled,
|
||||
integratedPeerValidator: integratedPeerValidator,
|
||||
}
|
||||
allAccounts := store.GetAllAccounts()
|
||||
// enable single account mode only if configured by user and number of existing accounts is not grater than 1
|
||||
|
||||
@@ -3,17 +3,11 @@ package account
|
||||
type ExtraSettings struct {
|
||||
// PeerApprovalEnabled enables or disables the need for peers bo be approved by an administrator
|
||||
PeerApprovalEnabled bool
|
||||
|
||||
// IntegratedApprovalGroups list of group IDs to be used with integrated approval configurations
|
||||
IntegratedApprovalGroups []string `gorm:"serializer:json"`
|
||||
}
|
||||
|
||||
// Copy copies the ExtraSettings struct
|
||||
func (e *ExtraSettings) Copy() *ExtraSettings {
|
||||
var cpGroup []string
|
||||
|
||||
return &ExtraSettings{
|
||||
PeerApprovalEnabled: e.PeerApprovalEnabled,
|
||||
IntegratedApprovalGroups: append(cpGroup, e.IntegratedApprovalGroups...),
|
||||
PeerApprovalEnabled: e.PeerApprovalEnabled,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,34 +12,20 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/golang-jwt/jwt"
|
||||
|
||||
nbdns "github.com/netbirdio/netbird/dns"
|
||||
"github.com/netbirdio/netbird/management/server/activity"
|
||||
nbpeer "github.com/netbirdio/netbird/management/server/peer"
|
||||
"github.com/netbirdio/netbird/management/server/posture"
|
||||
"github.com/netbirdio/netbird/route"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||
|
||||
nbdns "github.com/netbirdio/netbird/dns"
|
||||
"github.com/netbirdio/netbird/management/server/account"
|
||||
"github.com/netbirdio/netbird/management/server/activity"
|
||||
"github.com/netbirdio/netbird/management/server/jwtclaims"
|
||||
nbpeer "github.com/netbirdio/netbird/management/server/peer"
|
||||
"github.com/netbirdio/netbird/management/server/posture"
|
||||
"github.com/netbirdio/netbird/route"
|
||||
)
|
||||
|
||||
type MocIntegratedApproval struct {
|
||||
}
|
||||
|
||||
func (MocIntegratedApproval) PreparePeer(accountID string, peer *nbpeer.Peer, peersGroup []string, extraSettings *account.ExtraSettings) *nbpeer.Peer {
|
||||
return peer
|
||||
}
|
||||
|
||||
func (MocIntegratedApproval) IsRequiresApproval(accountID string, peer *nbpeer.Peer, peersGroup []string, extraSettings *account.ExtraSettings) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (MocIntegratedApproval) Stop() {
|
||||
|
||||
}
|
||||
|
||||
func verifyCanAddPeerToAccount(t *testing.T, manager AccountManager, account *Account, userID string) {
|
||||
t.Helper()
|
||||
peer := &nbpeer.Peer{
|
||||
@@ -2237,7 +2223,7 @@ func createManager(t *testing.T) (*DefaultAccountManager, error) {
|
||||
return nil, err
|
||||
}
|
||||
eventStore := &activity.InMemoryEventStore{}
|
||||
return BuildManager(store, NewPeersUpdateManager(nil), nil, "", "netbird.cloud", eventStore, nil, false, MocIntegratedApproval{})
|
||||
return BuildManager(store, NewPeersUpdateManager(nil), nil, "", "netbird.cloud", eventStore, nil, false)
|
||||
}
|
||||
|
||||
func createStore(t *testing.T) (Store, error) {
|
||||
|
||||
@@ -193,7 +193,7 @@ func createDNSManager(t *testing.T) (*DefaultAccountManager, error) {
|
||||
return nil, err
|
||||
}
|
||||
eventStore := &activity.InMemoryEventStore{}
|
||||
return BuildManager(store, NewPeersUpdateManager(nil), nil, "", "netbird.test", eventStore, nil, false, MocIntegratedApproval{})
|
||||
return BuildManager(store, NewPeersUpdateManager(nil), nil, "", "netbird.test", eventStore, nil, false)
|
||||
}
|
||||
|
||||
func createDNSStore(t *testing.T) (Store, error) {
|
||||
|
||||
@@ -676,28 +676,33 @@ func (s *FileStore) CalculateUsageStats(_ context.Context, accountID string, sta
|
||||
return nil, fmt.Errorf("account not found")
|
||||
}
|
||||
|
||||
stats := &AccountUsageStats{
|
||||
TotalUsers: 0,
|
||||
TotalPeers: int64(len(account.Peers)),
|
||||
}
|
||||
stats := &AccountUsageStats{}
|
||||
|
||||
activeUsers := make(map[string]struct{})
|
||||
for userID, user := range account.Users {
|
||||
if user.LastLogin.After(start) && user.LastLogin.Before(end) && !user.IsServiceUser {
|
||||
activeUsers[userID] = struct{}{}
|
||||
}
|
||||
|
||||
for _, user := range account.Users {
|
||||
if !user.IsServiceUser {
|
||||
stats.TotalUsers++
|
||||
}
|
||||
}
|
||||
|
||||
activeUsers := make(map[string]bool)
|
||||
for _, peer := range account.Peers {
|
||||
lastSeen := peer.Status.LastSeen
|
||||
if lastSeen.Compare(start) >= 0 && lastSeen.Compare(end) <= 0 {
|
||||
if _, exists := account.Users[peer.UserID]; exists && !activeUsers[peer.UserID] {
|
||||
activeUsers[peer.UserID] = true
|
||||
stats.ActiveUsers++
|
||||
}
|
||||
if peer.Status.LastSeen.After(start) && peer.Status.LastSeen.Before(end) {
|
||||
stats.ActivePeers++
|
||||
|
||||
// service users don't have peers, but we check regardless.
|
||||
if _, exists := account.Users[peer.UserID]; exists && !account.Users[peer.UserID].IsServiceUser {
|
||||
activeUsers[peer.UserID] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stats.ActiveUsers = int64(len(activeUsers))
|
||||
|
||||
stats.TotalPeers = int64(len(account.Peers))
|
||||
|
||||
return stats, nil
|
||||
}
|
||||
|
||||
@@ -674,15 +674,15 @@ func TestFileStore_CalculateUsageStats(t *testing.T) {
|
||||
stats1, err := store.CalculateUsageStats(context.TODO(), "account-1", startDate, endDate)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, int64(2), stats1.ActiveUsers)
|
||||
assert.Equal(t, int64(4), stats1.TotalUsers)
|
||||
assert.Equal(t, int64(3), stats1.ActiveUsers)
|
||||
assert.Equal(t, int64(5), stats1.TotalUsers)
|
||||
assert.Equal(t, int64(3), stats1.ActivePeers)
|
||||
assert.Equal(t, int64(7), stats1.TotalPeers)
|
||||
assert.Equal(t, int64(8), stats1.TotalPeers)
|
||||
|
||||
stats2, err := store.CalculateUsageStats(context.TODO(), "account-2", startDate, endDate)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, int64(1), stats2.ActiveUsers)
|
||||
assert.Equal(t, int64(2), stats2.ActiveUsers)
|
||||
assert.Equal(t, int64(2), stats2.TotalUsers)
|
||||
assert.Equal(t, int64(1), stats2.ActivePeers)
|
||||
assert.Equal(t, int64(2), stats2.TotalPeers)
|
||||
|
||||
@@ -274,15 +274,6 @@ func (am *DefaultAccountManager) DeleteGroup(accountId, userId, groupID string)
|
||||
}
|
||||
}
|
||||
|
||||
// check integrated peer approval
|
||||
if account.Settings.Extra != nil {
|
||||
for _, integratedPeerApprovalGroups := range account.Settings.Extra.IntegratedApprovalGroups {
|
||||
if groupID == integratedPeerApprovalGroups {
|
||||
return &GroupLinkError{"integrated approval", g.Name}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
delete(account.Groups, groupID)
|
||||
|
||||
account.Network.IncSerial()
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/rs/cors"
|
||||
|
||||
"github.com/netbirdio/management-integrations/integrations"
|
||||
|
||||
s "github.com/netbirdio/netbird/management/server"
|
||||
"github.com/netbirdio/netbird/management/server/geolocation"
|
||||
"github.com/netbirdio/netbird/management/server/http/middleware"
|
||||
|
||||
@@ -63,8 +63,8 @@ func (h *PeersHandler) getPeer(account *server.Account, peerID, userID string, w
|
||||
|
||||
netMap := account.GetPeerNetworkMap(peerID, h.accountManager.GetDNSDomain())
|
||||
accessiblePeers := toAccessiblePeers(netMap, dnsDomain)
|
||||
isRequiresApproval := h.accountManager.IsRequiresApproval(account.Id, peer, account.GetPeerGroupsList(peer.ID), account.Settings.Extra)
|
||||
util.WriteJSONObject(w, toSinglePeerResponse(peerToReturn, groupsInfo, dnsDomain, accessiblePeers, isRequiresApproval))
|
||||
|
||||
util.WriteJSONObject(w, toSinglePeerResponse(peerToReturn, groupsInfo, dnsDomain, accessiblePeers))
|
||||
}
|
||||
|
||||
func (h *PeersHandler) updatePeer(account *server.Account, user *server.User, peerID string, w http.ResponseWriter, r *http.Request) {
|
||||
@@ -94,8 +94,7 @@ func (h *PeersHandler) updatePeer(account *server.Account, user *server.User, pe
|
||||
netMap := account.GetPeerNetworkMap(peerID, h.accountManager.GetDNSDomain())
|
||||
accessiblePeers := toAccessiblePeers(netMap, dnsDomain)
|
||||
|
||||
// todo return with valid peer approval status
|
||||
util.WriteJSONObject(w, toSinglePeerResponse(peer, groupMinimumInfo, dnsDomain, accessiblePeers, false))
|
||||
util.WriteJSONObject(w, toSinglePeerResponse(peer, groupMinimumInfo, dnsDomain, accessiblePeers))
|
||||
}
|
||||
|
||||
func (h *PeersHandler) deletePeer(accountID, userID string, peerID string, w http.ResponseWriter) {
|
||||
@@ -167,7 +166,6 @@ func (h *PeersHandler) GetAllPeers(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
accessiblePeerNumbers := h.accessiblePeersNumber(account, peer.ID)
|
||||
|
||||
// todo extend with peer approval status
|
||||
respBody = append(respBody, toPeerListItemResponse(peerToReturn, groupMinimumInfo, dnsDomain, accessiblePeerNumbers))
|
||||
}
|
||||
util.WriteJSONObject(w, respBody)
|
||||
@@ -232,7 +230,7 @@ func toGroupsInfo(groups map[string]*server.Group, peerID string) []api.GroupMin
|
||||
return groupsInfo
|
||||
}
|
||||
|
||||
func toSinglePeerResponse(peer *nbpeer.Peer, groupsInfo []api.GroupMinimum, dnsDomain string, accessiblePeer []api.AccessiblePeer, approval bool) *api.Peer {
|
||||
func toSinglePeerResponse(peer *nbpeer.Peer, groupsInfo []api.GroupMinimum, dnsDomain string, accessiblePeer []api.AccessiblePeer) *api.Peer {
|
||||
osVersion := peer.Meta.OSVersion
|
||||
if osVersion == "" {
|
||||
osVersion = peer.Meta.Core
|
||||
@@ -259,7 +257,7 @@ func toSinglePeerResponse(peer *nbpeer.Peer, groupsInfo []api.GroupMinimum, dnsD
|
||||
LastLogin: peer.LastLogin,
|
||||
LoginExpired: peer.Status.LoginExpired,
|
||||
AccessiblePeers: accessiblePeer,
|
||||
ApprovalRequired: &approval,
|
||||
ApprovalRequired: &peer.Status.RequiresApproval,
|
||||
CountryCode: peer.Location.CountryCode,
|
||||
CityName: peer.Location.CityName,
|
||||
}
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/google/martian/v3/log"
|
||||
|
||||
"github.com/netbirdio/netbird/management/server/account"
|
||||
nbpeer "github.com/netbirdio/netbird/management/server/peer"
|
||||
)
|
||||
|
||||
// UpdateIntegratedApprovalGroups updates the integrated approval groups for a specified account.
|
||||
// It retrieves the account associated with the provided userID, then updates the integrated approval groups
|
||||
// with the provided list of group ids. The updated account is then saved.
|
||||
//
|
||||
// Parameters:
|
||||
// - accountID: The ID of the account for which integrated approval groups are to be updated.
|
||||
// - userID: The ID of the user whose account is being updated.
|
||||
// - groups: A slice of strings representing the ids of integrated approval groups to be updated.
|
||||
//
|
||||
// Returns:
|
||||
// - error: An error if any occurred during the process, otherwise returns nil
|
||||
func (am *DefaultAccountManager) UpdateIntegratedApprovalGroups(accountID string, userID string, groups []string) error {
|
||||
ok, err := am.GroupValidation(accountID, groups)
|
||||
if err != nil {
|
||||
log.Debugf("error validating groups: %s", err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
if !ok {
|
||||
log.Debugf("invalid groups")
|
||||
return errors.New("invalid groups")
|
||||
}
|
||||
|
||||
unlock := am.Store.AcquireAccountLock(accountID)
|
||||
defer unlock()
|
||||
|
||||
a, err := am.Store.GetAccountByUser(userID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var extra *account.ExtraSettings
|
||||
|
||||
if a.Settings.Extra != nil {
|
||||
extra = a.Settings.Extra
|
||||
} else {
|
||||
extra = &account.ExtraSettings{}
|
||||
a.Settings.Extra = extra
|
||||
}
|
||||
extra.IntegratedApprovalGroups = groups
|
||||
return am.Store.SaveAccount(a)
|
||||
}
|
||||
|
||||
func (am *DefaultAccountManager) IsPeerRequiresApproval(accountID string, peer *nbpeer.Peer) bool {
|
||||
return am.integratedPeerValidator.IsRequiresApproval(accountID, peer, nil, nil)
|
||||
}
|
||||
|
||||
func (am *DefaultAccountManager) GroupValidation(accountId string, groups []string) (bool, error) {
|
||||
if len(groups) == 0 {
|
||||
return true, nil
|
||||
}
|
||||
accountsGroups, err := am.ListGroups(accountId)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
for _, group := range groups {
|
||||
var found bool
|
||||
for _, accountGroup := range accountsGroups {
|
||||
if accountGroup.ID == group {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
package integrated_approval
|
||||
|
||||
import (
|
||||
"github.com/netbirdio/netbird/management/server/account"
|
||||
nbpeer "github.com/netbirdio/netbird/management/server/peer"
|
||||
)
|
||||
|
||||
// IntegratedApproval interface exists to avoid the circle dependencies
|
||||
type IntegratedApproval interface {
|
||||
PreparePeer(accountID string, peer *nbpeer.Peer, peersGroup []string, extraSettings *account.ExtraSettings) *nbpeer.Peer
|
||||
IsRequiresApproval(accountID string, peer *nbpeer.Peer, peersGroup []string, extraSettings *account.ExtraSettings) bool
|
||||
Stop()
|
||||
}
|
||||
@@ -9,6 +9,8 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/netbirdio/netbird/management/server/activity"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||
"google.golang.org/grpc"
|
||||
@@ -17,7 +19,6 @@ import (
|
||||
|
||||
"github.com/netbirdio/netbird/encryption"
|
||||
mgmtProto "github.com/netbirdio/netbird/management/proto"
|
||||
"github.com/netbirdio/netbird/management/server/activity"
|
||||
"github.com/netbirdio/netbird/util"
|
||||
)
|
||||
|
||||
@@ -412,7 +413,7 @@ func startManagement(t *testing.T, config *Config) (*grpc.Server, string, error)
|
||||
peersUpdateManager := NewPeersUpdateManager(nil)
|
||||
eventStore := &activity.InMemoryEventStore{}
|
||||
accountManager, err := BuildManager(store, peersUpdateManager, nil, "", "",
|
||||
eventStore, nil, false, MocIntegratedApproval{})
|
||||
eventStore, nil, false)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
@@ -2,8 +2,6 @@ package server_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/netbirdio/netbird/management/server/account"
|
||||
nbpeer "github.com/netbirdio/netbird/management/server/peer"
|
||||
"math/rand"
|
||||
"net"
|
||||
"os"
|
||||
@@ -12,19 +10,24 @@ import (
|
||||
sync2 "sync"
|
||||
"time"
|
||||
|
||||
pb "github.com/golang/protobuf/proto" //nolint
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||
"google.golang.org/grpc"
|
||||
"github.com/netbirdio/netbird/management/server/activity"
|
||||
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
"google.golang.org/grpc/keepalive"
|
||||
|
||||
"github.com/netbirdio/netbird/management/server"
|
||||
|
||||
pb "github.com/golang/protobuf/proto" //nolint
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/netbirdio/netbird/encryption"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/keepalive"
|
||||
|
||||
mgmtProto "github.com/netbirdio/netbird/management/proto"
|
||||
"github.com/netbirdio/netbird/management/server"
|
||||
"github.com/netbirdio/netbird/management/server/activity"
|
||||
"github.com/netbirdio/netbird/util"
|
||||
)
|
||||
|
||||
@@ -445,21 +448,6 @@ var _ = Describe("Management service", func() {
|
||||
})
|
||||
})
|
||||
|
||||
type MocIntegratedApproval struct {
|
||||
}
|
||||
|
||||
func (MocIntegratedApproval) PreparePeer(accountID string, peer *nbpeer.Peer, peersGroup []string, extraSettings *account.ExtraSettings) *nbpeer.Peer {
|
||||
return peer
|
||||
}
|
||||
|
||||
func (MocIntegratedApproval) IsRequiresApproval(accountID string, peer *nbpeer.Peer, peersGroup []string, extraSettings *account.ExtraSettings) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (MocIntegratedApproval) Stop() {
|
||||
|
||||
}
|
||||
|
||||
func loginPeerWithValidSetupKey(serverPubKey wgtypes.Key, key wgtypes.Key, client mgmtProto.ManagementServiceClient) *mgmtProto.LoginResponse {
|
||||
defer GinkgoRecover()
|
||||
|
||||
@@ -516,7 +504,7 @@ func startServer(config *server.Config) (*grpc.Server, net.Listener) {
|
||||
peersUpdateManager := server.NewPeersUpdateManager(nil)
|
||||
eventStore := &activity.InMemoryEventStore{}
|
||||
accountManager, err := server.BuildManager(store, peersUpdateManager, nil, "", "",
|
||||
eventStore, nil, false, MocIntegratedApproval{})
|
||||
eventStore, nil, false)
|
||||
if err != nil {
|
||||
log.Fatalf("failed creating a manager: %v", err)
|
||||
}
|
||||
|
||||
@@ -95,8 +95,6 @@ type MockAccountManager struct {
|
||||
ListPostureChecksFunc func(accountID, userID string) ([]*posture.Checks, error)
|
||||
GetUsageFunc func(ctx context.Context, accountID string, start, end time.Time) (*server.AccountUsageStats, error)
|
||||
GetIdpManagerFunc func() idp.Manager
|
||||
UpdateIntegratedApprovalGroupsFunc func(accountID string, userID string, groups []string) error
|
||||
GroupValidationFunc func(accountId string, groups []string) (bool, error)
|
||||
}
|
||||
|
||||
// GetUsersFromAccount mock implementation of GetUsersFromAccount from server.AccountManager interface
|
||||
@@ -724,19 +722,3 @@ func (am *MockAccountManager) GetIdpManager() idp.Manager {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateIntegratedApprovalGroups mocks UpdateIntegratedApprovalGroups of the AccountManager interface
|
||||
func (am *MockAccountManager) UpdateIntegratedApprovalGroups(accountID string, userID string, groups []string) error {
|
||||
if am.UpdateIntegratedApprovalGroupsFunc != nil {
|
||||
return am.UpdateIntegratedApprovalGroupsFunc(accountID, userID, groups)
|
||||
}
|
||||
return status.Errorf(codes.Unimplemented, "method UpdateIntegratedApprovalGroups is not implemented")
|
||||
}
|
||||
|
||||
// GroupValidation mocks GroupValidation of the AccountManager interface
|
||||
func (am *MockAccountManager) GroupValidation(accountId string, groups []string) (bool, error) {
|
||||
if am.GroupValidationFunc != nil {
|
||||
return am.GroupValidationFunc(accountId, groups)
|
||||
}
|
||||
return false, status.Errorf(codes.Unimplemented, "method GroupValidation is not implemented")
|
||||
}
|
||||
|
||||
@@ -759,7 +759,7 @@ func createNSManager(t *testing.T) (*DefaultAccountManager, error) {
|
||||
return nil, err
|
||||
}
|
||||
eventStore := &activity.InMemoryEventStore{}
|
||||
return BuildManager(store, NewPeersUpdateManager(nil), nil, "", "", eventStore, nil, false, MocIntegratedApproval{})
|
||||
return BuildManager(store, NewPeersUpdateManager(nil), nil, "", "", eventStore, nil, false)
|
||||
}
|
||||
|
||||
func createNSStore(t *testing.T) (Store, error) {
|
||||
|
||||
@@ -7,13 +7,16 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/rs/xid"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/netbirdio/management-integrations/additions"
|
||||
"github.com/netbirdio/netbird/management/proto"
|
||||
|
||||
"github.com/netbirdio/netbird/management/server/activity"
|
||||
nbpeer "github.com/netbirdio/netbird/management/server/peer"
|
||||
"github.com/netbirdio/netbird/management/server/status"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/netbirdio/netbird/management/proto"
|
||||
)
|
||||
|
||||
// PeerSync used as a data object between the gRPC API and AccountManager on Sync request.
|
||||
@@ -296,7 +299,6 @@ func (am *DefaultAccountManager) GetNetworkMap(peerID string) (*NetworkMap, erro
|
||||
if peer == nil {
|
||||
return nil, status.Errorf(status.NotFound, "peer with ID %s not found", peerID)
|
||||
}
|
||||
|
||||
return account.GetPeerNetworkMap(peer.ID, am.dnsDomain), nil
|
||||
}
|
||||
|
||||
@@ -428,6 +430,10 @@ func (am *DefaultAccountManager) AddPeer(setupKey, userID string, peer *nbpeer.P
|
||||
Ephemeral: ephemeral,
|
||||
}
|
||||
|
||||
if account.Settings.Extra != nil {
|
||||
newPeer = additions.PreparePeer(newPeer, account.Settings.Extra)
|
||||
}
|
||||
|
||||
// add peer to 'All' group
|
||||
group, err := account.GetGroupAll()
|
||||
if err != nil {
|
||||
@@ -456,8 +462,6 @@ func (am *DefaultAccountManager) AddPeer(setupKey, userID string, peer *nbpeer.P
|
||||
}
|
||||
}
|
||||
|
||||
newPeer = am.integratedPeerValidator.PreparePeer(account.Id, newPeer, account.GetPeerGroupsList(newPeer.ID), account.Settings.Extra)
|
||||
|
||||
if addedByUser {
|
||||
user, err := account.FindUser(userID)
|
||||
if err != nil {
|
||||
@@ -520,16 +524,6 @@ func (am *DefaultAccountManager) SyncPeer(sync PeerSync) (*nbpeer.Peer, *Network
|
||||
if peerLoginExpired(peer, account) {
|
||||
return nil, nil, status.Errorf(status.PermissionDenied, "peer login has expired, please log in once more")
|
||||
}
|
||||
|
||||
requiresApproval := am.integratedPeerValidator.IsRequiresApproval(account.Id, peer, account.GetPeerGroupsList(peer.ID), account.Settings.Extra)
|
||||
if peer.Status.RequiresApproval != requiresApproval {
|
||||
peer.Status.RequiresApproval = requiresApproval
|
||||
err = am.Store.SaveAccount(account)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return peer, account.GetPeerNetworkMap(peer.ID, am.dnsDomain), nil
|
||||
}
|
||||
|
||||
@@ -596,11 +590,6 @@ func (am *DefaultAccountManager) LoginPeer(login PeerLogin) (*nbpeer.Peer, *Netw
|
||||
am.StoreEvent(login.UserID, peer.ID, account.Id, activity.UserLoggedInPeer, peer.EventMeta(am.GetDNSDomain()))
|
||||
}
|
||||
|
||||
isRequiresApproval := am.integratedPeerValidator.IsRequiresApproval(account.Id, peer, account.GetPeerGroupsList(peer.ID), account.Settings.Extra)
|
||||
if peer.Status.RequiresApproval != isRequiresApproval {
|
||||
shouldStoreAccount = true
|
||||
}
|
||||
|
||||
peer, updated := updatePeerMeta(peer, login.Meta, account)
|
||||
if updated {
|
||||
shouldStoreAccount = true
|
||||
@@ -621,7 +610,6 @@ func (am *DefaultAccountManager) LoginPeer(login PeerLogin) (*nbpeer.Peer, *Netw
|
||||
if updateRemotePeers {
|
||||
am.updateAccountPeers(account)
|
||||
}
|
||||
|
||||
return peer, account.GetPeerNetworkMap(peer.ID, am.dnsDomain), nil
|
||||
}
|
||||
|
||||
|
||||
@@ -5,9 +5,8 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/netbirdio/management-integrations/additions"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/netbirdio/netbird/management/proto"
|
||||
"github.com/netbirdio/netbird/management/server/activity"
|
||||
|
||||
@@ -1014,7 +1014,7 @@ func createRouterManager(t *testing.T) (*DefaultAccountManager, error) {
|
||||
return nil, err
|
||||
}
|
||||
eventStore := &activity.InMemoryEventStore{}
|
||||
return BuildManager(store, NewPeersUpdateManager(nil), nil, "", "", eventStore, nil, false, MocIntegratedApproval{})
|
||||
return BuildManager(store, NewPeersUpdateManager(nil), nil, "", "", eventStore, nil, false)
|
||||
}
|
||||
|
||||
func createRouterStore(t *testing.T) (Store, error) {
|
||||
|
||||
@@ -505,10 +505,13 @@ func (s *SqliteStore) CalculateUsageStats(ctx context.Context, accountID string,
|
||||
stats := &AccountUsageStats{}
|
||||
|
||||
err := s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
err := tx.Model(&nbpeer.Peer{}).
|
||||
Where("account_id = ? AND peer_status_last_seen BETWEEN ? AND ?", accountID, start, end).
|
||||
Distinct("user_id").
|
||||
Count(&stats.ActiveUsers).Error
|
||||
err := tx.Raw(`
|
||||
SELECT COUNT(DISTINCT user_id) FROM (
|
||||
SELECT id as user_id FROM users WHERE account_id = ? AND last_login BETWEEN ? AND ? AND is_service_user = ?
|
||||
UNION
|
||||
SELECT user_id FROM peers WHERE account_id = ? AND peer_status_last_seen BETWEEN ? AND ?
|
||||
) AS combined`, accountID, start, end, false, accountID, start, end).Scan(&stats.ActiveUsers).Error
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("get active users: %w", err)
|
||||
}
|
||||
|
||||
@@ -360,15 +360,15 @@ func TestSqliteStore_CalculateUsageStats(t *testing.T) {
|
||||
stats1, err := store.CalculateUsageStats(context.TODO(), "account-1", startDate, endDate)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, int64(2), stats1.ActiveUsers)
|
||||
assert.Equal(t, int64(4), stats1.TotalUsers)
|
||||
assert.Equal(t, int64(3), stats1.ActiveUsers)
|
||||
assert.Equal(t, int64(5), stats1.TotalUsers)
|
||||
assert.Equal(t, int64(3), stats1.ActivePeers)
|
||||
assert.Equal(t, int64(7), stats1.TotalPeers)
|
||||
assert.Equal(t, int64(8), stats1.TotalPeers)
|
||||
|
||||
stats2, err := store.CalculateUsageStats(context.TODO(), "account-2", startDate, endDate)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, int64(1), stats2.ActiveUsers)
|
||||
assert.Equal(t, int64(2), stats2.ActiveUsers)
|
||||
assert.Equal(t, int64(2), stats2.TotalUsers)
|
||||
assert.Equal(t, int64(1), stats2.ActivePeers)
|
||||
assert.Equal(t, int64(2), stats2.TotalPeers)
|
||||
|
||||
39
management/server/testdata/store_stats.json
vendored
39
management/server/testdata/store_stats.json
vendored
@@ -13,20 +13,29 @@
|
||||
},
|
||||
"Users": {
|
||||
"user-1-account-1": {
|
||||
"Id": "user-1-account-1"
|
||||
"Id": "user-1-account-1",
|
||||
"LastLogin": "2023-01-15T00:00:00Z"
|
||||
},
|
||||
"user-2-account-1": {
|
||||
"Id": "user-2-account-1"
|
||||
"Id": "user-2-account-1",
|
||||
"LastLogin": "2024-01-02T00:00:00Z"
|
||||
},
|
||||
"user-3-account-1": {
|
||||
"Id": "user-3-account-1"
|
||||
"Id": "user-3-account-1",
|
||||
"LastLogin": "2024-02-05T00:00:00Z"
|
||||
},
|
||||
"user-4-account-1": {
|
||||
"Id": "user-4-account-1"
|
||||
"Id": "user-4-account-1",
|
||||
"LastLogin": "2024-01-20T00:00:00Z"
|
||||
},
|
||||
"user-5-account-1": {
|
||||
"Id": "user-5-account-1",
|
||||
"IsServiceUser": true
|
||||
"IsServiceUser": true,
|
||||
"LastLogin": "2024-02-15T00:00:00Z"
|
||||
},
|
||||
"user-6-account-1": {
|
||||
"Id": "user-6-account-1",
|
||||
"LastLogin": "2024-02-10T00:00:00Z"
|
||||
}
|
||||
},
|
||||
"Peers": {
|
||||
@@ -106,6 +115,17 @@
|
||||
"Meta": {
|
||||
"Hostname": "peer7-host"
|
||||
}
|
||||
},
|
||||
"peer-8-account-1": {
|
||||
"ID": "peer-8-account-1",
|
||||
"UserID": "user-6-account-1",
|
||||
"Status": {
|
||||
"LastSeen": "2024-01-01T00:00:00Z"
|
||||
},
|
||||
"Name": "Peer Eight",
|
||||
"Meta": {
|
||||
"Hostname": "peer8-host"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -122,14 +142,17 @@
|
||||
},
|
||||
"Users": {
|
||||
"user-1-account-2": {
|
||||
"Id": "user-1-account-2"
|
||||
"Id": "user-1-account-2",
|
||||
"LastLogin": "2023-12-15T00:00:00Z"
|
||||
},
|
||||
"user-2-account-2": {
|
||||
"Id": "user-1-account-2"
|
||||
"Id": "user-2-account-2",
|
||||
"LastLogin": "2024-02-28T00:00:00Z"
|
||||
},
|
||||
"user-3-account-2": {
|
||||
"Id": "user-3-account-2",
|
||||
"IsServiceUser": true
|
||||
"IsServiceUser": true,
|
||||
"LastLogin": "2024-01-30T00:00:00Z"
|
||||
}
|
||||
},
|
||||
"Peers": {
|
||||
|
||||
Reference in New Issue
Block a user