Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(api): validate before creating projects based on current subscription #2869

Merged
merged 8 commits into from
Nov 20, 2023
3 changes: 3 additions & 0 deletions api/app/settings/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@

HOSTED_SEATS_LIMIT = env.int("HOSTED_SEATS_LIMIT", default=0)

MAX_SEATS_IN_FREE_PLAN = 1
MAX_PROJECTS_IN_FREE_PLAN = 1

tushar5526 marked this conversation as resolved.
Show resolved Hide resolved
# Google Analytics Configuration
GOOGLE_ANALYTICS_KEY = env("GOOGLE_ANALYTICS_KEY", default="")
GOOGLE_SERVICE_ACCOUNT = env("GOOGLE_SERVICE_ACCOUNT", default=None)
Expand Down
2 changes: 1 addition & 1 deletion api/app/settings/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

# We dont want to track tests
ENABLE_TELEMETRY = False

MAX_PROJECTS_IN_FREE_PLAN = 10
REST_FRAMEWORK["DEFAULT_THROTTLE_RATES"] = {
"login": "100/min",
"mfa_code": "5/min",
Expand Down
6 changes: 4 additions & 2 deletions api/organisations/subscriptions/constants.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
from enum import Enum

from django.conf import settings

from organisations.subscriptions.metadata import BaseSubscriptionMetadata

MAX_SEATS_IN_FREE_PLAN = 1
MAX_SEATS_IN_FREE_PLAN = settings.MAX_SEATS_IN_FREE_PLAN or 1
tushar5526 marked this conversation as resolved.
Show resolved Hide resolved
MAX_API_CALLS_IN_FREE_PLAN = 50000
MAX_PROJECTS_IN_FREE_PLAN = 1
MAX_PROJECTS_IN_FREE_PLAN = settings.MAX_PROJECTS_IN_FREE_PLAN or 1
SUBSCRIPTION_DEFAULT_LIMITS = (
MAX_API_CALLS_IN_FREE_PLAN,
MAX_SEATS_IN_FREE_PLAN,
Expand Down
15 changes: 14 additions & 1 deletion api/projects/permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,22 @@ def has_permission(self, request, view):
if view.action == "create" and request.user.belongs_to(
int(request.data.get("organisation"))
):
organisation = Organisation.objects.get(
organisation = Organisation.objects.select_related("subscription").get(
id=int(request.data.get("organisation"))
)

# Allow project creation based on the active subscription
subscription_metadata = (
organisation.subscription.get_subscription_metadata()
)
total_projects_created = Project.objects.filter(
organisation=organisation
).count()
if (
subscription_metadata.projects
and total_projects_created >= subscription_metadata.projects
):
return False
if organisation.restrict_project_create_to_admin:
return request.user.is_organisation_admin(organisation.pk)
return request.user.has_organisation_permission(
Expand Down
33 changes: 31 additions & 2 deletions api/projects/tests/test_permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import pytest

from organisations.models import Organisation, OrganisationRole
from organisations.subscriptions.constants import MAX_PROJECTS_IN_FREE_PLAN
from projects.models import (
Project,
ProjectPermissionModel,
Expand All @@ -16,8 +17,8 @@
)
from users.models import FFAdminUser, UserPermissionGroup

mock_request = mock.MagicMock
mock_view = mock.MagicMock
mock_request = mock.MagicMock()
mock_view = mock.MagicMock()


@pytest.mark.django_db
Expand Down Expand Up @@ -371,3 +372,31 @@ def test_regular_user_has_no_destroy_permission(self):

# Then - exception thrown
assert not result


@pytest.mark.django_db
def test_free_plan_has_only_fixed_projects_permission():
tushar5526 marked this conversation as resolved.
Show resolved Hide resolved
# Given
organisation = Organisation.objects.create(name="Test organisation")

user = FFAdminUser.objects.create(email="[email protected]")
user_permission_group = UserPermissionGroup.objects.create(
name="Users", organisation=organisation
)
user_permission_group.users.add(user)
tushar5526 marked this conversation as resolved.
Show resolved Hide resolved
user.add_organisation(organisation, OrganisationRole.ADMIN)

project_permissions = ProjectPermissions()

mock_view = mock.MagicMock(action="create", detail=False)
mock_request = mock.MagicMock(
data={"name": "Test", "organisation": organisation.id}, user=user
)

# When
for i in range(MAX_PROJECTS_IN_FREE_PLAN):
assert project_permissions.has_permission(mock_request, mock_view)
Project.objects.create(name=f"Test project{i}", organisation=organisation)

# Then - free projects limit should be exhausted
assert not project_permissions.has_permission(mock_request, mock_view)