mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-10-22 19:34:22 +00:00
feat/PRWLR-3999 Implement providers endpoints (#21)
* feat(Backend): PRWLR-3989 add RLS to postgresql db and base models
* feat(API): PRWLR-3989 add TenantMiddleware
* chore(API, Backend): PRWLR-3989 create new db user without RLS bypass on migrations
* chore(Backend): PRWLR-3989 fix RLS bypasser for POST requests
* fix(Backend): PRWLR-3989 fix user permissions when migrating new models
* chore(Backend): PRWLR-3989 add testing view for RLS manual tests
* feat(API): PRWLR-3989 add tenant_id to API logging
* chore(API, Backend): PRWLR-3989 add TODOs
* test(API): PRWLR-3989 add new middleware unit tests
* chore(API): PRWLR-3989 refactor RLS code
* fix(tests): PRWLR-3989 fix testing db connector
* chore: PRWLR-3989 add references to JIRA tickets
* fix: PRWLR-3989 remove bypass logic and fix serializers
* fix: PRWLR-3989 improve drop SQL query for RLS models
* feat(Backend): PRWLR-3989 add specific permissions on each model
* fix(Backend): PRWLR-3989 fix database routing and grant select perms
* fix(test): PRWLR-3989 fix routing issues with unit tests
* chore: PRWLR-3989 remove references to JIRA tickets
* feat(Backend): PRWLR-3999 add Provider model
* feat: PRWLR-3999 add providers view logic
* fix: PRWLR-3999 fix unique index fields
* feat(API): PRWLR-3999 add custom exception handlers
* feat(API): PRWLR-3999 add /providers/{provider_id}/connection endpoint
* test(API): PRWLR-3999 add base unit tests for providers
* fix(API, Backend): PRWLR-3999 fix bugs after rebase
* chore(API, docker): PRWLR-3999 add dev feats for demo
* feat(API): PRWLR-3999 add drf-spectacular-jsonapi and improve docs
* test(API): PRWLR-3999 add providers unit tests
* chore(Backend): PRWLR-3989 adjust privileges for user on table tenant
* chore: PRWLR-3999 rename model custom validation error
* chore: PRWLR-3999 remove Test references
* chore: PRWLR-3999 update API v1 spec
* fix: PRWLR-3999 apply requested changes to filter and models
* feat: PRWLR-3999 add validation to PATCH /providers payload
* fix: PRWLR-3999 fix providers enum description
* chore: add more providers fixtures
* fix: PRWLR-3999 make providers.alias optional
This commit is contained in:
1 parent
e1a12bcb14
commit
4371ad1535
19 files changed
+1888
-481
No files matched your search
@@ -4,7 +4,6 @@ from django.db.migrations.recorder import MigrationRecorder
|
||||
from django.db.utils import ConnectionRouter
|
||||
|
||||
from api.db_router import MainRouter
|
||||
from api.models import Test
|
||||
from api.rls import Tenant
|
||||
from config.django.base import DATABASE_ROUTERS as PROD_DATABASE_ROUTERS
|
||||
|
||||
@@ -17,7 +16,7 @@ class TestMainDatabaseRouter:
|
||||
yield ConnectionRouter()
|
||||
settings.DATABASE_ROUTERS = testing_routers
|
||||
|
||||
@pytest.mark.parametrize("api_model", [Tenant, Test])
|
||||
@pytest.mark.parametrize("api_model", [Tenant])
|
||||
def test_router_api_models(self, api_model, router):
|
||||
assert router.db_for_read(api_model) == MainRouter.default_db
|
||||
assert router.db_for_write(api_model) == MainRouter.default_db
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
from datetime import datetime
|
||||
|
||||
import pytest
|
||||
from django.urls import reverse
|
||||
from rest_framework import status
|
||||
|
||||
from api.models import Provider
|
||||
from api.rls import Tenant
|
||||
|
||||
API_JSON_CONTENT_TYPE = "application/vnd.api+json"
|
||||
# TODO Change to 401 when authentication/authorization is implemented
|
||||
NO_TENANT_HTTP_STATUS = status.HTTP_403_FORBIDDEN
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@@ -50,8 +55,7 @@ class TestTenantViewSet:
|
||||
assert response.status_code == status.HTTP_200_OK
|
||||
assert response.json()["data"]["attributes"]["name"] == tenant1.name
|
||||
|
||||
def test_tenants_invalid_retrieve(self, client, tenants):
|
||||
tenant1, _ = tenants
|
||||
def test_tenants_invalid_retrieve(self, client):
|
||||
response = client.get(reverse("tenant-detail", kwargs={"pk": "random_id"}))
|
||||
assert response.status_code == status.HTTP_404_NOT_FOUND
|
||||
|
||||
@@ -177,3 +181,343 @@ class TestTenantViewSet:
|
||||
assert response.status_code == status.HTTP_200_OK
|
||||
assert len(response.json()["data"]) == 2
|
||||
assert response.json()["data"][0]["attributes"]["name"] == tenant2.name
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
class TestProviderViewSet:
|
||||
@pytest.fixture
|
||||
def providers(self, get_tenant):
|
||||
tenant = get_tenant
|
||||
provider1 = Provider.objects.create(
|
||||
provider="aws",
|
||||
provider_id="123456789012",
|
||||
alias="aws_testing_1",
|
||||
tenant_id=tenant.id,
|
||||
)
|
||||
provider2 = Provider.objects.create(
|
||||
provider="aws",
|
||||
provider_id="123456789013",
|
||||
alias="aws_testing_2",
|
||||
tenant_id=tenant.id,
|
||||
)
|
||||
provider3 = Provider.objects.create(
|
||||
provider="gcp",
|
||||
provider_id="a12322-test321",
|
||||
alias="gcp_testing",
|
||||
tenant_id=tenant.id,
|
||||
)
|
||||
provider4 = Provider.objects.create(
|
||||
provider="kubernetes",
|
||||
provider_id="kubernetes-test-12345",
|
||||
alias="k8s_testing",
|
||||
tenant_id=tenant.id,
|
||||
)
|
||||
provider5 = Provider.objects.create(
|
||||
provider="azure",
|
||||
provider_id="37b065f8-26b0-4218-a665-0b23d07b27d9",
|
||||
alias="azure_testing",
|
||||
tenant_id=tenant.id,
|
||||
)
|
||||
|
||||
return provider1, provider2, provider3, provider4, provider5
|
||||
|
||||
def test_providers_rls(self, client):
|
||||
response = client.get(reverse("provider-list"))
|
||||
assert response.status_code == NO_TENANT_HTTP_STATUS
|
||||
|
||||
def test_providers_list(self, client, providers, tenant_header):
|
||||
response = client.get(reverse("provider-list"), headers=tenant_header)
|
||||
assert response.status_code == status.HTTP_200_OK
|
||||
assert len(response.json()["data"]) == len(providers)
|
||||
|
||||
def test_providers_retrieve(self, client, providers, tenant_header):
|
||||
provider1, *_ = providers
|
||||
response = client.get(
|
||||
reverse("provider-detail", kwargs={"pk": provider1.id}),
|
||||
headers=tenant_header,
|
||||
)
|
||||
assert response.status_code == status.HTTP_200_OK
|
||||
assert response.json()["data"]["attributes"]["provider"] == provider1.provider
|
||||
assert (
|
||||
response.json()["data"]["attributes"]["provider_id"]
|
||||
== provider1.provider_id
|
||||
)
|
||||
assert response.json()["data"]["attributes"]["alias"] == provider1.alias
|
||||
|
||||
def test_providers_invalid_retrieve(self, client, tenant_header):
|
||||
response = client.get(
|
||||
reverse("provider-detail", kwargs={"pk": "random_id"}),
|
||||
headers=tenant_header,
|
||||
)
|
||||
assert response.status_code == status.HTTP_404_NOT_FOUND
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"provider_json_payload",
|
||||
(
|
||||
[
|
||||
{"provider": "aws", "provider_id": "111111111111", "alias": "test"},
|
||||
{"provider": "gcp", "provider_id": "a12322-test54321", "alias": "test"},
|
||||
{
|
||||
"provider": "kubernetes",
|
||||
"provider_id": "kubernetes-test-123456789",
|
||||
"alias": "test",
|
||||
},
|
||||
{
|
||||
"provider": "azure",
|
||||
"provider_id": "8851db6b-42e5-4533-aa9e-30a32d67e875",
|
||||
"alias": "test",
|
||||
},
|
||||
]
|
||||
),
|
||||
)
|
||||
def test_providers_create_valid(self, client, tenant_header, provider_json_payload):
|
||||
response = client.post(
|
||||
reverse("provider-list"),
|
||||
data=provider_json_payload,
|
||||
format="json",
|
||||
headers=tenant_header,
|
||||
)
|
||||
assert response.status_code == status.HTTP_201_CREATED
|
||||
assert Provider.objects.count() == 1
|
||||
assert Provider.objects.get().provider == provider_json_payload["provider"]
|
||||
assert (
|
||||
Provider.objects.get().provider_id == provider_json_payload["provider_id"]
|
||||
)
|
||||
assert Provider.objects.get().alias == provider_json_payload["alias"]
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"provider_json_payload, error_code",
|
||||
(
|
||||
[
|
||||
(
|
||||
{"provider": "aws", "provider_id": "1", "alias": "test"},
|
||||
"min_length",
|
||||
),
|
||||
(
|
||||
{
|
||||
"provider": "aws",
|
||||
"provider_id": "1111111111111",
|
||||
"alias": "test",
|
||||
},
|
||||
"aws-provider-id",
|
||||
),
|
||||
(
|
||||
{"provider": "aws", "provider_id": "aaaaaaaaaaaa", "alias": "test"},
|
||||
"aws-provider-id",
|
||||
),
|
||||
(
|
||||
{"provider": "gcp", "provider_id": "1234asdf", "alias": "test"},
|
||||
"gcp-provider-id",
|
||||
),
|
||||
(
|
||||
{
|
||||
"provider": "kubernetes",
|
||||
"provider_id": "-1234asdf",
|
||||
"alias": "test",
|
||||
},
|
||||
"kubernetes-provider-id",
|
||||
),
|
||||
(
|
||||
{
|
||||
"provider": "azure",
|
||||
"provider_id": "8851db6b-42e5-4533-aa9e-30a32d67e87",
|
||||
"alias": "test",
|
||||
},
|
||||
"azure-provider-id",
|
||||
),
|
||||
]
|
||||
),
|
||||
)
|
||||
def test_providers_invalid_create(
|
||||
self, client, tenant_header, provider_json_payload, error_code
|
||||
):
|
||||
response = client.post(
|
||||
reverse("provider-list"),
|
||||
data=provider_json_payload,
|
||||
format="json",
|
||||
headers=tenant_header,
|
||||
)
|
||||
assert response.status_code == status.HTTP_400_BAD_REQUEST
|
||||
assert response.json()["errors"][0]["code"] == error_code
|
||||
assert (
|
||||
response.json()["errors"][0]["source"]["pointer"]
|
||||
== "/data/attributes/provider_id"
|
||||
)
|
||||
|
||||
def test_providers_partial_update(self, client, providers, tenant_header):
|
||||
provider1, *_ = providers
|
||||
new_alias = "This is the new name"
|
||||
payload = {
|
||||
"data": {
|
||||
"type": "Provider",
|
||||
"id": provider1.id,
|
||||
"attributes": {"alias": new_alias},
|
||||
},
|
||||
}
|
||||
response = client.patch(
|
||||
reverse("provider-detail", kwargs={"pk": provider1.id}),
|
||||
data=payload,
|
||||
content_type=API_JSON_CONTENT_TYPE,
|
||||
headers=tenant_header,
|
||||
)
|
||||
assert response.status_code == status.HTTP_200_OK
|
||||
provider1.refresh_from_db()
|
||||
assert provider1.alias == new_alias
|
||||
|
||||
def test_providers_partial_update_invalid_content_type(
|
||||
self, client, providers, tenant_header
|
||||
):
|
||||
provider1, *_ = providers
|
||||
response = client.patch(
|
||||
reverse("provider-detail", kwargs={"pk": provider1.id}),
|
||||
data={},
|
||||
headers=tenant_header,
|
||||
)
|
||||
assert response.status_code == status.HTTP_415_UNSUPPORTED_MEDIA_TYPE
|
||||
|
||||
def test_providers_partial_update_invalid_content(
|
||||
self, client, providers, tenant_header
|
||||
):
|
||||
provider1, *_ = providers
|
||||
new_name = "This is the new name"
|
||||
payload = {"alias": new_name}
|
||||
response = client.patch(
|
||||
reverse("provider-detail", kwargs={"pk": provider1.id}),
|
||||
data=payload,
|
||||
content_type=API_JSON_CONTENT_TYPE,
|
||||
headers=tenant_header,
|
||||
)
|
||||
assert response.status_code == status.HTTP_400_BAD_REQUEST
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"attribute_key, attribute_value",
|
||||
[
|
||||
("provider", "aws"),
|
||||
("provider_id", "123456789012"),
|
||||
],
|
||||
)
|
||||
def test_providers_partial_update_invalid_fields(
|
||||
self, client, providers, tenant_header, attribute_key, attribute_value
|
||||
):
|
||||
provider1, *_ = providers
|
||||
payload = {
|
||||
"data": {
|
||||
"type": "Provider",
|
||||
"id": provider1.id,
|
||||
"attributes": {attribute_key: attribute_value},
|
||||
},
|
||||
}
|
||||
response = client.patch(
|
||||
reverse("provider-detail", kwargs={"pk": provider1.id}),
|
||||
data=payload,
|
||||
content_type=API_JSON_CONTENT_TYPE,
|
||||
headers=tenant_header,
|
||||
)
|
||||
assert response.status_code == status.HTTP_400_BAD_REQUEST
|
||||
|
||||
def test_providers_delete(self, client, providers, tenant_header):
|
||||
provider1, *_ = providers
|
||||
response = client.delete(
|
||||
reverse("provider-detail", kwargs={"pk": provider1.id}),
|
||||
headers=tenant_header,
|
||||
)
|
||||
assert response.status_code == status.HTTP_202_ACCEPTED
|
||||
assert "Content-Location" in response.headers
|
||||
assert Provider.objects.count() == len(providers) - 1
|
||||
# TODO Assert a task is returned when they are implemented
|
||||
|
||||
def test_providers_delete_invalid(self, client, tenant_header):
|
||||
response = client.delete(
|
||||
reverse("provider-detail", kwargs={"pk": "random_id"}),
|
||||
headers=tenant_header,
|
||||
)
|
||||
assert response.status_code == status.HTTP_404_NOT_FOUND
|
||||
|
||||
def test_providers_connection(self, client, providers, tenant_header):
|
||||
provider1, *_ = providers
|
||||
assert provider1.connected is None
|
||||
assert provider1.connection_last_checked_at is None
|
||||
|
||||
response = client.post(
|
||||
reverse("provider-connection", kwargs={"pk": provider1.id}),
|
||||
headers=tenant_header,
|
||||
)
|
||||
assert response.status_code == status.HTTP_202_ACCEPTED
|
||||
assert "Content-Location" in response.headers
|
||||
# TODO Assert a task is returned when they are implemented
|
||||
|
||||
provider1.refresh_from_db()
|
||||
assert provider1.connected is True
|
||||
assert isinstance(provider1.connection_last_checked_at, datetime)
|
||||
|
||||
def test_providers_connection_invalid_provider(
|
||||
self, client, providers, tenant_header
|
||||
):
|
||||
response = client.post(
|
||||
reverse("provider-connection", kwargs={"pk": "random_id"}),
|
||||
headers=tenant_header,
|
||||
)
|
||||
assert response.status_code == status.HTTP_404_NOT_FOUND
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"filter_name, filter_value",
|
||||
(
|
||||
[
|
||||
("provider", "aws"),
|
||||
("provider_id", "12345"),
|
||||
("alias", "test"),
|
||||
("search", "test"),
|
||||
("inserted_at", "2024-01-01 00:00:00"),
|
||||
("updated_at", "2024-01-01 00:00:00"),
|
||||
]
|
||||
),
|
||||
)
|
||||
def test_providers_filters(self, client, tenant_header, filter_name, filter_value):
|
||||
response = client.get(
|
||||
reverse("provider-list"),
|
||||
{f"filter[{filter_name}]": filter_value},
|
||||
headers=tenant_header,
|
||||
)
|
||||
assert response.status_code == status.HTTP_200_OK
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"filter_name",
|
||||
(
|
||||
[
|
||||
"provider", # Valid filter, invalid value
|
||||
"invalid",
|
||||
]
|
||||
),
|
||||
)
|
||||
def test_providers_filters_invalid(self, client, tenant_header, filter_name):
|
||||
response = client.get(
|
||||
reverse("provider-list"),
|
||||
{f"filter[{filter_name}]": "whatever"},
|
||||
headers=tenant_header,
|
||||
)
|
||||
assert response.status_code == status.HTTP_400_BAD_REQUEST
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"sort_field",
|
||||
(
|
||||
[
|
||||
"provider",
|
||||
"provider_id",
|
||||
"alias",
|
||||
"connected",
|
||||
"inserted_at",
|
||||
"updated_at",
|
||||
]
|
||||
),
|
||||
)
|
||||
def test_providers_sort(self, client, tenant_header, sort_field):
|
||||
response = client.get(
|
||||
reverse("provider-list"), {"sort": sort_field}, headers=tenant_header
|
||||
)
|
||||
assert response.status_code == status.HTTP_200_OK
|
||||
|
||||
def test_providers_sort_invalid(self, client, tenant_header):
|
||||
response = client.get(
|
||||
reverse("provider-list"), {"sort": "invalid"}, headers=tenant_header
|
||||
)
|
||||
assert response.status_code == status.HTTP_400_BAD_REQUEST
|
||||
Reference in new issue
Block a user