mirror of
https://gitlab.com/bramw/baserow.git
synced 2025-04-17 10:22:36 +00:00
Final branch licensing system 2
This commit is contained in:
parent
9f59c5208f
commit
cac27f1628
138 changed files with 6073 additions and 459 deletions
.gitlab-ci.yml
backend
pytest.ini
changelog.mdrequirements
src/baserow
api
config/settings
contrib/database/export
core
test_utils
tests
premium
__init__.py
backend
__init__.pysetup.py
src/baserow_premium
admin
api
apps.pyexport
license
migrations
models.pypublic_key.pempublic_key_debug.pemrow_comments
tasks.pytests/baserow_premium
__init__.py
admin
dashboard
groups
users
api
conftest.pyexport
fixtures.pylicense
premium
row_comments
ws
web-frontend/modules/baserow_premium
|
@ -69,5 +69,7 @@ backend-setup:
|
|||
script:
|
||||
- pip install -e ./backend
|
||||
- python -c 'import baserow'
|
||||
- pip install -e ./premium/backend
|
||||
- python -c 'import baserow_premium'
|
||||
- export DJANGO_SETTINGS_MODULE='baserow.config.settings.base'
|
||||
- timeout --preserve-status 10s gunicorn --workers=1 -b 0.0.0.0:8000 -k uvicorn.workers.UvicornWorker baserow.config.asgi:application
|
||||
|
|
|
@ -3,7 +3,6 @@ DJANGO_SETTINGS_MODULE = baserow.config.settings.test
|
|||
python_files = test_*.py
|
||||
env =
|
||||
DJANGO_SETTINGS_MODULE = baserow.config.settings.test
|
||||
ADDITIONAL_APPS = baserow_premium
|
||||
testpaths =
|
||||
tests
|
||||
plugins
|
||||
|
|
|
@ -25,4 +25,5 @@ celery-redbeat==2.0.0
|
|||
service-identity==21.1.0
|
||||
regex==2021.8.3
|
||||
click==7.1.2
|
||||
cryptography==3.4.8
|
||||
antlr4-python3-runtime==4.8.0
|
||||
|
|
|
@ -11,4 +11,5 @@ argh==0.26.2
|
|||
black==21.7b0
|
||||
pyinstrument==4.0.3
|
||||
pyfakefs==4.5.0
|
||||
pytest-xdist==2.3.0
|
||||
pytest-xdist==2.3.0
|
||||
responses==0.13.4
|
||||
|
|
|
@ -10,3 +10,12 @@ class SettingsSerializer(serializers.ModelSerializer):
|
|||
extra_kwargs = {
|
||||
"allow_new_signups": {"required": False},
|
||||
}
|
||||
|
||||
|
||||
class InstanceIdSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Settings
|
||||
fields = ("instance_id",)
|
||||
extra_kwargs = {
|
||||
"instance_id": {"read_only": True},
|
||||
}
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
from django.urls import re_path
|
||||
|
||||
from .views import SettingsView, UpdateSettingsView
|
||||
from .views import SettingsView, InstanceIdView, UpdateSettingsView
|
||||
|
||||
|
||||
app_name = "baserow.api.settings"
|
||||
|
||||
urlpatterns = [
|
||||
re_path(r"^update/$", UpdateSettingsView.as_view(), name="update"),
|
||||
re_path(r"^instance-id/$", InstanceIdView.as_view(), name="instance_id"),
|
||||
re_path(r"^$", SettingsView.as_view(), name="get"),
|
||||
]
|
||||
|
|
|
@ -9,7 +9,7 @@ from rest_framework.permissions import AllowAny, IsAdminUser
|
|||
from baserow.api.decorators import validate_body
|
||||
from baserow.core.handler import CoreHandler
|
||||
|
||||
from .serializers import SettingsSerializer
|
||||
from .serializers import SettingsSerializer, InstanceIdSerializer
|
||||
|
||||
|
||||
class SettingsView(APIView):
|
||||
|
@ -25,14 +25,31 @@ class SettingsView(APIView):
|
|||
auth=[None],
|
||||
)
|
||||
def get(self, request):
|
||||
"""
|
||||
Responds with all the admin configured settings.
|
||||
"""
|
||||
"""Responds with all the admin configured settings."""
|
||||
|
||||
settings = CoreHandler().get_settings()
|
||||
return Response(SettingsSerializer(settings).data)
|
||||
|
||||
|
||||
class InstanceIdView(APIView):
|
||||
permission_classes = (IsAdminUser,)
|
||||
|
||||
@extend_schema(
|
||||
tags=["Settings"],
|
||||
operation_id="get_instance_id",
|
||||
description="Responds with the self hosted instance id. Only a user with "
|
||||
"staff permissions can request it.",
|
||||
responses={
|
||||
200: InstanceIdSerializer,
|
||||
},
|
||||
)
|
||||
def get(self, request):
|
||||
"""Responds with the instance id."""
|
||||
|
||||
settings = CoreHandler().get_settings()
|
||||
return Response(InstanceIdSerializer(settings).data)
|
||||
|
||||
|
||||
class UpdateSettingsView(APIView):
|
||||
permission_classes = (IsAdminUser,)
|
||||
|
||||
|
|
|
@ -1,8 +1,18 @@
|
|||
from baserow.api.user.registries import user_data_registry
|
||||
|
||||
|
||||
from .serializers import UserSerializer
|
||||
|
||||
|
||||
def jwt_response_payload_handler(token, user=None, request=None, issued_at=None):
|
||||
return {
|
||||
payload = {
|
||||
"token": token,
|
||||
"user": UserSerializer(user, context={"request": request}).data,
|
||||
}
|
||||
|
||||
# Update the payload with the additional user data that must be added. The
|
||||
# `user_data_registry` contains instances that want to add additional information
|
||||
# to this payload.
|
||||
payload.update(**user_data_registry.get_all_user_data(user, request))
|
||||
|
||||
return payload
|
||||
|
|
73
backend/src/baserow/api/user/registries.py
Normal file
73
backend/src/baserow/api/user/registries.py
Normal file
|
@ -0,0 +1,73 @@
|
|||
from baserow.core.registry import Instance, Registry
|
||||
|
||||
|
||||
class UserDataType(Instance):
|
||||
"""
|
||||
The user data type can be used to inject an additional payload to the API
|
||||
JWT response. This is the response when a user authenticates or refreshes his
|
||||
token. The returned dict of the `get_user_data` method is added to the payload
|
||||
under the key containing the type name.
|
||||
|
||||
Example:
|
||||
|
||||
class TestUserDataType(UserDataType):
|
||||
type = "test"
|
||||
|
||||
def get_user_data(user, request):
|
||||
return {"test": "value"}
|
||||
|
||||
user_data_registry.register(TestUserDataType())
|
||||
|
||||
Will result into the following response when the user authenticates:
|
||||
{
|
||||
"token": "eyJ....",
|
||||
"user: {
|
||||
"id": 1,
|
||||
...
|
||||
},
|
||||
"test": {
|
||||
"test": "value"
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
def get_user_data(self, user, request) -> dict:
|
||||
"""
|
||||
Should return a dict containing the additional information that must be added
|
||||
to the response payload after the user authenticates.
|
||||
|
||||
:param user: The related user that just authenticated.
|
||||
:type user: User
|
||||
:param request: The request when the user authenticated.
|
||||
:type request: Request
|
||||
:return: a dict containing the user data that must be added to the response.
|
||||
"""
|
||||
|
||||
raise NotImplementedError(
|
||||
"The get_user_data must be implemented and should return a dict."
|
||||
)
|
||||
|
||||
|
||||
class UserDataRegistry(Registry):
|
||||
name = "api_user_data"
|
||||
|
||||
def get_all_user_data(self, user, request) -> dict:
|
||||
"""
|
||||
Collects the additional user data of all the registered user data type
|
||||
instances.
|
||||
|
||||
:param user: The user that just authenticated.
|
||||
:type user: User
|
||||
:param request: The request when the user authenticated.
|
||||
:type request: Request
|
||||
:return: a dict containing all additional user data payload for all the
|
||||
registered instances.
|
||||
"""
|
||||
|
||||
return {
|
||||
key: value.get_user_data(user, request)
|
||||
for key, value in self.registry.items()
|
||||
}
|
||||
|
||||
|
||||
user_data_registry = UserDataRegistry()
|
|
@ -26,6 +26,7 @@ from baserow.api.groups.invitations.errors import (
|
|||
ERROR_GROUP_INVITATION_EMAIL_MISMATCH,
|
||||
)
|
||||
from baserow.api.schemas import get_error_schema
|
||||
from baserow.api.user.registries import user_data_registry
|
||||
from baserow.core.exceptions import (
|
||||
BaseURLHostnameNotAllowed,
|
||||
GroupInvitationEmailMismatch,
|
||||
|
@ -195,6 +196,7 @@ class UserView(APIView):
|
|||
payload = jwt_payload_handler(user)
|
||||
token = jwt_encode_handler(payload)
|
||||
response.update(token=token)
|
||||
response.update(**user_data_registry.get_all_user_data(user, request))
|
||||
|
||||
return Response(response)
|
||||
|
||||
|
|
|
@ -31,6 +31,7 @@ INSTALLED_APPS = [
|
|||
"baserow.api",
|
||||
"baserow.ws",
|
||||
"baserow.contrib.database",
|
||||
"baserow_premium",
|
||||
]
|
||||
|
||||
ADDITIONAL_APPS = os.getenv("ADDITIONAL_APPS", None)
|
||||
|
|
|
@ -84,6 +84,10 @@ class ExportHandler:
|
|||
:return: The created export job.
|
||||
"""
|
||||
|
||||
exporter_type = export_options.pop("exporter_type")
|
||||
exporter = table_exporter_registry.get(exporter_type)
|
||||
exporter.before_job_create(user, table, view, export_options)
|
||||
|
||||
table.database.group.has_user(user, raise_error=True)
|
||||
|
||||
if view and view.table.id != table.id:
|
||||
|
@ -91,8 +95,6 @@ class ExportHandler:
|
|||
|
||||
_cancel_unfinished_jobs(user)
|
||||
|
||||
exporter_type = export_options.pop("exporter_type")
|
||||
|
||||
_raise_if_invalid_view_or_table_for_exporter(exporter_type, view)
|
||||
|
||||
job = ExportJob.objects.create(
|
||||
|
|
|
@ -19,6 +19,21 @@ class TableExporter(Instance, ABC):
|
|||
without specifying a particular view.
|
||||
"""
|
||||
|
||||
def before_job_create(self, user, table, view, export_options):
|
||||
"""
|
||||
This method is called just before an export job is created. It can be used to
|
||||
do some additional checking of the provided values.
|
||||
|
||||
:param user: The user that requested the creation of the job.
|
||||
:type user: User
|
||||
:param table: The table that must be exported.
|
||||
:type table: Table
|
||||
:param view: The view that must be exported.
|
||||
:type view: None or View
|
||||
:param export_options: The additional user provided export options.
|
||||
:type export_options: dict
|
||||
"""
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def file_extension(self) -> str:
|
||||
|
|
36
backend/src/baserow/core/db.py
Normal file
36
backend/src/baserow/core/db.py
Normal file
|
@ -0,0 +1,36 @@
|
|||
from django.db import DEFAULT_DB_ALIAS
|
||||
from django.db.transaction import Atomic, get_connection
|
||||
|
||||
|
||||
class LockedAtomicTransaction(Atomic):
|
||||
"""
|
||||
Does a atomic transaction, but also locks the entire table for any transactions,
|
||||
for the duration of this transaction. Although this is the only way to avoid
|
||||
concurrency issues in certain situations, it should be used with caution,
|
||||
since it has impacts on performance, for obvious reasons...
|
||||
"""
|
||||
|
||||
def __init__(self, model, using=None, savepoint=True, durable=False):
|
||||
if using is None:
|
||||
using = DEFAULT_DB_ALIAS
|
||||
|
||||
super().__init__(using, savepoint, durable)
|
||||
self.model = model
|
||||
|
||||
def __enter__(self):
|
||||
super(LockedAtomicTransaction, self).__enter__()
|
||||
|
||||
cursor = None
|
||||
try:
|
||||
cursor = get_connection(self.using).cursor()
|
||||
cursor.execute(
|
||||
"LOCK TABLE {db_table_name}".format(
|
||||
db_table_name=self.model._meta.db_table
|
||||
)
|
||||
)
|
||||
finally:
|
||||
if cursor and not cursor.closed:
|
||||
cursor.close()
|
||||
|
||||
def __exit__(self, *args, **kwargs):
|
||||
return super().__exit__(*args, **kwargs)
|
|
@ -95,8 +95,9 @@ class CoreHandler:
|
|||
if not settings_instance:
|
||||
settings_instance = self.get_settings()
|
||||
|
||||
for name, value in kwargs.items():
|
||||
setattr(settings_instance, name, value)
|
||||
settings_instance = set_allowed_attrs(
|
||||
kwargs, ["allow_new_signups"], settings_instance
|
||||
)
|
||||
|
||||
settings_instance.save()
|
||||
return settings_instance
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
# Generated by Django 3.2.6 on 2021-10-07 19:26
|
||||
|
||||
from django.db import migrations, models
|
||||
import secrets
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("core", "0010_fix_trash_constraint"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="settings",
|
||||
name="instance_id",
|
||||
field=models.SlugField(default=secrets.token_urlsafe),
|
||||
),
|
||||
]
|
|
@ -1,3 +1,5 @@
|
|||
import secrets
|
||||
|
||||
from django.db import models
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
|
@ -55,6 +57,7 @@ class Settings(models.Model):
|
|||
change. This table can only contain a single row.
|
||||
"""
|
||||
|
||||
instance_id = models.SlugField(default=secrets.token_urlsafe)
|
||||
allow_new_signups = models.BooleanField(
|
||||
default=True,
|
||||
help_text="Indicates whether new users can create a new account when signing "
|
||||
|
|
0
backend/src/baserow/test_utils/__init__.py
Normal file
0
backend/src/baserow/test_utils/__init__.py
Normal file
|
@ -21,7 +21,7 @@ def _parse_date(date):
|
|||
return parse_date(date)
|
||||
|
||||
|
||||
def setup_interesting_test_table(data_fixture):
|
||||
def setup_interesting_test_table(data_fixture, user_kwargs=None):
|
||||
"""
|
||||
Constructs a testing table with every field type, their sub types and any other
|
||||
interesting baserow edge cases worth testing when writing a comphensive "does this
|
||||
|
@ -31,7 +31,10 @@ def setup_interesting_test_table(data_fixture):
|
|||
:return:
|
||||
"""
|
||||
|
||||
user = data_fixture.create_user()
|
||||
if not user_kwargs:
|
||||
user_kwargs = {}
|
||||
|
||||
user = data_fixture.create_user(**user_kwargs)
|
||||
database = data_fixture.create_database_application(user=user)
|
||||
table = data_fixture.create_database_table(database=database, user=user)
|
||||
link_table = data_fixture.create_database_table(database=database, user=user)
|
53
backend/src/baserow/test_utils/pytest_conftest.py
Normal file
53
backend/src/baserow/test_utils/pytest_conftest.py
Normal file
|
@ -0,0 +1,53 @@
|
|||
import os
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def data_fixture():
|
||||
from .fixtures import Fixtures
|
||||
|
||||
return Fixtures()
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def api_client():
|
||||
from rest_framework.test import APIClient
|
||||
|
||||
return APIClient()
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def environ():
|
||||
original_env = os.environ.copy()
|
||||
yield os.environ
|
||||
for key, value in original_env.items():
|
||||
os.environ[key] = value
|
||||
|
||||
|
||||
# We reuse this file in the premium backend folder, if you run a pytest session over
|
||||
# plugins and the core at the same time pytest will crash if this called multiple times.
|
||||
def pytest_addoption(parser):
|
||||
# Unfortunately a simple decorator doesn't work here as pytest is doing some
|
||||
# exciting reflection of sorts over this function and crashes if it is wrapped.
|
||||
if not hasattr(pytest_addoption, "already_run"):
|
||||
parser.addoption(
|
||||
"--runslow", action="store_true", default=False, help="run slow tests"
|
||||
)
|
||||
pytest_addoption.already_run = True
|
||||
|
||||
|
||||
def pytest_configure(config):
|
||||
if not hasattr(pytest_configure, "already_run"):
|
||||
config.addinivalue_line("markers", "slow: mark test as slow to run")
|
||||
pytest_configure.already_run = True
|
||||
|
||||
|
||||
def pytest_collection_modifyitems(config, items):
|
||||
if config.getoption("--runslow"):
|
||||
# --runslow given in cli: do not skip slow tests
|
||||
return
|
||||
skip_slow = pytest.mark.skip(reason="need --runslow option to run")
|
||||
for item in items:
|
||||
if "slow" in item.keywords:
|
||||
item.add_marker(skip_slow)
|
|
@ -1,6 +1,11 @@
|
|||
import pytest
|
||||
|
||||
from rest_framework.status import HTTP_200_OK, HTTP_400_BAD_REQUEST, HTTP_403_FORBIDDEN
|
||||
from rest_framework.status import (
|
||||
HTTP_200_OK,
|
||||
HTTP_400_BAD_REQUEST,
|
||||
HTTP_401_UNAUTHORIZED,
|
||||
HTTP_403_FORBIDDEN,
|
||||
)
|
||||
|
||||
from django.shortcuts import reverse
|
||||
|
||||
|
@ -13,6 +18,7 @@ def test_get_settings(api_client):
|
|||
response = api_client.get(reverse("api:settings:get"))
|
||||
assert response.status_code == HTTP_200_OK
|
||||
response_json = response.json()
|
||||
assert "instance_id" not in response_json
|
||||
assert response_json["allow_new_signups"] is True
|
||||
|
||||
settings = Settings.objects.first()
|
||||
|
@ -22,9 +28,46 @@ def test_get_settings(api_client):
|
|||
response = api_client.get(reverse("api:settings:get"))
|
||||
assert response.status_code == HTTP_200_OK
|
||||
response_json = response.json()
|
||||
assert "instance_id" not in response_json
|
||||
assert response_json["allow_new_signups"] is False
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_get_instance_id(api_client, data_fixture):
|
||||
user, token = data_fixture.create_user_and_token(is_staff=True)
|
||||
user_2, token_2 = data_fixture.create_user_and_token()
|
||||
|
||||
response = api_client.get(reverse("api:settings:instance_id"))
|
||||
assert response.status_code == HTTP_401_UNAUTHORIZED
|
||||
|
||||
response = api_client.get(
|
||||
reverse("api:settings:instance_id"),
|
||||
HTTP_AUTHORIZATION=f"JWT {token_2}",
|
||||
)
|
||||
assert response.status_code == HTTP_403_FORBIDDEN
|
||||
assert CoreHandler().get_settings().allow_new_signups is True
|
||||
|
||||
response = api_client.get(
|
||||
reverse("api:settings:instance_id"),
|
||||
HTTP_AUTHORIZATION=f"JWT {token}",
|
||||
)
|
||||
assert response.status_code == HTTP_200_OK
|
||||
response_json = response.json()
|
||||
assert len(response_json["instance_id"]) > 32
|
||||
|
||||
settings = Settings.objects.first()
|
||||
settings.allow_new_signups = False
|
||||
settings.save()
|
||||
|
||||
response = api_client.get(
|
||||
reverse("api:settings:instance_id"),
|
||||
HTTP_AUTHORIZATION=f"JWT {token}",
|
||||
)
|
||||
assert response.status_code == HTTP_200_OK
|
||||
response_json = response.json()
|
||||
assert response_json["instance_id"] == settings.instance_id
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_update_settings(api_client, data_fixture):
|
||||
user, token = data_fixture.create_user_and_token(is_staff=True)
|
||||
|
@ -50,6 +93,15 @@ def test_update_settings(api_client, data_fixture):
|
|||
assert response_json["error"] == "ERROR_REQUEST_BODY_VALIDATION"
|
||||
assert response_json["detail"]["allow_new_signups"][0]["code"] == "invalid"
|
||||
|
||||
response = api_client.patch(
|
||||
reverse("api:settings:update"),
|
||||
{"instance_id": "test"},
|
||||
format="json",
|
||||
HTTP_AUTHORIZATION=f"JWT {token}",
|
||||
)
|
||||
assert response.status_code == HTTP_200_OK
|
||||
assert CoreHandler().get_settings().instance_id != "test"
|
||||
|
||||
response = api_client.patch(
|
||||
reverse("api:settings:update"),
|
||||
{"allow_new_signups": False},
|
||||
|
@ -59,6 +111,7 @@ def test_update_settings(api_client, data_fixture):
|
|||
assert response.status_code == HTTP_200_OK
|
||||
response_json = response.json()
|
||||
assert response_json["allow_new_signups"] is False
|
||||
assert "instance_id" not in response_json
|
||||
assert CoreHandler().get_settings().allow_new_signups is False
|
||||
|
||||
response = api_client.patch(
|
||||
|
@ -70,4 +123,5 @@ def test_update_settings(api_client, data_fixture):
|
|||
assert response.status_code == HTTP_200_OK
|
||||
response_json = response.json()
|
||||
assert response_json["allow_new_signups"] is False
|
||||
assert "instance_id" not in response_json
|
||||
assert CoreHandler().get_settings().allow_new_signups is False
|
||||
|
|
|
@ -1,12 +1,19 @@
|
|||
import os
|
||||
import pytest
|
||||
from unittest.mock import patch
|
||||
from freezegun import freeze_time
|
||||
from rest_framework.status import HTTP_200_OK, HTTP_400_BAD_REQUEST, HTTP_404_NOT_FOUND
|
||||
from rest_framework.status import (
|
||||
HTTP_200_OK,
|
||||
HTTP_201_CREATED,
|
||||
HTTP_400_BAD_REQUEST,
|
||||
HTTP_404_NOT_FOUND,
|
||||
)
|
||||
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.shortcuts import reverse
|
||||
from django.conf import settings
|
||||
|
||||
from baserow.api.user.registries import user_data_registry, UserDataType
|
||||
from baserow.contrib.database.models import Database, Table
|
||||
from baserow.core.handler import CoreHandler
|
||||
from baserow.core.models import Group, GroupUser
|
||||
|
@ -642,3 +649,46 @@ def test_dashboard(data_fixture, client):
|
|||
assert response_json["group_invitations"][0]["group"] == "Test1"
|
||||
assert response_json["group_invitations"][0]["message"] == invitation_1.message
|
||||
assert "created_on" in response_json["group_invitations"][0]
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_additional_user_data(api_client, data_fixture):
|
||||
class TmpUserDataType(UserDataType):
|
||||
type = "type"
|
||||
|
||||
def get_user_data(self, user, request) -> dict:
|
||||
return True
|
||||
|
||||
plugin_mock = TmpUserDataType()
|
||||
with patch.dict(user_data_registry.registry, {"tmp": plugin_mock}):
|
||||
response = api_client.post(
|
||||
reverse("api:user:index"),
|
||||
{
|
||||
"name": "Test1",
|
||||
"email": "test@test.nl",
|
||||
"password": "thisIsAValidPassword",
|
||||
"authenticate": True,
|
||||
},
|
||||
format="json",
|
||||
)
|
||||
response_json = response.json()
|
||||
assert response.status_code == HTTP_200_OK
|
||||
assert response_json["tmp"] is True
|
||||
|
||||
response = api_client.post(
|
||||
reverse("api:user:token_auth"),
|
||||
{"username": "test@test.nl", "password": "thisIsAValidPassword"},
|
||||
format="json",
|
||||
)
|
||||
response_json = response.json()
|
||||
assert response.status_code == HTTP_201_CREATED
|
||||
assert response_json["tmp"] is True
|
||||
|
||||
response = api_client.post(
|
||||
reverse("api:user:token_refresh"),
|
||||
{"token": response_json["token"]},
|
||||
format="json",
|
||||
)
|
||||
response_json = response.json()
|
||||
assert response.status_code == HTTP_201_CREATED
|
||||
assert response_json["tmp"] is True
|
||||
|
|
|
@ -8,7 +8,7 @@ from baserow.contrib.database.api.rows.serializers import (
|
|||
)
|
||||
from baserow.contrib.database.fields.models import SelectOption
|
||||
from baserow.contrib.database.fields.registries import field_type_registry
|
||||
from tests.test_utils import setup_interesting_test_table
|
||||
from baserow.test_utils.helpers import setup_interesting_test_table
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
|
|
|
@ -16,7 +16,7 @@ from baserow.contrib.database.rows.registries import (
|
|||
row_metadata_registry,
|
||||
)
|
||||
from baserow.contrib.database.views.models import GridView
|
||||
from tests.test_utils import register_instance_temporarily
|
||||
from baserow.test_utils.helpers import register_instance_temporarily
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
|
|
|
@ -38,7 +38,7 @@ from baserow.contrib.database.fields.handler import FieldHandler
|
|||
from baserow.contrib.database.rows.handler import RowHandler
|
||||
from baserow.contrib.database.views.exceptions import ViewNotInTable
|
||||
from baserow.contrib.database.views.models import GridView, GridViewFieldOptions
|
||||
from tests.test_utils import setup_interesting_test_table
|
||||
from baserow.test_utils.helpers import setup_interesting_test_table
|
||||
|
||||
|
||||
def _parse_datetime(datetime):
|
||||
|
|
|
@ -15,7 +15,7 @@ from baserow.contrib.database.fields.models import (
|
|||
)
|
||||
from baserow.contrib.database.fields.registries import field_type_registry
|
||||
from baserow.contrib.database.rows.handler import RowHandler
|
||||
from tests.test_utils import setup_interesting_test_table
|
||||
from baserow.test_utils.helpers import setup_interesting_test_table
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
|
@ -369,7 +369,7 @@ def test_email_field_type(data_fixture):
|
|||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@override_settings(debug=True)
|
||||
@override_settings(DEBUG=True)
|
||||
def test_phone_number_field_type(data_fixture):
|
||||
user = data_fixture.create_user()
|
||||
table = data_fixture.create_database_table(user=user)
|
||||
|
|
|
@ -12,7 +12,7 @@ from baserow.contrib.database.rows.registries import (
|
|||
RowMetadataType,
|
||||
row_metadata_registry,
|
||||
)
|
||||
from tests.test_utils import register_instance_temporarily
|
||||
from baserow.test_utils.helpers import register_instance_temporarily
|
||||
|
||||
|
||||
@pytest.mark.django_db(transaction=True)
|
||||
|
|
26
backend/tests/baserow/core/test_core_db.py
Normal file
26
backend/tests/baserow/core/test_core_db.py
Normal file
|
@ -0,0 +1,26 @@
|
|||
import pytest
|
||||
|
||||
from django.db import connection
|
||||
from django.test.utils import override_settings
|
||||
|
||||
from baserow.core.db import LockedAtomicTransaction
|
||||
from baserow.core.models import Settings
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@override_settings(DEBUG=True)
|
||||
def test_locked_atomic_transaction(api_client, data_fixture):
|
||||
def is_locked(model):
|
||||
with connection.cursor() as cursor:
|
||||
table_name = model._meta.db_table
|
||||
cursor.execute(
|
||||
"select count(*) from pg_locks l join pg_class t on l.relation = "
|
||||
"t.oid WHERE relname = %(table_name)s;",
|
||||
{"table_name": table_name},
|
||||
)
|
||||
return cursor.fetchone()[0] > 0
|
||||
|
||||
assert not is_locked(Settings)
|
||||
|
||||
with LockedAtomicTransaction(Settings):
|
||||
assert is_locked(Settings)
|
|
@ -45,6 +45,7 @@ from baserow.core.user_files.models import UserFile
|
|||
def test_get_settings():
|
||||
settings = CoreHandler().get_settings()
|
||||
assert isinstance(settings, Settings)
|
||||
assert len(settings.instance_id) > 32
|
||||
assert settings.allow_new_signups is True
|
||||
|
||||
|
||||
|
@ -56,10 +57,14 @@ def test_update_settings(data_fixture):
|
|||
with pytest.raises(IsNotAdminError):
|
||||
CoreHandler().update_settings(user_2, allow_new_signups=False)
|
||||
|
||||
settings = CoreHandler().update_settings(user_1, allow_new_signups=False)
|
||||
settings = CoreHandler().update_settings(
|
||||
user_1, allow_new_signups=False, instance_id="test"
|
||||
)
|
||||
assert settings.allow_new_signups is False
|
||||
assert settings.instance_id != "test"
|
||||
|
||||
settings = Settings.objects.all().first()
|
||||
assert settings.instance_id != "test"
|
||||
assert settings.allow_new_signups is False
|
||||
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ from baserow.contrib.database.fields.handler import FieldHandler
|
|||
from baserow.contrib.database.management.commands.fill_table import fill_table
|
||||
from baserow.contrib.database.rows.handler import RowHandler
|
||||
from baserow.core.trash.handler import TrashHandler
|
||||
from tests.test_utils import setup_interesting_test_table
|
||||
from baserow.test_utils.helpers import setup_interesting_test_table
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
|
|
|
@ -4,7 +4,7 @@ from pyinstrument import Profiler
|
|||
from baserow.contrib.database.management.commands.fill_table import fill_table
|
||||
from baserow.core.models import TrashEntry
|
||||
from baserow.core.trash.handler import TrashHandler
|
||||
from tests.test_utils import setup_interesting_test_table
|
||||
from baserow.test_utils.helpers import setup_interesting_test_table
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
|
|
|
@ -1,55 +1,5 @@
|
|||
from __future__ import print_function
|
||||
|
||||
import os
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def data_fixture():
|
||||
from .fixtures import Fixtures
|
||||
|
||||
return Fixtures()
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def api_client():
|
||||
from rest_framework.test import APIClient
|
||||
|
||||
return APIClient()
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def environ():
|
||||
original_env = os.environ.copy()
|
||||
yield os.environ
|
||||
for key, value in original_env.items():
|
||||
os.environ[key] = value
|
||||
|
||||
|
||||
# We reuse this file in the premium backend folder, if you run a pytest session over
|
||||
# plugins and the core at the same time pytest will crash if this called multiple times.
|
||||
def pytest_addoption(parser):
|
||||
# Unfortunately a simple decorator doesn't work here as pytest is doing some
|
||||
# exciting reflection of sorts over this function and crashes if it is wrapped.
|
||||
if not hasattr(pytest_addoption, "already_run"):
|
||||
parser.addoption(
|
||||
"--runslow", action="store_true", default=False, help="run slow tests"
|
||||
)
|
||||
pytest_addoption.already_run = True
|
||||
|
||||
|
||||
def pytest_configure(config):
|
||||
if not hasattr(pytest_configure, "already_run"):
|
||||
config.addinivalue_line("markers", "slow: mark test as slow to run")
|
||||
pytest_configure.already_run = True
|
||||
|
||||
|
||||
def pytest_collection_modifyitems(config, items):
|
||||
if config.getoption("--runslow"):
|
||||
# --runslow given in cli: do not skip slow tests
|
||||
return
|
||||
skip_slow = pytest.mark.skip(reason="need --runslow option to run")
|
||||
for item in items:
|
||||
if "slow" in item.keywords:
|
||||
item.add_marker(skip_slow)
|
||||
# noinspection PyUnresolvedReferences
|
||||
from baserow.test_utils.pytest_conftest import * # noqa: F403, F401
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
env variable. If using MEDIA_PORT you now need to set MEDIA_URL also.
|
||||
* **Breaking Change**: Baserow's `docker-compose.yml` container names have changed to
|
||||
no longer be hardcoded to prevent naming clashes.
|
||||
* Added a licensing system for the premium version.
|
||||
* Fixed bug where it was possible to create duplicate trash entries.
|
||||
|
||||
## Released (2021-10-05)
|
||||
|
|
0
premium/__init__.py
Normal file
0
premium/__init__.py
Normal file
0
premium/backend/__init__.py
Normal file
0
premium/backend/__init__.py
Normal file
19
premium/backend/setup.py
Normal file
19
premium/backend/setup.py
Normal file
|
@ -0,0 +1,19 @@
|
|||
#!/usr/bin/env python
|
||||
import os
|
||||
|
||||
from setuptools import find_packages, setup
|
||||
|
||||
|
||||
PROJECT_DIR = os.path.dirname(__file__)
|
||||
|
||||
|
||||
setup(
|
||||
name="baserow_premium",
|
||||
url="https://baserow.io",
|
||||
author="Bram Wiepjes (Baserow)",
|
||||
author_email="bram@baserow.io",
|
||||
platforms=["linux"],
|
||||
package_dir={"": "src"},
|
||||
packages=find_packages("src"),
|
||||
include_package_data=True,
|
||||
)
|
|
@ -2,6 +2,7 @@ from baserow.core.exceptions import IsNotAdminError
|
|||
from baserow.core.signals import group_deleted
|
||||
from baserow.core.trash.handler import TrashHandler
|
||||
from baserow_premium.admin.groups.exceptions import CannotDeleteATemplateGroupError
|
||||
from baserow_premium.license.handler import check_active_premium_license
|
||||
|
||||
|
||||
class GroupsAdminHandler:
|
||||
|
@ -16,6 +17,8 @@ class GroupsAdminHandler:
|
|||
:raises IsNotAdminError: If the user is not admin or staff.
|
||||
"""
|
||||
|
||||
check_active_premium_license(user)
|
||||
|
||||
if not user.is_staff:
|
||||
raise IsNotAdminError()
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ from baserow_premium.admin.users.exceptions import (
|
|||
CannotDeleteYourselfException,
|
||||
UserDoesNotExistException,
|
||||
)
|
||||
from baserow_premium.license.handler import check_active_premium_license
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
|
@ -44,6 +45,7 @@ class UserAdminHandler:
|
|||
a valid password.
|
||||
"""
|
||||
|
||||
check_active_premium_license(requesting_user)
|
||||
self._raise_if_not_permitted(requesting_user)
|
||||
self._raise_if_locking_self_out_of_admin(
|
||||
is_active, is_staff, requesting_user, user_id
|
||||
|
@ -101,6 +103,7 @@ class UserAdminHandler:
|
|||
UnknownUserException.
|
||||
"""
|
||||
|
||||
check_active_premium_license(requesting_user)
|
||||
self._raise_if_not_permitted(requesting_user)
|
||||
|
||||
if requesting_user.id == user_id:
|
||||
|
|
|
@ -12,6 +12,7 @@ from baserow.api.decorators import accept_timezone
|
|||
from baserow.core.models import Group, Application
|
||||
|
||||
from baserow_premium.admin.dashboard.handler import AdminDashboardHandler
|
||||
from baserow_premium.license.handler import check_active_premium_license
|
||||
|
||||
from .serializers import AdminDashboardSerializer
|
||||
|
||||
|
@ -47,6 +48,8 @@ class AdminDashboardView(APIView):
|
|||
last 30 days is also included.
|
||||
"""
|
||||
|
||||
check_active_premium_license(request.user)
|
||||
|
||||
handler = AdminDashboardHandler()
|
||||
total_users = User.objects.filter(is_active=True).count()
|
||||
total_groups = Group.objects.all().count()
|
||||
|
|
|
@ -16,8 +16,9 @@ from baserow.core.exceptions import GroupDoesNotExist
|
|||
from baserow_premium.admin.groups.exceptions import CannotDeleteATemplateGroupError
|
||||
from baserow_premium.api.admin.views import AdminListingView
|
||||
from baserow_premium.admin.groups.handler import GroupsAdminHandler
|
||||
from .errors import ERROR_CANNOT_DELETE_A_TEMPLATE_GROUP
|
||||
from baserow_premium.license.handler import check_active_premium_license
|
||||
|
||||
from .errors import ERROR_CANNOT_DELETE_A_TEMPLATE_GROUP
|
||||
from .serializers import GroupsAdminResponseSerializer
|
||||
|
||||
|
||||
|
@ -49,8 +50,9 @@ class GroupsAdminView(AdminListingView):
|
|||
"groups", serializer_class, search_fields, sort_field_mapping
|
||||
),
|
||||
)
|
||||
def get(self, *args, **kwargs):
|
||||
return super().get(*args, **kwargs)
|
||||
def get(self, request):
|
||||
check_active_premium_license(request.user)
|
||||
return super().get(request)
|
||||
|
||||
|
||||
class GroupAdminView(APIView):
|
||||
|
|
|
@ -27,6 +27,7 @@ from baserow_premium.admin.users.handler import UserAdminHandler
|
|||
from django.contrib.auth import get_user_model
|
||||
|
||||
from baserow_premium.api.admin.views import AdminListingView
|
||||
from baserow_premium.license.handler import check_active_premium_license
|
||||
|
||||
|
||||
User = get_user_model()
|
||||
|
@ -58,8 +59,9 @@ class UsersAdminView(AdminListingView):
|
|||
"users", serializer_class, search_fields, sort_field_mapping
|
||||
),
|
||||
)
|
||||
def get(self, *args, **kwargs):
|
||||
return super().get(*args, **kwargs)
|
||||
def get(self, request):
|
||||
check_active_premium_license(request.user)
|
||||
return super().get(request)
|
||||
|
||||
|
||||
class UserAdminView(APIView):
|
||||
|
|
45
premium/backend/src/baserow_premium/api/license/errors.py
Normal file
45
premium/backend/src/baserow_premium/api/license/errors.py
Normal file
|
@ -0,0 +1,45 @@
|
|||
from rest_framework.status import HTTP_400_BAD_REQUEST, HTTP_404_NOT_FOUND
|
||||
|
||||
|
||||
ERROR_PREMIUM_LICENSE_DOES_NOT_EXIST = (
|
||||
"ERROR_PREMIUM_LICENSE_DOES_NOT_EXIST",
|
||||
HTTP_404_NOT_FOUND,
|
||||
"The requested license does not exist. Please use the internal `id` and not the "
|
||||
"`license_id`.",
|
||||
)
|
||||
ERROR_INVALID_PREMIUM_LICENSE = (
|
||||
"ERROR_INVALID_PREMIUM_LICENSE",
|
||||
HTTP_400_BAD_REQUEST,
|
||||
"The provided license is invalid.",
|
||||
)
|
||||
ERROR_UNSUPPORTED_PREMIUM_LICENSE = (
|
||||
"ERROR_UNSUPPORTED_PREMIUM_LICENSE",
|
||||
HTTP_400_BAD_REQUEST,
|
||||
"This license version is not supported. You probably need to update your Baserow "
|
||||
"installation.",
|
||||
)
|
||||
ERROR_PREMIUM_LICENSE_INSTANCE_ID_MISMATCH = (
|
||||
"ERROR_PREMIUM_LICENSE_INSTANCE_ID_MISMATCH",
|
||||
HTTP_400_BAD_REQUEST,
|
||||
"The `instance_id` of the license doesn't match the instance id of Baserow copy.",
|
||||
)
|
||||
ERROR_PREMIUM_LICENSE_ALREADY_EXISTS = (
|
||||
"ERROR_PREMIUM_LICENSE_ALREADY_EXISTS",
|
||||
HTTP_400_BAD_REQUEST,
|
||||
"The provided premium license already exists.",
|
||||
)
|
||||
ERROR_PREMIUM_LICENSE_HAS_EXPIRED = (
|
||||
"ERROR_PREMIUM_LICENSE_HAS_EXPIRED",
|
||||
HTTP_400_BAD_REQUEST,
|
||||
"The provided premium license has already expired.",
|
||||
)
|
||||
ERROR_USER_ALREADY_ON_PREMIUM_LICENSE = (
|
||||
"ERROR_USER_ALREADY_IN_PREMIUM_LICENSE",
|
||||
HTTP_400_BAD_REQUEST,
|
||||
"The provided user is already on the premium license.",
|
||||
)
|
||||
ERROR_NO_SEATS_LEFT_IN_PREMIUM_LICENSE = (
|
||||
"ERROR_NO_SEATS_LEFT_IN_PREMIUM_LICENSE",
|
||||
HTTP_400_BAD_REQUEST,
|
||||
"Can't add the user because there are not seats left in the license.",
|
||||
)
|
100
premium/backend/src/baserow_premium/api/license/serializers.py
Normal file
100
premium/backend/src/baserow_premium/api/license/serializers.py
Normal file
|
@ -0,0 +1,100 @@
|
|||
from django.contrib.auth import get_user_model
|
||||
from rest_framework import serializers
|
||||
from drf_spectacular.utils import extend_schema_field
|
||||
from drf_spectacular.openapi import OpenApiTypes
|
||||
|
||||
from baserow_premium.license.models import License
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
|
||||
class PremiumLicenseSerializer(serializers.ModelSerializer):
|
||||
license_id = serializers.CharField(help_text="Unique identifier of the license.")
|
||||
is_active = serializers.BooleanField(
|
||||
help_text="Indicates if the backend deems the license valid."
|
||||
)
|
||||
valid_from = serializers.DateTimeField(
|
||||
help_text="From which timestamp the license becomes active."
|
||||
)
|
||||
valid_through = serializers.DateTimeField(
|
||||
help_text="Until which timestamp the license is active."
|
||||
)
|
||||
seats_taken = serializers.SerializerMethodField(
|
||||
help_text="The amount of users that are currently using the license."
|
||||
)
|
||||
seats = serializers.IntegerField(
|
||||
help_text="The maximum amount of users that can use the license."
|
||||
)
|
||||
product_code = serializers.CharField(
|
||||
help_text="The product code that indicates what the license unlocks."
|
||||
)
|
||||
issued_on = serializers.DateTimeField(
|
||||
help_text="The date when the license was issued. It could be that a new "
|
||||
"license is issued with the same `license_id` because it was updated. In that "
|
||||
"case, the one that has been issued last should be used."
|
||||
)
|
||||
issued_to_email = serializers.EmailField(
|
||||
help_text="Indicates to which email address the license has been issued."
|
||||
)
|
||||
issued_to_name = serializers.CharField(
|
||||
help_text="Indicates to whom the license has been issued."
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = License
|
||||
fields = (
|
||||
"id",
|
||||
"license_id",
|
||||
"is_active",
|
||||
"last_check",
|
||||
"valid_from",
|
||||
"valid_through",
|
||||
"seats_taken",
|
||||
"seats",
|
||||
"product_code",
|
||||
"issued_on",
|
||||
"issued_to_email",
|
||||
"issued_to_name",
|
||||
)
|
||||
|
||||
@extend_schema_field(OpenApiTypes.INT)
|
||||
def get_seats_taken(self, obj):
|
||||
return (
|
||||
obj.seats_taken if hasattr(obj, "seats_taken") else obj.users.all().count()
|
||||
)
|
||||
|
||||
|
||||
class RegisterPremiumLicenseSerializer(serializers.Serializer):
|
||||
license = serializers.CharField(help_text="The license that you want to register.")
|
||||
|
||||
|
||||
class PremiumLicenseUserSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = User
|
||||
fields = ("id", "first_name", "email")
|
||||
|
||||
|
||||
class PremiumLicenseWithUsersSerializer(PremiumLicenseSerializer):
|
||||
users = serializers.SerializerMethodField()
|
||||
|
||||
class Meta(PremiumLicenseSerializer.Meta):
|
||||
fields = PremiumLicenseSerializer.Meta.fields + ("users",)
|
||||
|
||||
@extend_schema_field(PremiumLicenseUserSerializer(many=True))
|
||||
def get_users(self, object):
|
||||
users = [user.user for user in object.users.all()]
|
||||
return PremiumLicenseUserSerializer(users, many=True).data
|
||||
|
||||
|
||||
class PremiumLicenseUserLookupSerializer(serializers.ModelSerializer):
|
||||
value = serializers.SerializerMethodField(
|
||||
help_text="The name and the email " "address of the user."
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = User
|
||||
fields = ("id", "value")
|
||||
|
||||
@extend_schema_field(OpenApiTypes.STR)
|
||||
def get_value(self, object):
|
||||
return f"{object.first_name} ({object.email})"
|
44
premium/backend/src/baserow_premium/api/license/urls.py
Normal file
44
premium/backend/src/baserow_premium/api/license/urls.py
Normal file
|
@ -0,0 +1,44 @@
|
|||
from django.conf.urls import re_path
|
||||
|
||||
from .views import (
|
||||
AdminLicensesView,
|
||||
AdminLicenseView,
|
||||
AdminLicenseFillSeatsView,
|
||||
AdminRemoveAllUsersFromLicenseView,
|
||||
AdminLicenseUserView,
|
||||
AdminLicenseLookupUsersView,
|
||||
AdminCheckLicense,
|
||||
)
|
||||
|
||||
|
||||
app_name = "baserow_premium.api.license"
|
||||
|
||||
urlpatterns = [
|
||||
re_path(r"^$", AdminLicensesView.as_view(), name="list"),
|
||||
re_path(r"^(?P<id>[0-9]+)/$", AdminLicenseView.as_view(), name="item"),
|
||||
re_path(
|
||||
r"^(?P<id>[0-9]+)/lookup-users/$",
|
||||
AdminLicenseLookupUsersView.as_view(),
|
||||
name="lookup_users",
|
||||
),
|
||||
re_path(
|
||||
r"^(?P<id>[0-9]+)/fill-seats/$",
|
||||
AdminLicenseFillSeatsView.as_view(),
|
||||
name="fill_seats",
|
||||
),
|
||||
re_path(
|
||||
r"^(?P<id>[0-9]+)/remove-all-users/$",
|
||||
AdminRemoveAllUsersFromLicenseView.as_view(),
|
||||
name="remove_all_users",
|
||||
),
|
||||
re_path(
|
||||
r"^(?P<id>[0-9]+)/check/$",
|
||||
AdminCheckLicense.as_view(),
|
||||
name="check",
|
||||
),
|
||||
re_path(
|
||||
r"^(?P<id>[0-9]+)/(?P<user_id>[0-9]+)/$",
|
||||
AdminLicenseUserView.as_view(),
|
||||
name="user",
|
||||
),
|
||||
]
|
458
premium/backend/src/baserow_premium/api/license/views.py
Normal file
458
premium/backend/src/baserow_premium/api/license/views.py
Normal file
|
@ -0,0 +1,458 @@
|
|||
from django.contrib.auth import get_user_model
|
||||
from django.db.models import Count, Q
|
||||
from django.conf import settings
|
||||
from django.db import transaction
|
||||
|
||||
from drf_spectacular.types import OpenApiTypes
|
||||
from drf_spectacular.utils import extend_schema, OpenApiParameter
|
||||
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.permissions import IsAdminUser
|
||||
from rest_framework.views import APIView
|
||||
|
||||
from baserow.api.schemas import get_error_schema
|
||||
from baserow.api.decorators import validate_body, map_exceptions
|
||||
from baserow.api.user.errors import ERROR_USER_NOT_FOUND
|
||||
from baserow.api.pagination import PageNumberPagination
|
||||
from baserow.api.serializers import get_example_pagination_serializer_class
|
||||
from baserow.core.db import LockedAtomicTransaction
|
||||
|
||||
from baserow_premium.license.models import License
|
||||
from baserow_premium.license.handler import (
|
||||
register_license,
|
||||
remove_license,
|
||||
check_licenses,
|
||||
add_user_to_license,
|
||||
remove_user_from_license,
|
||||
fill_remaining_seats_of_license,
|
||||
remove_all_users_from_license,
|
||||
)
|
||||
from baserow_premium.license.exceptions import (
|
||||
InvalidPremiumLicenseError,
|
||||
UnsupportedPremiumLicenseError,
|
||||
PremiumLicenseInstanceIdMismatchError,
|
||||
PremiumLicenseHasExpired,
|
||||
PremiumLicenseAlreadyExists,
|
||||
UserAlreadyOnPremiumLicenseError,
|
||||
NoSeatsLeftInPremiumLicenseError,
|
||||
)
|
||||
|
||||
from .errors import (
|
||||
ERROR_PREMIUM_LICENSE_DOES_NOT_EXIST,
|
||||
ERROR_INVALID_PREMIUM_LICENSE,
|
||||
ERROR_UNSUPPORTED_PREMIUM_LICENSE,
|
||||
ERROR_PREMIUM_LICENSE_INSTANCE_ID_MISMATCH,
|
||||
ERROR_PREMIUM_LICENSE_HAS_EXPIRED,
|
||||
ERROR_PREMIUM_LICENSE_ALREADY_EXISTS,
|
||||
ERROR_USER_ALREADY_ON_PREMIUM_LICENSE,
|
||||
ERROR_NO_SEATS_LEFT_IN_PREMIUM_LICENSE,
|
||||
)
|
||||
from .serializers import (
|
||||
PremiumLicenseSerializer,
|
||||
RegisterPremiumLicenseSerializer,
|
||||
PremiumLicenseUserSerializer,
|
||||
PremiumLicenseWithUsersSerializer,
|
||||
PremiumLicenseUserLookupSerializer,
|
||||
)
|
||||
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
|
||||
class AdminLicensesView(APIView):
|
||||
permission_classes = (IsAdminUser,)
|
||||
|
||||
@extend_schema(
|
||||
tags=["Admin"],
|
||||
operation_id="admin_premium_licenses",
|
||||
description=(
|
||||
"Lists all the valid licenses that are registered to this instance. A "
|
||||
"premium license can be used to unlock the premium features for a fixed "
|
||||
"amount of users. More information about self hosted licenses can be "
|
||||
"found on our pricing page https://baserow.io/pricing."
|
||||
),
|
||||
responses={
|
||||
200: PremiumLicenseSerializer(many=True),
|
||||
},
|
||||
)
|
||||
def get(self, request):
|
||||
licenses = License.objects.all().annotate(seats_taken=Count("users"))
|
||||
# We will sort the items in memory because we don't have access to the
|
||||
# `is_active` and `valid_from` property after the instance has been
|
||||
# generated. This is because it needs to decode the license. We first want to
|
||||
# show the active licenses because those are more important.
|
||||
licenses = sorted(licenses, key=lambda x: (not x.is_active, x.valid_from))
|
||||
return Response(PremiumLicenseSerializer(licenses, many=True).data)
|
||||
|
||||
@extend_schema(
|
||||
tags=["Admin"],
|
||||
operation_id="admin_register_premium_license",
|
||||
description=(
|
||||
"Registers a new license. After registering you can assign users to the "
|
||||
"license that will be able to use the premium features while the license "
|
||||
"is active. If an existing license with the same `license_id` already "
|
||||
"exists and the provided license has been issued later than that one, "
|
||||
"the existing one will be upgraded."
|
||||
),
|
||||
request=RegisterPremiumLicenseSerializer,
|
||||
responses={
|
||||
200: PremiumLicenseSerializer,
|
||||
400: get_error_schema(
|
||||
[
|
||||
"ERROR_INVALID_PREMIUM_LICENSE",
|
||||
"ERROR_UNSUPPORTED_PREMIUM_LICENSE",
|
||||
"ERROR_PREMIUM_LICENSE_INSTANCE_ID_MISMATCH",
|
||||
"ERROR_PREMIUM_LICENSE_HAS_EXPIRED",
|
||||
"ERROR_PREMIUM_LICENSE_ALREADY_EXISTS",
|
||||
]
|
||||
),
|
||||
},
|
||||
)
|
||||
@validate_body(RegisterPremiumLicenseSerializer)
|
||||
@map_exceptions(
|
||||
{
|
||||
InvalidPremiumLicenseError: ERROR_INVALID_PREMIUM_LICENSE,
|
||||
UnsupportedPremiumLicenseError: ERROR_UNSUPPORTED_PREMIUM_LICENSE,
|
||||
PremiumLicenseInstanceIdMismatchError: (
|
||||
ERROR_PREMIUM_LICENSE_INSTANCE_ID_MISMATCH
|
||||
),
|
||||
PremiumLicenseHasExpired: ERROR_PREMIUM_LICENSE_HAS_EXPIRED,
|
||||
PremiumLicenseAlreadyExists: ERROR_PREMIUM_LICENSE_ALREADY_EXISTS,
|
||||
}
|
||||
)
|
||||
def post(self, request, data):
|
||||
with LockedAtomicTransaction(License):
|
||||
license_object = register_license(request.user, data["license"])
|
||||
return Response(PremiumLicenseSerializer(license_object).data)
|
||||
|
||||
|
||||
class AdminLicenseView(APIView):
|
||||
permission_classes = (IsAdminUser,)
|
||||
|
||||
@extend_schema(
|
||||
parameters=[
|
||||
OpenApiParameter(
|
||||
name="id",
|
||||
location=OpenApiParameter.PATH,
|
||||
type=OpenApiTypes.INT,
|
||||
description="The internal identifier of the license.",
|
||||
),
|
||||
],
|
||||
tags=["Admin"],
|
||||
operation_id="admin_get_premium_license",
|
||||
description=(
|
||||
"Responds with detailed information about the license related to the "
|
||||
"provided parameter."
|
||||
),
|
||||
responses={
|
||||
200: PremiumLicenseWithUsersSerializer,
|
||||
404: get_error_schema(["ERROR_PREMIUM_LICENSE_DOES_NOT_EXIST"]),
|
||||
},
|
||||
)
|
||||
@map_exceptions({License.DoesNotExist: ERROR_PREMIUM_LICENSE_DOES_NOT_EXIST})
|
||||
def get(self, request, id):
|
||||
license = License.objects.prefetch_related("users__user").get(pk=id)
|
||||
return Response(PremiumLicenseWithUsersSerializer(license).data)
|
||||
|
||||
@extend_schema(
|
||||
parameters=[
|
||||
OpenApiParameter(
|
||||
name="id",
|
||||
location=OpenApiParameter.PATH,
|
||||
type=OpenApiTypes.INT,
|
||||
description="The internal identifier of the license, this is `id` and "
|
||||
"not `license_id`.",
|
||||
),
|
||||
],
|
||||
tags=["Admin"],
|
||||
operation_id="admin_remove_premium_license",
|
||||
description=(
|
||||
"Removes the existing license related to the provided parameter. If the "
|
||||
"license is active, then all the users that are using the license will "
|
||||
"lose access to the premium version."
|
||||
),
|
||||
responses={
|
||||
204: None,
|
||||
404: get_error_schema(["ERROR_PREMIUM_LICENSE_DOES_NOT_EXIST"]),
|
||||
},
|
||||
)
|
||||
@map_exceptions({License.DoesNotExist: ERROR_PREMIUM_LICENSE_DOES_NOT_EXIST})
|
||||
@transaction.atomic
|
||||
def delete(self, request, id):
|
||||
license = License.objects.get(pk=id)
|
||||
remove_license(request.user, license)
|
||||
return Response(status=204)
|
||||
|
||||
|
||||
class AdminLicenseUserView(APIView):
|
||||
permission_classes = (IsAdminUser,)
|
||||
|
||||
@extend_schema(
|
||||
parameters=[
|
||||
OpenApiParameter(
|
||||
name="id",
|
||||
location=OpenApiParameter.PATH,
|
||||
type=OpenApiTypes.INT,
|
||||
description="The internal identifier of the license, this is `id` and "
|
||||
"not `license_id`.",
|
||||
),
|
||||
OpenApiParameter(
|
||||
name="user_id",
|
||||
location=OpenApiParameter.PATH,
|
||||
type=OpenApiTypes.INT,
|
||||
description="The ID of the user that must be added to the license.",
|
||||
),
|
||||
],
|
||||
tags=["Admin"],
|
||||
operation_id="admin_add_user_to_premium_license",
|
||||
description=(
|
||||
"Adds the user related to the provided parameter and to the license "
|
||||
"related to the parameter. This only happens if there are enough seats "
|
||||
"left on the license and if the user is not already on the license."
|
||||
),
|
||||
request=None,
|
||||
responses={
|
||||
200: PremiumLicenseUserSerializer,
|
||||
400: get_error_schema(
|
||||
[
|
||||
"ERROR_USER_ALREADY_ON_PREMIUM_LICENSE",
|
||||
"ERROR_NO_SEATS_LEFT_IN_PREMIUM_LICENSE",
|
||||
]
|
||||
),
|
||||
404: get_error_schema(
|
||||
["ERROR_PREMIUM_LICENSE_DOES_NOT_EXIST", "ERROR_USER_NOT_FOUND"]
|
||||
),
|
||||
},
|
||||
)
|
||||
@map_exceptions(
|
||||
{
|
||||
License.DoesNotExist: ERROR_PREMIUM_LICENSE_DOES_NOT_EXIST,
|
||||
User.DoesNotExist: ERROR_USER_NOT_FOUND,
|
||||
UserAlreadyOnPremiumLicenseError: ERROR_USER_ALREADY_ON_PREMIUM_LICENSE,
|
||||
NoSeatsLeftInPremiumLicenseError: ERROR_NO_SEATS_LEFT_IN_PREMIUM_LICENSE,
|
||||
}
|
||||
)
|
||||
@transaction.atomic
|
||||
def post(self, request, id, user_id):
|
||||
license = License.objects.select_for_update().get(pk=id)
|
||||
user = User.objects.get(pk=user_id)
|
||||
add_user_to_license(request.user, license, user)
|
||||
return Response(PremiumLicenseUserSerializer(user).data)
|
||||
|
||||
@extend_schema(
|
||||
parameters=[
|
||||
OpenApiParameter(
|
||||
name="id",
|
||||
location=OpenApiParameter.PATH,
|
||||
type=OpenApiTypes.INT,
|
||||
description="The internal identifier of the license, this is `id` and "
|
||||
"not `license_id`.",
|
||||
),
|
||||
OpenApiParameter(
|
||||
name="user_id",
|
||||
location=OpenApiParameter.PATH,
|
||||
type=OpenApiTypes.INT,
|
||||
description="The ID of the user that must be removed from the license.",
|
||||
),
|
||||
],
|
||||
tags=["Admin"],
|
||||
operation_id="admin_remove_user_from_premium_license",
|
||||
description=(
|
||||
"Removes the user related to the provided parameter and to the license "
|
||||
"related to the parameter. This only happens if the user is on the "
|
||||
"license, otherwise nothing will happen."
|
||||
),
|
||||
request=None,
|
||||
responses={
|
||||
204: None,
|
||||
404: get_error_schema(
|
||||
["ERROR_PREMIUM_LICENSE_DOES_NOT_EXIST", "ERROR_USER_NOT_FOUND"]
|
||||
),
|
||||
},
|
||||
)
|
||||
@map_exceptions(
|
||||
{
|
||||
License.DoesNotExist: ERROR_PREMIUM_LICENSE_DOES_NOT_EXIST,
|
||||
User.DoesNotExist: ERROR_USER_NOT_FOUND,
|
||||
}
|
||||
)
|
||||
@transaction.atomic
|
||||
def delete(self, request, id, user_id):
|
||||
license = License.objects.select_for_update().get(pk=id)
|
||||
user = User.objects.get(pk=user_id)
|
||||
remove_user_from_license(request.user, license, user)
|
||||
return Response(status=204)
|
||||
|
||||
|
||||
class AdminLicenseFillSeatsView(APIView):
|
||||
permission_classes = (IsAdminUser,)
|
||||
|
||||
@extend_schema(
|
||||
parameters=[
|
||||
OpenApiParameter(
|
||||
name="id",
|
||||
location=OpenApiParameter.PATH,
|
||||
type=OpenApiTypes.INT,
|
||||
description="The internal identifier of the license, this is `id` and "
|
||||
"not `license_id`.",
|
||||
),
|
||||
],
|
||||
tags=["Admin"],
|
||||
operation_id="admin_fill_remaining_seats_of_premium_license",
|
||||
description=(
|
||||
"Fills the remaining empty seats of the license with the first users that "
|
||||
"are found."
|
||||
),
|
||||
request=None,
|
||||
responses={
|
||||
200: PremiumLicenseUserSerializer(many=True),
|
||||
404: get_error_schema(["ERROR_PREMIUM_LICENSE_DOES_NOT_EXIST"]),
|
||||
},
|
||||
)
|
||||
@map_exceptions({License.DoesNotExist: ERROR_PREMIUM_LICENSE_DOES_NOT_EXIST})
|
||||
@transaction.atomic
|
||||
def post(self, request, id):
|
||||
license = License.objects.get(pk=id)
|
||||
license_users = fill_remaining_seats_of_license(request.user, license)
|
||||
users = [license_user.user for license_user in license_users]
|
||||
return Response(PremiumLicenseUserSerializer(users, many=True).data)
|
||||
|
||||
|
||||
class AdminRemoveAllUsersFromLicenseView(APIView):
|
||||
permission_classes = (IsAdminUser,)
|
||||
|
||||
@extend_schema(
|
||||
parameters=[
|
||||
OpenApiParameter(
|
||||
name="id",
|
||||
location=OpenApiParameter.PATH,
|
||||
type=OpenApiTypes.INT,
|
||||
description="The internal identifier of the license, this is `id` and "
|
||||
"not `license_id`.",
|
||||
),
|
||||
],
|
||||
tags=["Admin"],
|
||||
operation_id="admin_remove_all_users_from_premium_license",
|
||||
description=(
|
||||
"Removes all the users the users that are on the license. This will "
|
||||
"empty all the seats."
|
||||
),
|
||||
request=None,
|
||||
responses={
|
||||
204: None,
|
||||
404: get_error_schema(["ERROR_PREMIUM_LICENSE_DOES_NOT_EXIST"]),
|
||||
},
|
||||
)
|
||||
@map_exceptions({License.DoesNotExist: ERROR_PREMIUM_LICENSE_DOES_NOT_EXIST})
|
||||
@transaction.atomic
|
||||
def post(self, request, id):
|
||||
license = License.objects.get(pk=id)
|
||||
remove_all_users_from_license(request.user, license)
|
||||
return Response(status=204)
|
||||
|
||||
|
||||
class AdminLicenseLookupUsersView(APIView):
|
||||
permission_classes = (IsAdminUser,)
|
||||
|
||||
@extend_schema(
|
||||
tags=["Admin"],
|
||||
parameters=[
|
||||
OpenApiParameter(
|
||||
name="id",
|
||||
location=OpenApiParameter.PATH,
|
||||
type=OpenApiTypes.INT,
|
||||
description="The internal identifier of the license, this is `id` and "
|
||||
"not `license_id`.",
|
||||
),
|
||||
OpenApiParameter(
|
||||
name="search",
|
||||
location=OpenApiParameter.QUERY,
|
||||
type=OpenApiTypes.STR,
|
||||
description="If provided, only users where the name or email "
|
||||
"contains the value are returned.",
|
||||
),
|
||||
OpenApiParameter(
|
||||
name="page",
|
||||
location=OpenApiParameter.QUERY,
|
||||
type=OpenApiTypes.INT,
|
||||
description="Defines which page of users should be returned.",
|
||||
),
|
||||
OpenApiParameter(
|
||||
name="size",
|
||||
location=OpenApiParameter.QUERY,
|
||||
type=OpenApiTypes.INT,
|
||||
description=f"Defines how many users should be returned per " f"page.",
|
||||
),
|
||||
],
|
||||
operation_id="admin_premium_license_lookup_users",
|
||||
description=(
|
||||
"This endpoint can be used to lookup users that must be added to a "
|
||||
"premium license. Users that are already in the license are not returned "
|
||||
"here. Optionally a `search` query parameter can be provided to filter "
|
||||
"the results."
|
||||
),
|
||||
responses={
|
||||
200: get_example_pagination_serializer_class(
|
||||
PremiumLicenseUserLookupSerializer
|
||||
),
|
||||
404: get_error_schema(["ERROR_PREMIUM_LICENSE_DOES_NOT_EXIST"]),
|
||||
},
|
||||
)
|
||||
@map_exceptions({License.DoesNotExist: ERROR_PREMIUM_LICENSE_DOES_NOT_EXIST})
|
||||
def get(self, request, id):
|
||||
license = License.objects.get(pk=id)
|
||||
search = request.GET.get("search")
|
||||
queryset = User.objects.filter(
|
||||
~Q(id__in=license.users.all().values_list("user_id", flat=True))
|
||||
).order_by("first_name")
|
||||
|
||||
if search:
|
||||
queryset = queryset.filter(
|
||||
Q(first_name__icontains=search) | Q(email__icontains=search)
|
||||
)
|
||||
|
||||
paginator = PageNumberPagination(limit_page_size=settings.ROW_PAGE_SIZE_LIMIT)
|
||||
page = paginator.paginate_queryset(queryset, request, self)
|
||||
serializer = PremiumLicenseUserLookupSerializer(page, many=True)
|
||||
return paginator.get_paginated_response(serializer.data)
|
||||
|
||||
|
||||
class AdminCheckLicense(APIView):
|
||||
permission_classes = (IsAdminUser,)
|
||||
|
||||
@extend_schema(
|
||||
tags=["Admin"],
|
||||
parameters=[
|
||||
OpenApiParameter(
|
||||
name="id",
|
||||
location=OpenApiParameter.PATH,
|
||||
type=OpenApiTypes.INT,
|
||||
description="The internal identifier of the license, this is `id` and "
|
||||
"not `license_id`.",
|
||||
),
|
||||
],
|
||||
operation_id="admin_premium_license_check",
|
||||
description=(
|
||||
"This endpoint checks with the authority if the license needs to be "
|
||||
"updated. It also checks if the license is operating within its limits "
|
||||
"and might take action on that. It could also happen that the license has "
|
||||
"been deleted because there is an instance id mismatch or because it's "
|
||||
"invalid. In that case a `204` status code is returned."
|
||||
),
|
||||
responses={
|
||||
200: PremiumLicenseWithUsersSerializer,
|
||||
404: get_error_schema(["ERROR_PREMIUM_LICENSE_DOES_NOT_EXIST"]),
|
||||
},
|
||||
)
|
||||
@map_exceptions({License.DoesNotExist: ERROR_PREMIUM_LICENSE_DOES_NOT_EXIST})
|
||||
def get(self, request, id):
|
||||
license_object = License.objects.get(pk=id)
|
||||
updated_licenses = check_licenses([license_object])
|
||||
if not updated_licenses[0].pk:
|
||||
# If the primary key is None, it means that the license has been deleted
|
||||
# which could happen when checking the license. In that case, we want to
|
||||
# respond with a 204 (empty response) respond to indicate that it was
|
||||
# deleted.
|
||||
return Response(status=204)
|
||||
else:
|
||||
return Response(PremiumLicenseWithUsersSerializer(updated_licenses[0]).data)
|
|
@ -1,4 +1,5 @@
|
|||
from django.conf import settings
|
||||
from django.db import transaction
|
||||
from drf_spectacular.types import OpenApiTypes
|
||||
from drf_spectacular.utils import extend_schema, OpenApiParameter
|
||||
from rest_framework.pagination import LimitOffsetPagination
|
||||
|
@ -16,6 +17,8 @@ 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
|
||||
|
||||
|
||||
|
@ -85,6 +88,8 @@ 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:
|
||||
|
@ -139,7 +144,10 @@ 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"]
|
||||
)
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
from django.urls import path, include
|
||||
|
||||
from .license import urls as license_urls
|
||||
from .admin import urls as admin_urls
|
||||
from .row_comments import urls as row_comments_urls
|
||||
|
||||
|
||||
app_name = "baserow_premium.api"
|
||||
|
||||
urlpatterns = [
|
||||
path("licenses/", include(license_urls, namespace="license")),
|
||||
path("admin/", include(admin_urls, namespace="admin")),
|
||||
path("row_comments/", include(row_comments_urls, namespace="row_comments")),
|
||||
]
|
||||
|
|
0
premium/backend/src/baserow_premium/api/user/__init__.py
Normal file
0
premium/backend/src/baserow_premium/api/user/__init__.py
Normal file
|
@ -0,0 +1,15 @@
|
|||
from baserow.api.user.registries import UserDataType
|
||||
|
||||
from baserow_premium.license.handler import has_active_premium_license
|
||||
|
||||
|
||||
class PremiumUserDataType(UserDataType):
|
||||
type = "premium"
|
||||
|
||||
def get_user_data(self, user, request) -> dict:
|
||||
"""
|
||||
Someone who authenticates via the API should know beforehand if the related
|
||||
user has a valid license for the premioum version.
|
||||
"""
|
||||
|
||||
return {"valid_license": has_active_premium_license(user)}
|
|
@ -6,12 +6,14 @@ class BaserowPremiumConfig(AppConfig):
|
|||
|
||||
def ready(self):
|
||||
from baserow.core.registries import plugin_registry
|
||||
from baserow.api.user.registries import user_data_registry
|
||||
from baserow.contrib.database.export.registries import table_exporter_registry
|
||||
from baserow.contrib.database.rows.registries import row_metadata_registry
|
||||
|
||||
from baserow_premium.row_comments.row_metadata_types import (
|
||||
RowCommentCountMetadataType,
|
||||
)
|
||||
from baserow_premium.api.user.user_data_types import PremiumUserDataType
|
||||
|
||||
# noinspection PyUnresolvedReferences
|
||||
import baserow_premium.row_comments.recievers # noqa: F401
|
||||
|
@ -26,6 +28,8 @@ class BaserowPremiumConfig(AppConfig):
|
|||
|
||||
row_metadata_registry.register(RowCommentCountMetadataType())
|
||||
|
||||
user_data_registry.register(PremiumUserDataType())
|
||||
|
||||
# The signals must always be imported last because they use the registries
|
||||
# which need to be filled first.
|
||||
import baserow_premium.ws.signals # noqa: F403, F401
|
||||
|
|
0
premium/backend/src/baserow_premium/export/__init__.py
Normal file
0
premium/backend/src/baserow_premium/export/__init__.py
Normal file
|
@ -12,9 +12,21 @@ 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 .utils import get_unique_name, safe_xml_tag_name, to_xml
|
||||
|
||||
|
||||
class PremiumTableExporter(TableExporter):
|
||||
def before_job_create(self, user, table, view, export_options):
|
||||
"""
|
||||
Checks if the related user access to a valid license before the job is created.
|
||||
"""
|
||||
|
||||
check_active_premium_license(user)
|
||||
super().before_job_create(user, table, view, export_options)
|
||||
|
||||
|
||||
class JSONQuerysetSerializer(QuerysetSerializer):
|
||||
def write_to_file(self, file_writer: FileWriter, export_charset="utf-8"):
|
||||
"""
|
||||
|
@ -49,7 +61,7 @@ class JSONQuerysetSerializer(QuerysetSerializer):
|
|||
file_writer.write("\n]\n", encoding=export_charset)
|
||||
|
||||
|
||||
class JSONTableExporter(TableExporter):
|
||||
class JSONTableExporter(PremiumTableExporter):
|
||||
type = "json"
|
||||
|
||||
@property
|
||||
|
@ -112,7 +124,7 @@ class XMLQuerysetSerializer(QuerysetSerializer):
|
|||
file_writer.write("</rows>\n", encoding=export_charset)
|
||||
|
||||
|
||||
class XMLTableExporter(TableExporter):
|
||||
class XMLTableExporter(PremiumTableExporter):
|
||||
type = "xml"
|
||||
|
||||
@property
|
||||
|
|
0
premium/backend/src/baserow_premium/license/__init__.py
Normal file
0
premium/backend/src/baserow_premium/license/__init__.py
Normal file
5
premium/backend/src/baserow_premium/license/constants.py
Normal file
5
premium/backend/src/baserow_premium/license/constants.py
Normal file
|
@ -0,0 +1,5 @@
|
|||
AUTHORITY_RESPONSE_OK = "ok"
|
||||
AUTHORITY_RESPONSE_UPDATE = "update"
|
||||
AUTHORITY_RESPONSE_DOES_NOT_EXIST = "does_not_exist"
|
||||
AUTHORITY_RESPONSE_INSTANCE_ID_MISMATCH = "instance_id_mismatch"
|
||||
AUTHORITY_RESPONSE_INVALID = "invalid"
|
61
premium/backend/src/baserow_premium/license/exceptions.py
Normal file
61
premium/backend/src/baserow_premium/license/exceptions.py
Normal file
|
@ -0,0 +1,61 @@
|
|||
from rest_framework.status import HTTP_402_PAYMENT_REQUIRED
|
||||
from rest_framework.exceptions import APIException
|
||||
|
||||
|
||||
class NoPremiumLicenseError(APIException):
|
||||
"""
|
||||
Raised when the related user does not have an active license for the premium
|
||||
version.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
{
|
||||
"error": "ERROR_NO_ACTIVE_PREMIUM_LICENSE",
|
||||
"detail": "The related user does not have access to the premium "
|
||||
"version.",
|
||||
},
|
||||
code=HTTP_402_PAYMENT_REQUIRED,
|
||||
)
|
||||
self.status_code = HTTP_402_PAYMENT_REQUIRED
|
||||
|
||||
|
||||
class InvalidPremiumLicenseError(Exception):
|
||||
"""
|
||||
Raised when a provided premium license is not valid. This could be because the
|
||||
signature is incorrect or the payload does not contain the required information.
|
||||
"""
|
||||
|
||||
|
||||
class UnsupportedPremiumLicenseError(Exception):
|
||||
"""
|
||||
Raised when the version of the license is not supported. This probably means that
|
||||
Baserow must be upgraded.
|
||||
"""
|
||||
|
||||
|
||||
class PremiumLicenseInstanceIdMismatchError(Exception):
|
||||
"""
|
||||
Raised when trying to register a license and the instance ids of the license and
|
||||
self hosted copy don't match.
|
||||
"""
|
||||
|
||||
|
||||
class PremiumLicenseAlreadyExists(Exception):
|
||||
"""Raised when trying to register a license that already exists."""
|
||||
|
||||
|
||||
class PremiumLicenseHasExpired(Exception):
|
||||
"""Raised when trying to register a license that is expired."""
|
||||
|
||||
|
||||
class UserAlreadyOnPremiumLicenseError(Exception):
|
||||
"""Raised when the user already has a seat in the license."""
|
||||
|
||||
|
||||
class NoSeatsLeftInPremiumLicenseError(Exception):
|
||||
"""Raised when there are no seats left in the license."""
|
||||
|
||||
|
||||
class LicenseAuthorityUnavailable(Exception):
|
||||
"""Raised when the license authority can't be reached."""
|
542
premium/backend/src/baserow_premium/license/handler.py
Normal file
542
premium/backend/src/baserow_premium/license/handler.py
Normal file
|
@ -0,0 +1,542 @@
|
|||
import base64
|
||||
import binascii
|
||||
import hashlib
|
||||
import json
|
||||
import logging
|
||||
from typing import Union, List
|
||||
from os.path import dirname, join
|
||||
from dateutil import parser
|
||||
|
||||
import requests
|
||||
from requests.exceptions import RequestException
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.auth.models import User as DjangoUser
|
||||
from django.utils.timezone import now, make_aware, utc
|
||||
from django.db import transaction
|
||||
from django.db.models import Q
|
||||
|
||||
from cryptography.exceptions import InvalidSignature
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
from cryptography.hazmat.primitives import serialization, hashes
|
||||
from cryptography.hazmat.primitives.asymmetric import padding
|
||||
|
||||
from rest_framework.status import HTTP_200_OK
|
||||
|
||||
from baserow.core.exceptions import IsNotAdminError
|
||||
from baserow.core.handler import CoreHandler
|
||||
from baserow.ws.signals import broadcast_to_users
|
||||
|
||||
from .models import License, LicenseUser
|
||||
from .exceptions import (
|
||||
NoPremiumLicenseError,
|
||||
InvalidPremiumLicenseError,
|
||||
UnsupportedPremiumLicenseError,
|
||||
PremiumLicenseInstanceIdMismatchError,
|
||||
PremiumLicenseAlreadyExists,
|
||||
PremiumLicenseHasExpired,
|
||||
UserAlreadyOnPremiumLicenseError,
|
||||
NoSeatsLeftInPremiumLicenseError,
|
||||
LicenseAuthorityUnavailable,
|
||||
)
|
||||
from .constants import (
|
||||
AUTHORITY_RESPONSE_UPDATE,
|
||||
AUTHORITY_RESPONSE_DOES_NOT_EXIST,
|
||||
AUTHORITY_RESPONSE_INSTANCE_ID_MISMATCH,
|
||||
AUTHORITY_RESPONSE_INVALID,
|
||||
)
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
User = get_user_model()
|
||||
|
||||
|
||||
def has_active_premium_license(user: DjangoUser) -> bool:
|
||||
"""
|
||||
Checks if the provided user has an active license.
|
||||
|
||||
:param user: The user for whom must be checked if it has an active license.
|
||||
:return: True if the user has an active license to the version.
|
||||
"""
|
||||
|
||||
available_licenses = License.objects.filter(users__user_id__in=[user.id]).distinct()
|
||||
|
||||
for available_license in available_licenses:
|
||||
try:
|
||||
if (
|
||||
available_license.product_code == "premium"
|
||||
and available_license.is_active
|
||||
):
|
||||
return True
|
||||
except InvalidPremiumLicenseError:
|
||||
pass
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def check_active_premium_license(user):
|
||||
"""
|
||||
Raises the `NoPremiumLicenseError` if the user does not have an active premium
|
||||
license.
|
||||
"""
|
||||
|
||||
if not has_active_premium_license(user):
|
||||
raise NoPremiumLicenseError()
|
||||
|
||||
|
||||
def get_public_key():
|
||||
"""
|
||||
Returns the public key instance that can be used to verify licenses. A different
|
||||
key file is loaded when Baserow is in debug mode.
|
||||
"""
|
||||
|
||||
import baserow_premium
|
||||
|
||||
file_name = "public_key_debug.pem" if settings.DEBUG else "public_key.pem"
|
||||
public_key_path = join(dirname(baserow_premium.__file__), file_name)
|
||||
with open(public_key_path, "rb") as key_file:
|
||||
public_key = serialization.load_pem_public_key(
|
||||
key_file.read(), backend=default_backend()
|
||||
)
|
||||
return public_key
|
||||
|
||||
|
||||
def decode_license(license_payload: bytes) -> dict:
|
||||
"""
|
||||
Tries to decode the provided license and returns the payload if successful.
|
||||
|
||||
:param license_payload: The raw license that must be decoded.
|
||||
:raises InvalidPremiumLicenseError: When the provided license is invalid. This
|
||||
could for example be when the signature or payload is invalid.
|
||||
:raises UnsupportedPremiumLicenseError: When the provided license payload is an
|
||||
unsupported version. If this happens, you probably need to update your Baserow
|
||||
installation.
|
||||
:return: If successful, the decoded license payload is returned.
|
||||
"""
|
||||
|
||||
try:
|
||||
payload_base64, signature_base64 = license_payload.split(b".")
|
||||
except ValueError:
|
||||
raise InvalidPremiumLicenseError(
|
||||
"The provided payload does not follow the expected format."
|
||||
)
|
||||
|
||||
pre_hashed = hashlib.sha256(payload_base64).hexdigest().encode()
|
||||
|
||||
try:
|
||||
signature = base64.urlsafe_b64decode(signature_base64)
|
||||
except binascii.Error:
|
||||
raise InvalidPremiumLicenseError("Invalid base64 signature provided.")
|
||||
|
||||
public_key = get_public_key()
|
||||
|
||||
try:
|
||||
public_key.verify(
|
||||
signature,
|
||||
pre_hashed,
|
||||
padding.PSS(
|
||||
mgf=padding.MGF1(hashes.SHA256()), salt_length=padding.PSS.MAX_LENGTH
|
||||
),
|
||||
hashes.SHA256(),
|
||||
)
|
||||
except InvalidSignature:
|
||||
raise InvalidPremiumLicenseError(
|
||||
"The signature of the premium license is invalid."
|
||||
)
|
||||
|
||||
try:
|
||||
payload_json = base64.urlsafe_b64decode(payload_base64)
|
||||
except binascii.Error:
|
||||
raise InvalidPremiumLicenseError("Invalid base64 payload provided.")
|
||||
|
||||
try:
|
||||
payload = json.loads(payload_json)
|
||||
except json.decoder.JSONDecodeError:
|
||||
raise InvalidPremiumLicenseError("Invalid JSON payload provided.")
|
||||
|
||||
if "version" not in payload:
|
||||
raise InvalidPremiumLicenseError("The payload does not contain a version.")
|
||||
|
||||
if payload["version"] != 1:
|
||||
raise UnsupportedPremiumLicenseError(
|
||||
"Only license version 1 is supported. You probably need to update your "
|
||||
"copy of Baserow."
|
||||
)
|
||||
|
||||
return payload
|
||||
|
||||
|
||||
def fetch_license_status_with_authority(license_payloads: List[Union[str, bytes]]):
|
||||
"""
|
||||
Fetches the state of the license with the authority. It could be that the license
|
||||
must be updated because it has changed, it might have been deleted, the instance_id
|
||||
might not match anymore or it might be invalid.
|
||||
|
||||
:param license_payloads: A list of licenses that must be checked with the authority.
|
||||
:return: The state of each license provided.
|
||||
"""
|
||||
|
||||
license_payloads = [
|
||||
payload if isinstance(payload, str) else payload.decode()
|
||||
for payload in license_payloads
|
||||
]
|
||||
|
||||
settings_object = CoreHandler().get_settings()
|
||||
|
||||
try:
|
||||
base_url = (
|
||||
"http://172.17.0.1:8001" if settings.DEBUG else "https://api.baserow.io"
|
||||
)
|
||||
response = requests.post(
|
||||
f"{base_url}/api/saas/licenses/check/",
|
||||
json={
|
||||
"licenses": license_payloads,
|
||||
"instance_id": settings_object.instance_id,
|
||||
},
|
||||
timeout=10,
|
||||
)
|
||||
|
||||
if response.status_code == HTTP_200_OK:
|
||||
return response.json()
|
||||
else:
|
||||
raise LicenseAuthorityUnavailable(
|
||||
"The license authority can't be reached because it didn't returned "
|
||||
"with an ok response."
|
||||
)
|
||||
except RequestException:
|
||||
raise LicenseAuthorityUnavailable(
|
||||
"The license authority can't be reached because of a network error."
|
||||
)
|
||||
except json.decoder.JSONDecodeError:
|
||||
raise LicenseAuthorityUnavailable(
|
||||
"The license authority did not respond with valid json."
|
||||
)
|
||||
|
||||
|
||||
def check_licenses(license_objects: List[License]) -> List[License]:
|
||||
"""
|
||||
Checks the state of the licenses with the authority and checks if the licenses
|
||||
are operating within their limits.
|
||||
|
||||
- It will update the license payload if needed.
|
||||
- It removes the license if it doesn't exist, if it's invalid or if the instance
|
||||
id doesn't match.
|
||||
- It also checks if the license is operating within its limit. For example if the
|
||||
license has not crossed the maximum amount of seats.
|
||||
|
||||
:param license_objects: The license objects that must be checked
|
||||
:return: The updated license objects.
|
||||
"""
|
||||
|
||||
licenses_to_check = [license_object.license for license_object in license_objects]
|
||||
|
||||
try:
|
||||
authority_response = fetch_license_status_with_authority(licenses_to_check)
|
||||
|
||||
for license_object in license_objects:
|
||||
if license_object.license not in authority_response:
|
||||
continue
|
||||
|
||||
authority_check = authority_response[license_object.license]
|
||||
|
||||
if authority_check["type"] == AUTHORITY_RESPONSE_UPDATE:
|
||||
license_object.license = authority_check["new_license_payload"]
|
||||
license_object.save()
|
||||
elif authority_check["type"] in [
|
||||
AUTHORITY_RESPONSE_DOES_NOT_EXIST,
|
||||
AUTHORITY_RESPONSE_INSTANCE_ID_MISMATCH,
|
||||
AUTHORITY_RESPONSE_INVALID,
|
||||
]:
|
||||
license_object.delete()
|
||||
|
||||
except LicenseAuthorityUnavailable as e:
|
||||
# If the license authority is unavailable for whatever reason, we don't want
|
||||
# the check to fail because the self hosted instance might not have
|
||||
# internet and the license is already validated locally.
|
||||
logger.warning(str(e))
|
||||
|
||||
for license_object in license_objects:
|
||||
# If the license object has been deleted we can skip it.
|
||||
if not license_object.pk:
|
||||
continue
|
||||
|
||||
# If the license payload could not be decoded, it must be deleted.
|
||||
try:
|
||||
license_object.payload
|
||||
except InvalidPremiumLicenseError:
|
||||
license_object.delete()
|
||||
continue
|
||||
|
||||
seats_taken = license_object.users.all().count()
|
||||
if seats_taken > license_object.seats:
|
||||
# If there are more seats taken than the license allows, we need to
|
||||
# remove the active seats that are outside of the limit.
|
||||
LicenseUser.objects.filter(
|
||||
pk__in=license_object.users.all()
|
||||
.order_by("pk")
|
||||
.values_list("pk")[license_object.seats : seats_taken]
|
||||
).delete()
|
||||
|
||||
license_object.last_check = now()
|
||||
license_object.save()
|
||||
|
||||
return license_objects
|
||||
|
||||
|
||||
def register_license(
|
||||
requesting_user: User, license_payload: Union[bytes, str]
|
||||
) -> License:
|
||||
"""
|
||||
Registers a new license by adding it to the database. If a license with same id
|
||||
already exists and the provided one was issued later, then the existing one will
|
||||
be updated.
|
||||
|
||||
:param requesting_user: The user on whose behalf the license is registered.
|
||||
:param license_payload: The license that must be decoded and added.
|
||||
:raises PremiumLicenseAlreadyExists: When the license already exists.
|
||||
:raises PremiumLicenseHasExpired: When the license has expired.
|
||||
:return: The created license instance.
|
||||
"""
|
||||
|
||||
if not requesting_user.is_staff:
|
||||
raise IsNotAdminError()
|
||||
|
||||
if isinstance(license_payload, str):
|
||||
license_payload_as_string = license_payload
|
||||
license_payload = license_payload.encode()
|
||||
else:
|
||||
license_payload_as_string = license_payload.decode()
|
||||
|
||||
try:
|
||||
authority_response = fetch_license_status_with_authority(
|
||||
[license_payload_as_string]
|
||||
)
|
||||
authority_check = authority_response[license_payload_as_string]
|
||||
|
||||
if authority_check["type"] == AUTHORITY_RESPONSE_UPDATE:
|
||||
# If there is a newer version of the license, we can replace the license
|
||||
# payload that we have in memory with that one.
|
||||
license_payload_as_string = authority_check["new_license_payload"]
|
||||
license_payload = license_payload_as_string.encode()
|
||||
elif authority_check["type"] == AUTHORITY_RESPONSE_DOES_NOT_EXIST:
|
||||
# If the authority tells us that the license does not exist there,
|
||||
# we must stop the registering.
|
||||
raise InvalidPremiumLicenseError(
|
||||
"The license does not exist according to the authority."
|
||||
)
|
||||
elif authority_check["type"] == AUTHORITY_RESPONSE_INSTANCE_ID_MISMATCH:
|
||||
# If the authority tells us the instance id doesn't match,
|
||||
# we can immediately raise that error.
|
||||
raise PremiumLicenseInstanceIdMismatchError(
|
||||
"The instance id doesn't match according to the authority."
|
||||
)
|
||||
elif authority_check["type"] == AUTHORITY_RESPONSE_INVALID:
|
||||
raise InvalidPremiumLicenseError(
|
||||
"The license is invalid according to the authority."
|
||||
)
|
||||
|
||||
except LicenseAuthorityUnavailable as e:
|
||||
# If the license authority is unavailable for whatever reason, we don't want
|
||||
# the registering to fail because the self hosted instance might not have
|
||||
# internet and the license can be validated locally with the public key.
|
||||
logger.warning(str(e))
|
||||
|
||||
# Try to decode the provided license payload in order to trigger the errors if
|
||||
# needed. We also need the `valid_through` date to check if the license has expired.
|
||||
decoded_license_payload = decode_license(license_payload)
|
||||
valid_through = make_aware(
|
||||
parser.parse(decoded_license_payload["valid_through"]), utc
|
||||
)
|
||||
issued_on = make_aware(parser.parse(decoded_license_payload["issued_on"]), utc)
|
||||
|
||||
if valid_through < now():
|
||||
raise PremiumLicenseHasExpired(
|
||||
"Cannot add the license because it has already expired."
|
||||
)
|
||||
|
||||
# The `instance_id` of the license must match with the `instance_id` of the self
|
||||
# hosted copy.
|
||||
settings_object = CoreHandler().get_settings()
|
||||
if decoded_license_payload["instance_id"] != settings_object.instance_id:
|
||||
raise PremiumLicenseInstanceIdMismatchError(
|
||||
"The license instance id does not match the instance id."
|
||||
)
|
||||
|
||||
# Loop over all licenses to check if a license with the same ID already exists. We
|
||||
# can't use `objects.filter` because we need to decode the license with the
|
||||
# public key before we can extract the id.
|
||||
for license_object in License.objects.all():
|
||||
if license_object.license_id == decoded_license_payload["id"]:
|
||||
# If the `issued_on` date of the existing license is lower then the new
|
||||
# license, we want to update it because a new one has been issued later
|
||||
# and is newer.
|
||||
if license_object.issued_on < issued_on:
|
||||
license_object.license = license_payload_as_string
|
||||
license_object.save()
|
||||
return license_object
|
||||
# If the `issued_on` date of the existing license is higher or equal to
|
||||
# the new license, we want to raise the exception that the most license
|
||||
# already exists.
|
||||
else:
|
||||
raise PremiumLicenseAlreadyExists("The license already exists.")
|
||||
|
||||
# If the license doesn't exist we want to create a new one.
|
||||
return License.objects.create(license=license_payload_as_string)
|
||||
|
||||
|
||||
def remove_license(requesting_user: User, license: License):
|
||||
"""
|
||||
Removes an existing license. If the license is still active, all the users that
|
||||
are on that license will lose access to the premium features.
|
||||
|
||||
:param requesting_user: The user on whose behalf the license is removed.
|
||||
:param license: The license that must be removed.
|
||||
"""
|
||||
|
||||
if not requesting_user.is_staff:
|
||||
raise IsNotAdminError()
|
||||
|
||||
license.delete()
|
||||
|
||||
|
||||
def add_user_to_license(
|
||||
requesting_user: User, license_object: License, user: User
|
||||
) -> LicenseUser:
|
||||
"""
|
||||
Adds a user to the provided license.
|
||||
|
||||
:param requesting_user: The user on whose behalf the user is added to the license.
|
||||
:param license_object: The license that the user must be added to.
|
||||
:param user: The user that must be added to the license.
|
||||
:raises UserAlreadyInPremiumLicenseError: When the user already has a seat in the
|
||||
license.
|
||||
:raises NoSeatsLeftInPremiumLicenseError: When the license doesn't have any seats
|
||||
left.
|
||||
:return: The newly created license user object.
|
||||
"""
|
||||
|
||||
if not requesting_user.is_staff:
|
||||
raise IsNotAdminError()
|
||||
|
||||
if LicenseUser.objects.filter(license=license_object, user=user).exists():
|
||||
raise UserAlreadyOnPremiumLicenseError(
|
||||
"The user already has a seat on this license."
|
||||
)
|
||||
|
||||
seats_taken = license_object.users.all().count()
|
||||
if seats_taken >= license_object.seats:
|
||||
raise NoSeatsLeftInPremiumLicenseError(
|
||||
"There aren't any seats left in the license."
|
||||
)
|
||||
|
||||
if license_object.is_active:
|
||||
transaction.on_commit(
|
||||
lambda: broadcast_to_users.delay(
|
||||
[user.id],
|
||||
{
|
||||
"type": "user_data_updated",
|
||||
"user_data": {"premium": {"valid_license": True}},
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
return LicenseUser.objects.create(license=license_object, user=user)
|
||||
|
||||
|
||||
def remove_user_from_license(
|
||||
requesting_user: User, license_object: License, user: User
|
||||
):
|
||||
"""
|
||||
Removes the provided user from the provided license if the user has a seat.
|
||||
|
||||
:param requesting_user: The user on whose behalf the user is removed from the
|
||||
license.
|
||||
:param license_object: The license object where the user must be removed from.
|
||||
:param user: The user that must be removed from the license.
|
||||
"""
|
||||
|
||||
if not requesting_user.is_staff:
|
||||
raise IsNotAdminError()
|
||||
|
||||
LicenseUser.objects.filter(license=license_object, user=user).delete()
|
||||
|
||||
if license_object.is_active:
|
||||
transaction.on_commit(
|
||||
lambda: broadcast_to_users.delay(
|
||||
[user.id],
|
||||
{
|
||||
"type": "user_data_updated",
|
||||
"user_data": {"premium": {"valid_license": False}},
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def fill_remaining_seats_of_license(
|
||||
requesting_user: User, license_object: License
|
||||
) -> List[LicenseUser]:
|
||||
"""
|
||||
Fills the remaining seats of the license with additional users.
|
||||
|
||||
:param requesting_user: The user on whose behalf the request is made.
|
||||
:param license_object: The license object where the users must be added to.
|
||||
:return: A list of created license users.
|
||||
"""
|
||||
|
||||
if not requesting_user.is_staff:
|
||||
raise IsNotAdminError()
|
||||
|
||||
already_in_license = license_object.users.all().values_list("user_id", flat=True)
|
||||
remaining_seats = license_object.seats - len(already_in_license)
|
||||
|
||||
if remaining_seats > 0:
|
||||
users_to_add = User.objects.filter(~Q(id__in=already_in_license)).order_by(
|
||||
"id"
|
||||
)[:remaining_seats]
|
||||
user_licenses = [
|
||||
LicenseUser(license=license_object, user=user) for user in users_to_add
|
||||
]
|
||||
LicenseUser.objects.bulk_create(user_licenses)
|
||||
|
||||
if license_object.is_active:
|
||||
transaction.on_commit(
|
||||
lambda: broadcast_to_users.delay(
|
||||
[user_license.user_id for user_license in user_licenses],
|
||||
{
|
||||
"type": "user_data_updated",
|
||||
"user_data": {"premium": {"valid_license": True}},
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
return user_licenses
|
||||
|
||||
return []
|
||||
|
||||
|
||||
def remove_all_users_from_license(requesting_user: User, license_object: License):
|
||||
"""
|
||||
Removed all the users from a license. This will clear up all the seats.
|
||||
|
||||
:param requesting_user: The user on whose behalf the users are removed.
|
||||
:param license_object: The license object where the users must be removed from.
|
||||
"""
|
||||
|
||||
if not requesting_user.is_staff:
|
||||
raise IsNotAdminError()
|
||||
|
||||
license_users = LicenseUser.objects.filter(license=license_object)
|
||||
license_user_ids = list(license_users.values_list("user_id", flat=True))
|
||||
license_users.delete()
|
||||
|
||||
if license_object.is_active:
|
||||
transaction.on_commit(
|
||||
lambda: broadcast_to_users.delay(
|
||||
license_user_ids,
|
||||
{
|
||||
"type": "user_data_updated",
|
||||
"user_data": {"premium": {"valid_license": False}},
|
||||
},
|
||||
)
|
||||
)
|
74
premium/backend/src/baserow_premium/license/models.py
Normal file
74
premium/backend/src/baserow_premium/license/models.py
Normal file
|
@ -0,0 +1,74 @@
|
|||
from dateutil import parser
|
||||
|
||||
from django.db import models
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.utils.timezone import utc, now, make_aware
|
||||
from django.utils.functional import cached_property
|
||||
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
|
||||
class License(models.Model):
|
||||
license = models.TextField()
|
||||
last_check = models.DateTimeField(null=True)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
try:
|
||||
del self.payload
|
||||
except AttributeError:
|
||||
pass
|
||||
return super().save(*args, **kwargs)
|
||||
|
||||
@cached_property
|
||||
def payload(self):
|
||||
from .handler import decode_license
|
||||
|
||||
premium_license = self.license
|
||||
if isinstance(premium_license, str):
|
||||
premium_license = premium_license.encode()
|
||||
return decode_license(premium_license)
|
||||
|
||||
@property
|
||||
def license_id(self):
|
||||
return self.payload["id"]
|
||||
|
||||
@property
|
||||
def valid_from(self):
|
||||
return make_aware(parser.parse(self.payload["valid_from"]), utc)
|
||||
|
||||
@property
|
||||
def valid_through(self):
|
||||
return make_aware(parser.parse(self.payload["valid_through"]), utc)
|
||||
|
||||
@property
|
||||
def is_active(self):
|
||||
return self.valid_from <= now() <= self.valid_through
|
||||
|
||||
@property
|
||||
def product_code(self):
|
||||
return self.payload["product_code"]
|
||||
|
||||
@property
|
||||
def seats(self):
|
||||
return self.payload["seats"]
|
||||
|
||||
@property
|
||||
def issued_on(self):
|
||||
return make_aware(parser.parse(self.payload["issued_on"]), utc)
|
||||
|
||||
@property
|
||||
def issued_to_email(self):
|
||||
return self.payload["issued_to_email"]
|
||||
|
||||
@property
|
||||
def issued_to_name(self):
|
||||
return self.payload["issued_to_name"]
|
||||
|
||||
|
||||
class LicenseUser(models.Model):
|
||||
license = models.ForeignKey(License, on_delete=models.CASCADE, related_name="users")
|
||||
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||
|
||||
class Meta:
|
||||
unique_together = ("license", "user")
|
27
premium/backend/src/baserow_premium/license/tasks.py
Normal file
27
premium/backend/src/baserow_premium/license/tasks.py
Normal file
|
@ -0,0 +1,27 @@
|
|||
from datetime import timedelta
|
||||
|
||||
from django.db import transaction
|
||||
|
||||
from baserow.config.celery import app
|
||||
|
||||
|
||||
@app.task(bind=True, queue="export")
|
||||
def license_check(self):
|
||||
"""
|
||||
Periodic tasks that check all the licenses with the authority.
|
||||
"""
|
||||
|
||||
from .handler import check_licenses
|
||||
from .models import License
|
||||
|
||||
all_licenses = License.objects.all()
|
||||
|
||||
if len(all_licenses) > 0:
|
||||
with transaction.atomic():
|
||||
check_licenses(all_licenses)
|
||||
|
||||
|
||||
# noinspection PyUnusedLocal
|
||||
@app.on_after_finalize.connect
|
||||
def setup_periodic_tasks(sender, **kwargs):
|
||||
sender.add_periodic_task(timedelta(hours=1), license_check.s())
|
|
@ -0,0 +1,64 @@
|
|||
# Generated by Django 3.2.6 on 2021-10-12 19:13
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
("baserow_premium", "0001_row_comments"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="License",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("license", models.TextField()),
|
||||
("last_check", models.DateTimeField(null=True)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="LicenseUser",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
(
|
||||
"license",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="users",
|
||||
to="baserow_premium.license",
|
||||
),
|
||||
),
|
||||
(
|
||||
"user",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"unique_together": {("license", "user")},
|
||||
},
|
||||
),
|
||||
]
|
5
premium/backend/src/baserow_premium/models.py
Normal file
5
premium/backend/src/baserow_premium/models.py
Normal file
|
@ -0,0 +1,5 @@
|
|||
from .license.models import License, LicenseUser
|
||||
from .row_comments.models import RowComment
|
||||
|
||||
|
||||
__all__ = ["License", "LicenseUser", "RowComment"]
|
9
premium/backend/src/baserow_premium/public_key.pem
Normal file
9
premium/backend/src/baserow_premium/public_key.pem
Normal file
|
@ -0,0 +1,9 @@
|
|||
-----BEGIN PUBLIC KEY-----
|
||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArolpEK7fTTeaM0JiLb/G
|
||||
eDGneylpJBoKcHpQi0FPqh1bgLagGMtylFYlzz8ZyabI81cJ9VN40WrgbwuMyk8q
|
||||
OUoA9e26TcZ5N33Xjao/iksWrNbQf6Q/gwYM6uP1rJggzqH4Si1hmv96+OPRCuPG
|
||||
4KltzmrGazI4HPi6vdZ2fPuFaqYbUo6Ygddk7Y9Grd3HUb80r5FanotesVe5xeta
|
||||
GLT1UT8eIt/nfhFGd2LIVbgYG8gpa4ZnJo8IWr48+AMIVeFjZgdvOSaabSrvlTfb
|
||||
Fu70047ZpUmghlSBLL/O9xyRO2fIrRsccV7pfgeOLV6qxcb2ASWnQRCaGprGot5u
|
||||
DQIDAQAB
|
||||
-----END PUBLIC KEY-----
|
9
premium/backend/src/baserow_premium/public_key_debug.pem
Normal file
9
premium/backend/src/baserow_premium/public_key_debug.pem
Normal file
|
@ -0,0 +1,9 @@
|
|||
-----BEGIN PUBLIC KEY-----
|
||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu9/6ZS7rvJvxkCrJB+Y+
|
||||
E2oGIOUVju3hyueB+Gs3Fy0GFtklt8IvB/qatgMyzYjxGUzQLHu8kkEUmNEztgQD
|
||||
jny1Yy/kVjBRBRDps4kaw29z4QgGqiS83vTN2iQ0u5w3Ok23+VxGzbFrRLUyYuWW
|
||||
pkMz0SC3FnuAZzpWg9pgyqpiS+s6gVCgcVOuN9+/MDNkCkaLmW59COp8hHPAVJgp
|
||||
IhZTxzY7/ESNLBgVYwDoj7gyNHNuwiAxdAs6WpxN69YAEGgmMt9aEA75pdJzOWsD
|
||||
YgooCo7Ptyl5BsSsIYYPW4ZFPdzElpj704dOO8flPDxhHNpnMEWoucZ2BQikJWmL
|
||||
BQIDAQAB
|
||||
-----END PUBLIC KEY-----
|
|
@ -6,6 +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
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
|
@ -53,6 +54,8 @@ class RowCommentHandler:
|
|||
:raises InvalidRowCommentException: If the comment is blank or None.
|
||||
"""
|
||||
|
||||
check_active_premium_license(requesting_user)
|
||||
|
||||
if comment is None or comment == "":
|
||||
raise InvalidRowCommentException()
|
||||
|
||||
|
|
3
premium/backend/src/baserow_premium/tasks.py
Normal file
3
premium/backend/src/baserow_premium/tasks.py
Normal file
|
@ -0,0 +1,3 @@
|
|||
from .license.tasks import license_check, setup_periodic_tasks
|
||||
|
||||
__all__ = ["license_check", "setup_periodic_tasks"]
|
0
premium/backend/tests/baserow_premium/__init__.py
Normal file
0
premium/backend/tests/baserow_premium/__init__.py
Normal file
|
@ -3,31 +3,38 @@ from pytz import timezone
|
|||
|
||||
from datetime import timedelta, datetime, date
|
||||
|
||||
from django.test.utils import override_settings
|
||||
|
||||
from baserow.core.models import UserLogEntry
|
||||
|
||||
from baserow_premium.admin.dashboard.handler import AdminDashboardHandler
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_get_new_user_counts(data_fixture):
|
||||
@override_settings(DEBUG=True)
|
||||
def test_get_new_user_counts(premium_data_fixture):
|
||||
tz = timezone("UTC")
|
||||
|
||||
data_fixture.create_user(date_joined=datetime(2020, 12, 30, tzinfo=tz))
|
||||
data_fixture.create_user(date_joined=datetime(2021, 1, 1, tzinfo=tz))
|
||||
data_fixture.create_user(date_joined=datetime(2021, 1, 2, tzinfo=tz))
|
||||
data_fixture.create_user(date_joined=datetime(2021, 1, 3, tzinfo=tz))
|
||||
data_fixture.create_user(date_joined=datetime(2021, 1, 4, tzinfo=tz))
|
||||
data_fixture.create_user(date_joined=datetime(2021, 1, 5, tzinfo=tz))
|
||||
data_fixture.create_user(date_joined=datetime(2021, 1, 10, tzinfo=tz))
|
||||
data_fixture.create_user(date_joined=datetime(2021, 1, 23, tzinfo=tz))
|
||||
data_fixture.create_user(date_joined=datetime(2021, 1, 24, tzinfo=tz))
|
||||
data_fixture.create_user(date_joined=datetime(2021, 1, 25, tzinfo=tz))
|
||||
data_fixture.create_user(date_joined=datetime(2021, 1, 26, tzinfo=tz))
|
||||
data_fixture.create_user(date_joined=datetime(2021, 1, 27, tzinfo=tz))
|
||||
data_fixture.create_user(date_joined=datetime(2021, 1, 28, tzinfo=tz))
|
||||
data_fixture.create_user(date_joined=datetime(2021, 1, 29, tzinfo=tz))
|
||||
data_fixture.create_user(date_joined=datetime(2021, 1, 30, 12, 1, tzinfo=tz))
|
||||
data_fixture.create_user(date_joined=datetime(2021, 1, 30, 15, 1, tzinfo=tz))
|
||||
premium_data_fixture.create_user(date_joined=datetime(2020, 12, 30, tzinfo=tz))
|
||||
premium_data_fixture.create_user(date_joined=datetime(2021, 1, 1, tzinfo=tz))
|
||||
premium_data_fixture.create_user(date_joined=datetime(2021, 1, 2, tzinfo=tz))
|
||||
premium_data_fixture.create_user(date_joined=datetime(2021, 1, 3, tzinfo=tz))
|
||||
premium_data_fixture.create_user(date_joined=datetime(2021, 1, 4, tzinfo=tz))
|
||||
premium_data_fixture.create_user(date_joined=datetime(2021, 1, 5, tzinfo=tz))
|
||||
premium_data_fixture.create_user(date_joined=datetime(2021, 1, 10, tzinfo=tz))
|
||||
premium_data_fixture.create_user(date_joined=datetime(2021, 1, 23, tzinfo=tz))
|
||||
premium_data_fixture.create_user(date_joined=datetime(2021, 1, 24, tzinfo=tz))
|
||||
premium_data_fixture.create_user(date_joined=datetime(2021, 1, 25, tzinfo=tz))
|
||||
premium_data_fixture.create_user(date_joined=datetime(2021, 1, 26, tzinfo=tz))
|
||||
premium_data_fixture.create_user(date_joined=datetime(2021, 1, 27, tzinfo=tz))
|
||||
premium_data_fixture.create_user(date_joined=datetime(2021, 1, 28, tzinfo=tz))
|
||||
premium_data_fixture.create_user(date_joined=datetime(2021, 1, 29, tzinfo=tz))
|
||||
premium_data_fixture.create_user(
|
||||
date_joined=datetime(2021, 1, 30, 12, 1, tzinfo=tz)
|
||||
)
|
||||
premium_data_fixture.create_user(
|
||||
date_joined=datetime(2021, 1, 30, 15, 1, tzinfo=tz)
|
||||
)
|
||||
|
||||
handler = AdminDashboardHandler()
|
||||
now = datetime(2021, 1, 30, 23, 59, tzinfo=tz)
|
||||
|
@ -67,12 +74,13 @@ def test_get_new_user_counts(data_fixture):
|
|||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_get_active_user_counts(data_fixture):
|
||||
@override_settings(DEBUG=True)
|
||||
def test_get_active_user_counts(premium_data_fixture):
|
||||
tz = timezone("UTC")
|
||||
|
||||
user_1 = data_fixture.create_user()
|
||||
user_2 = data_fixture.create_user()
|
||||
user_3 = data_fixture.create_user()
|
||||
user_1 = premium_data_fixture.create_user()
|
||||
user_2 = premium_data_fixture.create_user()
|
||||
user_3 = premium_data_fixture.create_user()
|
||||
|
||||
def create_entries(user, dates):
|
||||
for d in dates:
|
||||
|
@ -184,18 +192,33 @@ def test_get_active_user_counts(data_fixture):
|
|||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_get_new_users_per_day(data_fixture):
|
||||
@override_settings(DEBUG=True)
|
||||
def test_get_new_users_per_day(premium_data_fixture):
|
||||
utc = timezone("UTC")
|
||||
gmt3 = timezone("Etc/GMT+3")
|
||||
|
||||
data_fixture.create_user(date_joined=datetime(2020, 12, 29, 12, 1, tzinfo=utc))
|
||||
data_fixture.create_user(date_joined=datetime(2021, 1, 1, 1, 1, tzinfo=utc))
|
||||
data_fixture.create_user(date_joined=datetime(2021, 1, 1, 12, 1, tzinfo=utc))
|
||||
data_fixture.create_user(date_joined=datetime(2021, 1, 2, 12, 1, tzinfo=utc))
|
||||
data_fixture.create_user(date_joined=datetime(2021, 1, 2, 12, 1, tzinfo=utc))
|
||||
data_fixture.create_user(date_joined=datetime(2021, 1, 2, 12, 1, tzinfo=utc))
|
||||
data_fixture.create_user(date_joined=datetime(2021, 1, 30, 12, 1, tzinfo=utc))
|
||||
data_fixture.create_user(date_joined=datetime(2021, 1, 30, 15, 1, tzinfo=utc))
|
||||
premium_data_fixture.create_user(
|
||||
date_joined=datetime(2020, 12, 29, 12, 1, tzinfo=utc)
|
||||
)
|
||||
premium_data_fixture.create_user(date_joined=datetime(2021, 1, 1, 1, 1, tzinfo=utc))
|
||||
premium_data_fixture.create_user(
|
||||
date_joined=datetime(2021, 1, 1, 12, 1, tzinfo=utc)
|
||||
)
|
||||
premium_data_fixture.create_user(
|
||||
date_joined=datetime(2021, 1, 2, 12, 1, tzinfo=utc)
|
||||
)
|
||||
premium_data_fixture.create_user(
|
||||
date_joined=datetime(2021, 1, 2, 12, 1, tzinfo=utc)
|
||||
)
|
||||
premium_data_fixture.create_user(
|
||||
date_joined=datetime(2021, 1, 2, 12, 1, tzinfo=utc)
|
||||
)
|
||||
premium_data_fixture.create_user(
|
||||
date_joined=datetime(2021, 1, 30, 12, 1, tzinfo=utc)
|
||||
)
|
||||
premium_data_fixture.create_user(
|
||||
date_joined=datetime(2021, 1, 30, 15, 1, tzinfo=utc)
|
||||
)
|
||||
|
||||
handler = AdminDashboardHandler()
|
||||
|
||||
|
@ -225,13 +248,14 @@ def test_get_new_users_per_day(data_fixture):
|
|||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_get_active_users_per_day(data_fixture):
|
||||
@override_settings(DEBUG=True)
|
||||
def test_get_active_users_per_day(premium_data_fixture):
|
||||
utc = timezone("UTC")
|
||||
gmt3 = timezone("Etc/GMT+3")
|
||||
|
||||
user_1 = data_fixture.create_user()
|
||||
user_2 = data_fixture.create_user()
|
||||
user_3 = data_fixture.create_user()
|
||||
user_1 = premium_data_fixture.create_user()
|
||||
user_2 = premium_data_fixture.create_user()
|
||||
user_3 = premium_data_fixture.create_user()
|
||||
|
||||
def create_entries(user, dates):
|
||||
for d in dates:
|
||||
|
|
|
@ -2,6 +2,7 @@ from unittest.mock import patch
|
|||
|
||||
import pytest
|
||||
from django.db import connection
|
||||
from django.test.utils import override_settings
|
||||
|
||||
from baserow.contrib.database.models import Database, Table
|
||||
from baserow.core.exceptions import IsNotAdminError
|
||||
|
@ -11,24 +12,30 @@ from baserow.core.models import (
|
|||
)
|
||||
from baserow_premium.admin.groups.exceptions import CannotDeleteATemplateGroupError
|
||||
from baserow_premium.admin.groups.handler import GroupsAdminHandler
|
||||
from baserow_premium.license.exceptions import NoPremiumLicenseError
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@override_settings(DEBUG=True)
|
||||
@patch("baserow.core.signals.group_deleted.send")
|
||||
def test_delete_group(send_mock, data_fixture):
|
||||
staff_user = data_fixture.create_user(is_staff=True)
|
||||
normal_user = data_fixture.create_user(is_staff=False)
|
||||
other_user = data_fixture.create_user()
|
||||
group_1 = data_fixture.create_group(user=other_user)
|
||||
database = data_fixture.create_database_application(group=group_1)
|
||||
table = data_fixture.create_database_table(database=database)
|
||||
def test_delete_group(send_mock, premium_data_fixture):
|
||||
staff_user_with_premium_license = premium_data_fixture.create_user(
|
||||
is_staff=True, has_active_premium_license=True
|
||||
)
|
||||
normal_user = premium_data_fixture.create_user(
|
||||
is_staff=False, has_active_premium_license=True
|
||||
)
|
||||
other_user = premium_data_fixture.create_user()
|
||||
group_1 = premium_data_fixture.create_group(user=other_user)
|
||||
database = premium_data_fixture.create_database_application(group=group_1)
|
||||
table = premium_data_fixture.create_database_table(database=database)
|
||||
|
||||
handler = GroupsAdminHandler()
|
||||
|
||||
with pytest.raises(IsNotAdminError):
|
||||
handler.delete_group(normal_user, group_1)
|
||||
|
||||
handler.delete_group(staff_user, group_1)
|
||||
handler.delete_group(staff_user_with_premium_license, group_1)
|
||||
|
||||
send_mock.assert_called_once()
|
||||
assert send_mock.call_args[1]["group"].id == group_1.id
|
||||
|
@ -44,14 +51,17 @@ def test_delete_group(send_mock, data_fixture):
|
|||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@override_settings(DEBUG=True)
|
||||
@patch("baserow.core.signals.group_deleted.send")
|
||||
def test_cant_delete_template_group(send_mock, data_fixture):
|
||||
staff_user = data_fixture.create_user(is_staff=True)
|
||||
group_1 = data_fixture.create_group(user=staff_user)
|
||||
database = data_fixture.create_database_application(group=group_1)
|
||||
data_fixture.create_database_table(database=database)
|
||||
def test_cant_delete_template_group(send_mock, premium_data_fixture):
|
||||
staff_user = premium_data_fixture.create_user(
|
||||
is_staff=True, has_active_premium_license=True
|
||||
)
|
||||
group_1 = premium_data_fixture.create_group(user=staff_user)
|
||||
database = premium_data_fixture.create_database_application(group=group_1)
|
||||
premium_data_fixture.create_database_table(database=database)
|
||||
|
||||
data_fixture.create_template(group=group_1)
|
||||
premium_data_fixture.create_template(group=group_1)
|
||||
|
||||
handler = GroupsAdminHandler()
|
||||
|
||||
|
@ -60,3 +70,20 @@ def test_cant_delete_template_group(send_mock, data_fixture):
|
|||
|
||||
send_mock.assert_not_called()
|
||||
assert Group.objects.all().count() == 1
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@override_settings(DEBUG=True)
|
||||
@patch("baserow.core.signals.group_deleted.send")
|
||||
def test_delete_group_without_premium_license(send_mock, premium_data_fixture):
|
||||
staff_user = premium_data_fixture.create_user(is_staff=True)
|
||||
other_user = premium_data_fixture.create_user()
|
||||
group_1 = premium_data_fixture.create_group(user=other_user)
|
||||
|
||||
handler = GroupsAdminHandler()
|
||||
|
||||
with pytest.raises(NoPremiumLicenseError):
|
||||
handler.delete_group(staff_user, group_1)
|
||||
|
||||
send_mock.assert_not_called()
|
||||
assert Group.objects.all().count() == 1
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import pytest
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.test.utils import override_settings
|
||||
|
||||
from baserow.core.exceptions import IsNotAdminError
|
||||
from baserow.core.user.exceptions import PasswordDoesNotMatchValidation
|
||||
|
@ -11,6 +12,7 @@ from baserow_premium.admin.users.exceptions import (
|
|||
from baserow_premium.admin.users.handler import (
|
||||
UserAdminHandler,
|
||||
)
|
||||
from baserow_premium.license.exceptions import NoPremiumLicenseError
|
||||
|
||||
User = get_user_model()
|
||||
invalid_passwords = [
|
||||
|
@ -32,52 +34,74 @@ invalid_passwords = [
|
|||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_admin_can_delete_user(data_fixture):
|
||||
@override_settings(DEBUG=True)
|
||||
def test_admin_can_delete_user(premium_data_fixture):
|
||||
handler = UserAdminHandler()
|
||||
admin_user = data_fixture.create_user(
|
||||
admin_user = premium_data_fixture.create_user(
|
||||
email="test@test.nl",
|
||||
password="password",
|
||||
first_name="Test1",
|
||||
is_staff=True,
|
||||
has_active_premium_license=True,
|
||||
)
|
||||
user_to_delete = data_fixture.create_user(
|
||||
user_to_delete = premium_data_fixture.create_user(
|
||||
email="delete_me@test.nl",
|
||||
password="password",
|
||||
first_name="Test1",
|
||||
is_staff=True,
|
||||
has_active_premium_license=True,
|
||||
)
|
||||
handler.delete_user(admin_user, user_to_delete.id)
|
||||
assert not User.objects.filter(id=user_to_delete.id).exists()
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_non_admin_cant_delete_user(data_fixture):
|
||||
@override_settings(DEBUG=True)
|
||||
def test_non_admin_cant_delete_user(premium_data_fixture):
|
||||
handler = UserAdminHandler()
|
||||
non_admin_user = data_fixture.create_user(
|
||||
non_admin_user = premium_data_fixture.create_user(
|
||||
email="test@test.nl",
|
||||
password="password",
|
||||
first_name="Test1",
|
||||
is_staff=False,
|
||||
has_active_premium_license=True,
|
||||
)
|
||||
with pytest.raises(IsNotAdminError):
|
||||
handler.delete_user(non_admin_user, non_admin_user.id)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_admin_can_modify_allowed_user_attributes(data_fixture):
|
||||
@override_settings(DEBUG=True)
|
||||
def test_admin_delete_user_without_premium_license(premium_data_fixture):
|
||||
handler = UserAdminHandler()
|
||||
admin_user = data_fixture.create_user(
|
||||
admin_user = premium_data_fixture.create_user(
|
||||
email="test@test.nl",
|
||||
password="password",
|
||||
first_name="Test1",
|
||||
is_staff=True,
|
||||
)
|
||||
user_to_modify = data_fixture.create_user(
|
||||
with pytest.raises(NoPremiumLicenseError):
|
||||
handler.delete_user(admin_user, admin_user.id)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@override_settings(DEBUG=True)
|
||||
def test_admin_can_modify_allowed_user_attributes(premium_data_fixture):
|
||||
handler = UserAdminHandler()
|
||||
admin_user = premium_data_fixture.create_user(
|
||||
email="test@test.nl",
|
||||
password="password",
|
||||
first_name="Test1",
|
||||
is_staff=True,
|
||||
has_active_premium_license=True,
|
||||
)
|
||||
user_to_modify = premium_data_fixture.create_user(
|
||||
email="delete_me@test.nl",
|
||||
password="password",
|
||||
first_name="Test1",
|
||||
is_staff=False,
|
||||
is_active=False,
|
||||
has_active_premium_license=True,
|
||||
)
|
||||
old_password = user_to_modify.password
|
||||
handler.update_user(
|
||||
|
@ -101,25 +125,29 @@ def test_admin_can_modify_allowed_user_attributes(data_fixture):
|
|||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_admin_can_deactive_and_unstaff_other_users(data_fixture):
|
||||
@override_settings(DEBUG=True)
|
||||
def test_admin_can_deactive_and_unstaff_other_users(premium_data_fixture):
|
||||
handler = UserAdminHandler()
|
||||
admin_user = data_fixture.create_user(
|
||||
admin_user = premium_data_fixture.create_user(
|
||||
email="test@test.nl",
|
||||
password="password",
|
||||
first_name="Test1",
|
||||
is_staff=True,
|
||||
has_active_premium_license=True,
|
||||
)
|
||||
staff_user = data_fixture.create_user(
|
||||
staff_user = premium_data_fixture.create_user(
|
||||
email="staff@test.nl",
|
||||
password="password",
|
||||
first_name="Test1",
|
||||
is_staff=True,
|
||||
has_active_premium_license=True,
|
||||
)
|
||||
active_user = data_fixture.create_user(
|
||||
active_user = premium_data_fixture.create_user(
|
||||
email="active@test.nl",
|
||||
password="password",
|
||||
first_name="Test1",
|
||||
is_active=True,
|
||||
has_active_premium_license=True,
|
||||
)
|
||||
|
||||
handler.update_user(
|
||||
|
@ -140,22 +168,25 @@ def test_admin_can_deactive_and_unstaff_other_users(data_fixture):
|
|||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@override_settings(DEBUG=True)
|
||||
def test_updating_a_users_password_uses_djangos_built_in_smart_set_password(
|
||||
data_fixture, mocker
|
||||
premium_data_fixture, mocker
|
||||
):
|
||||
handler = UserAdminHandler()
|
||||
admin_user = data_fixture.create_user(
|
||||
admin_user = premium_data_fixture.create_user(
|
||||
email="test@test.nl",
|
||||
password="password",
|
||||
first_name="Test1",
|
||||
is_staff=True,
|
||||
has_active_premium_license=True,
|
||||
)
|
||||
user_to_modify = data_fixture.create_user(
|
||||
user_to_modify = premium_data_fixture.create_user(
|
||||
email="delete_me@test.nl",
|
||||
password="password",
|
||||
first_name="Test1",
|
||||
is_staff=False,
|
||||
is_active=False,
|
||||
has_active_premium_license=True,
|
||||
)
|
||||
old_password_hash = user_to_modify.password
|
||||
set_password_spy = mocker.spy(User, "set_password")
|
||||
|
@ -170,25 +201,28 @@ def test_updating_a_users_password_uses_djangos_built_in_smart_set_password(
|
|||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@override_settings(DEBUG=True)
|
||||
@pytest.mark.parametrize("invalid_password", invalid_passwords)
|
||||
def test_updating_a_users_password_with_invalid_password_raises_error(
|
||||
data_fixture, invalid_password
|
||||
premium_data_fixture, invalid_password
|
||||
):
|
||||
handler = UserAdminHandler()
|
||||
valid_password = "thisIsAValidPassword"
|
||||
|
||||
admin_user = data_fixture.create_user(
|
||||
admin_user = premium_data_fixture.create_user(
|
||||
email="test@test.nl",
|
||||
password=valid_password,
|
||||
first_name="Test1",
|
||||
is_staff=True,
|
||||
has_active_premium_license=True,
|
||||
)
|
||||
user_to_modify = data_fixture.create_user(
|
||||
user_to_modify = premium_data_fixture.create_user(
|
||||
email="delete_me@test.nl",
|
||||
password=valid_password,
|
||||
first_name="Test1",
|
||||
is_staff=False,
|
||||
is_active=False,
|
||||
has_active_premium_license=True,
|
||||
)
|
||||
|
||||
with pytest.raises(PasswordDoesNotMatchValidation):
|
||||
|
@ -202,13 +236,15 @@ def test_updating_a_users_password_with_invalid_password_raises_error(
|
|||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_non_admin_cant_edit_user(data_fixture):
|
||||
@override_settings(DEBUG=True)
|
||||
def test_non_admin_cant_edit_user(premium_data_fixture):
|
||||
handler = UserAdminHandler()
|
||||
non_admin_user = data_fixture.create_user(
|
||||
non_admin_user = premium_data_fixture.create_user(
|
||||
email="test@test.nl",
|
||||
password="password",
|
||||
first_name="Test1",
|
||||
is_staff=False,
|
||||
has_active_premium_license=True,
|
||||
)
|
||||
with pytest.raises(IsNotAdminError):
|
||||
handler.update_user(non_admin_user, non_admin_user.id, "new_email@example.com")
|
||||
|
@ -217,14 +253,16 @@ def test_non_admin_cant_edit_user(data_fixture):
|
|||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_admin_cant_deactivate_themselves(data_fixture):
|
||||
@override_settings(DEBUG=True)
|
||||
def test_admin_cant_deactivate_themselves(premium_data_fixture):
|
||||
handler = UserAdminHandler()
|
||||
admin_user = data_fixture.create_user(
|
||||
admin_user = premium_data_fixture.create_user(
|
||||
email="test@test.nl",
|
||||
password="password",
|
||||
first_name="Test1",
|
||||
is_staff=True,
|
||||
is_active=True,
|
||||
has_active_premium_license=True,
|
||||
)
|
||||
with pytest.raises(CannotDeactivateYourselfException):
|
||||
handler.update_user(
|
||||
|
@ -237,14 +275,16 @@ def test_admin_cant_deactivate_themselves(data_fixture):
|
|||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_admin_cant_destaff_themselves(data_fixture):
|
||||
@override_settings(DEBUG=True)
|
||||
def test_admin_cant_destaff_themselves(premium_data_fixture):
|
||||
handler = UserAdminHandler()
|
||||
admin_user = data_fixture.create_user(
|
||||
admin_user = premium_data_fixture.create_user(
|
||||
email="test@test.nl",
|
||||
password="password",
|
||||
first_name="Test1",
|
||||
is_staff=True,
|
||||
is_active=True,
|
||||
has_active_premium_license=True,
|
||||
)
|
||||
with pytest.raises(CannotDeactivateYourselfException):
|
||||
handler.update_user(
|
||||
|
@ -257,14 +297,30 @@ def test_admin_cant_destaff_themselves(data_fixture):
|
|||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_admin_cant_delete_themselves(data_fixture):
|
||||
@override_settings(DEBUG=True)
|
||||
def test_admin_update_user_without_premium_license(premium_data_fixture):
|
||||
handler = UserAdminHandler()
|
||||
admin_user = data_fixture.create_user(
|
||||
admin_user = premium_data_fixture.create_user(
|
||||
email="test@test.nl",
|
||||
password="password",
|
||||
first_name="Test1",
|
||||
is_staff=True,
|
||||
)
|
||||
with pytest.raises(NoPremiumLicenseError):
|
||||
handler.update_user(admin_user, admin_user.id)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@override_settings(DEBUG=True)
|
||||
def test_admin_cant_delete_themselves(premium_data_fixture):
|
||||
handler = UserAdminHandler()
|
||||
admin_user = premium_data_fixture.create_user(
|
||||
email="test@test.nl",
|
||||
password="password",
|
||||
first_name="Test1",
|
||||
is_staff=True,
|
||||
is_active=True,
|
||||
has_active_premium_license=True,
|
||||
)
|
||||
with pytest.raises(CannotDeleteYourselfException):
|
||||
handler.delete_user(admin_user, admin_user.id)
|
||||
|
@ -273,28 +329,32 @@ def test_admin_cant_delete_themselves(data_fixture):
|
|||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_raises_exception_when_deleting_an_unknown_user(data_fixture):
|
||||
@override_settings(DEBUG=True)
|
||||
def test_raises_exception_when_deleting_an_unknown_user(premium_data_fixture):
|
||||
handler = UserAdminHandler()
|
||||
admin_user = data_fixture.create_user(
|
||||
admin_user = premium_data_fixture.create_user(
|
||||
email="test@test.nl",
|
||||
password="password",
|
||||
first_name="Test1",
|
||||
is_staff=True,
|
||||
is_active=True,
|
||||
has_active_premium_license=True,
|
||||
)
|
||||
with pytest.raises(UserDoesNotExistException):
|
||||
handler.delete_user(admin_user, 99999)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_raises_exception_when_updating_an_unknown_user(data_fixture):
|
||||
@override_settings(DEBUG=True)
|
||||
def test_raises_exception_when_updating_an_unknown_user(premium_data_fixture):
|
||||
handler = UserAdminHandler()
|
||||
admin_user = data_fixture.create_user(
|
||||
admin_user = premium_data_fixture.create_user(
|
||||
email="test@test.nl",
|
||||
password="password",
|
||||
first_name="Test1",
|
||||
is_staff=True,
|
||||
is_active=True,
|
||||
has_active_premium_license=True,
|
||||
)
|
||||
with pytest.raises(UserDoesNotExistException):
|
||||
handler.update_user(admin_user, 99999, username="new_password")
|
||||
|
|
0
premium/backend/tests/baserow_premium/api/__init__.py
Normal file
0
premium/backend/tests/baserow_premium/api/__init__.py
Normal file
|
@ -2,9 +2,11 @@ import pytest
|
|||
from freezegun import freeze_time
|
||||
|
||||
from django.shortcuts import reverse
|
||||
from django.test.utils import override_settings
|
||||
|
||||
from rest_framework.status import (
|
||||
HTTP_200_OK,
|
||||
HTTP_402_PAYMENT_REQUIRED,
|
||||
HTTP_403_FORBIDDEN,
|
||||
)
|
||||
|
||||
|
@ -12,16 +14,17 @@ from baserow.core.models import UserLogEntry
|
|||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_admin_dashboard(api_client, data_fixture):
|
||||
@override_settings(DEBUG=True)
|
||||
def test_admin_dashboard(api_client, premium_data_fixture):
|
||||
with freeze_time("2020-01-01 00:01"):
|
||||
normal_user, normal_token = data_fixture.create_user_and_token(
|
||||
is_staff=False,
|
||||
normal_user, normal_token = premium_data_fixture.create_user_and_token(
|
||||
is_staff=False, has_active_premium_license=True
|
||||
)
|
||||
admin_user, admin_token = data_fixture.create_user_and_token(
|
||||
is_staff=True,
|
||||
admin_user, admin_token = premium_data_fixture.create_user_and_token(
|
||||
is_staff=True, has_active_premium_license=True
|
||||
)
|
||||
|
||||
data_fixture.create_database_application(user=normal_user)
|
||||
premium_data_fixture.create_database_application(user=normal_user)
|
||||
UserLogEntry.objects.create(actor=admin_user, action="SIGNED_IN")
|
||||
|
||||
response = api_client.get(
|
||||
|
@ -83,3 +86,13 @@ def test_admin_dashboard(api_client, data_fixture):
|
|||
"new_users_per_day": [{"date": "2019-12-31", "count": 2}],
|
||||
"active_users_per_day": [{"date": "2019-12-31", "count": 1}],
|
||||
}
|
||||
|
||||
premium_data_fixture.remove_all_active_premium_licenses(admin_user)
|
||||
url = reverse("api:premium:admin:dashboard:dashboard")
|
||||
response = api_client.get(
|
||||
url,
|
||||
format="json",
|
||||
HTTP_AUTHORIZATION=f"JWT {admin_token}",
|
||||
)
|
||||
assert response.status_code == HTTP_402_PAYMENT_REQUIRED
|
||||
assert response.json()["error"] == "ERROR_NO_ACTIVE_PREMIUM_LICENSE"
|
||||
|
|
|
@ -2,9 +2,11 @@ import pytest
|
|||
|
||||
from django.utils.timezone import make_aware, datetime, utc
|
||||
from django.shortcuts import reverse
|
||||
from django.test.utils import override_settings
|
||||
|
||||
from rest_framework.status import (
|
||||
HTTP_200_OK,
|
||||
HTTP_402_PAYMENT_REQUIRED,
|
||||
HTTP_403_FORBIDDEN,
|
||||
HTTP_404_NOT_FOUND,
|
||||
HTTP_400_BAD_REQUEST,
|
||||
|
@ -14,7 +16,8 @@ from baserow.core.models import Group
|
|||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_list_admin_groups(api_client, data_fixture, django_assert_num_queries):
|
||||
@override_settings(DEBUG=True)
|
||||
def test_list_admin_groups(api_client, premium_data_fixture, django_assert_num_queries):
|
||||
"""
|
||||
This endpoint doesn't need to be tested extensively because it uses the same base
|
||||
class as the list users endpoint which already has extensive tests. We only need to
|
||||
|
@ -22,22 +25,30 @@ def test_list_admin_groups(api_client, data_fixture, django_assert_num_queries):
|
|||
"""
|
||||
|
||||
created = make_aware(datetime(2020, 4, 10, 0, 0, 0), utc)
|
||||
staff_user, staff_token = data_fixture.create_user_and_token(is_staff=True)
|
||||
normal_user, normal_token = data_fixture.create_user_and_token()
|
||||
group_1 = data_fixture.create_group(name="A")
|
||||
staff_user, staff_token = premium_data_fixture.create_user_and_token(
|
||||
is_staff=True, has_active_premium_license=True
|
||||
)
|
||||
normal_user, normal_token = premium_data_fixture.create_user_and_token(
|
||||
has_active_premium_license=True
|
||||
)
|
||||
group_1 = premium_data_fixture.create_group(name="A")
|
||||
group_1.created_on = created
|
||||
group_1.save()
|
||||
group_2 = data_fixture.create_group(name="B", created_on=created)
|
||||
group_2 = premium_data_fixture.create_group(name="B", created_on=created)
|
||||
group_2.created_on = created
|
||||
group_2.save()
|
||||
template_group = data_fixture.create_group(name="Template", created_on=created)
|
||||
data_fixture.create_template(group=template_group)
|
||||
data_fixture.create_user_group(
|
||||
template_group = premium_data_fixture.create_group(
|
||||
name="Template", created_on=created
|
||||
)
|
||||
premium_data_fixture.create_template(group=template_group)
|
||||
premium_data_fixture.create_user_group(
|
||||
group=group_1, user=normal_user, permissions="MEMBER"
|
||||
)
|
||||
data_fixture.create_user_group(group=group_2, user=normal_user, permissions="ADMIN")
|
||||
data_fixture.create_database_application(group=group_1)
|
||||
data_fixture.create_database_application(group=group_1)
|
||||
premium_data_fixture.create_user_group(
|
||||
group=group_2, user=normal_user, permissions="ADMIN"
|
||||
)
|
||||
premium_data_fixture.create_database_application(group=group_1)
|
||||
premium_data_fixture.create_database_application(group=group_1)
|
||||
|
||||
response = api_client.get(
|
||||
reverse("api:premium:admin:groups:list"),
|
||||
|
@ -46,7 +57,7 @@ def test_list_admin_groups(api_client, data_fixture, django_assert_num_queries):
|
|||
)
|
||||
assert response.status_code == HTTP_403_FORBIDDEN
|
||||
|
||||
with django_assert_num_queries(5):
|
||||
with django_assert_num_queries(6):
|
||||
response = api_client.get(
|
||||
reverse("api:premium:admin:groups:list"),
|
||||
format="json",
|
||||
|
@ -158,12 +169,26 @@ def test_list_admin_groups(api_client, data_fixture, django_assert_num_queries):
|
|||
],
|
||||
}
|
||||
|
||||
premium_data_fixture.remove_all_active_premium_licenses(staff_user)
|
||||
response = api_client.get(
|
||||
f'{reverse("api:premium:admin:groups:list")}',
|
||||
format="json",
|
||||
HTTP_AUTHORIZATION=f"JWT {staff_token}",
|
||||
)
|
||||
assert response.status_code == HTTP_402_PAYMENT_REQUIRED
|
||||
assert response.json()["error"] == "ERROR_NO_ACTIVE_PREMIUM_LICENSE"
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_delete_group(api_client, data_fixture):
|
||||
normal_user, normal_token = data_fixture.create_user_and_token()
|
||||
staff_user, staff_token = data_fixture.create_user_and_token(is_staff=True)
|
||||
group = data_fixture.create_group()
|
||||
@override_settings(DEBUG=True)
|
||||
def test_delete_group(api_client, premium_data_fixture):
|
||||
normal_user, normal_token = premium_data_fixture.create_user_and_token(
|
||||
has_active_premium_license=True
|
||||
)
|
||||
staff_user, staff_token = premium_data_fixture.create_user_and_token(
|
||||
is_staff=True, has_active_premium_license=True
|
||||
)
|
||||
group = premium_data_fixture.create_group()
|
||||
|
||||
url = reverse("api:premium:admin:groups:edit", kwargs={"group_id": 99999})
|
||||
response = api_client.delete(url, HTTP_AUTHORIZATION=f"JWT {staff_token}")
|
||||
|
@ -179,12 +204,22 @@ def test_delete_group(api_client, data_fixture):
|
|||
assert response.status_code == 204
|
||||
assert Group.objects.all().count() == 0
|
||||
|
||||
premium_data_fixture.remove_all_active_premium_licenses(staff_user)
|
||||
group = premium_data_fixture.create_group()
|
||||
url = reverse("api:premium:admin:groups:edit", kwargs={"group_id": group.id})
|
||||
response = api_client.delete(url, HTTP_AUTHORIZATION=f"JWT {staff_token}")
|
||||
assert response.status_code == HTTP_402_PAYMENT_REQUIRED
|
||||
assert response.json()["error"] == "ERROR_NO_ACTIVE_PREMIUM_LICENSE"
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_cant_delete_template_group(api_client, data_fixture):
|
||||
staff_user, staff_token = data_fixture.create_user_and_token(is_staff=True)
|
||||
group = data_fixture.create_group()
|
||||
data_fixture.create_template(group=group)
|
||||
@override_settings(DEBUG=True)
|
||||
def test_cant_delete_template_group(api_client, premium_data_fixture):
|
||||
staff_user, staff_token = premium_data_fixture.create_user_and_token(
|
||||
is_staff=True, has_active_premium_license=True
|
||||
)
|
||||
group = premium_data_fixture.create_group()
|
||||
premium_data_fixture.create_template(group=group)
|
||||
|
||||
url = reverse("api:premium:admin:groups:edit", kwargs={"group_id": group.id})
|
||||
response = api_client.delete(url, HTTP_AUTHORIZATION=f"JWT {staff_token}")
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
import json
|
||||
|
||||
import pytest
|
||||
|
||||
from django.shortcuts import reverse
|
||||
from django.utils import timezone
|
||||
from django.utils.datetime_safe import datetime
|
||||
from django.test.utils import override_settings
|
||||
|
||||
from rest_framework.status import (
|
||||
HTTP_200_OK,
|
||||
HTTP_402_PAYMENT_REQUIRED,
|
||||
HTTP_204_NO_CONTENT,
|
||||
HTTP_400_BAD_REQUEST,
|
||||
HTTP_401_UNAUTHORIZED,
|
||||
|
@ -29,9 +32,14 @@ invalid_passwords = [
|
|||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_non_admin_cannot_see_admin_users_endpoint(api_client, data_fixture):
|
||||
non_staff_user, token = data_fixture.create_user_and_token(
|
||||
email="test@test.nl", password="password", first_name="Test1", is_staff=False
|
||||
@override_settings(DEBUG=True)
|
||||
def test_non_admin_cannot_see_admin_users_endpoint(api_client, premium_data_fixture):
|
||||
non_staff_user, token = premium_data_fixture.create_user_and_token(
|
||||
email="test@test.nl",
|
||||
password="password",
|
||||
first_name="Test1",
|
||||
is_staff=False,
|
||||
has_active_premium_license=True,
|
||||
)
|
||||
response = api_client.get(
|
||||
reverse("api:premium:admin:users:list"),
|
||||
|
@ -42,22 +50,24 @@ def test_non_admin_cannot_see_admin_users_endpoint(api_client, data_fixture):
|
|||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_admin_can_see_admin_users_endpoint(api_client, data_fixture):
|
||||
staff_user, token = data_fixture.create_user_and_token(
|
||||
@override_settings(DEBUG=True)
|
||||
def test_admin_can_see_admin_users_endpoint(api_client, premium_data_fixture):
|
||||
staff_user, token = premium_data_fixture.create_user_and_token(
|
||||
email="test@test.nl",
|
||||
password="password",
|
||||
first_name="Test1",
|
||||
is_staff=True,
|
||||
date_joined=datetime(2021, 4, 1, 1, 0, 0, 0, tzinfo=timezone.utc),
|
||||
has_active_premium_license=True,
|
||||
)
|
||||
group_user_is_admin_of = data_fixture.create_group()
|
||||
data_fixture.create_user_group(
|
||||
group_user_is_admin_of = premium_data_fixture.create_group()
|
||||
premium_data_fixture.create_user_group(
|
||||
group=group_user_is_admin_of,
|
||||
user=staff_user,
|
||||
permissions=GROUP_USER_PERMISSION_ADMIN,
|
||||
)
|
||||
group_user_is_not_admin_of = data_fixture.create_group()
|
||||
data_fixture.create_user_group(
|
||||
group_user_is_not_admin_of = premium_data_fixture.create_group()
|
||||
premium_data_fixture.create_user_group(
|
||||
group=group_user_is_not_admin_of,
|
||||
user=staff_user,
|
||||
permissions=GROUP_USER_PERMISSION_MEMBER,
|
||||
|
@ -99,12 +109,35 @@ def test_admin_can_see_admin_users_endpoint(api_client, data_fixture):
|
|||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_admin_with_invalid_token_cannot_see_admin_users(api_client, data_fixture):
|
||||
data_fixture.create_user_and_token(
|
||||
@override_settings(DEBUG=True)
|
||||
def test_admin_list_users_without_premium_license(api_client, premium_data_fixture):
|
||||
staff_user, token = premium_data_fixture.create_user_and_token(
|
||||
email="test@test.nl",
|
||||
password="password",
|
||||
first_name="Test1",
|
||||
is_staff=True,
|
||||
date_joined=datetime(2021, 4, 1, 1, 0, 0, 0, tzinfo=timezone.utc),
|
||||
)
|
||||
response = api_client.get(
|
||||
reverse("api:premium:admin:users:list"),
|
||||
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_admin_with_invalid_token_cannot_see_admin_users(
|
||||
api_client, premium_data_fixture
|
||||
):
|
||||
premium_data_fixture.create_user_and_token(
|
||||
email="test@test.nl",
|
||||
password="password",
|
||||
first_name="Test1",
|
||||
is_staff=True,
|
||||
has_active_premium_license=True,
|
||||
)
|
||||
response = api_client.get(
|
||||
reverse("api:premium:admin:users:list"),
|
||||
|
@ -116,14 +149,16 @@ def test_admin_with_invalid_token_cannot_see_admin_users(api_client, data_fixtur
|
|||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@override_settings(DEBUG=True)
|
||||
def test_admin_accessing_invalid_user_admin_page_returns_error(
|
||||
api_client, data_fixture
|
||||
api_client, premium_data_fixture
|
||||
):
|
||||
_, token = data_fixture.create_user_and_token(
|
||||
_, token = premium_data_fixture.create_user_and_token(
|
||||
email="test@test.nl",
|
||||
password="password",
|
||||
first_name="Test1",
|
||||
is_staff=True,
|
||||
has_active_premium_license=True,
|
||||
)
|
||||
url = reverse("api:premium:admin:users:list")
|
||||
response = api_client.get(
|
||||
|
@ -136,14 +171,16 @@ def test_admin_accessing_invalid_user_admin_page_returns_error(
|
|||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@override_settings(DEBUG=True)
|
||||
def test_admin_accessing_user_admin_with_invalid_page_size_returns_error(
|
||||
api_client, data_fixture
|
||||
api_client, premium_data_fixture
|
||||
):
|
||||
_, token = data_fixture.create_user_and_token(
|
||||
_, token = premium_data_fixture.create_user_and_token(
|
||||
email="test@test.nl",
|
||||
password="password",
|
||||
first_name="Test1",
|
||||
is_staff=True,
|
||||
has_active_premium_license=True,
|
||||
)
|
||||
url = reverse("api:premium:admin:users:list")
|
||||
response = api_client.get(
|
||||
|
@ -156,18 +193,21 @@ def test_admin_accessing_user_admin_with_invalid_page_size_returns_error(
|
|||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_admin_can_search_users(api_client, data_fixture):
|
||||
_, token = data_fixture.create_user_and_token(
|
||||
@override_settings(DEBUG=True)
|
||||
def test_admin_can_search_users(api_client, premium_data_fixture):
|
||||
_, token = premium_data_fixture.create_user_and_token(
|
||||
email="test@test.nl",
|
||||
password="password",
|
||||
first_name="Test1",
|
||||
is_staff=True,
|
||||
has_active_premium_license=True,
|
||||
)
|
||||
searched_for_user = data_fixture.create_user(
|
||||
searched_for_user = premium_data_fixture.create_user(
|
||||
email="specific_user@test.nl",
|
||||
password="password",
|
||||
first_name="Test1",
|
||||
date_joined=datetime(2021, 4, 1, 1, 0, 0, 0, tzinfo=timezone.utc),
|
||||
has_active_premium_license=True,
|
||||
)
|
||||
url = reverse("api:premium:admin:users:list")
|
||||
response = api_client.get(
|
||||
|
@ -196,18 +236,21 @@ def test_admin_can_search_users(api_client, data_fixture):
|
|||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_admin_can_sort_users(api_client, data_fixture):
|
||||
_, token = data_fixture.create_user_and_token(
|
||||
@override_settings(DEBUG=True)
|
||||
def test_admin_can_sort_users(api_client, premium_data_fixture):
|
||||
_, token = premium_data_fixture.create_user_and_token(
|
||||
email="test@test.nl",
|
||||
password="password",
|
||||
first_name="Test1",
|
||||
is_staff=True,
|
||||
has_active_premium_license=True,
|
||||
)
|
||||
searched_for_user = data_fixture.create_user(
|
||||
searched_for_user = premium_data_fixture.create_user(
|
||||
email="specific_user@test.nl",
|
||||
password="password",
|
||||
first_name="Test1",
|
||||
date_joined=datetime(2021, 4, 1, 1, 0, 0, 0, tzinfo=timezone.utc),
|
||||
has_active_premium_license=True,
|
||||
)
|
||||
url = reverse("api:premium:admin:users:list")
|
||||
response = api_client.get(
|
||||
|
@ -236,14 +279,16 @@ def test_admin_can_sort_users(api_client, data_fixture):
|
|||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@override_settings(DEBUG=True)
|
||||
def test_returns_error_response_if_invalid_sort_field_provided(
|
||||
api_client, data_fixture
|
||||
api_client, premium_data_fixture
|
||||
):
|
||||
_, token = data_fixture.create_user_and_token(
|
||||
_, token = premium_data_fixture.create_user_and_token(
|
||||
email="test@test.nl",
|
||||
password="password",
|
||||
first_name="Test1",
|
||||
is_staff=True,
|
||||
has_active_premium_license=True,
|
||||
)
|
||||
url = reverse("api:premium:admin:users:list")
|
||||
response = api_client.get(
|
||||
|
@ -256,14 +301,16 @@ def test_returns_error_response_if_invalid_sort_field_provided(
|
|||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@override_settings(DEBUG=True)
|
||||
def test_returns_error_response_if_sort_direction_not_provided(
|
||||
api_client, data_fixture
|
||||
api_client, premium_data_fixture
|
||||
):
|
||||
_, token = data_fixture.create_user_and_token(
|
||||
_, token = premium_data_fixture.create_user_and_token(
|
||||
email="test@test.nl",
|
||||
password="password",
|
||||
first_name="Test1",
|
||||
is_staff=True,
|
||||
has_active_premium_license=True,
|
||||
)
|
||||
url = reverse("api:premium:admin:users:list")
|
||||
response = api_client.get(
|
||||
|
@ -276,14 +323,16 @@ def test_returns_error_response_if_sort_direction_not_provided(
|
|||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@override_settings(DEBUG=True)
|
||||
def test_returns_error_response_if_invalid_sort_direction_provided(
|
||||
api_client, data_fixture
|
||||
api_client, premium_data_fixture
|
||||
):
|
||||
_, token = data_fixture.create_user_and_token(
|
||||
_, token = premium_data_fixture.create_user_and_token(
|
||||
email="test@test.nl",
|
||||
password="password",
|
||||
first_name="Test1",
|
||||
is_staff=True,
|
||||
has_active_premium_license=True,
|
||||
)
|
||||
url = reverse("api:premium:admin:users:list")
|
||||
response = api_client.get(
|
||||
|
@ -296,14 +345,16 @@ def test_returns_error_response_if_invalid_sort_direction_provided(
|
|||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@override_settings(DEBUG=True)
|
||||
def test_returns_error_response_if_invalid_sorts_mixed_with_valid_ones(
|
||||
api_client, data_fixture
|
||||
api_client, premium_data_fixture
|
||||
):
|
||||
_, token = data_fixture.create_user_and_token(
|
||||
_, token = premium_data_fixture.create_user_and_token(
|
||||
email="test@test.nl",
|
||||
password="password",
|
||||
first_name="Test1",
|
||||
is_staff=True,
|
||||
has_active_premium_license=True,
|
||||
)
|
||||
url = reverse("api:premium:admin:users:list")
|
||||
response = api_client.get(
|
||||
|
@ -316,12 +367,16 @@ def test_returns_error_response_if_invalid_sorts_mixed_with_valid_ones(
|
|||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_returns_error_response_if_blank_sorts_provided(api_client, data_fixture):
|
||||
_, token = data_fixture.create_user_and_token(
|
||||
@override_settings(DEBUG=True)
|
||||
def test_returns_error_response_if_blank_sorts_provided(
|
||||
api_client, premium_data_fixture
|
||||
):
|
||||
_, token = premium_data_fixture.create_user_and_token(
|
||||
email="test@test.nl",
|
||||
password="password",
|
||||
first_name="Test1",
|
||||
is_staff=True,
|
||||
has_active_premium_license=True,
|
||||
)
|
||||
url = reverse("api:premium:admin:users:list")
|
||||
response = api_client.get(
|
||||
|
@ -334,12 +389,14 @@ def test_returns_error_response_if_blank_sorts_provided(api_client, data_fixture
|
|||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_returns_error_response_if_no_sorts_provided(api_client, data_fixture):
|
||||
_, token = data_fixture.create_user_and_token(
|
||||
@override_settings(DEBUG=True)
|
||||
def test_returns_error_response_if_no_sorts_provided(api_client, premium_data_fixture):
|
||||
_, token = premium_data_fixture.create_user_and_token(
|
||||
email="test@test.nl",
|
||||
password="password",
|
||||
first_name="Test1",
|
||||
is_staff=True,
|
||||
has_active_premium_license=True,
|
||||
)
|
||||
url = reverse("api:premium:admin:users:list")
|
||||
response = api_client.get(
|
||||
|
@ -352,17 +409,20 @@ def test_returns_error_response_if_no_sorts_provided(api_client, data_fixture):
|
|||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_non_admin_cannot_delete_user(api_client, data_fixture):
|
||||
_, non_admin_token = data_fixture.create_user_and_token(
|
||||
@override_settings(DEBUG=True)
|
||||
def test_non_admin_cannot_delete_user(api_client, premium_data_fixture):
|
||||
_, non_admin_token = premium_data_fixture.create_user_and_token(
|
||||
email="test@test.nl",
|
||||
password="password",
|
||||
first_name="Test1",
|
||||
is_staff=False,
|
||||
has_active_premium_license=True,
|
||||
)
|
||||
user_to_delete = data_fixture.create_user(
|
||||
user_to_delete = premium_data_fixture.create_user(
|
||||
email="specific_user@test.nl",
|
||||
password="password",
|
||||
first_name="Test1",
|
||||
has_active_premium_license=True,
|
||||
)
|
||||
url = reverse("api:premium:admin:users:edit", kwargs={"user_id": user_to_delete.id})
|
||||
response = api_client.delete(
|
||||
|
@ -374,19 +434,48 @@ def test_non_admin_cannot_delete_user(api_client, data_fixture):
|
|||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_admin_can_delete_user(api_client, data_fixture):
|
||||
_, token = data_fixture.create_user_and_token(
|
||||
@override_settings(DEBUG=True)
|
||||
def test_admin_cannot_delete_user_without_premium_license(
|
||||
api_client, premium_data_fixture
|
||||
):
|
||||
_, admin_token = premium_data_fixture.create_user_and_token(
|
||||
email="test@test.nl",
|
||||
password="password",
|
||||
first_name="Test1",
|
||||
is_staff=True,
|
||||
)
|
||||
user_to_delete = data_fixture.create_user(
|
||||
user_to_delete = premium_data_fixture.create_user(
|
||||
email="specific_user@test.nl",
|
||||
password="password",
|
||||
first_name="Test1",
|
||||
)
|
||||
url = reverse("api:premium:admin:users:edit", kwargs={"user_id": user_to_delete.id})
|
||||
response = api_client.delete(
|
||||
url,
|
||||
format="json",
|
||||
HTTP_AUTHORIZATION=f"JWT {admin_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_admin_can_delete_user(api_client, premium_data_fixture):
|
||||
_, token = premium_data_fixture.create_user_and_token(
|
||||
email="test@test.nl",
|
||||
password="password",
|
||||
first_name="Test1",
|
||||
is_staff=True,
|
||||
has_active_premium_license=True,
|
||||
)
|
||||
user_to_delete = premium_data_fixture.create_user(
|
||||
email="specific_user@test.nl",
|
||||
password="password",
|
||||
first_name="Test1",
|
||||
has_active_premium_license=True,
|
||||
)
|
||||
url = reverse("api:premium:admin:users:edit", kwargs={"user_id": user_to_delete.id})
|
||||
response = api_client.delete(
|
||||
url,
|
||||
format="json",
|
||||
|
@ -404,12 +493,14 @@ def test_admin_can_delete_user(api_client, data_fixture):
|
|||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_non_admin_cannot_patch_user(api_client, data_fixture):
|
||||
non_admin_user, non_admin_user_token = data_fixture.create_user_and_token(
|
||||
@override_settings(DEBUG=True)
|
||||
def test_non_admin_cannot_patch_user(api_client, premium_data_fixture):
|
||||
non_admin_user, non_admin_user_token = premium_data_fixture.create_user_and_token(
|
||||
email="test@test.nl",
|
||||
password="password",
|
||||
first_name="Test1",
|
||||
is_staff=False,
|
||||
has_active_premium_license=True,
|
||||
)
|
||||
url = reverse("api:premium:admin:users:edit", kwargs={"user_id": non_admin_user.id})
|
||||
response = api_client.patch(
|
||||
|
@ -425,13 +516,40 @@ def test_non_admin_cannot_patch_user(api_client, data_fixture):
|
|||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_admin_can_patch_user(api_client, data_fixture):
|
||||
user, token = data_fixture.create_user_and_token(
|
||||
@override_settings(DEBUG=True)
|
||||
def test_non_admin_cannot_patch_user_without_premium_license(
|
||||
api_client, premium_data_fixture
|
||||
):
|
||||
non_admin_user, non_admin_user_token = premium_data_fixture.create_user_and_token(
|
||||
email="test@test.nl",
|
||||
password="password",
|
||||
first_name="Test1",
|
||||
is_staff=True,
|
||||
)
|
||||
url = reverse("api:premium:admin:users:edit", kwargs={"user_id": non_admin_user.id})
|
||||
response = api_client.patch(
|
||||
url,
|
||||
{"username": "some_other_email@test.nl"},
|
||||
format="json",
|
||||
HTTP_AUTHORIZATION=f"JWT {non_admin_user_token}",
|
||||
)
|
||||
assert response.status_code == HTTP_402_PAYMENT_REQUIRED
|
||||
assert response.json()["error"] == "ERROR_NO_ACTIVE_PREMIUM_LICENSE"
|
||||
|
||||
non_admin_user.refresh_from_db()
|
||||
assert non_admin_user.email == "test@test.nl"
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@override_settings(DEBUG=True)
|
||||
def test_admin_can_patch_user(api_client, premium_data_fixture):
|
||||
user, token = premium_data_fixture.create_user_and_token(
|
||||
email="test@test.nl",
|
||||
password="password",
|
||||
first_name="Test1",
|
||||
is_staff=True,
|
||||
date_joined=datetime(2021, 4, 1, 1, 0, 0, 0, tzinfo=timezone.utc),
|
||||
has_active_premium_license=True,
|
||||
)
|
||||
url = reverse("api:premium:admin:users:edit", kwargs={"user_id": user.id})
|
||||
old_password = user.password
|
||||
|
@ -457,13 +575,17 @@ def test_admin_can_patch_user(api_client, data_fixture):
|
|||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_admin_can_patch_user_without_providing_password(api_client, data_fixture):
|
||||
user, token = data_fixture.create_user_and_token(
|
||||
@override_settings(DEBUG=True)
|
||||
def test_admin_can_patch_user_without_providing_password(
|
||||
api_client, premium_data_fixture
|
||||
):
|
||||
user, token = premium_data_fixture.create_user_and_token(
|
||||
email="test@test.nl",
|
||||
password="password",
|
||||
first_name="Test1",
|
||||
is_staff=True,
|
||||
date_joined=datetime(2021, 4, 1, 1, 0, 0, 0, tzinfo=timezone.utc),
|
||||
has_active_premium_license=True,
|
||||
)
|
||||
url = reverse("api:premium:admin:users:edit", kwargs={"user_id": user.id})
|
||||
old_password = user.password
|
||||
|
@ -489,17 +611,21 @@ def test_admin_can_patch_user_without_providing_password(api_client, data_fixtur
|
|||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@override_settings(DEBUG=True)
|
||||
@pytest.mark.parametrize("invalid_password", invalid_passwords)
|
||||
def test_invalid_password_returns_400(api_client, data_fixture, invalid_password):
|
||||
user, token = data_fixture.create_user_and_token(
|
||||
def test_invalid_password_returns_400(
|
||||
api_client, premium_data_fixture, invalid_password
|
||||
):
|
||||
user, token = premium_data_fixture.create_user_and_token(
|
||||
email="test@test.nl",
|
||||
password="password",
|
||||
first_name="Test1",
|
||||
is_staff=True,
|
||||
date_joined=datetime(2021, 4, 1, 1, 0, 0, 0, tzinfo=timezone.utc),
|
||||
has_active_premium_license=True,
|
||||
)
|
||||
|
||||
user_to_edit = data_fixture.create_user(
|
||||
user_to_edit = premium_data_fixture.create_user(
|
||||
email="second@test.nl",
|
||||
password="password",
|
||||
first_name="Test1",
|
||||
|
@ -534,13 +660,17 @@ def test_invalid_password_returns_400(api_client, data_fixture, invalid_password
|
|||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_error_returned_when_invalid_field_supplied_to_edit(api_client, data_fixture):
|
||||
user, token = data_fixture.create_user_and_token(
|
||||
@override_settings(DEBUG=True)
|
||||
def test_error_returned_when_invalid_field_supplied_to_edit(
|
||||
api_client, premium_data_fixture
|
||||
):
|
||||
user, token = premium_data_fixture.create_user_and_token(
|
||||
email="test@test.nl",
|
||||
password="password",
|
||||
first_name="Test1",
|
||||
is_staff=True,
|
||||
date_joined=datetime(2021, 4, 1, 1, 0, 0, 0, tzinfo=timezone.utc),
|
||||
has_active_premium_license=True,
|
||||
)
|
||||
url = reverse("api:premium:admin:users:edit", kwargs={"user_id": user.id})
|
||||
|
||||
|
@ -558,13 +688,17 @@ def test_error_returned_when_invalid_field_supplied_to_edit(api_client, data_fix
|
|||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_error_returned_when_updating_user_with_invalid_email(api_client, data_fixture):
|
||||
user, token = data_fixture.create_user_and_token(
|
||||
@override_settings(DEBUG=True)
|
||||
def test_error_returned_when_updating_user_with_invalid_email(
|
||||
api_client, premium_data_fixture
|
||||
):
|
||||
user, token = premium_data_fixture.create_user_and_token(
|
||||
email="test@test.nl",
|
||||
password="password",
|
||||
first_name="Test1",
|
||||
is_staff=True,
|
||||
date_joined=datetime(2021, 4, 1, 1, 0, 0, 0, tzinfo=timezone.utc),
|
||||
has_active_premium_license=True,
|
||||
)
|
||||
url = reverse("api:premium:admin:users:edit", kwargs={"user_id": user.id})
|
||||
|
||||
|
@ -582,15 +716,17 @@ def test_error_returned_when_updating_user_with_invalid_email(api_client, data_f
|
|||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@override_settings(DEBUG=True)
|
||||
def test_error_returned_when_valid_and_invalid_fields_supplied_to_edit(
|
||||
api_client, data_fixture
|
||||
api_client, premium_data_fixture
|
||||
):
|
||||
user, token = data_fixture.create_user_and_token(
|
||||
user, token = premium_data_fixture.create_user_and_token(
|
||||
email="test@test.nl",
|
||||
password="password",
|
||||
first_name="Test1",
|
||||
is_staff=True,
|
||||
date_joined=datetime(2021, 4, 1, 1, 0, 0, 0, tzinfo=timezone.utc),
|
||||
has_active_premium_license=True,
|
||||
)
|
||||
url = reverse("api:premium:admin:users:edit", kwargs={"user_id": user.id})
|
||||
|
||||
|
@ -608,19 +744,21 @@ def test_error_returned_when_valid_and_invalid_fields_supplied_to_edit(
|
|||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@override_settings(DEBUG=True)
|
||||
def test_admin_getting_view_users_only_runs_two_queries_instead_of_n(
|
||||
data_fixture, django_assert_num_queries, api_client
|
||||
premium_data_fixture, django_assert_num_queries, api_client
|
||||
):
|
||||
_, token = data_fixture.create_user_and_token(
|
||||
_, token = premium_data_fixture.create_user_and_token(
|
||||
email="test@test.nl",
|
||||
password="password",
|
||||
first_name="Test1",
|
||||
is_staff=True,
|
||||
has_active_premium_license=True,
|
||||
)
|
||||
fixed_num_of_queries_unrelated_to_number_of_rows = 5
|
||||
fixed_num_of_queries_unrelated_to_number_of_rows = 6
|
||||
|
||||
for i in range(10):
|
||||
data_fixture.create_user_group()
|
||||
premium_data_fixture.create_user_group()
|
||||
|
||||
with django_assert_num_queries(fixed_num_of_queries_unrelated_to_number_of_rows):
|
||||
response = api_client.get(
|
||||
|
@ -633,7 +771,7 @@ def test_admin_getting_view_users_only_runs_two_queries_instead_of_n(
|
|||
|
||||
# Make even more to ensure that more rows don't result in more queries.
|
||||
for i in range(10):
|
||||
data_fixture.create_user_group()
|
||||
premium_data_fixture.create_user_group()
|
||||
|
||||
with django_assert_num_queries(fixed_num_of_queries_unrelated_to_number_of_rows):
|
||||
response = api_client.get(
|
||||
|
|
|
@ -1,33 +1,46 @@
|
|||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
from django.core.files.storage import FileSystemStorage
|
||||
from django.urls import reverse
|
||||
from django.utils.dateparse import parse_datetime
|
||||
from django.utils.timezone import utc, make_aware
|
||||
from django.test.utils import override_settings
|
||||
|
||||
from freezegun import freeze_time
|
||||
from rest_framework.status import HTTP_402_PAYMENT_REQUIRED
|
||||
from rest_framework.fields import DateTimeField
|
||||
|
||||
from baserow.contrib.database.rows.handler import RowHandler
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@override_settings(DEBUG=True)
|
||||
def test_exporting_json_writes_file_to_storage(
|
||||
data_fixture, api_client, tmpdir, settings, django_capture_on_commit_callbacks
|
||||
premium_data_fixture,
|
||||
api_client,
|
||||
tmpdir,
|
||||
settings,
|
||||
django_capture_on_commit_callbacks,
|
||||
):
|
||||
user, token = data_fixture.create_user_and_token()
|
||||
table = data_fixture.create_database_table(user=user)
|
||||
text_field = data_fixture.create_text_field(table=table, name="text_field", order=0)
|
||||
option_field = data_fixture.create_single_select_field(
|
||||
user, token = premium_data_fixture.create_user_and_token(
|
||||
has_active_premium_license=True
|
||||
)
|
||||
table = premium_data_fixture.create_database_table(user=user)
|
||||
text_field = premium_data_fixture.create_text_field(
|
||||
table=table, name="text_field", order=0
|
||||
)
|
||||
option_field = premium_data_fixture.create_single_select_field(
|
||||
table=table, name="option_field", order=1
|
||||
)
|
||||
option_a = data_fixture.create_select_option(
|
||||
option_a = premium_data_fixture.create_select_option(
|
||||
field=option_field, value="A", color="blue"
|
||||
)
|
||||
option_b = data_fixture.create_select_option(
|
||||
option_b = premium_data_fixture.create_select_option(
|
||||
field=option_field, value="B", color="red"
|
||||
)
|
||||
date_field = data_fixture.create_date_field(
|
||||
date_field = premium_data_fixture.create_date_field(
|
||||
table=table,
|
||||
date_include_time=True,
|
||||
date_format="US",
|
||||
|
@ -35,11 +48,11 @@ def test_exporting_json_writes_file_to_storage(
|
|||
order=2,
|
||||
)
|
||||
|
||||
grid_view = data_fixture.create_grid_view(table=table)
|
||||
data_fixture.create_view_filter(
|
||||
grid_view = premium_data_fixture.create_grid_view(table=table)
|
||||
premium_data_fixture.create_view_filter(
|
||||
view=grid_view, field=text_field, type="contains", value="test"
|
||||
)
|
||||
data_fixture.create_view_sort(view=grid_view, field=text_field, order="ASC")
|
||||
premium_data_fixture.create_view_sort(view=grid_view, field=text_field, order="ASC")
|
||||
|
||||
row_handler = RowHandler()
|
||||
row_handler.create_row(
|
||||
|
@ -135,24 +148,50 @@ def test_exporting_json_writes_file_to_storage(
|
|||
real = written_file.read()
|
||||
assert real == expected
|
||||
|
||||
premium_data_fixture.remove_all_active_premium_licenses(user)
|
||||
response = api_client.post(
|
||||
reverse(
|
||||
"api:database:export:export_table",
|
||||
kwargs={"table_id": table.id},
|
||||
),
|
||||
data={
|
||||
"view_id": grid_view.id,
|
||||
"exporter_type": "json",
|
||||
"json_charset": "utf-8",
|
||||
},
|
||||
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_exporting_xml_writes_file_to_storage(
|
||||
data_fixture, api_client, tmpdir, settings, django_capture_on_commit_callbacks
|
||||
premium_data_fixture,
|
||||
api_client,
|
||||
tmpdir,
|
||||
settings,
|
||||
django_capture_on_commit_callbacks,
|
||||
):
|
||||
user, token = data_fixture.create_user_and_token()
|
||||
table = data_fixture.create_database_table(user=user)
|
||||
text_field = data_fixture.create_text_field(table=table, name="text_field", order=0)
|
||||
option_field = data_fixture.create_single_select_field(
|
||||
user, token = premium_data_fixture.create_user_and_token(
|
||||
has_active_premium_license=True
|
||||
)
|
||||
table = premium_data_fixture.create_database_table(user=user)
|
||||
text_field = premium_data_fixture.create_text_field(
|
||||
table=table, name="text_field", order=0
|
||||
)
|
||||
option_field = premium_data_fixture.create_single_select_field(
|
||||
table=table, name="option_field", order=1
|
||||
)
|
||||
option_a = data_fixture.create_select_option(
|
||||
option_a = premium_data_fixture.create_select_option(
|
||||
field=option_field, value="A", color="blue"
|
||||
)
|
||||
option_b = data_fixture.create_select_option(
|
||||
option_b = premium_data_fixture.create_select_option(
|
||||
field=option_field, value="B", color="red"
|
||||
)
|
||||
date_field = data_fixture.create_date_field(
|
||||
date_field = premium_data_fixture.create_date_field(
|
||||
table=table,
|
||||
date_include_time=True,
|
||||
date_format="US",
|
||||
|
@ -160,11 +199,11 @@ def test_exporting_xml_writes_file_to_storage(
|
|||
order=2,
|
||||
)
|
||||
|
||||
grid_view = data_fixture.create_grid_view(table=table)
|
||||
data_fixture.create_view_filter(
|
||||
grid_view = premium_data_fixture.create_grid_view(table=table)
|
||||
premium_data_fixture.create_view_filter(
|
||||
view=grid_view, field=text_field, type="contains", value="test"
|
||||
)
|
||||
data_fixture.create_view_sort(view=grid_view, field=text_field, order="ASC")
|
||||
premium_data_fixture.create_view_sort(view=grid_view, field=text_field, order="ASC")
|
||||
|
||||
row_handler = RowHandler()
|
||||
row_handler.create_row(
|
||||
|
@ -263,6 +302,23 @@ def test_exporting_xml_writes_file_to_storage(
|
|||
expected
|
||||
)
|
||||
|
||||
premium_data_fixture.remove_all_active_premium_licenses(user)
|
||||
response = api_client.post(
|
||||
reverse(
|
||||
"api:database:export:export_table",
|
||||
kwargs={"table_id": table.id},
|
||||
),
|
||||
data={
|
||||
"view_id": grid_view.id,
|
||||
"exporter_type": "xml",
|
||||
"xml_charset": "utf-8",
|
||||
},
|
||||
format="json",
|
||||
HTTP_AUTHORIZATION=f"JWT {token}",
|
||||
)
|
||||
assert response.status_code == HTTP_402_PAYMENT_REQUIRED
|
||||
assert response.json()["error"] == "ERROR_NO_ACTIVE_PREMIUM_LICENSE"
|
||||
|
||||
|
||||
def strip_indents_and_newlines(xml):
|
||||
return "".join([line.strip() for line in xml.split("\n")])
|
||||
|
|
|
@ -0,0 +1,775 @@
|
|||
import pytest
|
||||
import responses
|
||||
from freezegun import freeze_time
|
||||
|
||||
from django.shortcuts import reverse
|
||||
from django.test.utils import override_settings
|
||||
from django.utils.timezone import make_aware, datetime, utc
|
||||
|
||||
from rest_framework.status import (
|
||||
HTTP_200_OK,
|
||||
HTTP_204_NO_CONTENT,
|
||||
HTTP_400_BAD_REQUEST,
|
||||
HTTP_403_FORBIDDEN,
|
||||
HTTP_404_NOT_FOUND,
|
||||
)
|
||||
|
||||
from baserow_premium.license.models import License, LicenseUser
|
||||
|
||||
|
||||
VALID_ONE_SEAT_LICENSE = (
|
||||
# id: "1", instance_id: "1"
|
||||
b"eyJ2ZXJzaW9uIjogMSwgImlkIjogIjEiLCAidmFsaWRfZnJvbSI6ICIyMDIxLTA4LTI5VDE5OjUyOjU3"
|
||||
b"Ljg0MjY5NiIsICJ2YWxpZF90aHJvdWdoIjogIjIwMjEtMDktMjlUMTk6NTI6NTcuODQyNjk2IiwgInBy"
|
||||
b"b2R1Y3RfY29kZSI6ICJwcmVtaXVtIiwgInNlYXRzIjogMSwgImlzc3VlZF9vbiI6ICIyMDIxLTA4LTI5"
|
||||
b"VDE5OjUyOjU3Ljg0MjY5NiIsICJpc3N1ZWRfdG9fZW1haWwiOiAiYnJhbUBiYXNlcm93LmlvIiwgImlz"
|
||||
b"c3VlZF90b19uYW1lIjogIkJyYW0iLCAiaW5zdGFuY2VfaWQiOiAiMSJ9.e33Z4CxLSmD-R55Es24P3mR"
|
||||
b"8Oqn3LpaXvgYLzF63oFHat3paon7IBjBmOX3eyd8KjirVf3empJds4uUw2Nn2m7TVvRAtJ8XzNl-8ytf"
|
||||
b"2RLtmjMx1Xkgp5VZ8S7UqJ_cKLyl76eVRtGEA1DH2HdPKu1vBPJ4bzDfnhDPYl4k5z9XSSgqAbQ9WO0U"
|
||||
b"5kiI3BYjVRZSKnZMeguAGZ47ezDj_WArGcHAB8Pa2v3HFp5Y34DMJ8r3_hD5hxCKgoNx4AHx1Q-hRDqp"
|
||||
b"Aroj-4jl7KWvlP-OJNc1BgH2wnhFmeKHotv-Iumi83JQohyceUbG6j8rDDQvJfcn0W2_ebmUH3TKr-w="
|
||||
b"="
|
||||
)
|
||||
VALID_TWO_SEAT_LICENSE = (
|
||||
# id: "2", instance_id: "1"
|
||||
b"eyJ2ZXJzaW9uIjogMSwgImlkIjogIjIiLCAidmFsaWRfZnJvbSI6ICIyMDIxLTA4LTI5VDE5OjUzOjM3"
|
||||
b"LjA5MjMwMyIsICJ2YWxpZF90aHJvdWdoIjogIjIwMjEtMDktMjlUMTk6NTM6MzcuMDkyMzAzIiwgInBy"
|
||||
b"b2R1Y3RfY29kZSI6ICJwcmVtaXVtIiwgInNlYXRzIjogMiwgImlzc3VlZF9vbiI6ICIyMDIxLTA4LTI5"
|
||||
b"VDE5OjUzOjM3LjA5MjMwMyIsICJpc3N1ZWRfdG9fZW1haWwiOiAiYnJhbUBiYXNlcm93LmlvIiwgImlz"
|
||||
b"c3VlZF90b19uYW1lIjogIkJyYW0iLCAiaW5zdGFuY2VfaWQiOiAiMSJ9.d41tB1kx69gw-9xDrRI0kER"
|
||||
b"KDUtR-P6yRM3ufKZ_XRDewVCBAniCLe9-ce7TKabnMedE2cqHjYVLlI66Dfa5oH8fGswnyC16c9ZHlOU"
|
||||
b"jQ5CpHTorZm6eyCXaP6MDdhstCNKdDrZns3qvVMAqDpmxS8wmiG9Y6gZjvBGXZWeoCraF1SVcUnFBBlf"
|
||||
b"UemfGSQUwPitVlxJ6GWN-hzi7b1GZqWJKDb2YYJ0T30VMJeNO7oi6YHMUOH33041FU79DSET2A2NNEFu"
|
||||
b"e-jnCcw5NFpH-zGzBDv1wpR3DFmJa78KwGbj0Kdzim85AUzi1xGRlIyxxTdTkVy2B-08lPaoG8Q62bw="
|
||||
b"="
|
||||
)
|
||||
VALID_INSTANCE_TWO_LICENSE = (
|
||||
# id: "2", instance_id: "2"
|
||||
b"eyJ2ZXJzaW9uIjogMSwgImlkIjogIjEiLCAidmFsaWRfZnJvbSI6ICIyMDIxLTA4LTI5VDE5OjUyOjU3"
|
||||
b"Ljg0MjY5NiIsICJ2YWxpZF90aHJvdWdoIjogIjIwMjEtMDktMjlUMTk6NTI6NTcuODQyNjk2IiwgInBy"
|
||||
b"b2R1Y3RfY29kZSI6ICJwcmVtaXVtIiwgInNlYXRzIjogMSwgImlzc3VlZF9vbiI6ICIyMDIxLTA4LTI5"
|
||||
b"VDE5OjUyOjU3Ljg0MjY5NiIsICJpc3N1ZWRfdG9fZW1haWwiOiAiYnJhbUBiYXNlcm93LmlvIiwgImlz"
|
||||
b"c3VlZF90b19uYW1lIjogIkJyYW0iLCAiaW5zdGFuY2VfaWQiOiAiMiJ9.i3Og4ZJwz__TxWyFc2B6lDi"
|
||||
b"ZBAIOVTZv_jXVzQQqcjG-flPAicqXFECl7MbbexVmtsMES-U7VPebOh0t4oPoDXL1LiftfjmT63wO4An"
|
||||
b"A3FMS0Ip0GIx2upkQC-MlU1kSR9Tltrr1qySuQvXORDRUaSxaRQQacwZTOIviVdcxG9vesjkFwn6LMYp"
|
||||
b"-GhmCJXB0YfMgsvPm6kj6qTWPh3ed8aLNFnekUhB-dUwA4tqPicCQHRQCRZqzo9vx-hKdeHCGZMg0htG"
|
||||
b"EB4cAeV4I29JXPC83qtwt6DSCPxudlJsli3tYsLMcxAHysVN3H_FAY8qg54MP33OKvZuwww5uFDITMQ="
|
||||
b"="
|
||||
)
|
||||
INVALID_SIGNATURE_LICENSE = (
|
||||
b"eyJ2ZXJzaW9uIjogMSwgImlkIjogMSwgInZhbGlkX2Zyb20iOiAiMjAyMS0wOC0yOVQxOTo1NDoxMi4w"
|
||||
b"NjY4NDYiLCAidmFsaWRfdGhyb3VnaCI6ICIyMDIxLTA5LTI5VDE5OjU0OjEyLjA2Njg0NiIsICJwcm9k"
|
||||
b"dWN0X2NvZGUiOiAicHJlbWl1bSIsICJzZWF0cyI6IDEsICJ0b19lbWFpbCI6ICJicmFtQGJhc2Vyb3cu"
|
||||
b"aW8iLCAidG9fbmFtZSI6ICJCcmFtIn0=.hYaWGO0M6s1pA9bhcBlk1fE1QMrhlDGNiBIBG_2O2AMGFPj"
|
||||
b"gnsHdwfUIe_eo6dyAyvsToxBrpxr6N1vRqPdA61cKjTlhUdFqvj7NTeydS4Z9TlfP-vFslQk9CO_ok7Z"
|
||||
b"ws8AHTQ2pKfsdzqcWNZnWKZeQGEtO73MIoFJbHr07mtWA1ZZgJNBTBpp-7BNtvj2bQyUeXyRKD5LVj8G"
|
||||
b"ESDcapZCNt5ufesbYvpfs1c6p6UP4z3gszOYrzMApMqWHty7j10SDjcLIEsUTd02r_Pbip-KxmGfecXg"
|
||||
b"B0HF7HJZwkY9ZdlZ7ODGtV0e455dQwh5sSHa3RRd71AXVou-cuOS87g=="
|
||||
)
|
||||
INVALID_VERSION_LICENSE = (
|
||||
b"eyJ2ZXJzaW9uIjogOTk5OX0=.rzAyL6qBkz_Eb3GYaSOXy9CJ2HJg4uAxtrbinh4aDYy7Eq4e4RpfaPm"
|
||||
b"4dZLocIRxSmx_wUYSI0CMqmkwABHgzxRVmzVAmXf5MxX7vAGjjEnQX_dQOl8kY15gXhEQZv5pjSPVcZW"
|
||||
b"CLll95OFoBUJhtOQqNC6JLA1LZdiSPG6zFhvi5V27sRGBz3E8jhFLWY-Y2WIq5_9q2d_hVFM0KHwRcxb"
|
||||
b"CVof8RBUq1DgMcDKEGE7WRHYDVP1QugBjf4GZlvIE4ZVr3tKr0aKPX8nuNVhbQeudCW8tnturmxevpRN"
|
||||
b"vLS5ETSQzJoP46cGuw0HUV20P4SnvQP_NRd5zifgllJqsUw=="
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@override_settings(DEBUG=True)
|
||||
def test_admin_list_licenses(api_client, data_fixture, django_assert_num_queries):
|
||||
user_1 = data_fixture.create_user()
|
||||
user_2 = data_fixture.create_user()
|
||||
user_3 = data_fixture.create_user()
|
||||
|
||||
license_1 = License.objects.create(license=VALID_ONE_SEAT_LICENSE.decode())
|
||||
LicenseUser.objects.create(license=license_1, user=user_1)
|
||||
|
||||
license_2 = License.objects.create(
|
||||
license=VALID_TWO_SEAT_LICENSE.decode(),
|
||||
last_check=make_aware(datetime(2021, 8, 29, 19, 52, 57, 842696), utc),
|
||||
)
|
||||
LicenseUser.objects.create(license=license_2, user=user_2)
|
||||
LicenseUser.objects.create(license=license_2, user=user_3)
|
||||
|
||||
with freeze_time("2021-09-01 00:00"):
|
||||
normal_user, normal_token = data_fixture.create_user_and_token(
|
||||
is_staff=False,
|
||||
)
|
||||
admin_user, admin_token = data_fixture.create_user_and_token(
|
||||
is_staff=True,
|
||||
)
|
||||
|
||||
response = api_client.get(
|
||||
reverse("api:premium:license:list"),
|
||||
format="json",
|
||||
HTTP_AUTHORIZATION=f"JWT {normal_token}",
|
||||
)
|
||||
assert response.status_code == HTTP_403_FORBIDDEN
|
||||
|
||||
# We expect one query for the user check and one for the fetching the
|
||||
# licenses, including the count of seats that are taken.
|
||||
with django_assert_num_queries(2):
|
||||
response = api_client.get(
|
||||
reverse("api:premium:license:list"),
|
||||
format="json",
|
||||
HTTP_AUTHORIZATION=f"JWT {admin_token}",
|
||||
)
|
||||
assert response.status_code == HTTP_200_OK
|
||||
response_json = response.json()
|
||||
assert len(response_json) == 2
|
||||
assert response_json[0]["license_id"] == "1"
|
||||
assert response_json[0]["is_active"] is True
|
||||
assert response_json[0]["last_check"] is None
|
||||
assert response_json[0]["valid_from"] == "2021-08-29T19:52:57.842696Z"
|
||||
assert response_json[0]["valid_through"] == "2021-09-29T19:52:57.842696Z"
|
||||
assert response_json[0]["seats_taken"] == 1
|
||||
assert response_json[0]["seats"] == 1
|
||||
assert response_json[0]["product_code"] == "premium"
|
||||
assert response_json[0]["issued_on"] == "2021-08-29T19:52:57.842696Z"
|
||||
assert response_json[0]["issued_to_email"] == "bram@baserow.io"
|
||||
assert response_json[0]["issued_to_name"] == "Bram"
|
||||
assert response_json[1]["license_id"] == "2"
|
||||
assert response_json[1]["is_active"] is True
|
||||
assert response_json[1]["last_check"] == "2021-08-29T19:52:57.842696Z"
|
||||
assert response_json[1]["valid_from"] == "2021-08-29T19:53:37.092303Z"
|
||||
assert response_json[1]["valid_through"] == "2021-09-29T19:53:37.092303Z"
|
||||
assert response_json[1]["seats_taken"] == 2
|
||||
assert response_json[1]["seats"] == 2
|
||||
assert response_json[1]["issued_on"] == "2021-08-29T19:53:37.092303Z"
|
||||
assert response_json[1]["issued_to_email"] == "bram@baserow.io"
|
||||
assert response_json[1]["issued_to_name"] == "Bram"
|
||||
|
||||
assert response_json[1]["product_code"] == "premium"
|
||||
|
||||
with freeze_time("2021-09-29 19:53"):
|
||||
admin_user, admin_token = data_fixture.create_user_and_token(
|
||||
is_staff=True,
|
||||
)
|
||||
|
||||
response = api_client.get(
|
||||
reverse("api:premium:license:list"),
|
||||
format="json",
|
||||
HTTP_AUTHORIZATION=f"JWT {admin_token}",
|
||||
)
|
||||
assert response.status_code == HTTP_200_OK
|
||||
response_json = response.json()
|
||||
assert len(response_json) == 2
|
||||
assert response_json[0]["license_id"] == "2"
|
||||
assert response_json[0]["is_active"] is True
|
||||
assert response_json[0]["last_check"] == "2021-08-29T19:52:57.842696Z"
|
||||
assert response_json[0]["valid_from"] == "2021-08-29T19:53:37.092303Z"
|
||||
assert response_json[0]["valid_through"] == "2021-09-29T19:53:37.092303Z"
|
||||
assert response_json[0]["seats_taken"] == 2
|
||||
assert response_json[0]["seats"] == 2
|
||||
assert response_json[0]["product_code"] == "premium"
|
||||
assert response_json[0]["issued_on"] == "2021-08-29T19:53:37.092303Z"
|
||||
assert response_json[0]["issued_to_email"] == "bram@baserow.io"
|
||||
assert response_json[0]["issued_to_name"] == "Bram"
|
||||
assert response_json[1]["license_id"] == "1"
|
||||
assert response_json[1]["is_active"] is False
|
||||
assert response_json[1]["last_check"] is None
|
||||
assert response_json[1]["valid_from"] == "2021-08-29T19:52:57.842696Z"
|
||||
assert response_json[1]["valid_through"] == "2021-09-29T19:52:57.842696Z"
|
||||
assert response_json[1]["seats_taken"] == 1
|
||||
assert response_json[1]["seats"] == 1
|
||||
assert response_json[1]["product_code"] == "premium"
|
||||
assert response_json[1]["issued_on"] == "2021-08-29T19:52:57.842696Z"
|
||||
assert response_json[1]["issued_to_email"] == "bram@baserow.io"
|
||||
assert response_json[1]["issued_to_name"] == "Bram"
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@override_settings(DEBUG=True)
|
||||
@responses.activate
|
||||
def test_admin_register_license(api_client, data_fixture):
|
||||
data_fixture.update_settings(instance_id="1")
|
||||
normal_user, normal_token = data_fixture.create_user_and_token(
|
||||
is_staff=False,
|
||||
)
|
||||
admin_user, admin_token = data_fixture.create_user_and_token(
|
||||
is_staff=True,
|
||||
)
|
||||
|
||||
response = api_client.post(
|
||||
reverse("api:premium:license:list"),
|
||||
{"license": VALID_ONE_SEAT_LICENSE.decode()},
|
||||
format="json",
|
||||
HTTP_AUTHORIZATION=f"JWT {normal_token}",
|
||||
)
|
||||
assert response.status_code == HTTP_403_FORBIDDEN
|
||||
|
||||
response = api_client.post(
|
||||
reverse("api:premium:license:list"),
|
||||
{"license": INVALID_SIGNATURE_LICENSE.decode()},
|
||||
format="json",
|
||||
HTTP_AUTHORIZATION=f"JWT {admin_token}",
|
||||
)
|
||||
assert response.status_code == HTTP_400_BAD_REQUEST
|
||||
assert response.json()["error"] == "ERROR_INVALID_PREMIUM_LICENSE"
|
||||
|
||||
with freeze_time("2021-10-01 00:00"):
|
||||
admin_token = data_fixture.generate_token(admin_user)
|
||||
response = api_client.post(
|
||||
reverse("api:premium:license:list"),
|
||||
{"license": VALID_ONE_SEAT_LICENSE.decode()},
|
||||
format="json",
|
||||
HTTP_AUTHORIZATION=f"JWT {admin_token}",
|
||||
)
|
||||
assert response.status_code == HTTP_400_BAD_REQUEST
|
||||
assert response.json()["error"] == "ERROR_PREMIUM_LICENSE_HAS_EXPIRED"
|
||||
|
||||
with freeze_time("2021-09-01 00:00"):
|
||||
admin_token = data_fixture.generate_token(admin_user)
|
||||
response = api_client.post(
|
||||
reverse("api:premium:license:list"),
|
||||
{"license": INVALID_VERSION_LICENSE.decode()},
|
||||
format="json",
|
||||
HTTP_AUTHORIZATION=f"JWT {admin_token}",
|
||||
)
|
||||
assert response.status_code == HTTP_400_BAD_REQUEST
|
||||
assert response.json()["error"] == "ERROR_UNSUPPORTED_PREMIUM_LICENSE"
|
||||
|
||||
response = api_client.post(
|
||||
reverse("api:premium:license:list"),
|
||||
{"license": VALID_INSTANCE_TWO_LICENSE.decode()},
|
||||
format="json",
|
||||
HTTP_AUTHORIZATION=f"JWT {admin_token}",
|
||||
)
|
||||
assert response.status_code == HTTP_400_BAD_REQUEST
|
||||
assert response.json()["error"] == "ERROR_PREMIUM_LICENSE_INSTANCE_ID_MISMATCH"
|
||||
|
||||
response = api_client.post(
|
||||
reverse("api:premium:license:list"),
|
||||
{"license": VALID_TWO_SEAT_LICENSE.decode()},
|
||||
format="json",
|
||||
HTTP_AUTHORIZATION=f"JWT {admin_token}",
|
||||
)
|
||||
assert response.status_code == HTTP_200_OK
|
||||
|
||||
licenses = License.objects.all()
|
||||
assert len(licenses) == 1
|
||||
|
||||
response_json = response.json()
|
||||
assert response_json["id"] == licenses[0].id
|
||||
assert response_json["license_id"] == "2"
|
||||
assert response_json["is_active"] is True
|
||||
assert response_json["last_check"] is None
|
||||
assert response_json["valid_from"] == "2021-08-29T19:53:37.092303Z"
|
||||
assert response_json["valid_through"] == "2021-09-29T19:53:37.092303Z"
|
||||
assert response_json["seats_taken"] == 0
|
||||
assert response_json["seats"] == 2
|
||||
assert response_json["product_code"] == "premium"
|
||||
assert response_json["issued_on"] == "2021-08-29T19:53:37.092303Z"
|
||||
assert response_json["issued_to_email"] == "bram@baserow.io"
|
||||
assert response_json["issued_to_name"] == "Bram"
|
||||
|
||||
response = api_client.post(
|
||||
reverse("api:premium:license:list"),
|
||||
{"license": VALID_TWO_SEAT_LICENSE.decode()},
|
||||
format="json",
|
||||
HTTP_AUTHORIZATION=f"JWT {admin_token}",
|
||||
)
|
||||
assert response.status_code == HTTP_400_BAD_REQUEST
|
||||
assert response.json()["error"] == "ERROR_PREMIUM_LICENSE_ALREADY_EXISTS"
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@override_settings(DEBUG=True)
|
||||
def test_admin_get_license(api_client, data_fixture, django_assert_num_queries):
|
||||
normal_user, normal_token = data_fixture.create_user_and_token(
|
||||
is_staff=False,
|
||||
)
|
||||
admin_user, admin_token = data_fixture.create_user_and_token(
|
||||
is_staff=True,
|
||||
)
|
||||
|
||||
license = License.objects.create(license=VALID_TWO_SEAT_LICENSE.decode())
|
||||
LicenseUser.objects.create(license=license, user=normal_user)
|
||||
LicenseUser.objects.create(license=license, user=admin_user)
|
||||
|
||||
response = api_client.get(
|
||||
reverse("api:premium:license:item", kwargs={"id": license.id}),
|
||||
format="json",
|
||||
HTTP_AUTHORIZATION=f"JWT {normal_token}",
|
||||
)
|
||||
assert response.status_code == HTTP_403_FORBIDDEN
|
||||
|
||||
response = api_client.get(
|
||||
reverse("api:premium:license:item", kwargs={"id": 0}),
|
||||
format="json",
|
||||
HTTP_AUTHORIZATION=f"JWT {admin_token}",
|
||||
)
|
||||
assert response.status_code == HTTP_404_NOT_FOUND
|
||||
assert response.json()["error"] == "ERROR_PREMIUM_LICENSE_DOES_NOT_EXIST"
|
||||
|
||||
with freeze_time("2021-09-01 00:00"):
|
||||
with django_assert_num_queries(4):
|
||||
response = api_client.get(
|
||||
reverse("api:premium:license:item", kwargs={"id": license.id}),
|
||||
format="json",
|
||||
HTTP_AUTHORIZATION=f"JWT {admin_token}",
|
||||
)
|
||||
assert response.status_code == HTTP_200_OK
|
||||
response_json = response.json()
|
||||
assert response_json["id"] == license.id
|
||||
assert response_json["license_id"] == "2"
|
||||
assert response_json["is_active"] is True
|
||||
assert response_json["last_check"] is None
|
||||
assert response_json["valid_from"] == "2021-08-29T19:53:37.092303Z"
|
||||
assert response_json["valid_through"] == "2021-09-29T19:53:37.092303Z"
|
||||
assert response_json["seats_taken"] == 2
|
||||
assert response_json["seats"] == 2
|
||||
assert response_json["product_code"] == "premium"
|
||||
assert response_json["issued_on"] == "2021-08-29T19:53:37.092303Z"
|
||||
assert response_json["issued_to_email"] == "bram@baserow.io"
|
||||
assert response_json["issued_to_name"] == "Bram"
|
||||
assert len(response_json["users"]) == 2
|
||||
assert response_json["users"][0]["id"] == normal_user.id
|
||||
assert response_json["users"][0]["email"] == normal_user.email
|
||||
assert response_json["users"][0]["first_name"] == normal_user.first_name
|
||||
assert response_json["users"][1]["id"] == admin_user.id
|
||||
assert response_json["users"][1]["email"] == admin_user.email
|
||||
assert response_json["users"][1]["first_name"] == admin_user.first_name
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@override_settings(DEBUG=True)
|
||||
def test_admin_delete_license(api_client, data_fixture):
|
||||
normal_user, normal_token = data_fixture.create_user_and_token(
|
||||
is_staff=False,
|
||||
)
|
||||
admin_user, admin_token = data_fixture.create_user_and_token(
|
||||
is_staff=True,
|
||||
)
|
||||
|
||||
license = License.objects.create(license=VALID_TWO_SEAT_LICENSE.decode())
|
||||
|
||||
response = api_client.delete(
|
||||
reverse("api:premium:license:item", kwargs={"id": license.id}),
|
||||
format="json",
|
||||
HTTP_AUTHORIZATION=f"JWT {normal_token}",
|
||||
)
|
||||
assert response.status_code == HTTP_403_FORBIDDEN
|
||||
|
||||
response = api_client.delete(
|
||||
reverse("api:premium:license:item", kwargs={"id": 0}),
|
||||
format="json",
|
||||
HTTP_AUTHORIZATION=f"JWT {admin_token}",
|
||||
)
|
||||
assert response.status_code == HTTP_404_NOT_FOUND
|
||||
assert response.json()["error"] == "ERROR_PREMIUM_LICENSE_DOES_NOT_EXIST"
|
||||
|
||||
response = api_client.delete(
|
||||
reverse("api:premium:license:item", kwargs={"id": license.id}),
|
||||
format="json",
|
||||
HTTP_AUTHORIZATION=f"JWT {admin_token}",
|
||||
)
|
||||
assert response.status_code == HTTP_204_NO_CONTENT
|
||||
assert License.objects.all().count() == 0
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@override_settings(DEBUG=True)
|
||||
def test_admin_add_user_to_license(api_client, data_fixture):
|
||||
normal_user, normal_token = data_fixture.create_user_and_token(
|
||||
is_staff=False,
|
||||
)
|
||||
admin_user, admin_token = data_fixture.create_user_and_token(
|
||||
is_staff=True,
|
||||
)
|
||||
|
||||
license = License.objects.create(license=VALID_TWO_SEAT_LICENSE.decode())
|
||||
|
||||
response = api_client.post(
|
||||
reverse(
|
||||
"api:premium:license:user",
|
||||
kwargs={"id": license.id, "user_id": normal_user.id},
|
||||
),
|
||||
format="json",
|
||||
HTTP_AUTHORIZATION=f"JWT {normal_token}",
|
||||
)
|
||||
assert response.status_code == HTTP_403_FORBIDDEN
|
||||
|
||||
response = api_client.post(
|
||||
reverse(
|
||||
"api:premium:license:user",
|
||||
kwargs={"id": license.id, "user_id": 0},
|
||||
),
|
||||
format="json",
|
||||
HTTP_AUTHORIZATION=f"JWT {admin_token}",
|
||||
)
|
||||
assert response.status_code == HTTP_400_BAD_REQUEST
|
||||
assert response.json()["error"] == "ERROR_USER_NOT_FOUND"
|
||||
|
||||
response = api_client.post(
|
||||
reverse(
|
||||
"api:premium:license:user",
|
||||
kwargs={"id": 0, "user_id": normal_user.id},
|
||||
),
|
||||
format="json",
|
||||
HTTP_AUTHORIZATION=f"JWT {admin_token}",
|
||||
)
|
||||
assert response.status_code == HTTP_404_NOT_FOUND
|
||||
assert response.json()["error"] == "ERROR_PREMIUM_LICENSE_DOES_NOT_EXIST"
|
||||
|
||||
response = api_client.post(
|
||||
reverse(
|
||||
"api:premium:license:user",
|
||||
kwargs={"id": license.id, "user_id": normal_user.id},
|
||||
),
|
||||
format="json",
|
||||
HTTP_AUTHORIZATION=f"JWT {admin_token}",
|
||||
)
|
||||
assert response.status_code == HTTP_200_OK
|
||||
response_json = response.json()
|
||||
assert response_json["id"] == normal_user.id
|
||||
assert response_json["first_name"] == normal_user.first_name
|
||||
assert response_json["email"] == normal_user.email
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@override_settings(DEBUG=True)
|
||||
def test_admin_delete_user_from_license(api_client, data_fixture):
|
||||
normal_user, normal_token = data_fixture.create_user_and_token(
|
||||
is_staff=False,
|
||||
)
|
||||
admin_user, admin_token = data_fixture.create_user_and_token(
|
||||
is_staff=True,
|
||||
)
|
||||
|
||||
license = License.objects.create(license=VALID_TWO_SEAT_LICENSE.decode())
|
||||
LicenseUser.objects.create(license=license, user=normal_user)
|
||||
|
||||
response = api_client.delete(
|
||||
reverse(
|
||||
"api:premium:license:user",
|
||||
kwargs={"id": license.id, "user_id": normal_user.id},
|
||||
),
|
||||
format="json",
|
||||
HTTP_AUTHORIZATION=f"JWT {normal_token}",
|
||||
)
|
||||
assert response.status_code == HTTP_403_FORBIDDEN
|
||||
|
||||
response = api_client.delete(
|
||||
reverse(
|
||||
"api:premium:license:user",
|
||||
kwargs={"id": license.id, "user_id": 0},
|
||||
),
|
||||
format="json",
|
||||
HTTP_AUTHORIZATION=f"JWT {admin_token}",
|
||||
)
|
||||
assert response.status_code == HTTP_400_BAD_REQUEST
|
||||
assert response.json()["error"] == "ERROR_USER_NOT_FOUND"
|
||||
|
||||
response = api_client.delete(
|
||||
reverse(
|
||||
"api:premium:license:user",
|
||||
kwargs={"id": 0, "user_id": normal_user.id},
|
||||
),
|
||||
format="json",
|
||||
HTTP_AUTHORIZATION=f"JWT {admin_token}",
|
||||
)
|
||||
assert response.status_code == HTTP_404_NOT_FOUND
|
||||
assert response.json()["error"] == "ERROR_PREMIUM_LICENSE_DOES_NOT_EXIST"
|
||||
|
||||
response = api_client.delete(
|
||||
reverse(
|
||||
"api:premium:license:user",
|
||||
kwargs={"id": license.id, "user_id": normal_user.id},
|
||||
),
|
||||
format="json",
|
||||
HTTP_AUTHORIZATION=f"JWT {admin_token}",
|
||||
)
|
||||
assert response.status_code == HTTP_204_NO_CONTENT
|
||||
assert LicenseUser.objects.all().count() == 0
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@override_settings(DEBUG=True)
|
||||
def test_admin_fill_users_in_license(api_client, data_fixture):
|
||||
normal_user, normal_token = data_fixture.create_user_and_token(
|
||||
is_staff=False,
|
||||
)
|
||||
admin_user, admin_token = data_fixture.create_user_and_token(
|
||||
is_staff=True,
|
||||
)
|
||||
|
||||
license = License.objects.create(license=VALID_TWO_SEAT_LICENSE.decode())
|
||||
|
||||
response = api_client.post(
|
||||
reverse(
|
||||
"api:premium:license:fill_seats",
|
||||
kwargs={"id": license.id},
|
||||
),
|
||||
format="json",
|
||||
HTTP_AUTHORIZATION=f"JWT {normal_token}",
|
||||
)
|
||||
assert response.status_code == HTTP_403_FORBIDDEN
|
||||
|
||||
response = api_client.post(
|
||||
reverse(
|
||||
"api:premium:license:fill_seats",
|
||||
kwargs={"id": 0},
|
||||
),
|
||||
format="json",
|
||||
HTTP_AUTHORIZATION=f"JWT {admin_token}",
|
||||
)
|
||||
assert response.status_code == HTTP_404_NOT_FOUND
|
||||
assert response.json()["error"] == "ERROR_PREMIUM_LICENSE_DOES_NOT_EXIST"
|
||||
|
||||
response = api_client.post(
|
||||
reverse(
|
||||
"api:premium:license:fill_seats",
|
||||
kwargs={"id": license.id},
|
||||
),
|
||||
format="json",
|
||||
HTTP_AUTHORIZATION=f"JWT {admin_token}",
|
||||
)
|
||||
assert response.status_code == HTTP_200_OK
|
||||
response_json = response.json()
|
||||
print(response_json)
|
||||
assert len(response_json) == 2
|
||||
assert response_json[0]["id"] == normal_user.id
|
||||
assert response_json[0]["email"] == normal_user.email
|
||||
assert response_json[0]["first_name"] == normal_user.first_name
|
||||
assert response_json[1]["id"] == admin_user.id
|
||||
assert response_json[1]["email"] == admin_user.email
|
||||
assert response_json[1]["first_name"] == admin_user.first_name
|
||||
assert LicenseUser.objects.all().count() == 2
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@override_settings(DEBUG=True)
|
||||
def test_admin_remove_all_users(api_client, data_fixture):
|
||||
normal_user, normal_token = data_fixture.create_user_and_token(
|
||||
is_staff=False,
|
||||
)
|
||||
admin_user, admin_token = data_fixture.create_user_and_token(
|
||||
is_staff=True,
|
||||
)
|
||||
|
||||
license = License.objects.create(license=VALID_TWO_SEAT_LICENSE.decode())
|
||||
LicenseUser.objects.create(license=license, user=normal_user)
|
||||
LicenseUser.objects.create(license=license, user=admin_user)
|
||||
|
||||
response = api_client.post(
|
||||
reverse(
|
||||
"api:premium:license:remove_all_users",
|
||||
kwargs={"id": license.id},
|
||||
),
|
||||
format="json",
|
||||
HTTP_AUTHORIZATION=f"JWT {normal_token}",
|
||||
)
|
||||
assert response.status_code == HTTP_403_FORBIDDEN
|
||||
|
||||
response = api_client.post(
|
||||
reverse(
|
||||
"api:premium:license:remove_all_users",
|
||||
kwargs={"id": 0},
|
||||
),
|
||||
format="json",
|
||||
HTTP_AUTHORIZATION=f"JWT {admin_token}",
|
||||
)
|
||||
assert response.status_code == HTTP_404_NOT_FOUND
|
||||
assert response.json()["error"] == "ERROR_PREMIUM_LICENSE_DOES_NOT_EXIST"
|
||||
|
||||
response = api_client.post(
|
||||
reverse(
|
||||
"api:premium:license:remove_all_users",
|
||||
kwargs={"id": license.id},
|
||||
),
|
||||
format="json",
|
||||
HTTP_AUTHORIZATION=f"JWT {admin_token}",
|
||||
)
|
||||
assert response.status_code == HTTP_204_NO_CONTENT
|
||||
assert LicenseUser.objects.all().count() == 0
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_admin_license_user_lookup(api_client, data_fixture):
|
||||
normal_user, normal_token = data_fixture.create_user_and_token(
|
||||
is_staff=False, first_name="Test", email="email@localhost"
|
||||
)
|
||||
normal_user_2 = data_fixture.create_user(first_name="Foo", email="tmp@localhost")
|
||||
admin_user, admin_token = data_fixture.create_user_and_token(
|
||||
is_staff=True, first_name="Admin test", email="admin@localhost"
|
||||
)
|
||||
|
||||
license = License.objects.create(license=VALID_TWO_SEAT_LICENSE.decode())
|
||||
LicenseUser.objects.create(license=license, user=admin_user)
|
||||
license_2 = License.objects.create(license=VALID_ONE_SEAT_LICENSE.decode())
|
||||
LicenseUser.objects.create(license=license_2, user=normal_user)
|
||||
|
||||
response = api_client.get(
|
||||
reverse(
|
||||
"api:premium:license:lookup_users",
|
||||
kwargs={"id": license.id},
|
||||
),
|
||||
format="json",
|
||||
HTTP_AUTHORIZATION=f"JWT {normal_token}",
|
||||
)
|
||||
assert response.status_code == HTTP_403_FORBIDDEN
|
||||
|
||||
response = api_client.get(
|
||||
reverse(
|
||||
"api:premium:license:lookup_users",
|
||||
kwargs={"id": 0},
|
||||
),
|
||||
format="json",
|
||||
HTTP_AUTHORIZATION=f"JWT {admin_token}",
|
||||
)
|
||||
assert response.status_code == HTTP_404_NOT_FOUND
|
||||
assert response.json()["error"] == "ERROR_PREMIUM_LICENSE_DOES_NOT_EXIST"
|
||||
|
||||
response = api_client.get(
|
||||
reverse(
|
||||
"api:premium:license:lookup_users",
|
||||
kwargs={"id": license.id},
|
||||
),
|
||||
format="json",
|
||||
HTTP_AUTHORIZATION=f"JWT {admin_token}",
|
||||
)
|
||||
assert response.status_code == HTTP_200_OK
|
||||
response_json = response.json()
|
||||
assert response_json["count"] == 2
|
||||
assert len(response_json["results"]) == 2
|
||||
assert response_json["results"][0]["id"] == normal_user_2.id
|
||||
assert response_json["results"][0]["value"] == "Foo (tmp@localhost)"
|
||||
|
||||
url = reverse(
|
||||
"api:premium:license:lookup_users",
|
||||
kwargs={"id": license.id},
|
||||
)
|
||||
response = api_client.get(
|
||||
f"{url}?page=2",
|
||||
format="json",
|
||||
HTTP_AUTHORIZATION=f"JWT {admin_token}",
|
||||
)
|
||||
assert response.status_code == HTTP_400_BAD_REQUEST
|
||||
assert response.json()["error"] == "ERROR_INVALID_PAGE"
|
||||
|
||||
url = reverse(
|
||||
"api:premium:license:lookup_users",
|
||||
kwargs={"id": license.id},
|
||||
)
|
||||
response = api_client.get(
|
||||
f"{url}?page=2&size=1",
|
||||
format="json",
|
||||
HTTP_AUTHORIZATION=f"JWT {admin_token}",
|
||||
)
|
||||
assert response.status_code == HTTP_200_OK
|
||||
response_json = response.json()
|
||||
assert response_json["count"] == 2
|
||||
assert len(response_json["results"]) == 1
|
||||
assert response_json["results"][0]["id"] == normal_user.id
|
||||
assert response_json["results"][0]["value"] == "Test (email@localhost)"
|
||||
|
||||
url = reverse(
|
||||
"api:premium:license:lookup_users",
|
||||
kwargs={"id": license.id},
|
||||
)
|
||||
response = api_client.get(
|
||||
f"{url}?search=test",
|
||||
format="json",
|
||||
HTTP_AUTHORIZATION=f"JWT {admin_token}",
|
||||
)
|
||||
assert response.status_code == HTTP_200_OK
|
||||
response_json = response.json()
|
||||
assert response_json["count"] == 1
|
||||
assert len(response_json["results"]) == 1
|
||||
assert response_json["results"][0]["id"] == normal_user.id
|
||||
assert response_json["results"][0]["value"] == "Test (email@localhost)"
|
||||
|
||||
url = reverse(
|
||||
"api:premium:license:lookup_users",
|
||||
kwargs={"id": license.id},
|
||||
)
|
||||
response = api_client.get(
|
||||
f"{url}?search=email",
|
||||
format="json",
|
||||
HTTP_AUTHORIZATION=f"JWT {admin_token}",
|
||||
)
|
||||
assert response.status_code == HTTP_200_OK
|
||||
response_json = response.json()
|
||||
assert response_json["count"] == 1
|
||||
assert len(response_json["results"]) == 1
|
||||
assert response_json["results"][0]["id"] == normal_user.id
|
||||
assert response_json["results"][0]["value"] == "Test (email@localhost)"
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@override_settings(DEBUG=True)
|
||||
@responses.activate
|
||||
def test_admin_check_license(api_client, data_fixture):
|
||||
normal_user, normal_token = data_fixture.create_user_and_token(
|
||||
is_staff=False,
|
||||
)
|
||||
admin_user, admin_token = data_fixture.create_user_and_token(
|
||||
is_staff=True,
|
||||
)
|
||||
|
||||
license_1 = License.objects.create(license=VALID_ONE_SEAT_LICENSE.decode())
|
||||
license_2 = License.objects.create(license=VALID_TWO_SEAT_LICENSE.decode())
|
||||
|
||||
response = api_client.get(
|
||||
reverse(
|
||||
"api:premium:license:check",
|
||||
kwargs={"id": license_1.id},
|
||||
),
|
||||
format="json",
|
||||
HTTP_AUTHORIZATION=f"JWT {normal_token}",
|
||||
)
|
||||
assert response.status_code == HTTP_403_FORBIDDEN
|
||||
|
||||
response = api_client.get(
|
||||
reverse(
|
||||
"api:premium:license:check",
|
||||
kwargs={"id": 0},
|
||||
),
|
||||
format="json",
|
||||
HTTP_AUTHORIZATION=f"JWT {admin_token}",
|
||||
)
|
||||
assert response.status_code == HTTP_404_NOT_FOUND
|
||||
assert response.json()["error"] == "ERROR_PREMIUM_LICENSE_DOES_NOT_EXIST"
|
||||
|
||||
with freeze_time("2021-07-01 12:00"):
|
||||
responses.add(
|
||||
responses.POST,
|
||||
"http://172.17.0.1:8001/api/saas/licenses/check/",
|
||||
json={
|
||||
VALID_ONE_SEAT_LICENSE.decode(): {
|
||||
"type": "invalid",
|
||||
"detail": "",
|
||||
},
|
||||
VALID_TWO_SEAT_LICENSE.decode(): {
|
||||
"type": "ok",
|
||||
"detail": "",
|
||||
},
|
||||
},
|
||||
status=200,
|
||||
)
|
||||
|
||||
response = api_client.get(
|
||||
reverse(
|
||||
"api:premium:license:check",
|
||||
kwargs={"id": license_1.id},
|
||||
),
|
||||
format="json",
|
||||
HTTP_AUTHORIZATION=f"JWT {admin_token}",
|
||||
)
|
||||
assert response.status_code == HTTP_204_NO_CONTENT
|
||||
|
||||
response = api_client.get(
|
||||
reverse(
|
||||
"api:premium:license:check",
|
||||
kwargs={"id": license_2.id},
|
||||
),
|
||||
format="json",
|
||||
HTTP_AUTHORIZATION=f"JWT {admin_token}",
|
||||
)
|
||||
assert response.status_code == HTTP_200_OK
|
||||
response_json = response.json()
|
||||
assert response_json["id"] == license_2.id
|
||||
assert response_json["license_id"] == "2"
|
||||
assert len(response_json["users"]) == 0
|
|
@ -1,8 +1,14 @@
|
|||
import pytest
|
||||
from django.conf import settings
|
||||
from django.test.utils import override_settings
|
||||
from freezegun import freeze_time
|
||||
from rest_framework.reverse import reverse
|
||||
from rest_framework.status import HTTP_200_OK, HTTP_400_BAD_REQUEST, HTTP_404_NOT_FOUND
|
||||
from rest_framework.status import (
|
||||
HTTP_200_OK,
|
||||
HTTP_402_PAYMENT_REQUIRED,
|
||||
HTTP_400_BAD_REQUEST,
|
||||
HTTP_404_NOT_FOUND,
|
||||
)
|
||||
|
||||
from baserow.core.models import TrashEntry
|
||||
from baserow.core.trash.handler import TrashHandler
|
||||
|
@ -10,9 +16,12 @@ from baserow_premium.row_comments.models import RowComment
|
|||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_row_comments_api_view(data_fixture, api_client):
|
||||
user, token = data_fixture.create_user_and_token(first_name="Test User")
|
||||
table, fields, rows = data_fixture.build_table(
|
||||
@override_settings(DEBUG=True)
|
||||
def test_row_comments_api_view(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
|
||||
)
|
||||
|
||||
|
@ -73,8 +82,35 @@ def test_row_comments_api_view(data_fixture, api_client):
|
|||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_row_comments_cant_view_comments_for_invalid_table(data_fixture, api_client):
|
||||
user, token = data_fixture.create_user_and_token(first_name="Test User")
|
||||
@override_settings(DEBUG=True)
|
||||
def test_row_comments_api_view_without_premium_license(
|
||||
premium_data_fixture, api_client
|
||||
):
|
||||
user, token = premium_data_fixture.create_user_and_token(first_name="Test User")
|
||||
table, fields, rows = premium_data_fixture.build_table(
|
||||
columns=[("text", "text")], rows=["first row", "second_row"], user=user
|
||||
)
|
||||
|
||||
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(
|
||||
premium_data_fixture, api_client
|
||||
):
|
||||
user, token = premium_data_fixture.create_user_and_token(
|
||||
first_name="Test User", has_active_premium_license=True
|
||||
)
|
||||
|
||||
response = api_client.get(
|
||||
reverse(
|
||||
|
@ -89,11 +125,14 @@ def test_row_comments_cant_view_comments_for_invalid_table(data_fixture, api_cli
|
|||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@override_settings(DEBUG=True)
|
||||
def test_row_comments_cant_view_comments_for_invalid_row_in_table(
|
||||
data_fixture, api_client
|
||||
premium_data_fixture, api_client
|
||||
):
|
||||
user, token = data_fixture.create_user_and_token(first_name="Test User")
|
||||
table, fields, rows = data_fixture.build_table(
|
||||
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"], user=user
|
||||
)
|
||||
|
||||
|
@ -110,12 +149,17 @@ def test_row_comments_cant_view_comments_for_invalid_row_in_table(
|
|||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@override_settings(DEBUG=True)
|
||||
def test_row_comments_users_cant_view_comments_for_table_they_are_not_in_group_for(
|
||||
data_fixture, api_client
|
||||
premium_data_fixture, api_client
|
||||
):
|
||||
user, token = data_fixture.create_user_and_token(first_name="Test User")
|
||||
other_user, other_token = data_fixture.create_user_and_token(first_name="Test User")
|
||||
table, fields, rows = data_fixture.build_table(
|
||||
user, token = premium_data_fixture.create_user_and_token(
|
||||
first_name="Test User", has_active_premium_license=True
|
||||
)
|
||||
other_user, other_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"], user=user
|
||||
)
|
||||
|
||||
|
@ -142,8 +186,13 @@ def test_row_comments_users_cant_view_comments_for_table_they_are_not_in_group_f
|
|||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_row_comments_cant_create_comments_in_invalid_table(data_fixture, api_client):
|
||||
user, token = data_fixture.create_user_and_token(first_name="Test User")
|
||||
@override_settings(DEBUG=True)
|
||||
def test_row_comments_cant_create_comments_in_invalid_table(
|
||||
premium_data_fixture, api_client
|
||||
):
|
||||
user, token = premium_data_fixture.create_user_and_token(
|
||||
first_name="Test User", has_active_premium_license=True
|
||||
)
|
||||
|
||||
response = api_client.post(
|
||||
reverse(
|
||||
|
@ -159,11 +208,14 @@ def test_row_comments_cant_create_comments_in_invalid_table(data_fixture, api_cl
|
|||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@override_settings(DEBUG=True)
|
||||
def test_row_comments_cant_create_comments_in_invalid_row_in_table(
|
||||
data_fixture, api_client
|
||||
premium_data_fixture, api_client
|
||||
):
|
||||
user, token = data_fixture.create_user_and_token(first_name="Test User")
|
||||
table, fields, rows = data_fixture.build_table(
|
||||
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"], user=user
|
||||
)
|
||||
|
||||
|
@ -181,12 +233,17 @@ def test_row_comments_cant_create_comments_in_invalid_row_in_table(
|
|||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@override_settings(DEBUG=True)
|
||||
def test_row_comments_users_cant_create_comments_for_table_they_are_not_in_group_for(
|
||||
data_fixture, api_client
|
||||
premium_data_fixture, api_client
|
||||
):
|
||||
user, token = data_fixture.create_user_and_token(first_name="Test User")
|
||||
other_user, other_token = data_fixture.create_user_and_token(first_name="Test User")
|
||||
table, fields, rows = data_fixture.build_table(
|
||||
user, token = premium_data_fixture.create_user_and_token(
|
||||
first_name="Test User", has_active_premium_license=True
|
||||
)
|
||||
other_user, other_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"], user=user
|
||||
)
|
||||
|
||||
|
@ -215,9 +272,12 @@ def test_row_comments_users_cant_create_comments_for_table_they_are_not_in_group
|
|||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_cant_make_a_blank_row_comment(data_fixture, api_client):
|
||||
user, token = data_fixture.create_user_and_token(first_name="Test User")
|
||||
table, fields, rows = data_fixture.build_table(
|
||||
@override_settings(DEBUG=True)
|
||||
def test_cant_make_a_blank_row_comment(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"], user=user
|
||||
)
|
||||
|
||||
|
@ -238,9 +298,14 @@ def test_cant_make_a_blank_row_comment(data_fixture, api_client):
|
|||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_cant_make_a_row_comment_greater_than_max_settings(data_fixture, api_client):
|
||||
user, token = data_fixture.create_user_and_token(first_name="Test User")
|
||||
table, fields, rows = data_fixture.build_table(
|
||||
@override_settings(DEBUG=True)
|
||||
def test_cant_make_a_row_comment_greater_than_max_settings(
|
||||
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"], user=user
|
||||
)
|
||||
response = api_client.post(
|
||||
|
@ -277,9 +342,12 @@ def test_cant_make_a_row_comment_greater_than_max_settings(data_fixture, api_cli
|
|||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_cant_make_a_null_row_comment(data_fixture, api_client):
|
||||
user, token = data_fixture.create_user_and_token(first_name="Test User")
|
||||
table, fields, rows = data_fixture.build_table(
|
||||
@override_settings(DEBUG=True)
|
||||
def test_cant_make_a_null_row_comment(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"], user=user
|
||||
)
|
||||
|
||||
|
@ -300,9 +368,33 @@ def test_cant_make_a_null_row_comment(data_fixture, api_client):
|
|||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_trashing_the_row_returns_404_for_comments(data_fixture, api_client):
|
||||
user, token = data_fixture.create_user_and_token(first_name="Test User")
|
||||
table, fields, rows = data_fixture.build_table(
|
||||
@override_settings(DEBUG=True)
|
||||
def test_cant_make_a_row_without_premium_license(premium_data_fixture, api_client):
|
||||
user, token = premium_data_fixture.create_user_and_token(first_name="Test User")
|
||||
table, fields, rows = premium_data_fixture.build_table(
|
||||
columns=[("text", "text")], rows=["first row"], user=user
|
||||
)
|
||||
|
||||
response = api_client.post(
|
||||
reverse(
|
||||
"api:premium:row_comments:item",
|
||||
kwargs={"table_id": table.id, "row_id": rows[0].id},
|
||||
),
|
||||
{"comment": "test"},
|
||||
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_trashing_the_row_returns_404_for_comments(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"], user=user
|
||||
)
|
||||
|
||||
|
@ -367,11 +459,14 @@ def test_trashing_the_row_returns_404_for_comments(data_fixture, api_client):
|
|||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@override_settings(DEBUG=True)
|
||||
def test_perm_deleting_a_trashed_row_with_comments_cleans_up_the_rows(
|
||||
data_fixture, api_client
|
||||
premium_data_fixture, api_client
|
||||
):
|
||||
user, token = data_fixture.create_user_and_token(first_name="Test User")
|
||||
table, fields, rows = data_fixture.build_table(
|
||||
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
|
||||
)
|
||||
|
||||
|
@ -424,14 +519,17 @@ def test_perm_deleting_a_trashed_row_with_comments_cleans_up_the_rows(
|
|||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@override_settings(DEBUG=True)
|
||||
def test_perm_deleting_a_trashed_table_with_comments_cleans_up_the_rows(
|
||||
data_fixture, api_client
|
||||
premium_data_fixture, api_client
|
||||
):
|
||||
user, token = data_fixture.create_user_and_token(first_name="Test User")
|
||||
table, fields, rows = data_fixture.build_table(
|
||||
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
|
||||
)
|
||||
other_table, other_fields, other_rows = data_fixture.build_table(
|
||||
other_table, other_fields, other_rows = premium_data_fixture.build_table(
|
||||
columns=[("text", "text")], rows=["first row", "second row"], user=user
|
||||
)
|
||||
|
||||
|
@ -483,17 +581,22 @@ def test_perm_deleting_a_trashed_table_with_comments_cleans_up_the_rows(
|
|||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@override_settings(DEBUG=True)
|
||||
def test_getting_row_comments_executes_fixed_number_of_queries(
|
||||
data_fixture, api_client, django_assert_num_queries
|
||||
premium_data_fixture, api_client, django_assert_num_queries
|
||||
):
|
||||
user, token = data_fixture.create_user_and_token(first_name="Test User")
|
||||
other_user, other_token = data_fixture.create_user_and_token(first_name="Test User")
|
||||
user, token = premium_data_fixture.create_user_and_token(
|
||||
first_name="Test User", has_active_premium_license=True
|
||||
)
|
||||
other_user, other_token = premium_data_fixture.create_user_and_token(
|
||||
first_name="Test User", has_active_premium_license=True
|
||||
)
|
||||
|
||||
table, fields, rows = data_fixture.build_table(
|
||||
table, fields, rows = premium_data_fixture.build_table(
|
||||
columns=[("text", "text")], rows=["first row", "second row"], user=user
|
||||
)
|
||||
|
||||
data_fixture.create_user_group(user=other_user, group=table.database.group)
|
||||
premium_data_fixture.create_user_group(user=other_user, group=table.database.group)
|
||||
|
||||
response = api_client.post(
|
||||
reverse(
|
||||
|
@ -506,7 +609,7 @@ def test_getting_row_comments_executes_fixed_number_of_queries(
|
|||
)
|
||||
assert response.status_code == HTTP_200_OK
|
||||
|
||||
expected_num_of_fixed_queries = 6
|
||||
expected_num_of_fixed_queries = 7
|
||||
with django_assert_num_queries(expected_num_of_fixed_queries):
|
||||
response = api_client.get(
|
||||
reverse(
|
||||
|
|
|
@ -1,2 +1,15 @@
|
|||
import pytest
|
||||
|
||||
|
||||
# noinspection PyUnresolvedReferences
|
||||
from tests.conftest import * # noqa: F403, F401
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def premium_data_fixture(data_fixture):
|
||||
from .fixtures import PremiumFixtures
|
||||
|
||||
class PremiumFixtures(PremiumFixtures, data_fixture.__class__):
|
||||
pass
|
||||
|
||||
return PremiumFixtures()
|
||||
|
|
|
@ -4,10 +4,14 @@ from unittest.mock import patch
|
|||
import pytest
|
||||
from django.utils.dateparse import parse_date, parse_datetime
|
||||
from django.utils.timezone import utc, make_aware
|
||||
from django.test.utils import override_settings
|
||||
|
||||
from baserow.contrib.database.export.handler import ExportHandler
|
||||
from baserow.contrib.database.rows.handler import RowHandler
|
||||
from tests.test_utils import setup_interesting_test_table
|
||||
|
||||
from baserow_premium.license.exceptions import NoPremiumLicenseError
|
||||
|
||||
from baserow.test_utils.helpers import setup_interesting_test_table
|
||||
|
||||
|
||||
def _parse_datetime(datetime):
|
||||
|
@ -19,12 +23,16 @@ def _parse_date(date):
|
|||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@override_settings(DEBUG=True)
|
||||
@patch("baserow.contrib.database.export.handler.default_storage")
|
||||
def test_can_export_every_interesting_different_field_to_json(
|
||||
storage_mock, data_fixture
|
||||
storage_mock, premium_data_fixture
|
||||
):
|
||||
contents = run_export_over_interesting_test_table(
|
||||
data_fixture, storage_mock, {"exporter_type": "json"}
|
||||
premium_data_fixture,
|
||||
storage_mock,
|
||||
{"exporter_type": "json"},
|
||||
user_kwargs={"has_active_premium_license": True},
|
||||
)
|
||||
assert (
|
||||
contents
|
||||
|
@ -128,16 +136,27 @@ def test_can_export_every_interesting_different_field_to_json(
|
|||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@override_settings(DEBUG=True)
|
||||
@patch("baserow.contrib.database.export.handler.default_storage")
|
||||
def test_if_duplicate_field_names_json_export(storage_mock, data_fixture):
|
||||
user = data_fixture.create_user()
|
||||
database = data_fixture.create_database_application(user=user)
|
||||
table = data_fixture.create_database_table(database=database)
|
||||
data_fixture.create_text_field(table=table, name="name", order=1)
|
||||
data_fixture.create_text_field(table=table, name="name", order=2)
|
||||
data_fixture.create_text_field(table=table, name="name", order=3)
|
||||
data_fixture.create_text_field(table=table, name='Another"name', order=4)
|
||||
data_fixture.create_text_field(table=table, name='Another"name', order=5)
|
||||
def test_cannot_export_json_without_premium_license(storage_mock, premium_data_fixture):
|
||||
with pytest.raises(NoPremiumLicenseError):
|
||||
run_export_over_interesting_test_table(
|
||||
premium_data_fixture, storage_mock, {"exporter_type": "json"}
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@override_settings(DEBUG=True)
|
||||
@patch("baserow.contrib.database.export.handler.default_storage")
|
||||
def test_if_duplicate_field_names_json_export(storage_mock, premium_data_fixture):
|
||||
user = premium_data_fixture.create_user(has_active_premium_license=True)
|
||||
database = premium_data_fixture.create_database_application(user=user)
|
||||
table = premium_data_fixture.create_database_table(database=database)
|
||||
premium_data_fixture.create_text_field(table=table, name="name", order=1)
|
||||
premium_data_fixture.create_text_field(table=table, name="name", order=2)
|
||||
premium_data_fixture.create_text_field(table=table, name="name", order=3)
|
||||
premium_data_fixture.create_text_field(table=table, name='Another"name', order=4)
|
||||
premium_data_fixture.create_text_field(table=table, name='Another"name', order=5)
|
||||
row_handler = RowHandler()
|
||||
row_handler.create_row(user=user, table=table)
|
||||
job, contents = run_export_job_with_mock_storage(
|
||||
|
@ -160,12 +179,16 @@ def test_if_duplicate_field_names_json_export(storage_mock, data_fixture):
|
|||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@override_settings(DEBUG=True)
|
||||
@patch("baserow.contrib.database.export.handler.default_storage")
|
||||
def test_can_export_every_interesting_different_field_to_xml(
|
||||
storage_mock, data_fixture
|
||||
storage_mock, premium_data_fixture
|
||||
):
|
||||
xml = run_export_over_interesting_test_table(
|
||||
data_fixture, storage_mock, {"exporter_type": "xml"}
|
||||
premium_data_fixture,
|
||||
storage_mock,
|
||||
{"exporter_type": "xml"},
|
||||
user_kwargs={"has_active_premium_license": True},
|
||||
)
|
||||
expected_xml = f"""<?xml version="1.0" encoding="utf-8" ?>
|
||||
<rows>
|
||||
|
@ -270,18 +293,21 @@ def test_can_export_every_interesting_different_field_to_xml(
|
|||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@override_settings(DEBUG=True)
|
||||
@patch("baserow.contrib.database.export.handler.default_storage")
|
||||
def test_if_xml_duplicate_name_and_value_are_escaped(storage_mock, data_fixture):
|
||||
user = data_fixture.create_user()
|
||||
database = data_fixture.create_database_application(user=user)
|
||||
table = data_fixture.create_database_table(database=database)
|
||||
text = data_fixture.create_text_field(table=table, name="<name>", order=0)
|
||||
data_fixture.create_text_field(table=table, name="name", order=1)
|
||||
data_fixture.create_text_field(table=table, name="Another name", order=2)
|
||||
data_fixture.create_text_field(table=table, name="Another@name", order=3)
|
||||
empty_1 = data_fixture.create_text_field(table=table, name="@", order=4)
|
||||
empty_2 = data_fixture.create_text_field(table=table, name="", order=5)
|
||||
data_fixture.create_text_field(table=table, name="1", order=6)
|
||||
def test_if_xml_duplicate_name_and_value_are_escaped(
|
||||
storage_mock, premium_data_fixture
|
||||
):
|
||||
user = premium_data_fixture.create_user(has_active_premium_license=True)
|
||||
database = premium_data_fixture.create_database_application(user=user)
|
||||
table = premium_data_fixture.create_database_table(database=database)
|
||||
text = premium_data_fixture.create_text_field(table=table, name="<name>", order=0)
|
||||
premium_data_fixture.create_text_field(table=table, name="name", order=1)
|
||||
premium_data_fixture.create_text_field(table=table, name="Another name", order=2)
|
||||
premium_data_fixture.create_text_field(table=table, name="Another@name", order=3)
|
||||
empty_1 = premium_data_fixture.create_text_field(table=table, name="@", order=4)
|
||||
empty_2 = premium_data_fixture.create_text_field(table=table, name="", order=5)
|
||||
premium_data_fixture.create_text_field(table=table, name="1", order=6)
|
||||
row_handler = RowHandler()
|
||||
row_handler.create_row(
|
||||
user=user,
|
||||
|
@ -310,13 +336,25 @@ def test_if_xml_duplicate_name_and_value_are_escaped(storage_mock, data_fixture)
|
|||
)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@override_settings(DEBUG=True)
|
||||
@patch("baserow.contrib.database.export.handler.default_storage")
|
||||
def test_cannot_export_xml_without_premium_license(storage_mock, premium_data_fixture):
|
||||
with pytest.raises(NoPremiumLicenseError):
|
||||
run_export_over_interesting_test_table(
|
||||
premium_data_fixture, storage_mock, {"exporter_type": "xml"}
|
||||
)
|
||||
|
||||
|
||||
def strip_indents_and_newlines(xml):
|
||||
return "".join([line.strip() for line in xml.split("\n")])
|
||||
|
||||
|
||||
def run_export_over_interesting_test_table(data_fixture, storage_mock, options):
|
||||
table, user, _, _ = setup_interesting_test_table(data_fixture)
|
||||
grid_view = data_fixture.create_grid_view(table=table)
|
||||
def run_export_over_interesting_test_table(
|
||||
premium_data_fixture, storage_mock, options, user_kwargs=None
|
||||
):
|
||||
table, user, _, _ = setup_interesting_test_table(premium_data_fixture, user_kwargs)
|
||||
grid_view = premium_data_fixture.create_grid_view(table=table)
|
||||
job, contents = run_export_job_with_mock_storage(
|
||||
table, grid_view, storage_mock, user, options
|
||||
)
|
||||
|
|
65
premium/backend/tests/baserow_premium/fixtures.py
Normal file
65
premium/backend/tests/baserow_premium/fixtures.py
Normal file
|
@ -0,0 +1,65 @@
|
|||
from baserow_premium.license.models import License, LicenseUser
|
||||
|
||||
|
||||
VALID_ONE_SEAT_LICENSE = (
|
||||
# id: "1", instance_id: "1"
|
||||
b"eyJ2ZXJzaW9uIjogMSwgImlkIjogIjEiLCAidmFsaWRfZnJvbSI6ICIyMDIxLTA4LTI5VDE5OjUyOjU3"
|
||||
b"Ljg0MjY5NiIsICJ2YWxpZF90aHJvdWdoIjogIjIwMjEtMDktMjlUMTk6NTI6NTcuODQyNjk2IiwgInBy"
|
||||
b"b2R1Y3RfY29kZSI6ICJwcmVtaXVtIiwgInNlYXRzIjogMSwgImlzc3VlZF9vbiI6ICIyMDIxLTA4LTI5"
|
||||
b"VDE5OjUyOjU3Ljg0MjY5NiIsICJpc3N1ZWRfdG9fZW1haWwiOiAiYnJhbUBiYXNlcm93LmlvIiwgImlz"
|
||||
b"c3VlZF90b19uYW1lIjogIkJyYW0iLCAiaW5zdGFuY2VfaWQiOiAiMSJ9.e33Z4CxLSmD-R55Es24P3mR"
|
||||
b"8Oqn3LpaXvgYLzF63oFHat3paon7IBjBmOX3eyd8KjirVf3empJds4uUw2Nn2m7TVvRAtJ8XzNl-8ytf"
|
||||
b"2RLtmjMx1Xkgp5VZ8S7UqJ_cKLyl76eVRtGEA1DH2HdPKu1vBPJ4bzDfnhDPYl4k5z9XSSgqAbQ9WO0U"
|
||||
b"5kiI3BYjVRZSKnZMeguAGZ47ezDj_WArGcHAB8Pa2v3HFp5Y34DMJ8r3_hD5hxCKgoNx4AHx1Q-hRDqp"
|
||||
b"Aroj-4jl7KWvlP-OJNc1BgH2wnhFmeKHotv-Iumi83JQohyceUbG6j8rDDQvJfcn0W2_ebmUH3TKr-w="
|
||||
b"="
|
||||
)
|
||||
VALID_100_SEAT_LICENSE_UNTIL_YEAR_2099 = (
|
||||
# id: "test-license", instance_id: "1"
|
||||
# valid from the year 1000 through the year 2099
|
||||
b"eyJ2ZXJzaW9uIjogMSwgImlkIjogInRlc3QtbGljZW5zZSIsICJ2YWxpZF9mcm9tIjogIjEwMDAtMDEt"
|
||||
b"MDFUMTI6MDA6MDAuMDAwMDAwIiwgInZhbGlkX3Rocm91Z2giOiAiMjA5OS0wMS0wMVQxMjowMDowMC4w"
|
||||
b"MDAwMDAiLCAicHJvZHVjdF9jb2RlIjogInByZW1pdW0iLCAic2VhdHMiOiAxMDAsICJpc3N1ZWRfb24i"
|
||||
b"OiAiMjAyMS0wOC0yOVQxOTo1Mjo1Ny44NDI2OTYiLCAiaXNzdWVkX3RvX2VtYWlsIjogImJyYW1AYmFz"
|
||||
b"ZXJvdy5pbyIsICJpc3N1ZWRfdG9fbmFtZSI6ICJCcmFtIiwgImluc3RhbmNlX2lkIjogIjEifQ==.SoF"
|
||||
b"QKxwNjNM-lvJ4iy7d8dc4EmWZagMBzgAmQgUJoGo6FtXaTOILOnc3Tm2uSwZ2MImBeehIff8GPE521-k"
|
||||
b"a9-0DDYEX4BYVgpLxLF3gFZxgX0QJgsNsauOjEZH8IGFGX1Asdsll2rNbzYDKz68jG7GgK04Lfn19cQ-"
|
||||
b"Qg0Qlgq0gB_7CoUulo73fhCjOZHoH1HAzxh77SbgXxJbDQOYlXqortVvl5vDpNcPTbar4IihBJRgaFTM"
|
||||
b"7DjR0On8GCX7j944VkXguiGPdglBXTcqRbPf1qqmZ8jaHrKX6wHYysBJs10OqWqT5p_s8cuRrr0IzLDz"
|
||||
b"Ss-q11zuFn-ekeJzo5A=="
|
||||
)
|
||||
|
||||
|
||||
class PremiumFixtures:
|
||||
def create_user(self, *args, **kwargs):
|
||||
has_active_premium_license = kwargs.pop("has_active_premium_license", False)
|
||||
user = super().create_user(*args, **kwargs)
|
||||
|
||||
if has_active_premium_license:
|
||||
self.create_active_premium_license_for_user(user)
|
||||
|
||||
return user
|
||||
|
||||
def create_active_premium_license_for_user(self, user):
|
||||
test_license, created = License.objects.get_or_create(
|
||||
license=VALID_100_SEAT_LICENSE_UNTIL_YEAR_2099.decode()
|
||||
)
|
||||
LicenseUser.objects.get_or_create(user=user, license=test_license)
|
||||
|
||||
def remove_all_active_premium_licenses(self, user):
|
||||
LicenseUser.objects.filter(user=user).delete()
|
||||
|
||||
def create_premium_license(self, **kwargs):
|
||||
if "license" not in kwargs:
|
||||
kwargs["license"] = VALID_100_SEAT_LICENSE_UNTIL_YEAR_2099.decode()
|
||||
|
||||
return License.objects.create(**kwargs)
|
||||
|
||||
def create_premium_license_user(self, **kwargs):
|
||||
if "user" not in kwargs:
|
||||
kwargs["user"] = self.create_user()
|
||||
|
||||
if "license" not in kwargs:
|
||||
kwargs["license"] = self.create_premium_license()
|
||||
|
||||
return LicenseUser.objects.create(**kwargs)
|
|
@ -0,0 +1,841 @@
|
|||
import pytest
|
||||
import responses
|
||||
import base64
|
||||
from freezegun import freeze_time
|
||||
from unittest.mock import patch
|
||||
|
||||
from cryptography.exceptions import InvalidSignature
|
||||
from cryptography.hazmat.primitives import hashes
|
||||
from cryptography.hazmat.primitives.asymmetric import padding
|
||||
|
||||
from django.db import transaction
|
||||
from django.test.utils import override_settings
|
||||
|
||||
from baserow.core.exceptions import IsNotAdminError
|
||||
|
||||
from baserow_premium.license.handler import (
|
||||
has_active_premium_license,
|
||||
check_active_premium_license,
|
||||
get_public_key,
|
||||
decode_license,
|
||||
fetch_license_status_with_authority,
|
||||
check_licenses,
|
||||
register_license,
|
||||
remove_license,
|
||||
add_user_to_license,
|
||||
remove_user_from_license,
|
||||
fill_remaining_seats_of_license,
|
||||
remove_all_users_from_license,
|
||||
)
|
||||
from baserow_premium.license.models import License, LicenseUser
|
||||
from baserow_premium.license.exceptions import (
|
||||
NoPremiumLicenseError,
|
||||
InvalidPremiumLicenseError,
|
||||
UnsupportedPremiumLicenseError,
|
||||
PremiumLicenseInstanceIdMismatchError,
|
||||
PremiumLicenseHasExpired,
|
||||
PremiumLicenseAlreadyExists,
|
||||
NoSeatsLeftInPremiumLicenseError,
|
||||
UserAlreadyOnPremiumLicenseError,
|
||||
LicenseAuthorityUnavailable,
|
||||
)
|
||||
|
||||
|
||||
VALID_ONE_SEAT_LICENSE = (
|
||||
# id: "1", instance_id: "1"
|
||||
b"eyJ2ZXJzaW9uIjogMSwgImlkIjogIjEiLCAidmFsaWRfZnJvbSI6ICIyMDIxLTA4LTI5VDE5OjUyOjU3"
|
||||
b"Ljg0MjY5NiIsICJ2YWxpZF90aHJvdWdoIjogIjIwMjEtMDktMjlUMTk6NTI6NTcuODQyNjk2IiwgInBy"
|
||||
b"b2R1Y3RfY29kZSI6ICJwcmVtaXVtIiwgInNlYXRzIjogMSwgImlzc3VlZF9vbiI6ICIyMDIxLTA4LTI5"
|
||||
b"VDE5OjUyOjU3Ljg0MjY5NiIsICJpc3N1ZWRfdG9fZW1haWwiOiAiYnJhbUBiYXNlcm93LmlvIiwgImlz"
|
||||
b"c3VlZF90b19uYW1lIjogIkJyYW0iLCAiaW5zdGFuY2VfaWQiOiAiMSJ9.e33Z4CxLSmD-R55Es24P3mR"
|
||||
b"8Oqn3LpaXvgYLzF63oFHat3paon7IBjBmOX3eyd8KjirVf3empJds4uUw2Nn2m7TVvRAtJ8XzNl-8ytf"
|
||||
b"2RLtmjMx1Xkgp5VZ8S7UqJ_cKLyl76eVRtGEA1DH2HdPKu1vBPJ4bzDfnhDPYl4k5z9XSSgqAbQ9WO0U"
|
||||
b"5kiI3BYjVRZSKnZMeguAGZ47ezDj_WArGcHAB8Pa2v3HFp5Y34DMJ8r3_hD5hxCKgoNx4AHx1Q-hRDqp"
|
||||
b"Aroj-4jl7KWvlP-OJNc1BgH2wnhFmeKHotv-Iumi83JQohyceUbG6j8rDDQvJfcn0W2_ebmUH3TKr-w="
|
||||
b"="
|
||||
)
|
||||
VALID_UPGRADED_TEN_SEAT_LICENSE = (
|
||||
# id: "1", instance_id: "1"
|
||||
b"eyJ2ZXJzaW9uIjogMSwgImlkIjogIjEiLCAidmFsaWRfZnJvbSI6ICIyMDIxLTA4LTI5VDE5OjUyOjU3"
|
||||
b"Ljg0MjY5NiIsICJ2YWxpZF90aHJvdWdoIjogIjIwMjEtMDktMjlUMTk6NTI6NTcuODQyNjk2IiwgInBy"
|
||||
b"b2R1Y3RfY29kZSI6ICJwcmVtaXVtIiwgInNlYXRzIjogMTAsICJpc3N1ZWRfb24iOiAiMjAyMS0wOC0z"
|
||||
b"MFQxOTo1Mjo1Ny44NDI2OTYiLCAiaXNzdWVkX3RvX2VtYWlsIjogImJyYW1AYmFzZXJvdy5pbyIsICJp"
|
||||
b"c3N1ZWRfdG9fbmFtZSI6ICJCcmFtIiwgImluc3RhbmNlX2lkIjogIjEifQ==.MLZn4TG1iZbXo1kjryk"
|
||||
b"B98fFnYf8tOu8DG_I9CpkS5UGboI1-BNcq0pdtKxRgaTkRb-Q09D4J-LHKri5KA9WyQQNY8bb4antS1s"
|
||||
b"svi8Yrp6p9VQhtCunKCqUuLA8mpHFNLV6nbsTKLds5imyFSMzT-8RLejT774RUQ3-DUYd2N-awbxBwDs"
|
||||
b"Zpsupq3_7UrYIPhDcpVs_5G47p8ZT-z2fcC2cPOB2tRc6eQw7eUx95-nIxcR9IbLsHmQjYj3dxOjdsmN"
|
||||
b"SDekPuwzbQiZnDpfy7kzc93-752AHTZ-O2gd83PFZziaIJSyu7mUsxWk4rkMQalO_XG9X0AOEraT0SQQ"
|
||||
b"r0A=="
|
||||
)
|
||||
VALID_TWO_SEAT_LICENSE = (
|
||||
# id: "2", instance_id: "1"
|
||||
b"eyJ2ZXJzaW9uIjogMSwgImlkIjogIjIiLCAidmFsaWRfZnJvbSI6ICIyMDIxLTA4LTI5VDE5OjUzOjM3"
|
||||
b"LjA5MjMwMyIsICJ2YWxpZF90aHJvdWdoIjogIjIwMjEtMDktMjlUMTk6NTM6MzcuMDkyMzAzIiwgInBy"
|
||||
b"b2R1Y3RfY29kZSI6ICJwcmVtaXVtIiwgInNlYXRzIjogMiwgImlzc3VlZF9vbiI6ICIyMDIxLTA4LTI5"
|
||||
b"VDE5OjUzOjM3LjA5MjMwMyIsICJpc3N1ZWRfdG9fZW1haWwiOiAiYnJhbUBiYXNlcm93LmlvIiwgImlz"
|
||||
b"c3VlZF90b19uYW1lIjogIkJyYW0iLCAiaW5zdGFuY2VfaWQiOiAiMSJ9.d41tB1kx69gw-9xDrRI0kER"
|
||||
b"KDUtR-P6yRM3ufKZ_XRDewVCBAniCLe9-ce7TKabnMedE2cqHjYVLlI66Dfa5oH8fGswnyC16c9ZHlOU"
|
||||
b"jQ5CpHTorZm6eyCXaP6MDdhstCNKdDrZns3qvVMAqDpmxS8wmiG9Y6gZjvBGXZWeoCraF1SVcUnFBBlf"
|
||||
b"UemfGSQUwPitVlxJ6GWN-hzi7b1GZqWJKDb2YYJ0T30VMJeNO7oi6YHMUOH33041FU79DSET2A2NNEFu"
|
||||
b"e-jnCcw5NFpH-zGzBDv1wpR3DFmJa78KwGbj0Kdzim85AUzi1xGRlIyxxTdTkVy2B-08lPaoG8Q62bw="
|
||||
b"="
|
||||
)
|
||||
VALID_INSTANCE_TWO_LICENSE = (
|
||||
# id: "2", instance_id: "2"
|
||||
b"eyJ2ZXJzaW9uIjogMSwgImlkIjogIjEiLCAidmFsaWRfZnJvbSI6ICIyMDIxLTA4LTI5VDE5OjUyOjU3"
|
||||
b"Ljg0MjY5NiIsICJ2YWxpZF90aHJvdWdoIjogIjIwMjEtMDktMjlUMTk6NTI6NTcuODQyNjk2IiwgInBy"
|
||||
b"b2R1Y3RfY29kZSI6ICJwcmVtaXVtIiwgInNlYXRzIjogMSwgImlzc3VlZF9vbiI6ICIyMDIxLTA4LTI5"
|
||||
b"VDE5OjUyOjU3Ljg0MjY5NiIsICJpc3N1ZWRfdG9fZW1haWwiOiAiYnJhbUBiYXNlcm93LmlvIiwgImlz"
|
||||
b"c3VlZF90b19uYW1lIjogIkJyYW0iLCAiaW5zdGFuY2VfaWQiOiAiMiJ9.i3Og4ZJwz__TxWyFc2B6lDi"
|
||||
b"ZBAIOVTZv_jXVzQQqcjG-flPAicqXFECl7MbbexVmtsMES-U7VPebOh0t4oPoDXL1LiftfjmT63wO4An"
|
||||
b"A3FMS0Ip0GIx2upkQC-MlU1kSR9Tltrr1qySuQvXORDRUaSxaRQQacwZTOIviVdcxG9vesjkFwn6LMYp"
|
||||
b"-GhmCJXB0YfMgsvPm6kj6qTWPh3ed8aLNFnekUhB-dUwA4tqPicCQHRQCRZqzo9vx-hKdeHCGZMg0htG"
|
||||
b"EB4cAeV4I29JXPC83qtwt6DSCPxudlJsli3tYsLMcxAHysVN3H_FAY8qg54MP33OKvZuwww5uFDITMQ="
|
||||
b"="
|
||||
)
|
||||
INVALID_SIGNATURE_LICENSE = (
|
||||
b"eyJ2ZXJzaW9uIjogMSwgImlkIjogMSwgInZhbGlkX2Zyb20iOiAiMjAyMS0wOC0yOVQxOTo1NDoxMi4w"
|
||||
b"NjY4NDYiLCAidmFsaWRfdGhyb3VnaCI6ICIyMDIxLTA5LTI5VDE5OjU0OjEyLjA2Njg0NiIsICJwcm9k"
|
||||
b"dWN0X2NvZGUiOiAicHJlbWl1bSIsICJzZWF0cyI6IDEsICJ0b19lbWFpbCI6ICJicmFtQGJhc2Vyb3cu"
|
||||
b"aW8iLCAidG9fbmFtZSI6ICJCcmFtIn0=.hYaWGO0M6s1pA9bhcBlk1fE1QMrhlDGNiBIBG_2O2AMGFPj"
|
||||
b"gnsHdwfUIe_eo6dyAyvsToxBrpxr6N1vRqPdA61cKjTlhUdFqvj7NTeydS4Z9TlfP-vFslQk9CO_ok7Z"
|
||||
b"ws8AHTQ2pKfsdzqcWNZnWKZeQGEtO73MIoFJbHr07mtWA1ZZgJNBTBpp-7BNtvj2bQyUeXyRKD5LVj8G"
|
||||
b"ESDcapZCNt5ufesbYvpfs1c6p6UP4z3gszOYrzMApMqWHty7j10SDjcLIEsUTd02r_Pbip-KxmGfecXg"
|
||||
b"B0HF7HJZwkY9ZdlZ7ODGtV0e455dQwh5sSHa3RRd71AXVou-cuOS87g=="
|
||||
)
|
||||
INVALID_PAYLOAD_LICENSE = (
|
||||
b"e30=.gtDuoJAHn-LTPX1ReoGo8cm3DsXq0mf9MwpIccwCQXucpnh-r6yeJzRGbx5F80OGKXZJ1XcxLRr"
|
||||
b"8-IssyxlGVcrhHt6iYXmNoPXUrxN1slOzMO4_tutvEHSuOntW5gctm9SFfcRrdbejYue_47brp779bP2"
|
||||
b"pzwejOdQbSLUeNQ4bHIKQJYZ4cCooW8yICz6a8m4NFRDu_gr0Y1ud1Eo3h2E_BL2upNg14v8BRZJCHpj"
|
||||
b"CC5Eg4ErKqm88iFStIEpub-vem9rEwKR2kIvdJ6DaD7AJTG507GEtbI9lNCkm2aPJSf142Rf8_NrTVh3"
|
||||
b"QBqZnCo-XrquQe1h4r3fvjAf5tQ=="
|
||||
)
|
||||
INVALID_VERSION_LICENSE = (
|
||||
b"eyJ2ZXJzaW9uIjogOTk5OX0=.rzAyL6qBkz_Eb3GYaSOXy9CJ2HJg4uAxtrbinh4aDYy7Eq4e4RpfaPm"
|
||||
b"4dZLocIRxSmx_wUYSI0CMqmkwABHgzxRVmzVAmXf5MxX7vAGjjEnQX_dQOl8kY15gXhEQZv5pjSPVcZW"
|
||||
b"CLll95OFoBUJhtOQqNC6JLA1LZdiSPG6zFhvi5V27sRGBz3E8jhFLWY-Y2WIq5_9q2d_hVFM0KHwRcxb"
|
||||
b"CVof8RBUq1DgMcDKEGE7WRHYDVP1QugBjf4GZlvIE4ZVr3tKr0aKPX8nuNVhbQeudCW8tnturmxevpRN"
|
||||
b"vLS5ETSQzJoP46cGuw0HUV20P4SnvQP_NRd5zifgllJqsUw=="
|
||||
)
|
||||
NOT_JSON_PAYLOAD_LICENSE = (
|
||||
b"dGVzdA==.I37aSmuKN0WSrw6IDTg2xBOlQ3UOE5cjaWfc4MF5pgIadMUjkOh0D32R7RqRhmsxhdsqK6a"
|
||||
b"bU8u8cT6ZG0PxjsRnFjrkbhdcFR1Yw9fHQ7plHShnpsj0NT8fMuDaVfCibKxyi-Z8nVmwHEIlyRkLfKV"
|
||||
b"NMTR7q2bzdM9-LZ-jJsgp4qqtSE8ct8YwwdwUS8clKzb-wVyCDeGD2kBRyxNRU_hhiwN_aDv6zEEqd6u"
|
||||
b"1lkIxotWs8hHJ3kT9EB9LY9Nb5qlm9Qt4bJY9OB4Bc8eEpXgEXGMUik11sTo5E3thoV6HJTUQWLwozbo"
|
||||
b"fXwhO9qsjxisGZPEFinezHN124jSWxQ=="
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@override_settings(DEBUG=True)
|
||||
def test_has_active_premium_license(data_fixture):
|
||||
user_in_license = data_fixture.create_user()
|
||||
second_user_in_license = data_fixture.create_user()
|
||||
user_not_in_license = data_fixture.create_user()
|
||||
license = License.objects.create(license=VALID_TWO_SEAT_LICENSE.decode())
|
||||
License.objects.create(license=VALID_ONE_SEAT_LICENSE.decode())
|
||||
LicenseUser.objects.create(license=license, user=user_in_license)
|
||||
license_user_2 = LicenseUser.objects.create(
|
||||
license=license, user=second_user_in_license
|
||||
)
|
||||
|
||||
with freeze_time("2021-08-01 12:00"):
|
||||
assert not has_active_premium_license(user_in_license)
|
||||
assert not has_active_premium_license(second_user_in_license)
|
||||
assert not has_active_premium_license(user_not_in_license)
|
||||
|
||||
with freeze_time("2021-09-01 12:00"):
|
||||
assert has_active_premium_license(user_in_license)
|
||||
assert has_active_premium_license(second_user_in_license)
|
||||
assert not has_active_premium_license(user_not_in_license)
|
||||
|
||||
with freeze_time("2021-10-01 12:00"):
|
||||
assert not has_active_premium_license(user_in_license)
|
||||
assert not has_active_premium_license(second_user_in_license)
|
||||
assert not has_active_premium_license(user_not_in_license)
|
||||
|
||||
license_user_2.delete()
|
||||
with freeze_time("2021-09-01 12:00"):
|
||||
assert has_active_premium_license(user_in_license)
|
||||
assert not has_active_premium_license(second_user_in_license)
|
||||
assert not has_active_premium_license(user_not_in_license)
|
||||
|
||||
check_active_premium_license(user_in_license)
|
||||
|
||||
with pytest.raises(NoPremiumLicenseError):
|
||||
check_active_premium_license(second_user_in_license)
|
||||
|
||||
with pytest.raises(NoPremiumLicenseError):
|
||||
check_active_premium_license(user_not_in_license)
|
||||
|
||||
# When the license can't be decoded, it should also return false.
|
||||
invalid_user = data_fixture.create_user()
|
||||
invalid_license = License.objects.create(license="invalid")
|
||||
LicenseUser.objects.create(license=invalid_license, user=invalid_user)
|
||||
assert not has_active_premium_license(invalid_user)
|
||||
|
||||
|
||||
@override_settings(DEBUG=True)
|
||||
def test_get_public_key_debug():
|
||||
public_key = get_public_key()
|
||||
signature = base64.b64decode(
|
||||
b"UnRzVNbgO8XxAHEjn6uzGrjdVjwf5rU2BcOe+G2nKHhF50m8nf/DAmmk6rsCFolrCXke2tJFnER"
|
||||
b"0aeoPKwjZItnYJhkX0xt1PwkpImBoSZYQfdGycVuLwRv28yQaWP1tGonNIqpUuAiyuTrTEOWPid"
|
||||
b"vbaYtAXu/I9aRwBSpjD3cM8mvyb4BE/lsC6RC1qYj6V2vUmoWum8sCQLHcToAs75CjV8NVVH97X"
|
||||
b"TUnUellH3s+UpwHL9Rauq8rnPdAWLf6wujcqeBtdtsjp4HakuTsNYK+AcceSGeGSrlVqD0OoQei"
|
||||
b"Cc2d0/5SkO3DyndZ/X73eX2psYpyd0p1ZDkCSbKJpA=="
|
||||
)
|
||||
|
||||
# We do not expect the `InvalidSignature` exception.
|
||||
assert (
|
||||
public_key.verify(
|
||||
signature,
|
||||
b"test",
|
||||
padding.PSS(
|
||||
mgf=padding.MGF1(hashes.SHA256()), salt_length=padding.PSS.MAX_LENGTH
|
||||
),
|
||||
hashes.SHA256(),
|
||||
)
|
||||
is None
|
||||
)
|
||||
|
||||
|
||||
@override_settings(DEBUG=False)
|
||||
def test_get_public_key_production():
|
||||
public_key = get_public_key()
|
||||
signature = base64.b64decode(
|
||||
b"UnRzVNbgO8XxAHEjn6uzGrjdVjwf5rU2BcOe+G2nKHhF50m8nf/DAmmk6rsCFolrCXke2tJFnER"
|
||||
b"0aeoPKwjZItnYJhkX0xt1PwkpImBoSZYQfdGycVuLwRv28yQaWP1tGonNIqpUuAiyuTrTEOWPid"
|
||||
b"vbaYtAXu/I9aRwBSpjD3cM8mvyb4BE/lsC6RC1qYj6V2vUmoWum8sCQLHcToAs75CjV8NVVH97X"
|
||||
b"TUnUellH3s+UpwHL9Rauq8rnPdAWLf6wujcqeBtdtsjp4HakuTsNYK+AcceSGeGSrlVqD0OoQei"
|
||||
b"Cc2d0/5SkO3DyndZ/X73eX2psYpyd0p1ZDkCSbKJpA=="
|
||||
)
|
||||
|
||||
# We expect the `InvalidSignature` exception because the signature has been
|
||||
# signed with the wrong private key. This way, we know the debug key is not used
|
||||
# in production.
|
||||
with pytest.raises(InvalidSignature):
|
||||
public_key.verify(
|
||||
signature,
|
||||
b"test",
|
||||
padding.PSS(
|
||||
mgf=padding.MGF1(hashes.SHA256()), salt_length=padding.PSS.MAX_LENGTH
|
||||
),
|
||||
hashes.SHA256(),
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@override_settings(DEBUG=True)
|
||||
@responses.activate
|
||||
def test_fetch_license_status_with_authority_unavailable(data_fixture):
|
||||
data_fixture.update_settings(instance_id="1")
|
||||
|
||||
with pytest.raises(LicenseAuthorityUnavailable):
|
||||
fetch_license_status_with_authority(["test"])
|
||||
|
||||
responses.add(
|
||||
responses.POST,
|
||||
"http://172.17.0.1:8001/api/saas/licenses/check/",
|
||||
json={"error": "error"},
|
||||
status=400,
|
||||
)
|
||||
|
||||
with pytest.raises(LicenseAuthorityUnavailable):
|
||||
fetch_license_status_with_authority(["test"])
|
||||
|
||||
responses.add(
|
||||
responses.POST,
|
||||
"http://172.17.0.1:8001/api/saas/licenses/check/",
|
||||
body="not_json",
|
||||
status=200,
|
||||
)
|
||||
|
||||
with pytest.raises(LicenseAuthorityUnavailable):
|
||||
fetch_license_status_with_authority(["test"])
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@override_settings(DEBUG=True)
|
||||
@responses.activate
|
||||
def test_fetch_license_status_with_authority_invalid_response(data_fixture):
|
||||
data_fixture.update_settings(instance_id="1")
|
||||
|
||||
responses.add(
|
||||
responses.POST,
|
||||
"http://172.17.0.1:8001/api/saas/licenses/check/",
|
||||
body="not_json",
|
||||
status=200,
|
||||
)
|
||||
|
||||
with pytest.raises(LicenseAuthorityUnavailable):
|
||||
fetch_license_status_with_authority(["test"])
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@override_settings(DEBUG=False)
|
||||
@responses.activate
|
||||
def test_fetch_license_status_in_production_mode(data_fixture):
|
||||
data_fixture.update_settings(instance_id="1")
|
||||
|
||||
responses.add(
|
||||
responses.POST,
|
||||
"https://api.baserow.io/api/saas/licenses/check/",
|
||||
json={"success": True},
|
||||
status=200,
|
||||
)
|
||||
|
||||
response = fetch_license_status_with_authority(["test"])
|
||||
assert response["success"] is True
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@override_settings(DEBUG=True)
|
||||
@responses.activate
|
||||
def test_fetch_license_status_with_authority(data_fixture):
|
||||
data_fixture.update_settings(instance_id="1")
|
||||
|
||||
responses.add(
|
||||
responses.POST,
|
||||
"http://172.17.0.1:8001/api/saas/licenses/check/",
|
||||
json={"test": {"type": "ok", "detail": ""}},
|
||||
status=200,
|
||||
)
|
||||
|
||||
response = fetch_license_status_with_authority(["test"])
|
||||
assert len(response) == 1
|
||||
assert response["test"]["type"] == "ok"
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@override_settings(DEBUG=True)
|
||||
# Activate the responses because we want to check with the authority to fail.
|
||||
@responses.activate
|
||||
def test_check_licenses_with_authority_check(premium_data_fixture):
|
||||
invalid_license = premium_data_fixture.create_premium_license(license="invalid")
|
||||
does_not_exist_license = premium_data_fixture.create_premium_license(
|
||||
license="does_not_exist"
|
||||
)
|
||||
instance_id_mismatch_license = premium_data_fixture.create_premium_license(
|
||||
license="instance_id_mismatch"
|
||||
)
|
||||
updated_license = premium_data_fixture.create_premium_license(license="update")
|
||||
ok_license = premium_data_fixture.create_premium_license(
|
||||
license=VALID_TWO_SEAT_LICENSE.decode()
|
||||
)
|
||||
|
||||
with freeze_time("2021-07-01 12:00"):
|
||||
responses.add(
|
||||
responses.POST,
|
||||
"http://172.17.0.1:8001/api/saas/licenses/check/",
|
||||
json={
|
||||
"invalid": {"type": "invalid", "detail": ""},
|
||||
"does_not_exist": {"type": "does_not_exist", "detail": ""},
|
||||
"instance_id_mismatch": {
|
||||
"type": "instance_id_mismatch",
|
||||
"detail": "",
|
||||
},
|
||||
"update": {
|
||||
"type": "update",
|
||||
"detail": "",
|
||||
"new_license_payload": VALID_ONE_SEAT_LICENSE.decode(),
|
||||
},
|
||||
VALID_TWO_SEAT_LICENSE.decode(): {"type": "ok", "detail": ""},
|
||||
},
|
||||
status=200,
|
||||
)
|
||||
|
||||
check_licenses(
|
||||
[
|
||||
invalid_license,
|
||||
does_not_exist_license,
|
||||
instance_id_mismatch_license,
|
||||
updated_license,
|
||||
ok_license,
|
||||
]
|
||||
)
|
||||
|
||||
all_licenses = License.objects.all().order_by("id")
|
||||
assert len(all_licenses) == 2
|
||||
assert all_licenses[0].id == updated_license.id
|
||||
assert all_licenses[0].license == VALID_ONE_SEAT_LICENSE.decode()
|
||||
assert all_licenses[0].license_id == "1"
|
||||
assert all_licenses[0].last_check.year == 2021
|
||||
assert all_licenses[1].id == ok_license.id
|
||||
assert all_licenses[1].last_check.year == 2021
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@override_settings(DEBUG=True)
|
||||
# Activate the responses because we want to check with the authority to fail.
|
||||
@responses.activate
|
||||
def test_check_licenses_without_authority_check(premium_data_fixture):
|
||||
with freeze_time("2021-07-01 12:00"):
|
||||
license_object = premium_data_fixture.create_premium_license(
|
||||
license=VALID_TWO_SEAT_LICENSE.decode()
|
||||
)
|
||||
premium_data_fixture.create_premium_license_user(license=license_object)
|
||||
premium_data_fixture.create_premium_license_user(license=license_object)
|
||||
premium_data_fixture.create_premium_license_user(license=license_object)
|
||||
premium_data_fixture.create_premium_license_user(license=license_object)
|
||||
|
||||
# This license is expected to be delete because the payload is invalid.
|
||||
license_object_2 = premium_data_fixture.create_premium_license(
|
||||
license="invalid_license"
|
||||
)
|
||||
|
||||
assert License.objects.all().count() == 2
|
||||
assert license_object.users.all().count() == 4
|
||||
check_licenses([license_object, license_object_2])
|
||||
assert License.objects.all().count() == 1
|
||||
assert license_object.users.all().count() == 2
|
||||
assert license_object.last_check.year == 2021
|
||||
|
||||
|
||||
@override_settings(DEBUG=True)
|
||||
def test_decode_license_with_valid_license():
|
||||
assert decode_license(VALID_ONE_SEAT_LICENSE) == {
|
||||
"version": 1,
|
||||
"id": "1",
|
||||
"valid_from": "2021-08-29T19:52:57.842696",
|
||||
"valid_through": "2021-09-29T19:52:57.842696",
|
||||
"product_code": "premium",
|
||||
"seats": 1,
|
||||
"issued_on": "2021-08-29T19:52:57.842696",
|
||||
"issued_to_email": "bram@baserow.io",
|
||||
"issued_to_name": "Bram",
|
||||
"instance_id": "1",
|
||||
}
|
||||
assert decode_license(VALID_UPGRADED_TEN_SEAT_LICENSE) == {
|
||||
"version": 1,
|
||||
"id": "1",
|
||||
"valid_from": "2021-08-29T19:52:57.842696",
|
||||
"valid_through": "2021-09-29T19:52:57.842696",
|
||||
"product_code": "premium",
|
||||
"seats": 10,
|
||||
"issued_on": "2021-08-30T19:52:57.842696",
|
||||
"issued_to_email": "bram@baserow.io",
|
||||
"issued_to_name": "Bram",
|
||||
"instance_id": "1",
|
||||
}
|
||||
assert decode_license(VALID_TWO_SEAT_LICENSE) == {
|
||||
"version": 1,
|
||||
"id": "2",
|
||||
"valid_from": "2021-08-29T19:53:37.092303",
|
||||
"valid_through": "2021-09-29T19:53:37.092303",
|
||||
"product_code": "premium",
|
||||
"seats": 2,
|
||||
"issued_on": "2021-08-29T19:53:37.092303",
|
||||
"issued_to_email": "bram@baserow.io",
|
||||
"issued_to_name": "Bram",
|
||||
"instance_id": "1",
|
||||
}
|
||||
assert decode_license(VALID_INSTANCE_TWO_LICENSE) == {
|
||||
"version": 1,
|
||||
"id": "1",
|
||||
"valid_from": "2021-08-29T19:52:57.842696",
|
||||
"valid_through": "2021-09-29T19:52:57.842696",
|
||||
"product_code": "premium",
|
||||
"seats": 1,
|
||||
"issued_on": "2021-08-29T19:52:57.842696",
|
||||
"issued_to_email": "bram@baserow.io",
|
||||
"issued_to_name": "Bram",
|
||||
"instance_id": "2",
|
||||
}
|
||||
|
||||
|
||||
@override_settings(DEBUG=True)
|
||||
def test_invalid_signature_decode_license():
|
||||
with pytest.raises(InvalidPremiumLicenseError):
|
||||
decode_license(INVALID_SIGNATURE_LICENSE)
|
||||
|
||||
with pytest.raises(InvalidPremiumLicenseError):
|
||||
decode_license(INVALID_PAYLOAD_LICENSE)
|
||||
|
||||
with pytest.raises(InvalidPremiumLicenseError):
|
||||
decode_license(b"test")
|
||||
|
||||
with pytest.raises(InvalidPremiumLicenseError):
|
||||
decode_license(b"test.test")
|
||||
|
||||
with pytest.raises(InvalidPremiumLicenseError):
|
||||
decode_license(b"test.test==")
|
||||
|
||||
with pytest.raises(InvalidPremiumLicenseError):
|
||||
decode_license(b"eyJ2ZXJzaW9uIjog.rzAyL6qBkz_Eb==")
|
||||
|
||||
with pytest.raises(InvalidPremiumLicenseError):
|
||||
decode_license(NOT_JSON_PAYLOAD_LICENSE)
|
||||
|
||||
|
||||
@override_settings(DEBUG=True)
|
||||
def test_unsupported_version_decode_license():
|
||||
with pytest.raises(UnsupportedPremiumLicenseError):
|
||||
decode_license(INVALID_VERSION_LICENSE)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@override_settings(DEBUG=True)
|
||||
@responses.activate
|
||||
def test_register_license_with_authority_check_ok(data_fixture):
|
||||
data_fixture.update_settings(instance_id="1")
|
||||
admin_user = data_fixture.create_user(is_staff=True)
|
||||
|
||||
with freeze_time("2021-07-01 12:00"):
|
||||
responses.add(
|
||||
responses.POST,
|
||||
"http://172.17.0.1:8001/api/saas/licenses/check/",
|
||||
json={VALID_ONE_SEAT_LICENSE.decode(): {"type": "ok", "detail": ""}},
|
||||
status=200,
|
||||
)
|
||||
|
||||
license_1 = register_license(admin_user, VALID_ONE_SEAT_LICENSE)
|
||||
assert license_1.license == VALID_ONE_SEAT_LICENSE.decode()
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@override_settings(DEBUG=True)
|
||||
@responses.activate
|
||||
def test_register_license_with_authority_check_updated(data_fixture):
|
||||
data_fixture.update_settings(instance_id="1")
|
||||
admin_user = data_fixture.create_user(is_staff=True)
|
||||
|
||||
with freeze_time("2021-07-01 12:00"):
|
||||
responses.add(
|
||||
responses.POST,
|
||||
"http://172.17.0.1:8001/api/saas/licenses/check/",
|
||||
json={
|
||||
VALID_ONE_SEAT_LICENSE.decode(): {
|
||||
"type": "update",
|
||||
"detail": "",
|
||||
"new_license_payload": VALID_UPGRADED_TEN_SEAT_LICENSE.decode(),
|
||||
}
|
||||
},
|
||||
status=200,
|
||||
)
|
||||
|
||||
license_1 = register_license(admin_user, VALID_ONE_SEAT_LICENSE)
|
||||
assert license_1.license == VALID_UPGRADED_TEN_SEAT_LICENSE.decode()
|
||||
assert license_1.license_id == "1"
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@override_settings(DEBUG=True)
|
||||
@responses.activate
|
||||
def test_register_license_with_authority_check_does_not_exist(data_fixture):
|
||||
data_fixture.update_settings(instance_id="1")
|
||||
admin_user = data_fixture.create_user(is_staff=True)
|
||||
|
||||
with freeze_time("2021-07-01 12:00"):
|
||||
responses.add(
|
||||
responses.POST,
|
||||
"http://172.17.0.1:8001/api/saas/licenses/check/",
|
||||
json={
|
||||
VALID_ONE_SEAT_LICENSE.decode(): {
|
||||
"type": "does_not_exist",
|
||||
"detail": "",
|
||||
}
|
||||
},
|
||||
status=200,
|
||||
)
|
||||
|
||||
with pytest.raises(InvalidPremiumLicenseError):
|
||||
register_license(admin_user, VALID_ONE_SEAT_LICENSE)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@override_settings(DEBUG=True)
|
||||
@responses.activate
|
||||
def test_register_license_with_authority_check_instance_id_mismatch(data_fixture):
|
||||
data_fixture.update_settings(instance_id="1")
|
||||
admin_user = data_fixture.create_user(is_staff=True)
|
||||
|
||||
with freeze_time("2021-07-01 12:00"):
|
||||
responses.add(
|
||||
responses.POST,
|
||||
"http://172.17.0.1:8001/api/saas/licenses/check/",
|
||||
json={
|
||||
VALID_ONE_SEAT_LICENSE.decode(): {
|
||||
"type": "instance_id_mismatch",
|
||||
"detail": "",
|
||||
}
|
||||
},
|
||||
status=200,
|
||||
)
|
||||
|
||||
with pytest.raises(PremiumLicenseInstanceIdMismatchError):
|
||||
register_license(admin_user, VALID_ONE_SEAT_LICENSE)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@override_settings(DEBUG=True)
|
||||
@responses.activate
|
||||
def test_register_license_with_authority_check_invalid(data_fixture):
|
||||
data_fixture.update_settings(instance_id="1")
|
||||
admin_user = data_fixture.create_user(is_staff=True)
|
||||
|
||||
with freeze_time("2021-07-01 12:00"):
|
||||
responses.add(
|
||||
responses.POST,
|
||||
"http://172.17.0.1:8001/api/saas/licenses/check/",
|
||||
json={
|
||||
VALID_ONE_SEAT_LICENSE.decode(): {
|
||||
"type": "invalid",
|
||||
"detail": "",
|
||||
}
|
||||
},
|
||||
status=200,
|
||||
)
|
||||
|
||||
with pytest.raises(InvalidPremiumLicenseError):
|
||||
register_license(admin_user, VALID_ONE_SEAT_LICENSE)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@override_settings(DEBUG=True)
|
||||
# We need to activate the responses here, so that the license authority check will
|
||||
# fail and therefore be ignored. There is another test, that tests the authority
|
||||
# responses.
|
||||
@responses.activate
|
||||
def test_register_license(data_fixture):
|
||||
data_fixture.update_settings(instance_id="1")
|
||||
normal_user = data_fixture.create_user()
|
||||
admin_user = data_fixture.create_user(is_staff=True)
|
||||
|
||||
with pytest.raises(IsNotAdminError):
|
||||
register_license(normal_user, VALID_ONE_SEAT_LICENSE)
|
||||
|
||||
with freeze_time("2021-10-01 12:00"):
|
||||
with pytest.raises(PremiumLicenseHasExpired):
|
||||
register_license(admin_user, VALID_ONE_SEAT_LICENSE)
|
||||
|
||||
with freeze_time("2021-07-01 12:00"):
|
||||
license_1 = register_license(admin_user, VALID_ONE_SEAT_LICENSE)
|
||||
assert license_1.license == VALID_ONE_SEAT_LICENSE.decode()
|
||||
|
||||
# Check if the license has actually been created.
|
||||
all_licenses = License.objects.all()
|
||||
assert len(all_licenses) == 1
|
||||
assert all_licenses[0].id == license_1.id
|
||||
|
||||
with freeze_time("2021-09-01 12:00"):
|
||||
with pytest.raises(PremiumLicenseInstanceIdMismatchError):
|
||||
register_license(admin_user, VALID_INSTANCE_TWO_LICENSE)
|
||||
|
||||
with pytest.raises(PremiumLicenseAlreadyExists):
|
||||
register_license(admin_user, VALID_ONE_SEAT_LICENSE)
|
||||
|
||||
license_2 = register_license(admin_user, VALID_TWO_SEAT_LICENSE.decode())
|
||||
assert license_2.license == VALID_TWO_SEAT_LICENSE.decode()
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@override_settings(DEBUG=True)
|
||||
# We need to activate the responses here, so that the license authority check will
|
||||
# fail and therefore be ignored. There is another test, that tests the authority
|
||||
# responses.
|
||||
@responses.activate
|
||||
def test_upgrade_license_by_register(data_fixture):
|
||||
data_fixture.update_settings(instance_id="1")
|
||||
admin_user = data_fixture.create_user(is_staff=True)
|
||||
|
||||
with freeze_time("2021-07-01 12:00"):
|
||||
first_license_1 = register_license(admin_user, VALID_ONE_SEAT_LICENSE)
|
||||
second_license_1 = register_license(admin_user, VALID_UPGRADED_TEN_SEAT_LICENSE)
|
||||
|
||||
assert first_license_1.id == second_license_1.id
|
||||
assert License.objects.all().count() == 1
|
||||
assert second_license_1.license_id == "1"
|
||||
assert second_license_1.seats == 10
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@override_settings(DEBUG=True)
|
||||
# We need to activate the responses here, so that the license authority check will
|
||||
# fail and therefore be ignored. There is another test, that tests the authority
|
||||
# responses.
|
||||
@responses.activate
|
||||
def test_register_an_older_license(data_fixture):
|
||||
data_fixture.update_settings(instance_id="1")
|
||||
admin_user = data_fixture.create_user(is_staff=True)
|
||||
|
||||
with freeze_time("2021-07-01 12:00"):
|
||||
register_license(admin_user, VALID_UPGRADED_TEN_SEAT_LICENSE)
|
||||
|
||||
# The same license already exists.
|
||||
with pytest.raises(PremiumLicenseAlreadyExists):
|
||||
register_license(admin_user, VALID_UPGRADED_TEN_SEAT_LICENSE)
|
||||
|
||||
# An older license already exists.
|
||||
with pytest.raises(PremiumLicenseAlreadyExists):
|
||||
register_license(admin_user, VALID_ONE_SEAT_LICENSE)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@override_settings(DEBUG=True)
|
||||
def test_remove_license(data_fixture):
|
||||
user_1 = data_fixture.create_user()
|
||||
user_2 = data_fixture.create_user()
|
||||
admin_1 = data_fixture.create_user(is_staff=True)
|
||||
|
||||
license_object = License.objects.create(license=VALID_TWO_SEAT_LICENSE.decode())
|
||||
LicenseUser.objects.create(license=license_object, user=user_1)
|
||||
license_object_2 = License.objects.create(license=VALID_TWO_SEAT_LICENSE.decode())
|
||||
LicenseUser.objects.create(license=license_object_2, user=user_1)
|
||||
LicenseUser.objects.create(license=license_object_2, user=user_2)
|
||||
|
||||
with pytest.raises(IsNotAdminError):
|
||||
remove_license(user_1, license_object)
|
||||
|
||||
remove_license(admin_1, license_object_2)
|
||||
licenses = License.objects.all()
|
||||
assert len(licenses) == 1
|
||||
assert licenses[0].id == license_object.id
|
||||
license_users = LicenseUser.objects.all()
|
||||
assert len(license_users) == 1
|
||||
assert license_users[0].user_id == user_1.id
|
||||
assert license_users[0].license_id == license_object.id
|
||||
|
||||
|
||||
@pytest.mark.django_db(transaction=True)
|
||||
@override_settings(DEBUG=True)
|
||||
@patch("baserow_premium.license.handler.broadcast_to_users")
|
||||
def test_add_user_to_license(mock_broadcast_to_users, data_fixture):
|
||||
with freeze_time("2021-09-01 12:00"):
|
||||
user_1 = data_fixture.create_user()
|
||||
user_2 = data_fixture.create_user()
|
||||
admin_1 = data_fixture.create_user(is_staff=True)
|
||||
license_object = License.objects.create(license=VALID_ONE_SEAT_LICENSE.decode())
|
||||
|
||||
with pytest.raises(IsNotAdminError):
|
||||
add_user_to_license(user_1, license_object, user_1)
|
||||
|
||||
license_user = add_user_to_license(admin_1, license_object, user_1)
|
||||
|
||||
assert license_user.user_id == user_1.id
|
||||
assert license_user.license_id == license_object.id
|
||||
|
||||
mock_broadcast_to_users.delay.assert_called_once()
|
||||
args = mock_broadcast_to_users.delay.call_args
|
||||
assert args[0][0] == [user_1.id]
|
||||
assert args[0][1]["type"] == "user_data_updated"
|
||||
assert args[0][1]["user_data"] == {"premium": {"valid_license": True}}
|
||||
|
||||
with pytest.raises(UserAlreadyOnPremiumLicenseError):
|
||||
add_user_to_license(admin_1, license_object, user_1)
|
||||
|
||||
with pytest.raises(NoSeatsLeftInPremiumLicenseError):
|
||||
add_user_to_license(admin_1, license_object, user_2)
|
||||
|
||||
|
||||
@pytest.mark.django_db(transaction=True)
|
||||
@override_settings(DEBUG=True)
|
||||
@patch("baserow_premium.license.handler.broadcast_to_users")
|
||||
def test_remove_user_from_license(mock_broadcast_to_users, data_fixture):
|
||||
with freeze_time("2021-09-01 12:00"):
|
||||
user_1 = data_fixture.create_user()
|
||||
admin_1 = data_fixture.create_user(is_staff=True)
|
||||
|
||||
license_object = License.objects.create(license=VALID_ONE_SEAT_LICENSE.decode())
|
||||
LicenseUser.objects.create(license=license_object, user=user_1)
|
||||
|
||||
with pytest.raises(IsNotAdminError):
|
||||
remove_user_from_license(user_1, license_object, user_1)
|
||||
|
||||
with transaction.atomic():
|
||||
remove_user_from_license(admin_1, license_object, user_1)
|
||||
|
||||
assert LicenseUser.objects.all().count() == 0
|
||||
|
||||
mock_broadcast_to_users.delay.assert_called_once()
|
||||
args = mock_broadcast_to_users.delay.call_args
|
||||
assert args[0][0] == [user_1.id]
|
||||
assert args[0][1]["type"] == "user_data_updated"
|
||||
assert args[0][1]["user_data"] == {"premium": {"valid_license": False}}
|
||||
|
||||
|
||||
@pytest.mark.django_db(transaction=True)
|
||||
@override_settings(DEBUG=True)
|
||||
@patch("baserow_premium.license.handler.broadcast_to_users")
|
||||
def test_fill_remaining_seats_in_license(mock_broadcast_to_users, data_fixture):
|
||||
with freeze_time("2021-09-01 12:00"):
|
||||
user_1 = data_fixture.create_user()
|
||||
user_2 = data_fixture.create_user()
|
||||
data_fixture.create_user()
|
||||
admin_1 = data_fixture.create_user(is_staff=True)
|
||||
|
||||
license_object = License.objects.create(license=VALID_TWO_SEAT_LICENSE.decode())
|
||||
LicenseUser.objects.create(license=license_object, user=user_1)
|
||||
|
||||
with pytest.raises(IsNotAdminError):
|
||||
fill_remaining_seats_of_license(user_1, license_object)
|
||||
|
||||
fill_remaining_seats_of_license(admin_1, license_object)
|
||||
license_users = LicenseUser.objects.filter(license=license_object).order_by(
|
||||
"user_id"
|
||||
)
|
||||
assert len(license_users) == 2
|
||||
assert license_users[0].license_id == license_object.id
|
||||
assert license_users[0].user_id == user_1.id
|
||||
assert license_users[1].license_id == license_object.id
|
||||
assert license_users[1].user_id == user_2.id
|
||||
|
||||
mock_broadcast_to_users.delay.assert_called_once()
|
||||
args = mock_broadcast_to_users.delay.call_args
|
||||
assert len(args[0][0]) == 1
|
||||
assert args[0][0][0] == user_2.id
|
||||
assert args[0][1]["type"] == "user_data_updated"
|
||||
assert args[0][1]["user_data"] == {"premium": {"valid_license": True}}
|
||||
|
||||
license_object_2 = License.objects.create(
|
||||
license=VALID_ONE_SEAT_LICENSE.decode()
|
||||
)
|
||||
created_license_users = fill_remaining_seats_of_license(
|
||||
admin_1, license_object_2
|
||||
)
|
||||
assert len(created_license_users) == 1
|
||||
assert created_license_users[0].license_id == license_object_2.id
|
||||
assert created_license_users[0].user_id == user_1.id
|
||||
assert LicenseUser.objects.all().count() == 3
|
||||
license_users = LicenseUser.objects.filter(license=license_object_2).order_by(
|
||||
"user_id"
|
||||
)
|
||||
assert len(license_users) == 1
|
||||
assert license_users[0].license_id == license_object_2.id
|
||||
assert license_users[0].user_id == user_1.id
|
||||
|
||||
|
||||
@pytest.mark.django_db(transaction=True)
|
||||
@override_settings(DEBUG=True)
|
||||
@patch("baserow_premium.license.handler.broadcast_to_users")
|
||||
def test_remove_all_users_from_license(mock_broadcast_to_users, data_fixture):
|
||||
with freeze_time("2021-09-01 12:00"):
|
||||
user_1 = data_fixture.create_user()
|
||||
user_2 = data_fixture.create_user()
|
||||
admin_1 = data_fixture.create_user(is_staff=True)
|
||||
|
||||
license_object = License.objects.create(license=VALID_TWO_SEAT_LICENSE.decode())
|
||||
LicenseUser.objects.create(license=license_object, user=user_1)
|
||||
license_object_2 = License.objects.create(
|
||||
license=VALID_TWO_SEAT_LICENSE.decode()
|
||||
)
|
||||
LicenseUser.objects.create(license=license_object_2, user=user_1)
|
||||
LicenseUser.objects.create(license=license_object_2, user=user_2)
|
||||
|
||||
with pytest.raises(IsNotAdminError):
|
||||
remove_all_users_from_license(user_1, license_object)
|
||||
|
||||
remove_all_users_from_license(admin_1, license_object_2)
|
||||
license_users = LicenseUser.objects.all()
|
||||
assert len(license_users) == 1
|
||||
assert license_users[0].license_id == license_object.id
|
||||
assert license_users[0].user_id == user_1.id
|
||||
assert LicenseUser.objects.all().count() == 1
|
||||
|
||||
mock_broadcast_to_users.delay.assert_called_once()
|
||||
args = mock_broadcast_to_users.delay.call_args
|
||||
assert len(args[0][0]) == 2
|
||||
assert user_1.id in args[0][0]
|
||||
assert user_2.id in args[0][0]
|
||||
assert args[0][1]["type"] == "user_data_updated"
|
||||
assert args[0][1]["user_data"] == {"premium": {"valid_license": False}}
|
|
@ -0,0 +1,91 @@
|
|||
import pytest
|
||||
from datetime import datetime
|
||||
from freezegun import freeze_time
|
||||
|
||||
from django.test.utils import override_settings
|
||||
from django.utils.timezone import utc, make_aware
|
||||
|
||||
from baserow_premium.license.models import License
|
||||
|
||||
|
||||
VALID_ONE_SEAT_LICENSE = (
|
||||
# id: "1", instance_id: "1"
|
||||
b"eyJ2ZXJzaW9uIjogMSwgImlkIjogIjEiLCAidmFsaWRfZnJvbSI6ICIyMDIxLTA4LTI5VDE5OjUyOjU3"
|
||||
b"Ljg0MjY5NiIsICJ2YWxpZF90aHJvdWdoIjogIjIwMjEtMDktMjlUMTk6NTI6NTcuODQyNjk2IiwgInBy"
|
||||
b"b2R1Y3RfY29kZSI6ICJwcmVtaXVtIiwgInNlYXRzIjogMSwgImlzc3VlZF9vbiI6ICIyMDIxLTA4LTI5"
|
||||
b"VDE5OjUyOjU3Ljg0MjY5NiIsICJpc3N1ZWRfdG9fZW1haWwiOiAiYnJhbUBiYXNlcm93LmlvIiwgImlz"
|
||||
b"c3VlZF90b19uYW1lIjogIkJyYW0iLCAiaW5zdGFuY2VfaWQiOiAiMSJ9.e33Z4CxLSmD-R55Es24P3mR"
|
||||
b"8Oqn3LpaXvgYLzF63oFHat3paon7IBjBmOX3eyd8KjirVf3empJds4uUw2Nn2m7TVvRAtJ8XzNl-8ytf"
|
||||
b"2RLtmjMx1Xkgp5VZ8S7UqJ_cKLyl76eVRtGEA1DH2HdPKu1vBPJ4bzDfnhDPYl4k5z9XSSgqAbQ9WO0U"
|
||||
b"5kiI3BYjVRZSKnZMeguAGZ47ezDj_WArGcHAB8Pa2v3HFp5Y34DMJ8r3_hD5hxCKgoNx4AHx1Q-hRDqp"
|
||||
b"Aroj-4jl7KWvlP-OJNc1BgH2wnhFmeKHotv-Iumi83JQohyceUbG6j8rDDQvJfcn0W2_ebmUH3TKr-w="
|
||||
b"="
|
||||
)
|
||||
VALID_TWO_SEAT_LICENSE = (
|
||||
# id: "2", instance_id: "1"
|
||||
b"eyJ2ZXJzaW9uIjogMSwgImlkIjogIjIiLCAidmFsaWRfZnJvbSI6ICIyMDIxLTA4LTI5VDE5OjUzOjM3"
|
||||
b"LjA5MjMwMyIsICJ2YWxpZF90aHJvdWdoIjogIjIwMjEtMDktMjlUMTk6NTM6MzcuMDkyMzAzIiwgInBy"
|
||||
b"b2R1Y3RfY29kZSI6ICJwcmVtaXVtIiwgInNlYXRzIjogMiwgImlzc3VlZF9vbiI6ICIyMDIxLTA4LTI5"
|
||||
b"VDE5OjUzOjM3LjA5MjMwMyIsICJpc3N1ZWRfdG9fZW1haWwiOiAiYnJhbUBiYXNlcm93LmlvIiwgImlz"
|
||||
b"c3VlZF90b19uYW1lIjogIkJyYW0iLCAiaW5zdGFuY2VfaWQiOiAiMSJ9.d41tB1kx69gw-9xDrRI0kER"
|
||||
b"KDUtR-P6yRM3ufKZ_XRDewVCBAniCLe9-ce7TKabnMedE2cqHjYVLlI66Dfa5oH8fGswnyC16c9ZHlOU"
|
||||
b"jQ5CpHTorZm6eyCXaP6MDdhstCNKdDrZns3qvVMAqDpmxS8wmiG9Y6gZjvBGXZWeoCraF1SVcUnFBBlf"
|
||||
b"UemfGSQUwPitVlxJ6GWN-hzi7b1GZqWJKDb2YYJ0T30VMJeNO7oi6YHMUOH33041FU79DSET2A2NNEFu"
|
||||
b"e-jnCcw5NFpH-zGzBDv1wpR3DFmJa78KwGbj0Kdzim85AUzi1xGRlIyxxTdTkVy2B-08lPaoG8Q62bw="
|
||||
b"="
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@override_settings(DEBUG=True)
|
||||
def test_premium_license_model_properties():
|
||||
license = License(license=VALID_ONE_SEAT_LICENSE.decode())
|
||||
assert license.license_id == "1"
|
||||
assert license.valid_from == make_aware(
|
||||
datetime(2021, 8, 29, 19, 52, 57, 842696), utc
|
||||
)
|
||||
assert license.valid_through == make_aware(
|
||||
datetime(2021, 9, 29, 19, 52, 57, 842696), utc
|
||||
)
|
||||
assert license.product_code == "premium"
|
||||
assert license.seats == 1
|
||||
assert license.issued_on == make_aware(
|
||||
datetime(2021, 8, 29, 19, 52, 57, 842696), utc
|
||||
)
|
||||
assert license.issued_to_email == "bram@baserow.io"
|
||||
assert license.issued_to_name == "Bram"
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@override_settings(DEBUG=True)
|
||||
def test_premium_license_model_is_active():
|
||||
license = License(license=VALID_ONE_SEAT_LICENSE.decode())
|
||||
|
||||
with freeze_time("2021-08-29 19:50"):
|
||||
assert not license.is_active
|
||||
|
||||
with freeze_time("2021-08-29 20:00"):
|
||||
assert license.is_active
|
||||
|
||||
with freeze_time("2021-09-29 19:50"):
|
||||
assert license.is_active
|
||||
|
||||
with freeze_time("2021-09-29 19:54"):
|
||||
assert not license.is_active
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@override_settings(DEBUG=True)
|
||||
def test_premium_license_model_save():
|
||||
license_1 = License(license=VALID_ONE_SEAT_LICENSE.decode())
|
||||
license_2 = License(license=VALID_TWO_SEAT_LICENSE.decode())
|
||||
|
||||
assert license_1.license_id == "1"
|
||||
|
||||
license_1.license = VALID_TWO_SEAT_LICENSE.decode()
|
||||
license_1.save()
|
||||
license_2.license = VALID_ONE_SEAT_LICENSE.decode()
|
||||
license_2.save()
|
||||
|
||||
assert license_1.license_id == "2"
|
||||
assert license_2.license_id == "1"
|
|
@ -0,0 +1,18 @@
|
|||
import pytest
|
||||
|
||||
from unittest.mock import patch
|
||||
|
||||
from baserow_premium.license.tasks import license_check
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@patch("baserow_premium.license.handler.check_licenses")
|
||||
def test_license_check(mock_check_licenses, premium_data_fixture):
|
||||
license_check()
|
||||
|
||||
assert not mock_check_licenses.called
|
||||
|
||||
premium_data_fixture.create_premium_license()
|
||||
license_check()
|
||||
|
||||
assert mock_check_licenses.called
|
|
@ -3,15 +3,21 @@ from unittest.mock import patch, call
|
|||
import pytest
|
||||
from freezegun import freeze_time
|
||||
|
||||
from django.test.utils import override_settings
|
||||
|
||||
from baserow.contrib.database.rows.handler import RowHandler
|
||||
from baserow_premium.row_comments.exceptions import InvalidRowCommentException
|
||||
from baserow_premium.row_comments.handler import RowCommentHandler
|
||||
from baserow_premium.license.exceptions import NoPremiumLicenseError
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_cant_make_null_comment_using_handler(data_fixture):
|
||||
user = data_fixture.create_user(first_name="Test User")
|
||||
table, fields, rows = data_fixture.build_table(
|
||||
@override_settings(DEBUG=True)
|
||||
def test_cant_make_null_comment_using_handler(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 pytest.raises(InvalidRowCommentException):
|
||||
|
@ -20,20 +26,39 @@ def test_cant_make_null_comment_using_handler(data_fixture):
|
|||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_cant_make_blank_comment_using_handler(data_fixture):
|
||||
user = data_fixture.create_user(first_name="Test User")
|
||||
table, fields, rows = data_fixture.build_table(
|
||||
@override_settings(DEBUG=True)
|
||||
def test_cant_make_blank_comment_using_handler(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 pytest.raises(InvalidRowCommentException):
|
||||
RowCommentHandler.create_comment(user, table.id, rows[0].id, "")
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@override_settings(DEBUG=True)
|
||||
def test_cant_create_comment_without_premium_license(premium_data_fixture):
|
||||
user = premium_data_fixture.create_user(first_name="Test User")
|
||||
table, fields, rows = premium_data_fixture.build_table(
|
||||
columns=[("text", "text")], rows=["first row", "second_row"], user=user
|
||||
)
|
||||
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")
|
||||
def test_row_comment_created_signal_called(mock_row_comment_created, data_fixture):
|
||||
user = data_fixture.create_user(first_name="test_user")
|
||||
table, fields, rows = data_fixture.build_table(
|
||||
def test_row_comment_created_signal_called(
|
||||
mock_row_comment_created, 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"], user=user
|
||||
)
|
||||
|
||||
|
|
|
@ -2,15 +2,19 @@ from unittest.mock import patch
|
|||
|
||||
import pytest
|
||||
from freezegun import freeze_time
|
||||
from django.test.utils import override_settings
|
||||
|
||||
from baserow_premium.row_comments.handler import RowCommentHandler
|
||||
|
||||
|
||||
@pytest.mark.django_db(transaction=True)
|
||||
@override_settings(DEBUG=True)
|
||||
@patch("baserow.ws.registries.broadcast_to_channel_group")
|
||||
def test_row_comment_created(mock_broadcast_to_channel_group, data_fixture):
|
||||
user = data_fixture.create_user(first_name="test_user")
|
||||
table, fields, rows = data_fixture.build_table(
|
||||
def test_row_comment_created(mock_broadcast_to_channel_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"], user=user
|
||||
)
|
||||
|
||||
|
|
|
@ -1,6 +1,19 @@
|
|||
import { AdminType } from '@baserow/modules/core/adminTypes'
|
||||
import { PremiumPlugin } from '@baserow_premium/plugins'
|
||||
|
||||
export class DashboardType extends AdminType {
|
||||
class PremiumAdminType extends AdminType {
|
||||
getDeactivatedText() {
|
||||
return this.app.i18n.t('premium.deactivated')
|
||||
}
|
||||
|
||||
isDeactivated() {
|
||||
return !PremiumPlugin.hasValidPremiumLicense(
|
||||
this.app.store.getters['auth/getAdditionalUserData']
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export class DashboardType extends PremiumAdminType {
|
||||
static getType() {
|
||||
return 'dashboard'
|
||||
}
|
||||
|
@ -22,7 +35,7 @@ export class DashboardType extends AdminType {
|
|||
}
|
||||
}
|
||||
|
||||
export class UsersAdminType extends AdminType {
|
||||
export class UsersAdminType extends PremiumAdminType {
|
||||
static getType() {
|
||||
return 'users'
|
||||
}
|
||||
|
@ -44,7 +57,7 @@ export class UsersAdminType extends AdminType {
|
|||
}
|
||||
}
|
||||
|
||||
export class GroupsAdminType extends AdminType {
|
||||
export class GroupsAdminType extends PremiumAdminType {
|
||||
static getType() {
|
||||
return 'groups'
|
||||
}
|
||||
|
@ -65,3 +78,25 @@ export class GroupsAdminType extends AdminType {
|
|||
return 3
|
||||
}
|
||||
}
|
||||
|
||||
export class LicensesAdminType extends AdminType {
|
||||
static getType() {
|
||||
return 'licenses'
|
||||
}
|
||||
|
||||
getIconClass() {
|
||||
return 'certificate'
|
||||
}
|
||||
|
||||
getName() {
|
||||
return 'Licenses'
|
||||
}
|
||||
|
||||
getRouteName() {
|
||||
return 'admin-licenses'
|
||||
}
|
||||
|
||||
getOrder() {
|
||||
return 10000
|
||||
}
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue