1
0
Fork 0
mirror of https://gitlab.com/bramw/baserow.git synced 2025-04-17 18:32:35 +00:00

Added the backend implementation of the premium license per group

This commit is contained in:
Bram Wiepjes 2022-06-27 17:56:19 +00:00
parent d4dc776e3a
commit ea66d4197a
17 changed files with 403 additions and 21 deletions
backend
premium

View file

@ -12,4 +12,4 @@ markers =
field_multiple_select: All tests related to multiple select field
field_link_row: All tests related to link row field
field_formula: All tests related to formula field
api_rows: All tests to manipulate rows via HTTP API
api_rows: All tests to manipulate rows via HTTP API

View file

@ -0,0 +1,5 @@
[pytest]
DJANGO_SETTINGS_MODULE = baserow.config.settings.test
python_files = test_*.py
env =
DJANGO_SETTINGS_MODULE = baserow.config.settings.test

View file

@ -17,7 +17,6 @@ from baserow.contrib.database.rows.exceptions import RowDoesNotExist
from baserow.contrib.database.table.exceptions import TableDoesNotExist
from baserow.core.exceptions import UserNotInGroup
from baserow_premium.row_comments.handler import RowCommentHandler
from baserow_premium.license.handler import check_active_premium_license
from .serializers import RowCommentSerializer, RowCommentCreateSerializer
@ -88,8 +87,6 @@ class RowCommentView(APIView):
}
)
def get(self, request, table_id, row_id):
check_active_premium_license(request.user)
comments = RowCommentHandler.get_comments(request.user, table_id, row_id)
if LimitOffsetPagination.limit_query_param in request.GET:
@ -146,8 +143,6 @@ class RowCommentView(APIView):
@validate_body(RowCommentCreateSerializer)
@transaction.atomic
def post(self, request, table_id, row_id, data):
check_active_premium_license(request.user)
new_row_comment = RowCommentHandler.create_comment(
request.user, table_id, row_id, data["comment"]
)

View file

@ -22,7 +22,7 @@ from baserow.core.exceptions import UserNotInGroup
from baserow_premium.views.models import KanbanView
from baserow_premium.views.exceptions import KanbanViewHasNoSingleSelectField
from baserow_premium.license.handler import check_active_premium_license
from baserow_premium.license.handler import check_active_premium_license_for_group
from baserow_premium.views.handler import get_rows_grouped_by_single_select_field
from .errors import (
@ -128,7 +128,7 @@ class KanbanViewView(APIView):
# We don't want to check if there is an active premium license if the group
# is a template because that feature must then be available for demo purposes.
if not group.has_template():
check_active_premium_license(request.user)
check_active_premium_license_for_group(request.user, group)
group.has_user(request.user, raise_error=True, allow_if_template=True)
single_select_option_field = view.single_select_field

View file

@ -12,7 +12,7 @@ from baserow.contrib.database.export.file_writer import (
from baserow.contrib.database.export.registries import TableExporter
from baserow.contrib.database.views.view_types import GridViewType
from baserow_premium.license.handler import check_active_premium_license
from baserow_premium.license.handler import check_active_premium_license_for_group
from .utils import get_unique_name, safe_xml_tag_name, to_xml
@ -23,7 +23,7 @@ class PremiumTableExporter(TableExporter):
Checks if the related user access to a valid license before the job is created.
"""
check_active_premium_license(user)
check_active_premium_license_for_group(user, table.database.group)
super().before_job_create(user, table, view, export_options)

View file

@ -26,6 +26,7 @@ from rest_framework.status import HTTP_200_OK
from baserow.core.exceptions import IsNotAdminError
from baserow.core.handler import CoreHandler
from baserow.core.models import Group
from baserow.ws.signals import broadcast_to_users
from .models import License, LicenseUser
@ -114,6 +115,39 @@ def has_active_premium_license_for(
return has_active_premium_license(user)
def check_active_premium_license_for_group(user: DjangoUser, group: Group):
"""
Checks if the provided user has premium access to the premium group.
:param user: The user for whom must be checked if it has an active license.
:param group: The group that the user must have active premium license for.
:raises NoPremiumLicenseError: if the user does not have an active premium
license for the provided group.
"""
active_license_for = has_active_premium_license_for(user)
# If the `active_license_for` is True, it means that the user has premium access
# for every group.
if active_license_for is True:
return
# If a list is returned, it means that the user only has access to specific
# items. In this case we check if the matching group is is present in that list.
if isinstance(active_license_for, list):
group_ids = [
license_for["id"]
for license_for in active_license_for
if license_for["type"] == "group"
]
if group.id in group_ids:
return
# If the user doesn't have a global or group specific premium license, we must
# raise an error.
raise NoPremiumLicenseError()
def get_public_key():
"""
Returns the public key instance that can be used to verify licenses. A different

View file

@ -6,7 +6,7 @@ from baserow.contrib.database.table.handler import TableHandler
from baserow_premium.row_comments.exceptions import InvalidRowCommentException
from baserow_premium.row_comments.models import RowComment
from baserow_premium.row_comments.signals import row_comment_created
from baserow_premium.license.handler import check_active_premium_license
from baserow_premium.license.handler import check_active_premium_license_for_group
User = get_user_model()
@ -28,6 +28,7 @@ class RowCommentHandler:
"""
table = TableHandler().get_table(table_id)
check_active_premium_license_for_group(requesting_user, table.database.group)
RowHandler().has_row(requesting_user, table, row_id, raise_error=True)
return (
RowComment.objects.select_related("user")
@ -54,12 +55,12 @@ class RowCommentHandler:
:raises InvalidRowCommentException: If the comment is blank or None.
"""
check_active_premium_license(requesting_user)
table = TableHandler().get_table(table_id)
check_active_premium_license_for_group(requesting_user, table.database.group)
if comment is None or comment == "":
raise InvalidRowCommentException()
table = TableHandler().get_table(table_id)
RowHandler().has_row(requesting_user, table, row_id, raise_error=True)
row_comment = RowComment.objects.create(
user=requesting_user, table=table, row_id=row_id, comment=comment

View file

@ -1,15 +1,17 @@
from baserow.contrib.database.views.registries import DecoratorType
from baserow_premium.license.handler import check_active_premium_license
from baserow_premium.license.handler import check_active_premium_license_for_group
class PremiumDecoratorType(DecoratorType):
def before_create_decoration(self, view, user):
if user:
check_active_premium_license(user)
check_active_premium_license_for_group(user, view.table.database.group)
def before_update_decoration(self, view, user):
def before_update_decoration(self, view_decoration, user):
if user:
check_active_premium_license(user)
check_active_premium_license_for_group(
user, view_decoration.view.table.database.group
)
class LeftBorderColorDecoratorType(PremiumDecoratorType):

View file

@ -12,7 +12,7 @@ from baserow.contrib.database.views.registries import (
view_filter_type_registry,
)
from baserow_premium.license.handler import check_active_premium_license
from baserow_premium.license.handler import check_active_premium_license_for_group
from .decorator_types import BackgroundColorDecoratorType, LeftBorderColorDecoratorType
from .serializers import (
@ -24,11 +24,13 @@ from .serializers import (
class PremiumDecoratorValueProviderType(DecoratorValueProviderType):
def before_create_decoration(self, view, user):
if user:
check_active_premium_license(user)
check_active_premium_license_for_group(user, view.table.database.group)
def before_update_decoration(self, view, user):
def before_update_decoration(self, view_decoration, user):
if user:
check_active_premium_license(user)
check_active_premium_license_for_group(
user, view_decoration.view.table.database.group
)
class SelectColorValueProviderType(PremiumDecoratorValueProviderType):

View file

@ -1,4 +1,5 @@
import pytest
from unittest.mock import patch
from django.conf import settings
from django.test.utils import override_settings
from freezegun import freeze_time
@ -103,6 +104,50 @@ def test_row_comments_api_view_without_premium_license(
assert response.json()["error"] == "ERROR_NO_ACTIVE_PREMIUM_LICENSE"
@pytest.mark.django_db
@override_settings(DEBUG=True)
def test_row_comments_api_view_without_premium_license_for_group(
premium_data_fixture, api_client
):
user, token = premium_data_fixture.create_user_and_token(
first_name="Test User", has_active_premium_license=True
)
table, fields, rows = premium_data_fixture.build_table(
columns=[("text", "text")], rows=["first row", "second_row"], user=user
)
with patch(
"baserow_premium.license.handler.has_active_premium_license_for"
) as mock_has_active_premium_license_for:
mock_has_active_premium_license_for.return_value = [
{"type": "group", "id": table.database.group.id}
]
response = api_client.get(
reverse(
"api:premium:row_comments:item",
kwargs={"table_id": table.id, "row_id": rows[0].id},
),
format="json",
HTTP_AUTHORIZATION=f"JWT {token}",
)
assert response.status_code == HTTP_200_OK
with patch(
"baserow_premium.license.handler.has_active_premium_license_for"
) as mock_has_active_premium_license_for:
mock_has_active_premium_license_for.return_value = [{"type": "group", "id": 0}]
response = api_client.get(
reverse(
"api:premium:row_comments:item",
kwargs={"table_id": table.id, "row_id": rows[0].id},
),
format="json",
HTTP_AUTHORIZATION=f"JWT {token}",
)
assert response.status_code == HTTP_402_PAYMENT_REQUIRED
assert response.json()["error"] == "ERROR_NO_ACTIVE_PREMIUM_LICENSE"
@pytest.mark.django_db
@override_settings(DEBUG=True)
def test_row_comments_cant_view_comments_for_invalid_table(

View file

@ -1,4 +1,5 @@
import pytest
from unittest.mock import patch
from django.shortcuts import reverse
from django.test.utils import override_settings
from rest_framework.status import (
@ -36,6 +37,35 @@ def test_list_without_valid_premium_license(api_client, premium_data_fixture):
assert response.status_code == HTTP_200_OK
@pytest.mark.django_db
@override_settings(DEBUG=True)
def test_list_without_valid_premium_license_for_group(api_client, premium_data_fixture):
user, token = premium_data_fixture.create_user_and_token(
has_active_premium_license=True
)
kanban = premium_data_fixture.create_kanban_view(user=user)
with patch(
"baserow_premium.license.handler.has_active_premium_license_for"
) as mock_has_active_premium_license_for:
mock_has_active_premium_license_for.return_value = [{"type": "group", "id": 0}]
url = reverse("api:database:views:kanban:list", kwargs={"view_id": kanban.id})
response = api_client.get(url, **{"HTTP_AUTHORIZATION": f"JWT {token}"})
assert response.status_code == HTTP_402_PAYMENT_REQUIRED
assert response.json()["error"] == "ERROR_NO_ACTIVE_PREMIUM_LICENSE"
with patch(
"baserow_premium.license.handler.has_active_premium_license_for"
) as mock_has_active_premium_license_for:
mock_has_active_premium_license_for.return_value = [
{"type": "group", "id": kanban.table.database.group.id}
]
premium_data_fixture.create_template(group=kanban.table.database.group)
url = reverse("api:database:views:kanban:list", kwargs={"view_id": kanban.id})
response = api_client.get(url, **{"HTTP_AUTHORIZATION": f"JWT {token}"})
assert response.status_code == HTTP_200_OK
@pytest.mark.django_db
@override_settings(DEBUG=True)
def test_list_rows_invalid_parameters(api_client, premium_data_fixture):

View file

@ -153,6 +153,27 @@ def test_cannot_export_json_without_premium_license(storage_mock, premium_data_f
)
@pytest.mark.django_db
@override_settings(DEBUG=True)
@patch("baserow.contrib.database.export.handler.default_storage")
def test_cannot_export_json_without_premium_license_for_group(
storage_mock, premium_data_fixture
):
with patch(
"baserow_premium.license.handler.has_active_premium_license_for"
) as mock_has_active_premium_license_for:
# Setting the group id to `0` will make sure that the user doesn't have
# premium access to the group.
mock_has_active_premium_license_for.return_value = [{"type": "group", "id": 0}]
with pytest.raises(NoPremiumLicenseError):
run_export_over_interesting_test_table(
premium_data_fixture,
storage_mock,
{"exporter_type": "json"},
user_kwargs={"has_active_premium_license": True},
)
@pytest.mark.django_db
@override_settings(DEBUG=True)
@patch("baserow.contrib.database.export.handler.default_storage")
@ -358,6 +379,27 @@ def test_cannot_export_xml_without_premium_license(storage_mock, premium_data_fi
)
@pytest.mark.django_db
@override_settings(DEBUG=True)
@patch("baserow.contrib.database.export.handler.default_storage")
def test_cannot_export_xml_without_premium_license_for_group(
storage_mock, premium_data_fixture
):
with patch(
"baserow_premium.license.handler.has_active_premium_license_for"
) as mock_has_active_premium_license_for:
# Setting the group id to `0` will make sure that the user doesn't have
# premium access to the group.
mock_has_active_premium_license_for.return_value = [{"type": "group", "id": 0}]
with pytest.raises(NoPremiumLicenseError):
run_export_over_interesting_test_table(
premium_data_fixture,
storage_mock,
{"exporter_type": "xml"},
user_kwargs={"has_active_premium_license": True},
)
def strip_indents_and_newlines(xml):
return "".join([line.strip() for line in xml.split("\n")])

View file

@ -16,6 +16,7 @@ from baserow.core.exceptions import IsNotAdminError
from baserow_premium.license.handler import (
has_active_premium_license,
check_active_premium_license,
check_active_premium_license_for_group,
get_public_key,
decode_license,
fetch_license_status_with_authority,
@ -175,6 +176,49 @@ def test_has_active_premium_license(data_fixture):
assert not has_active_premium_license(invalid_user)
@pytest.mark.django_db
@override_settings(DEBUG=True)
def test_check_active_premium_license_for_group_with_valid_license(data_fixture):
user_in_license = data_fixture.create_user()
group = data_fixture.create_group(user=user_in_license)
license = License.objects.create(license=VALID_TWO_SEAT_LICENSE.decode())
LicenseUser.objects.create(license=license, user=user_in_license)
with freeze_time("2021-08-01 12:00"):
with pytest.raises(NoPremiumLicenseError):
check_active_premium_license_for_group(user_in_license, group)
with freeze_time("2021-09-01 12:00"):
check_active_premium_license_for_group(user_in_license, group)
@pytest.mark.django_db
@override_settings(DEBUG=True)
def test_check_active_premium_license_for_group_with_patched_groups(data_fixture):
user_in_license = data_fixture.create_user()
group_1 = data_fixture.create_group(user=user_in_license)
group_2 = data_fixture.create_group(user=user_in_license)
group_3 = data_fixture.create_group(user=user_in_license)
group_4 = data_fixture.create_group(user=user_in_license)
with patch(
"baserow_premium.license.handler.has_active_premium_license_for"
) as mock_has_active_premium_license_for:
mock_has_active_premium_license_for.return_value = [
{"type": "group", "id": group_1.id},
{"type": "group", "id": group_2.id},
{"type": "something_else", "id": group_3.id},
]
check_active_premium_license_for_group(user_in_license, group_1)
check_active_premium_license_for_group(user_in_license, group_2)
with pytest.raises(NoPremiumLicenseError):
check_active_premium_license_for_group(user_in_license, group_3)
with pytest.raises(NoPremiumLicenseError):
check_active_premium_license_for_group(user_in_license, group_4)
@override_settings(DEBUG=True)
def test_get_public_key_debug():
public_key = get_public_key()

View file

@ -49,6 +49,32 @@ def test_cant_create_comment_without_premium_license(premium_data_fixture):
RowCommentHandler.create_comment(user, table.id, rows[0].id, "Test")
@pytest.mark.django_db
@override_settings(DEBUG=True)
def test_cant_create_comment_without_premium_license_for_group(premium_data_fixture):
user = premium_data_fixture.create_user(
first_name="Test User", has_active_premium_license=True
)
table, fields, rows = premium_data_fixture.build_table(
columns=[("text", "text")], rows=["first row", "second_row"], user=user
)
with patch(
"baserow_premium.license.handler.has_active_premium_license_for"
) as mock_has_active_premium_license_for:
mock_has_active_premium_license_for.return_value = [
{"type": "group", "id": table.database.group.id}
]
RowCommentHandler.create_comment(user, table.id, rows[0].id, "Test")
with patch(
"baserow_premium.license.handler.has_active_premium_license_for"
) as mock_has_active_premium_license_for:
mock_has_active_premium_license_for.return_value = [{"type": "group", "id": 0}]
with pytest.raises(NoPremiumLicenseError):
RowCommentHandler.create_comment(user, table.id, rows[0].id, "Test")
@pytest.mark.django_db(transaction=True)
@override_settings(DEBUG=True)
@patch("baserow_premium.row_comments.signals.row_comment_created.send")

View file

@ -1,4 +1,5 @@
import pytest
from unittest.mock import patch
from django.test.utils import override_settings
@ -52,6 +53,44 @@ def test_create_left_border_color_without_premium_license(premium_data_fixture):
assert isinstance(decoration, ViewDecoration)
@pytest.mark.django_db
@override_settings(DEBUG=True)
def test_create_left_border_color_without_premium_license_for_group(
premium_data_fixture,
):
user = premium_data_fixture.create_user(has_active_premium_license=True)
grid_view = premium_data_fixture.create_grid_view(user=user)
handler = ViewHandler()
with patch(
"baserow_premium.license.handler.has_active_premium_license_for"
) as mock_has_active_premium_license_for:
mock_has_active_premium_license_for.return_value = [
{"type": "group", "id": grid_view.table.database.group.id}
]
handler.create_decoration(
view=grid_view,
decorator_type_name="left_border_color",
value_provider_type_name="",
value_provider_conf={},
user=user,
)
with patch(
"baserow_premium.license.handler.has_active_premium_license_for"
) as mock_has_active_premium_license_for:
mock_has_active_premium_license_for.return_value = [{"type": "group", "id": 0}]
with pytest.raises(NoPremiumLicenseError):
handler.create_decoration(
view=grid_view,
decorator_type_name="left_border_color",
value_provider_type_name="",
value_provider_conf={},
user=user,
)
@pytest.mark.django_db
@override_settings(DEBUG=True)
def test_create_background_color_with_premium_license(premium_data_fixture):
@ -94,3 +133,41 @@ def test_create_background_color_without_premium_license(premium_data_fixture):
value_provider_conf={},
)
assert isinstance(decoration, ViewDecoration)
@pytest.mark.django_db
@override_settings(DEBUG=True)
def test_create_background_color_without_premium_license_for_group(
premium_data_fixture,
):
user = premium_data_fixture.create_user(has_active_premium_license=True)
grid_view = premium_data_fixture.create_grid_view(user=user)
handler = ViewHandler()
with patch(
"baserow_premium.license.handler.has_active_premium_license_for"
) as mock_has_active_premium_license_for:
mock_has_active_premium_license_for.return_value = [
{"type": "group", "id": grid_view.table.database.group.id}
]
handler.create_decoration(
view=grid_view,
decorator_type_name="background_color",
value_provider_type_name="",
value_provider_conf={},
user=user,
)
with patch(
"baserow_premium.license.handler.has_active_premium_license_for"
) as mock_has_active_premium_license_for:
mock_has_active_premium_license_for.return_value = [{"type": "group", "id": 0}]
with pytest.raises(NoPremiumLicenseError):
handler.create_decoration(
view=grid_view,
decorator_type_name="background_color",
value_provider_type_name="",
value_provider_conf={},
user=user,
)

View file

@ -1,4 +1,5 @@
import pytest
from unittest.mock import patch
from django.test.utils import override_settings
@ -262,6 +263,44 @@ def test_create_single_select_color_without_premium_license(premium_data_fixture
assert isinstance(decoration, ViewDecoration)
@pytest.mark.django_db
@override_settings(DEBUG=True)
def test_create_single_select_color_without_premium_license_for_group(
premium_data_fixture,
):
user = premium_data_fixture.create_user(has_active_premium_license=True)
grid_view = premium_data_fixture.create_grid_view(user=user)
handler = ViewHandler()
with patch(
"baserow_premium.license.handler.has_active_premium_license_for"
) as mock_has_active_premium_license_for:
mock_has_active_premium_license_for.return_value = [
{"type": "group", "id": grid_view.table.database.group.id}
]
handler.create_decoration(
view=grid_view,
decorator_type_name="left_border_color",
value_provider_type_name="single_select_color",
value_provider_conf={"field": None},
user=user,
)
with patch(
"baserow_premium.license.handler.has_active_premium_license_for"
) as mock_has_active_premium_license_for:
mock_has_active_premium_license_for.return_value = [{"type": "group", "id": 0}]
with pytest.raises(NoPremiumLicenseError):
handler.create_decoration(
view=grid_view,
decorator_type_name="left_border_color",
value_provider_type_name="single_select_color",
value_provider_conf={"field": None},
user=user,
)
@pytest.mark.django_db
@override_settings(DEBUG=True)
def test_create_conditional_color_with_premium_license(premium_data_fixture):
@ -304,3 +343,41 @@ def test_create_conditional_color_without_premium_license(premium_data_fixture):
value_provider_conf={},
)
assert isinstance(decoration, ViewDecoration)
@pytest.mark.django_db
@override_settings(DEBUG=True)
def test_create_conditional_color_without_premium_license_for_group(
premium_data_fixture,
):
user = premium_data_fixture.create_user(has_active_premium_license=True)
grid_view = premium_data_fixture.create_grid_view(user=user)
handler = ViewHandler()
with patch(
"baserow_premium.license.handler.has_active_premium_license_for"
) as mock_has_active_premium_license_for:
mock_has_active_premium_license_for.return_value = [
{"type": "group", "id": grid_view.table.database.group.id}
]
handler.create_decoration(
view=grid_view,
decorator_type_name="left_border_color",
value_provider_type_name="conditional_color",
value_provider_conf={"field": None},
user=user,
)
with patch(
"baserow_premium.license.handler.has_active_premium_license_for"
) as mock_has_active_premium_license_for:
mock_has_active_premium_license_for.return_value = [{"type": "group", "id": 0}]
with pytest.raises(NoPremiumLicenseError):
handler.create_decoration(
view=grid_view,
decorator_type_name="left_border_color",
value_provider_type_name="conditional_color",
value_provider_conf={"field": None},
user=user,
)

View file

@ -24,6 +24,7 @@
>
<div class="kanban-view__stacks">
<KanbanViewStack
:database="database"
:table="table"
:view="view"
:card-fields="cardFields"
@ -39,6 +40,7 @@
v-for="option in existingSelectOption"
:key="option.id"
:option="option"
:database="database"
:table="table"
:view="view"
:card-fields="cardFields"