mirror of
https://github.com/netbirdio/netbird.git
synced 2026-07-23 00:41:29 +02:00
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,8 +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
|
||||
GroupValidation(accountId string, groups []string) (bool, error)
|
||||
}
|
||||
|
||||
type DefaultAccountManager struct {
|
||||
@@ -157,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
|
||||
@@ -393,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
|
||||
@@ -579,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 {
|
||||
@@ -847,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,
|
||||
@@ -861,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"
|
||||
|
||||
@@ -1,132 +0,0 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/google/martian/v3/log"
|
||||
|
||||
"github.com/netbirdio/netbird/management/server/account"
|
||||
)
|
||||
|
||||
// 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 {
|
||||
unlock := am.Store.AcquireAccountLock(accountID)
|
||||
defer unlock()
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
am.cleanIntegratedApprovalFlag(a, groups)
|
||||
err = am.updateFlags(a, groups)
|
||||
if err != nil {
|
||||
saveErr := am.Store.SaveAccount(a)
|
||||
if saveErr != nil {
|
||||
log.Errorf("failed to save account: %s", saveErr)
|
||||
}
|
||||
return err
|
||||
}
|
||||
return am.Store.SaveAccount(a)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// updateFlags set the requiresIntegratedApproval flag to true for all peers in the account what is part of the groups, but the peer not part of the already approved list in the edr db
|
||||
func (am *DefaultAccountManager) updateFlags(a *Account, groups []string) error {
|
||||
approvedPeers, err := am.integratedPeerValidator.ApprovedPeersList(a.Id)
|
||||
if err != nil {
|
||||
log.Errorf("failed to get approved peers list: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
for peerID, peer := range a.Peers {
|
||||
peerGroups := a.GetPeerGroupsList(peerID)
|
||||
if !isPeerAssignedToIntegratedApprovalGroup(peerGroups, groups) {
|
||||
continue
|
||||
}
|
||||
|
||||
// set true only that case if not yet approved in the edr db
|
||||
_, ok := approvedPeers[peerID]
|
||||
if ok {
|
||||
continue
|
||||
}
|
||||
peer.Status.RequiresIntegratedApproval = true
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// cleanIntegratedApprovalFlag set the requireIntegratedApproval flag to false for all peers in the account what is not part of the groups
|
||||
func (am *DefaultAccountManager) cleanIntegratedApprovalFlag(a *Account, groups []string) {
|
||||
for peerID, peer := range a.Peers {
|
||||
peerGroups := a.GetPeerGroupsList(peerID)
|
||||
if isPeerAssignedToIntegratedApprovalGroup(peerGroups, groups) {
|
||||
continue
|
||||
}
|
||||
peer.Status.RequiresIntegratedApproval = false
|
||||
}
|
||||
}
|
||||
|
||||
func isPeerAssignedToIntegratedApprovalGroup(peersGroup []string, integratedApprovalGroups []string) bool {
|
||||
for _, peerGroup := range peersGroup {
|
||||
for _, ig := range integratedApprovalGroups {
|
||||
if ig == peerGroup {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -1,14 +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, bool)
|
||||
ApprovedPeersList(id string) (map[string]struct{}, error)
|
||||
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,17 +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, requiresIntegratedApproval := am.integratedPeerValidator.IsRequiresApproval(account.Id, peer, account.GetPeerGroupsList(peer.ID), account.Settings.Extra)
|
||||
if peer.Status.RequiresApproval != requiresApproval || peer.Status.RequiresIntegratedApproval != requiresIntegratedApproval {
|
||||
peer.Status.RequiresApproval = requiresApproval
|
||||
peer.Status.RequiresIntegratedApproval = requiresIntegratedApproval
|
||||
err = am.Store.SaveAccount(account)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return peer, account.GetPeerNetworkMap(peer.ID, am.dnsDomain), nil
|
||||
}
|
||||
|
||||
@@ -597,13 +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, isRequiresIntegratedApproval := am.integratedPeerValidator.IsRequiresApproval(account.Id, peer, account.GetPeerGroupsList(peer.ID), account.Settings.Extra)
|
||||
if peer.Status.RequiresApproval != isRequiresApproval || peer.Status.RequiresIntegratedApproval != isRequiresIntegratedApproval {
|
||||
peer.Status.RequiresApproval = isRequiresApproval
|
||||
peer.Status.RequiresIntegratedApproval = isRequiresIntegratedApproval
|
||||
shouldStoreAccount = true
|
||||
}
|
||||
|
||||
peer, updated := updatePeerMeta(peer, login.Meta, account)
|
||||
if updated {
|
||||
shouldStoreAccount = true
|
||||
@@ -624,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
|
||||
}
|
||||
|
||||
|
||||
@@ -57,8 +57,6 @@ type PeerStatus struct { //nolint:revive
|
||||
LoginExpired bool
|
||||
// RequiresApproval indicates whether peer requires approval or not
|
||||
RequiresApproval bool
|
||||
// RequiresIntegratedApproval indicates whether peer requires integrated approval or not
|
||||
RequiresIntegratedApproval bool
|
||||
}
|
||||
|
||||
// Location is a geo location information of a Peer based on public connection IP
|
||||
@@ -224,11 +222,10 @@ func (p *Peer) EventMeta(dnsDomain string) map[string]any {
|
||||
// Copy PeerStatus
|
||||
func (p *PeerStatus) Copy() *PeerStatus {
|
||||
return &PeerStatus{
|
||||
LastSeen: p.LastSeen,
|
||||
Connected: p.Connected,
|
||||
LoginExpired: p.LoginExpired,
|
||||
RequiresApproval: p.RequiresApproval,
|
||||
RequiresIntegratedApproval: p.RequiresIntegratedApproval,
|
||||
LastSeen: p.LastSeen,
|
||||
Connected: p.Connected,
|
||||
LoginExpired: p.LoginExpired,
|
||||
RequiresApproval: p.RequiresApproval,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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