diff --git a/management/server/networks/manager_test.go b/management/server/networks/manager_test.go new file mode 100644 index 000000000..5fa2b17f1 --- /dev/null +++ b/management/server/networks/manager_test.go @@ -0,0 +1,209 @@ +package networks + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/netbirdio/netbird/management/server/networks/types" + "github.com/netbirdio/netbird/management/server/permissions" + "github.com/netbirdio/netbird/management/server/store" +) + +func Test_GetAllNetworksReturnsNetworks(t *testing.T) { + ctx := context.Background() + accountID := "testAccountId" + userID := "allowedUser" + + s, cleanUp, err := store.NewTestStoreFromSQL(context.Background(), "../testdata/networks.sql", t.TempDir()) + if err != nil { + t.Fatal(err) + } + t.Cleanup(cleanUp) + permissionsManager := permissions.NewManagerMock() + manager := NewManager(s, permissionsManager) + + networks, err := manager.GetAllNetworks(ctx, accountID, userID) + require.NoError(t, err) + require.Len(t, networks, 1) + require.Equal(t, "testNetworkId", networks[0].ID) +} + +func Test_GetAllNetworksReturnsPermissionDenied(t *testing.T) { + ctx := context.Background() + accountID := "testAccountId" + userID := "invalidUser" + + s, cleanUp, err := store.NewTestStoreFromSQL(context.Background(), "../testdata/networks.sql", t.TempDir()) + if err != nil { + t.Fatal(err) + } + t.Cleanup(cleanUp) + permissionsManager := permissions.NewManagerMock() + manager := NewManager(s, permissionsManager) + + networks, err := manager.GetAllNetworks(ctx, accountID, userID) + require.Error(t, err) + require.Nil(t, networks) +} + +func Test_GetNetworkReturnsNetwork(t *testing.T) { + ctx := context.Background() + accountID := "testAccountId" + userID := "allowedUser" + networkID := "testNetworkId" + + s, cleanUp, err := store.NewTestStoreFromSQL(context.Background(), "../testdata/networks.sql", t.TempDir()) + if err != nil { + t.Fatal(err) + } + t.Cleanup(cleanUp) + permissionsManager := permissions.NewManagerMock() + manager := NewManager(s, permissionsManager) + + networks, err := manager.GetNetwork(ctx, accountID, userID, networkID) + require.NoError(t, err) + require.Equal(t, "testNetworkId", networks.ID) +} + +func Test_GetNetworkReturnsPermissionDenied(t *testing.T) { + ctx := context.Background() + accountID := "testAccountId" + userID := "invalidUser" + networkID := "testNetworkId" + + s, cleanUp, err := store.NewTestStoreFromSQL(context.Background(), "../testdata/networks.sql", t.TempDir()) + if err != nil { + t.Fatal(err) + } + t.Cleanup(cleanUp) + permissionsManager := permissions.NewManagerMock() + manager := NewManager(s, permissionsManager) + + network, err := manager.GetNetwork(ctx, accountID, userID, networkID) + require.Error(t, err) + require.Nil(t, network) +} + +func Test_CreateNetworkSuccessfully(t *testing.T) { + ctx := context.Background() + userID := "allowedUser" + network := &types.Network{ + AccountID: "testAccountId", + Name: "new-network", + } + + s, cleanUp, err := store.NewTestStoreFromSQL(context.Background(), "../testdata/networks.sql", t.TempDir()) + if err != nil { + t.Fatal(err) + } + t.Cleanup(cleanUp) + permissionsManager := permissions.NewManagerMock() + manager := NewManager(s, permissionsManager) + + createdNetwork, err := manager.CreateNetwork(ctx, userID, network) + require.NoError(t, err) + require.Equal(t, network.Name, createdNetwork.Name) +} + +func Test_CreateNetworkFailsWithPermissionDenied(t *testing.T) { + ctx := context.Background() + userID := "invalidUser" + network := &types.Network{ + AccountID: "testAccountId", + Name: "new-network", + } + + s, cleanUp, err := store.NewTestStoreFromSQL(context.Background(), "../testdata/networks.sql", t.TempDir()) + if err != nil { + t.Fatal(err) + } + t.Cleanup(cleanUp) + permissionsManager := permissions.NewManagerMock() + manager := NewManager(s, permissionsManager) + + createdNetwork, err := manager.CreateNetwork(ctx, userID, network) + require.Error(t, err) + require.Nil(t, createdNetwork) +} + +func Test_DeleteNetworkSuccessfully(t *testing.T) { + ctx := context.Background() + accountID := "testAccountId" + userID := "allowedUser" + networkID := "testNetworkId" + + s, cleanUp, err := store.NewTestStoreFromSQL(context.Background(), "../testdata/networks.sql", t.TempDir()) + if err != nil { + t.Fatal(err) + } + t.Cleanup(cleanUp) + permissionsManager := permissions.NewManagerMock() + manager := NewManager(s, permissionsManager) + + err = manager.DeleteNetwork(ctx, accountID, userID, networkID) + require.NoError(t, err) +} + +func Test_DeleteNetworkFailsWithPermissionDenied(t *testing.T) { + ctx := context.Background() + accountID := "testAccountId" + userID := "invalidUser" + networkID := "testNetworkId" + + s, cleanUp, err := store.NewTestStoreFromSQL(context.Background(), "../testdata/networks.sql", t.TempDir()) + if err != nil { + t.Fatal(err) + } + t.Cleanup(cleanUp) + permissionsManager := permissions.NewManagerMock() + manager := NewManager(s, permissionsManager) + + err = manager.DeleteNetwork(ctx, accountID, userID, networkID) + require.Error(t, err) +} + +func Test_UpdateNetworkSuccessfully(t *testing.T) { + ctx := context.Background() + userID := "allowedUser" + network := &types.Network{ + AccountID: "testAccountId", + ID: "testNetworkId", + Name: "new-network", + } + + s, cleanUp, err := store.NewTestStoreFromSQL(context.Background(), "../testdata/networks.sql", t.TempDir()) + if err != nil { + t.Fatal(err) + } + t.Cleanup(cleanUp) + permissionsManager := permissions.NewManagerMock() + manager := NewManager(s, permissionsManager) + + updatedNetwork, err := manager.UpdateNetwork(ctx, userID, network) + require.NoError(t, err) + require.Equal(t, network.Name, updatedNetwork.Name) +} + +func Test_UpdateNetworkFailsWithPermissionDenied(t *testing.T) { + ctx := context.Background() + userID := "invalidUser" + network := &types.Network{ + AccountID: "testAccountId", + ID: "testNetworkId", + Name: "new-network", + } + + s, cleanUp, err := store.NewTestStoreFromSQL(context.Background(), "../testdata/networks.sql", t.TempDir()) + if err != nil { + t.Fatal(err) + } + t.Cleanup(cleanUp) + permissionsManager := permissions.NewManagerMock() + manager := NewManager(s, permissionsManager) + + updatedNetwork, err := manager.UpdateNetwork(ctx, userID, network) + require.Error(t, err) + require.Nil(t, updatedNetwork) +} diff --git a/management/server/networks/resources/manager.go b/management/server/networks/resources/manager.go index ad62f7b03..8d13659bd 100644 --- a/management/server/networks/resources/manager.go +++ b/management/server/networks/resources/manager.go @@ -93,6 +93,11 @@ func (m *managerImpl) CreateResource(ctx context.Context, userID string, resourc return nil, fmt.Errorf("failed to create new network resource: %w", err) } + _, err = m.store.GetNetworkResourceByName(ctx, store.LockingStrengthShare, resource.AccountID, resource.Name) + if err == nil { + return nil, errors.New("resource already exists") + } + return resource, m.store.SaveNetworkResource(ctx, store.LockingStrengthUpdate, resource) } @@ -126,12 +131,23 @@ func (m *managerImpl) UpdateResource(ctx context.Context, userID string, resourc return nil, status.NewPermissionDeniedError() } - resourceType, err := types.GetResourceType(resource.Address) + resourceType, addr, err := types.GetResourceType(resource.Address) if err != nil { return nil, fmt.Errorf("failed to get resource type: %w", err) } resource.Type = resourceType + resource.Address = addr + + _, err = m.store.GetNetworkResourceByID(ctx, store.LockingStrengthShare, resource.AccountID, resource.ID) + if err != nil { + return nil, fmt.Errorf("failed to get network resource: %w", err) + } + + oldResource, err := m.store.GetNetworkResourceByName(ctx, store.LockingStrengthShare, resource.AccountID, resource.Name) + if err == nil && oldResource.ID != resource.ID { + return nil, errors.New("new resource name already exists") + } return resource, m.store.SaveNetworkResource(ctx, store.LockingStrengthUpdate, resource) } diff --git a/management/server/networks/resources/manager_test.go b/management/server/networks/resources/manager_test.go new file mode 100644 index 000000000..f053c56f0 --- /dev/null +++ b/management/server/networks/resources/manager_test.go @@ -0,0 +1,379 @@ +package resources + +import ( + "context" + "errors" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/netbirdio/netbird/management/server/networks/resources/types" + "github.com/netbirdio/netbird/management/server/permissions" + "github.com/netbirdio/netbird/management/server/status" + "github.com/netbirdio/netbird/management/server/store" +) + +func Test_GetAllResourcesInNetworkReturnsResources(t *testing.T) { + ctx := context.Background() + accountID := "testAccountId" + userID := "allowedUser" + networkID := "testNetworkId" + + s, cleanUp, err := store.NewTestStoreFromSQL(context.Background(), "../../testdata/networks.sql", t.TempDir()) + if err != nil { + t.Fatal(err) + } + t.Cleanup(cleanUp) + permissionsManager := permissions.NewManagerMock() + manager := NewManager(s, permissionsManager) + + resources, err := manager.GetAllResourcesInNetwork(ctx, accountID, userID, networkID) + require.NoError(t, err) + require.Len(t, resources, 2) +} + +func Test_GetAllResourcesInNetworkReturnsPermissionDenied(t *testing.T) { + ctx := context.Background() + accountID := "testAccountId" + userID := "invalidUser" + networkID := "testNetworkId" + + s, cleanUp, err := store.NewTestStoreFromSQL(context.Background(), "../../testdata/networks.sql", t.TempDir()) + if err != nil { + t.Fatal(err) + } + t.Cleanup(cleanUp) + permissionsManager := permissions.NewManagerMock() + manager := NewManager(s, permissionsManager) + + resources, err := manager.GetAllResourcesInNetwork(ctx, accountID, userID, networkID) + require.Error(t, err) + require.Equal(t, status.NewPermissionDeniedError(), err) + require.Nil(t, resources) +} +func Test_GetAllResourcesInAccountReturnsResources(t *testing.T) { + ctx := context.Background() + accountID := "testAccountId" + userID := "allowedUser" + + s, cleanUp, err := store.NewTestStoreFromSQL(context.Background(), "../../testdata/networks.sql", t.TempDir()) + if err != nil { + t.Fatal(err) + } + t.Cleanup(cleanUp) + permissionsManager := permissions.NewManagerMock() + manager := NewManager(s, permissionsManager) + + resources, err := manager.GetAllResourcesInAccount(ctx, accountID, userID) + require.NoError(t, err) + require.Len(t, resources, 2) +} + +func Test_GetAllResourcesInAccountReturnsPermissionDenied(t *testing.T) { + ctx := context.Background() + accountID := "testAccountId" + userID := "invalidUser" + + s, cleanUp, err := store.NewTestStoreFromSQL(context.Background(), "../../testdata/networks.sql", t.TempDir()) + if err != nil { + t.Fatal(err) + } + t.Cleanup(cleanUp) + permissionsManager := permissions.NewManagerMock() + manager := NewManager(s, permissionsManager) + + resources, err := manager.GetAllResourcesInAccount(ctx, accountID, userID) + require.Error(t, err) + require.Equal(t, status.NewPermissionDeniedError(), err) + require.Nil(t, resources) +} + +func Test_GetResourceInNetworkReturnsResources(t *testing.T) { + ctx := context.Background() + accountID := "testAccountId" + userID := "allowedUser" + networkID := "testNetworkId" + resourceID := "testResourceId" + + s, cleanUp, err := store.NewTestStoreFromSQL(context.Background(), "../../testdata/networks.sql", t.TempDir()) + if err != nil { + t.Fatal(err) + } + t.Cleanup(cleanUp) + permissionsManager := permissions.NewManagerMock() + manager := NewManager(s, permissionsManager) + + resource, err := manager.GetResource(ctx, accountID, userID, networkID, resourceID) + require.NoError(t, err) + require.Equal(t, resourceID, resource.ID) +} + +func Test_GetResourceInNetworkReturnsPermissionDenied(t *testing.T) { + ctx := context.Background() + accountID := "testAccountId" + userID := "invalidUser" + networkID := "testNetworkId" + resourceID := "testResourceId" + + s, cleanUp, err := store.NewTestStoreFromSQL(context.Background(), "../../testdata/networks.sql", t.TempDir()) + if err != nil { + t.Fatal(err) + } + t.Cleanup(cleanUp) + permissionsManager := permissions.NewManagerMock() + manager := NewManager(s, permissionsManager) + + resources, err := manager.GetResource(ctx, accountID, userID, networkID, resourceID) + require.Error(t, err) + require.Equal(t, status.NewPermissionDeniedError(), err) + require.Nil(t, resources) +} + +func Test_CreateResourceSuccessfully(t *testing.T) { + ctx := context.Background() + userID := "allowedUser" + resource := &types.NetworkResource{ + AccountID: "testAccountId", + NetworkID: "testNetworkId", + Name: "newResourceId", + Description: "description", + Address: "192.168.1.1", + } + + store, cleanUp, err := store.NewTestStoreFromSQL(context.Background(), "../../testdata/networks.sql", t.TempDir()) + if err != nil { + t.Fatal(err) + } + t.Cleanup(cleanUp) + permissionsManager := permissions.NewManagerMock() + manager := NewManager(store, permissionsManager) + + createdResource, err := manager.CreateResource(ctx, userID, resource) + require.NoError(t, err) + require.Equal(t, resource.Name, createdResource.Name) +} + +func Test_CreateResourceFailsWithPermissionDenied(t *testing.T) { + ctx := context.Background() + userID := "invalidUser" + resource := &types.NetworkResource{ + AccountID: "testAccountId", + NetworkID: "testNetworkId", + Name: "testResourceId", + Description: "description", + Address: "192.168.1.1", + } + + store, cleanUp, err := store.NewTestStoreFromSQL(context.Background(), "../../testdata/networks.sql", t.TempDir()) + if err != nil { + t.Fatal(err) + } + t.Cleanup(cleanUp) + permissionsManager := permissions.NewManagerMock() + manager := NewManager(store, permissionsManager) + + createdResource, err := manager.CreateResource(ctx, userID, resource) + require.Error(t, err) + require.Equal(t, status.NewPermissionDeniedError(), err) + require.Nil(t, createdResource) +} + +func Test_CreateResourceFailsWithInvalidAddress(t *testing.T) { + ctx := context.Background() + userID := "allowedUser" + resource := &types.NetworkResource{ + AccountID: "testAccountId", + NetworkID: "testNetworkId", + Name: "testResourceId", + Description: "description", + Address: "invalid-address", + } + + store, cleanUp, err := store.NewTestStoreFromSQL(context.Background(), "../../testdata/networks.sql", t.TempDir()) + if err != nil { + t.Fatal(err) + } + t.Cleanup(cleanUp) + permissionsManager := permissions.NewManagerMock() + manager := NewManager(store, permissionsManager) + + createdResource, err := manager.CreateResource(ctx, userID, resource) + require.Error(t, err) + require.Nil(t, createdResource) +} + +func Test_CreateResourceFailsWithUsedName(t *testing.T) { + ctx := context.Background() + userID := "allowedUser" + resource := &types.NetworkResource{ + AccountID: "testAccountId", + NetworkID: "testNetworkId", + Name: "testResourceId", + Description: "description", + Address: "invalid-address", + } + + store, cleanUp, err := store.NewTestStoreFromSQL(context.Background(), "../../testdata/networks.sql", t.TempDir()) + if err != nil { + t.Fatal(err) + } + t.Cleanup(cleanUp) + permissionsManager := permissions.NewManagerMock() + manager := NewManager(store, permissionsManager) + + createdResource, err := manager.CreateResource(ctx, userID, resource) + require.Error(t, err) + require.Nil(t, createdResource) +} + +func Test_UpdateResourceSuccessfully(t *testing.T) { + ctx := context.Background() + accountID := "testAccountId" + userID := "allowedUser" + networkID := "testNetworkId" + resourceID := "testResourceId" + resource := &types.NetworkResource{ + AccountID: accountID, + NetworkID: networkID, + Name: "someNewName", + ID: resourceID, + Description: "new-description", + Address: "1.2.3.0/24", + } + + s, cleanUp, err := store.NewTestStoreFromSQL(context.Background(), "../../testdata/networks.sql", t.TempDir()) + if err != nil { + t.Fatal(err) + } + t.Cleanup(cleanUp) + permissionsManager := permissions.NewManagerMock() + manager := NewManager(s, permissionsManager) + + updatedResource, err := manager.UpdateResource(ctx, userID, resource) + require.NoError(t, err) + require.NotNil(t, updatedResource) + require.Equal(t, "new-description", updatedResource.Description) + require.Equal(t, "1.2.3.0/24", updatedResource.Address) + require.Equal(t, types.NetworkResourceType("subnet"), updatedResource.Type) +} + +func Test_UpdateResourceFailsWithResourceNotFound(t *testing.T) { + ctx := context.Background() + accountID := "testAccountId" + userID := "allowedUser" + networkID := "testNetworkId" + resourceID := "otherResourceId" + resource := &types.NetworkResource{ + AccountID: accountID, + NetworkID: networkID, + Name: resourceID, + Description: "new-description", + Address: "1.2.3.0/24", + } + + s, cleanUp, err := store.NewTestStoreFromSQL(context.Background(), "../../testdata/networks.sql", t.TempDir()) + if err != nil { + t.Fatal(err) + } + t.Cleanup(cleanUp) + permissionsManager := permissions.NewManagerMock() + manager := NewManager(s, permissionsManager) + + updatedResource, err := manager.UpdateResource(ctx, userID, resource) + require.Error(t, err) + require.Nil(t, updatedResource) +} + +func Test_UpdateResourceFailsWithNameInUse(t *testing.T) { + ctx := context.Background() + accountID := "testAccountId" + userID := "allowedUser" + networkID := "testNetworkId" + resourceID := "testResourceId" + resource := &types.NetworkResource{ + AccountID: accountID, + NetworkID: networkID, + ID: resourceID, + Name: "used-name", + Description: "new-description", + Address: "1.2.3.0/24", + } + + s, cleanUp, err := store.NewTestStoreFromSQL(context.Background(), "../../testdata/networks.sql", t.TempDir()) + if err != nil { + t.Fatal(err) + } + t.Cleanup(cleanUp) + permissionsManager := permissions.NewManagerMock() + manager := NewManager(s, permissionsManager) + + updatedResource, err := manager.UpdateResource(ctx, userID, resource) + require.Error(t, err) + require.Equal(t, errors.New("new resource name already exists"), err) + require.Nil(t, updatedResource) +} + +func Test_UpdateResourceFailsWithPermissionDenied(t *testing.T) { + ctx := context.Background() + accountID := "testAccountId" + userID := "invalidUser" + networkID := "testNetworkId" + resourceID := "testResourceId" + resource := &types.NetworkResource{ + AccountID: accountID, + NetworkID: networkID, + Name: resourceID, + Description: "new-description", + Address: "1.2.3.0/24", + } + + s, cleanUp, err := store.NewTestStoreFromSQL(context.Background(), "../../testdata/networks.sql", t.TempDir()) + if err != nil { + t.Fatal(err) + } + t.Cleanup(cleanUp) + permissionsManager := permissions.NewManagerMock() + manager := NewManager(s, permissionsManager) + + updatedResource, err := manager.UpdateResource(ctx, userID, resource) + require.Error(t, err) + require.Nil(t, updatedResource) +} + +func Test_DeleteResourceSuccessfully(t *testing.T) { + ctx := context.Background() + accountID := "testAccountId" + userID := "allowedUser" + networkID := "testNetworkId" + resourceID := "testResourceId" + + s, cleanUp, err := store.NewTestStoreFromSQL(context.Background(), "../../testdata/networks.sql", t.TempDir()) + if err != nil { + t.Fatal(err) + } + t.Cleanup(cleanUp) + permissionsManager := permissions.NewManagerMock() + manager := NewManager(s, permissionsManager) + + err = manager.DeleteResource(ctx, accountID, userID, networkID, resourceID) + require.NoError(t, err) +} + +func Test_DeleteResourceFailsWithPermissionDenied(t *testing.T) { + ctx := context.Background() + accountID := "testAccountId" + userID := "invalidUser" + networkID := "testNetworkId" + resourceID := "testResourceId" + + store, cleanUp, err := store.NewTestStoreFromSQL(context.Background(), "../../testdata/networks.sql", t.TempDir()) + if err != nil { + t.Fatal(err) + } + t.Cleanup(cleanUp) + permissionsManager := permissions.NewManagerMock() + manager := NewManager(store, permissionsManager) + + err = manager.DeleteResource(ctx, accountID, userID, networkID, resourceID) + require.Error(t, err) +} diff --git a/management/server/networks/resources/types/resource.go b/management/server/networks/resources/types/resource.go index ed142083b..32aef433b 100644 --- a/management/server/networks/resources/types/resource.go +++ b/management/server/networks/resources/types/resource.go @@ -35,7 +35,7 @@ type NetworkResource struct { } func NewNetworkResource(accountID, networkID, name, description, address string) (*NetworkResource, error) { - resourceType, err := GetResourceType(address) + resourceType, address, err := GetResourceType(address) if err != nil { return nil, fmt.Errorf("invalid address: %w", err) } @@ -84,23 +84,26 @@ func (n *NetworkResource) Copy() *NetworkResource { } // GetResourceType returns the type of the resource based on the address -func GetResourceType(address string) (NetworkResourceType, error) { +func GetResourceType(address string) (NetworkResourceType, string, error) { if ip, cidr, err := net.ParseCIDR(address); err == nil { ones, _ := cidr.Mask.Size() - if strings.HasSuffix(address, "/32") || (ip != nil && ones == 32) { - return host, nil + if strings.HasSuffix(address, "/32") { + return host, address, nil } - return subnet, nil + if ip != nil && ones == 32 { + return host, address + "/32", nil + } + return subnet, address, nil } if net.ParseIP(address) != nil { - return host, nil + return host, address + "/32", nil } domainRegex := regexp.MustCompile(`^(\*\.)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$`) if domainRegex.MatchString(address) { - return domain, nil + return domain, address, nil } - return "", errors.New("not a host, subnet, or domain") + return "", "", errors.New("not a host, subnet, or domain") } diff --git a/management/server/networks/resources/types/resource_test.go b/management/server/networks/resources/types/resource_test.go index 6b12ca0fc..7647371ce 100644 --- a/management/server/networks/resources/types/resource_test.go +++ b/management/server/networks/resources/types/resource_test.go @@ -9,26 +9,27 @@ func TestGetResourceType(t *testing.T) { input string expectedType NetworkResourceType expectedErr bool + expectedAddr string }{ // Valid host IPs - {"1.1.1.1", host, false}, - {"1.1.1.1/32", host, false}, + {"1.1.1.1", host, false, "1.1.1.1/32"}, + {"1.1.1.1/32", host, false, "1.1.1.1/32"}, // Valid subnets - {"192.168.1.0/24", subnet, false}, - {"10.0.0.0/16", subnet, false}, + {"192.168.1.0/24", subnet, false, "192.168.1.0/24"}, + {"10.0.0.0/16", subnet, false, "10.0.0.0/16"}, // Valid domains - {"example.com", domain, false}, - {"*.example.com", domain, false}, - {"sub.example.com", domain, false}, + {"example.com", domain, false, "example.com"}, + {"*.example.com", domain, false, "*.example.com"}, + {"sub.example.com", domain, false, "sub.example.com"}, // Invalid inputs - {"invalid", "", true}, - {"1.1.1.1/abc", "", true}, - {"1234", "", true}, + {"invalid", "", true, ""}, + {"1.1.1.1/abc", "", true, ""}, + {"1234", "", true, ""}, } for _, tt := range tests { t.Run(tt.input, func(t *testing.T) { - result, err := GetResourceType(tt.input) + result, addr, err := GetResourceType(tt.input) if result != tt.expectedType { t.Errorf("Expected type %v, got %v", tt.expectedType, result) } @@ -36,6 +37,10 @@ func TestGetResourceType(t *testing.T) { if tt.expectedErr && err == nil { t.Errorf("Expected error, got nil") } + + if addr != tt.expectedAddr { + t.Errorf("Expected address %v, got %v", tt.expectedAddr, addr) + } }) } } diff --git a/management/server/networks/routers/manager_test.go b/management/server/networks/routers/manager_test.go new file mode 100644 index 000000000..18b876b1c --- /dev/null +++ b/management/server/networks/routers/manager_test.go @@ -0,0 +1,223 @@ +package routers + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/netbirdio/netbird/management/server/networks/routers/types" + "github.com/netbirdio/netbird/management/server/permissions" + "github.com/netbirdio/netbird/management/server/status" + "github.com/netbirdio/netbird/management/server/store" +) + +func Test_GetAllRoutersInNetworkReturnsRouters(t *testing.T) { + ctx := context.Background() + accountID := "testAccountId" + userID := "allowedUser" + networkID := "testNetworkId" + + s, cleanUp, err := store.NewTestStoreFromSQL(context.Background(), "../../testdata/networks.sql", t.TempDir()) + if err != nil { + t.Fatal(err) + } + t.Cleanup(cleanUp) + permissionsManager := permissions.NewManagerMock() + manager := NewManager(s, permissionsManager) + + routers, err := manager.GetAllRoutersInNetwork(ctx, accountID, userID, networkID) + require.NoError(t, err) + require.Len(t, routers, 1) + require.Equal(t, "testRouterId", routers[0].ID) +} + +func Test_GetAllRoutersInNetworkReturnsPermissionDenied(t *testing.T) { + ctx := context.Background() + accountID := "testAccountId" + userID := "invalidUser" + networkID := "testNetworkId" + + s, cleanUp, err := store.NewTestStoreFromSQL(context.Background(), "../../testdata/networks.sql", t.TempDir()) + if err != nil { + t.Fatal(err) + } + t.Cleanup(cleanUp) + permissionsManager := permissions.NewManagerMock() + manager := NewManager(s, permissionsManager) + + routers, err := manager.GetAllRoutersInNetwork(ctx, accountID, userID, networkID) + require.Error(t, err) + require.Equal(t, status.NewPermissionDeniedError(), err) + require.Nil(t, routers) +} + +func Test_GetRouterReturnsRouter(t *testing.T) { + ctx := context.Background() + accountID := "testAccountId" + userID := "allowedUser" + networkID := "testNetworkId" + resourceID := "testRouterId" + + s, cleanUp, err := store.NewTestStoreFromSQL(context.Background(), "../../testdata/networks.sql", t.TempDir()) + if err != nil { + t.Fatal(err) + } + t.Cleanup(cleanUp) + permissionsManager := permissions.NewManagerMock() + manager := NewManager(s, permissionsManager) + + router, err := manager.GetRouter(ctx, accountID, userID, networkID, resourceID) + require.NoError(t, err) + require.Equal(t, "testRouterId", router.ID) +} + +func Test_GetRouterReturnsPermissionDenied(t *testing.T) { + ctx := context.Background() + accountID := "testAccountId" + userID := "invalidUser" + networkID := "testNetworkId" + resourceID := "testRouterId" + + s, cleanUp, err := store.NewTestStoreFromSQL(context.Background(), "../../testdata/networks.sql", t.TempDir()) + if err != nil { + t.Fatal(err) + } + t.Cleanup(cleanUp) + permissionsManager := permissions.NewManagerMock() + manager := NewManager(s, permissionsManager) + + router, err := manager.GetRouter(ctx, accountID, userID, networkID, resourceID) + require.Error(t, err) + require.Equal(t, status.NewPermissionDeniedError(), err) + require.Nil(t, router) +} + +func Test_CreateRouterSuccessfully(t *testing.T) { + ctx := context.Background() + userID := "allowedUser" + router, err := types.NewNetworkRouter("testAccountId", "testNetworkId", "testPeerId", []string{}, false, 9999) + if err != nil { + require.NoError(t, err) + } + + s, cleanUp, err := store.NewTestStoreFromSQL(context.Background(), "../../testdata/networks.sql", t.TempDir()) + if err != nil { + t.Fatal(err) + } + t.Cleanup(cleanUp) + permissionsManager := permissions.NewManagerMock() + manager := NewManager(s, permissionsManager) + + createdRouter, err := manager.CreateRouter(ctx, userID, router) + require.NoError(t, err) + require.NotEqual(t, "", router.ID) + require.Equal(t, router.NetworkID, createdRouter.NetworkID) + require.Equal(t, router.Peer, createdRouter.Peer) + require.Equal(t, router.Metric, createdRouter.Metric) + require.Equal(t, router.Masquerade, createdRouter.Masquerade) +} + +func Test_CreateRouterFailsWithPermissionDenied(t *testing.T) { + ctx := context.Background() + userID := "invalidUser" + router, err := types.NewNetworkRouter("testAccountId", "testNetworkId", "testPeerId", []string{}, false, 9999) + if err != nil { + require.NoError(t, err) + } + + s, cleanUp, err := store.NewTestStoreFromSQL(context.Background(), "../../testdata/networks.sql", t.TempDir()) + if err != nil { + t.Fatal(err) + } + t.Cleanup(cleanUp) + permissionsManager := permissions.NewManagerMock() + manager := NewManager(s, permissionsManager) + + createdRouter, err := manager.CreateRouter(ctx, userID, router) + require.Error(t, err) + require.Equal(t, status.NewPermissionDeniedError(), err) + require.Nil(t, createdRouter) +} + +func Test_DeleteRouterSuccessfully(t *testing.T) { + ctx := context.Background() + accountID := "testAccountId" + userID := "allowedUser" + networkID := "testNetworkId" + routerID := "testRouterId" + + s, cleanUp, err := store.NewTestStoreFromSQL(context.Background(), "../../testdata/networks.sql", t.TempDir()) + if err != nil { + t.Fatal(err) + } + t.Cleanup(cleanUp) + permissionsManager := permissions.NewManagerMock() + manager := NewManager(s, permissionsManager) + + err = manager.DeleteRouter(ctx, accountID, userID, networkID, routerID) + require.NoError(t, err) +} + +func Test_DeleteRouterFailsWithPermissionDenied(t *testing.T) { + ctx := context.Background() + accountID := "testAccountId" + userID := "invalidUser" + networkID := "testNetworkId" + routerID := "testRouterId" + + s, cleanUp, err := store.NewTestStoreFromSQL(context.Background(), "../../testdata/networks.sql", t.TempDir()) + if err != nil { + t.Fatal(err) + } + t.Cleanup(cleanUp) + permissionsManager := permissions.NewManagerMock() + manager := NewManager(s, permissionsManager) + + err = manager.DeleteRouter(ctx, accountID, userID, networkID, routerID) + require.Error(t, err) + require.Equal(t, status.NewPermissionDeniedError(), err) +} + +func Test_UpdateRouterSuccessfully(t *testing.T) { + ctx := context.Background() + userID := "allowedUser" + router, err := types.NewNetworkRouter("testAccountId", "testNetworkId", "testPeerId", []string{}, false, 1) + if err != nil { + require.NoError(t, err) + } + + s, cleanUp, err := store.NewTestStoreFromSQL(context.Background(), "../../testdata/networks.sql", t.TempDir()) + if err != nil { + t.Fatal(err) + } + t.Cleanup(cleanUp) + permissionsManager := permissions.NewManagerMock() + manager := NewManager(s, permissionsManager) + + updatedRouter, err := manager.UpdateRouter(ctx, userID, router) + require.NoError(t, err) + require.Equal(t, router.Metric, updatedRouter.Metric) +} + +func Test_UpdateRouterFailsWithPermissionDenied(t *testing.T) { + ctx := context.Background() + userID := "invalidUser" + router, err := types.NewNetworkRouter("testAccountId", "testNetworkId", "testPeerId", []string{}, false, 1) + if err != nil { + require.NoError(t, err) + } + + s, cleanUp, err := store.NewTestStoreFromSQL(context.Background(), "../../testdata/networks.sql", t.TempDir()) + if err != nil { + t.Fatal(err) + } + t.Cleanup(cleanUp) + permissionsManager := permissions.NewManagerMock() + manager := NewManager(s, permissionsManager) + + updatedRouter, err := manager.UpdateRouter(ctx, userID, router) + require.Error(t, err) + require.Equal(t, status.NewPermissionDeniedError(), err) + require.Nil(t, updatedRouter) +} diff --git a/management/server/permissions/manager.go b/management/server/permissions/manager.go index f16b41fa0..320aad027 100644 --- a/management/server/permissions/manager.go +++ b/management/server/permissions/manager.go @@ -34,6 +34,9 @@ type managerImpl struct { settingsManager settings.Manager } +type managerMock struct { +} + func NewManager(userManager users.Manager, settingsManager settings.Manager) Manager { return &managerImpl{ userManager: userManager, @@ -86,3 +89,14 @@ func (m *managerImpl) validateRegularUserPermissions(ctx context.Context, accoun return false, nil } + +func NewManagerMock() Manager { + return &managerMock{} +} + +func (m *managerMock) ValidateUserPermissions(ctx context.Context, accountID, userID string, module Module, operation Operation) (bool, error) { + if userID == "allowedUser" { + return true, nil + } + return false, nil +} diff --git a/management/server/settings/manager.go b/management/server/settings/manager.go index 7d564a02e..37bc9f549 100644 --- a/management/server/settings/manager.go +++ b/management/server/settings/manager.go @@ -15,6 +15,9 @@ type managerImpl struct { store store.Store } +type managerMock struct { +} + func NewManager(store store.Store) Manager { return &managerImpl{ store: store, @@ -24,3 +27,11 @@ func NewManager(store store.Store) Manager { func (m *managerImpl) GetSettings(ctx context.Context, accountID string, userID string) (*types.Settings, error) { return m.store.GetAccountSettings(ctx, store.LockingStrengthShare, accountID) } + +func NewManagerMock() Manager { + return &managerMock{} +} + +func (m *managerMock) GetSettings(ctx context.Context, accountID string, userID string) (*types.Settings, error) { + return &types.Settings{}, nil +} diff --git a/management/server/store/sql_store.go b/management/server/store/sql_store.go index 7240a7a09..ed4d2fb28 100644 --- a/management/server/store/sql_store.go +++ b/management/server/store/sql_store.go @@ -1783,6 +1783,21 @@ func (s *SqlStore) GetNetworkResourceByID(ctx context.Context, lockStrength Lock return netResources, nil } +func (s *SqlStore) GetNetworkResourceByName(ctx context.Context, lockStrength LockingStrength, accountID, resourceName string) (*resourceTypes.NetworkResource, error) { + var netResources *resourceTypes.NetworkResource + result := s.db.Clauses(clause.Locking{Strength: string(lockStrength)}). + First(&netResources, "account_id = ? AND name = ?", accountID, resourceName) + if result.Error != nil { + if errors.Is(result.Error, gorm.ErrRecordNotFound) { + return nil, status.NewNetworkResourceNotFoundError(resourceName) + } + log.WithContext(ctx).Errorf("failed to get network resource from store: %v", result.Error) + return nil, status.Errorf(status.Internal, "failed to get network resource from store") + } + + return netResources, nil +} + func (s *SqlStore) SaveNetworkResource(ctx context.Context, lockStrength LockingStrength, resource *resourceTypes.NetworkResource) error { result := s.db.Clauses(clause.Locking{Strength: string(lockStrength)}).Save(resource) if result.Error != nil { diff --git a/management/server/store/store.go b/management/server/store/store.go index 4d77b95e9..6b0e862c4 100644 --- a/management/server/store/store.go +++ b/management/server/store/store.go @@ -158,6 +158,7 @@ type Store interface { GetNetworkResourcesByNetID(ctx context.Context, lockStrength LockingStrength, accountID, netID string) ([]*resourceTypes.NetworkResource, error) GetNetworkResourcesByAccountID(ctx context.Context, lockStrength LockingStrength, accountID string) ([]*resourceTypes.NetworkResource, error) GetNetworkResourceByID(ctx context.Context, lockStrength LockingStrength, accountID, resourceID string) (*resourceTypes.NetworkResource, error) + GetNetworkResourceByName(ctx context.Context, lockStrength LockingStrength, accountID, resourceName string) (*resourceTypes.NetworkResource, error) SaveNetworkResource(ctx context.Context, lockStrength LockingStrength, resource *resourceTypes.NetworkResource) error DeleteNetworkResource(ctx context.Context, lockStrength LockingStrength, accountID, resourceID string) error } diff --git a/management/server/testdata/networks.sql b/management/server/testdata/networks.sql new file mode 100644 index 000000000..8138ce520 --- /dev/null +++ b/management/server/testdata/networks.sql @@ -0,0 +1,18 @@ +CREATE TABLE `accounts` (`id` text,`created_by` text,`created_at` datetime,`domain` text,`domain_category` text,`is_domain_primary_account` numeric,`network_identifier` text,`network_net` text,`network_dns` text,`network_serial` integer,`dns_settings_disabled_management_groups` text,`settings_peer_login_expiration_enabled` numeric,`settings_peer_login_expiration` integer,`settings_regular_users_view_blocked` numeric,`settings_groups_propagation_enabled` numeric,`settings_jwt_groups_enabled` numeric,`settings_jwt_groups_claim_name` text,`settings_jwt_allow_groups` text,`settings_extra_peer_approval_enabled` numeric,`settings_extra_integrated_validator_groups` text,PRIMARY KEY (`id`)); +INSERT INTO accounts VALUES('testAccountId','','2024-10-02 16:01:38.000000000+00:00','test.com','private',1,'testNetworkIdentifier','{"IP":"100.64.0.0","Mask":"//8AAA=="}','',0,'[]',0,86400000000000,0,0,0,'',NULL,NULL,NULL); + +CREATE TABLE `groups` (`id` text,`account_id` text,`name` text,`issued` text,`peers` text,`integration_ref_id` integer,`integration_ref_integration_type` text,PRIMARY KEY (`id`),CONSTRAINT `fk_accounts_groups_g` FOREIGN KEY (`account_id`) REFERENCES `accounts`(`id`)); +INSERT INTO "groups" VALUES('testGroupId','testAccountId','testGroupName','api','[]',0,''); + +CREATE TABLE `peers` (`id` text,`account_id` text,`key` text,`setup_key` text,`ip` text,`meta_hostname` text,`meta_go_os` text,`meta_kernel` text,`meta_core` text,`meta_platform` text,`meta_os` text,`meta_os_version` text,`meta_wt_version` text,`meta_ui_version` text,`meta_kernel_version` text,`meta_network_addresses` text,`meta_system_serial_number` text,`meta_system_product_name` text,`meta_system_manufacturer` text,`meta_environment` text,`meta_files` text,`name` text,`dns_label` text,`peer_status_last_seen` datetime,`peer_status_connected` numeric,`peer_status_login_expired` numeric,`peer_status_requires_approval` numeric,`user_id` text,`ssh_key` text,`ssh_enabled` numeric,`login_expiration_enabled` numeric,`last_login` datetime,`created_at` datetime,`ephemeral` numeric,`location_connection_ip` text,`location_country_code` text,`location_city_name` text,`location_geo_name_id` integer,PRIMARY KEY (`id`),CONSTRAINT `fk_accounts_peers_g` FOREIGN KEY (`account_id`) REFERENCES `accounts`(`id`)); +INSERT INTO peers VALUES('testPeerId','testAccountId','5rvhvriKJZ3S9oxYToVj5TzDM9u9y8cxg7htIMWlYAg=','72546A29-6BC8-4311-BCFC-9CDBF33F1A48','"100.64.114.31"','f2a34f6a4731','linux','Linux','11','unknown','Debian GNU/Linux','','0.12.0','','',NULL,'','','','{"Cloud":"","Platform":""}',NULL,'f2a34f6a4731','f2a34f6a4731','2023-03-02 09:21:02.189035775+01:00',0,0,0,'','ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILzUUSYG/LGnV8zarb2SGN+tib/PZ+M7cL4WtTzUrTpk',0,1,'2023-03-01 19:48:19.817799698+01:00','2024-10-02 17:00:32.527947+02:00',0,'""','','',0); + +CREATE TABLE `networks` (`id` text,`account_id` text,`name` text,`description` text,PRIMARY KEY (`id`),CONSTRAINT `fk_accounts_networks` FOREIGN KEY (`account_id`) REFERENCES `accounts`(`id`)); +INSERT INTO networks VALUES('testNetworkId','testAccountId','some-name','some-description'); + +CREATE TABLE `network_routers` (`id` text,`network_id` text,`account_id` text,`peer` text,`peer_groups` text,`masquerade` numeric,`metric` integer,PRIMARY KEY (`id`),CONSTRAINT `fk_accounts_network_routers` FOREIGN KEY (`account_id`) REFERENCES `accounts`(`id`)); +INSERT INTO network_routers VALUES('testRouterId','testNetworkId','testAccountId','','["csquuo4jcko732k1ag00"]',0,9999); + +CREATE TABLE `network_resources` (`id` text,`network_id` text,`account_id` text,`name` text,`description` text,`type` text,`address` text,PRIMARY KEY (`id`),CONSTRAINT `fk_accounts_network_resources` FOREIGN KEY (`account_id`) REFERENCES `accounts`(`id`)); +INSERT INTO network_resources VALUES('testResourceId','testNetworkId','testAccountId','some-name','some-description','host','3.3.3.3/32'); +INSERT INTO network_resources VALUES('anotherTestResourceId','testNetworkId','testAccountId','used-name','some-description','host','3.3.3.3/32'); diff --git a/management/server/users/manager.go b/management/server/users/manager.go index 76291a678..718eb6190 100644 --- a/management/server/users/manager.go +++ b/management/server/users/manager.go @@ -2,6 +2,7 @@ package users import ( "context" + "errors" "github.com/netbirdio/netbird/management/server/store" "github.com/netbirdio/netbird/management/server/types" @@ -15,6 +16,9 @@ type managerImpl struct { store store.Store } +type managerMock struct { +} + func NewManager(store store.Store) Manager { return &managerImpl{ store: store, @@ -24,3 +28,22 @@ func NewManager(store store.Store) Manager { func (m *managerImpl) GetUser(ctx context.Context, userID string) (*types.User, error) { return m.store.GetUserByUserID(ctx, store.LockingStrengthShare, userID) } + +func NewManagerMock() Manager { + return &managerMock{} +} + +func (m *managerMock) GetUser(ctx context.Context, userID string) (*types.User, error) { + switch userID { + case "adminUser": + return &types.User{Id: userID, Role: types.UserRoleAdmin}, nil + case "regularUser": + return &types.User{Id: userID, Role: types.UserRoleUser}, nil + case "ownerUser": + return &types.User{Id: userID, Role: types.UserRoleOwner}, nil + case "billingUser": + return &types.User{Id: userID, Role: types.UserRoleBillingAdmin}, nil + default: + return nil, errors.New("user not found") + } +}