2024-04-25 09:22:28 +02:00
|
|
|
from typing import Optional
|
2020-08-14 11:23:55 +02:00
|
|
|
|
2021-04-14 17:45:39 +02:00
|
|
|
from django.contrib.auth import get_user_model
|
2020-08-14 11:23:55 +02:00
|
|
|
from django.db import models
|
2023-06-15 10:33:19 +02:00
|
|
|
from django.db.models import Q
|
2021-05-24 22:17:01 +02:00
|
|
|
from django.db.models.signals import post_save
|
|
|
|
from django.dispatch.dispatcher import receiver
|
2020-12-04 19:54:27 +01:00
|
|
|
from django.utils import timezone
|
2021-04-14 17:45:39 +02:00
|
|
|
from rest_framework import exceptions, status
|
2020-08-14 11:23:55 +02:00
|
|
|
|
2021-09-09 12:19:55 +02:00
|
|
|
from posthog.constants import AvailableFeature
|
2022-04-29 10:27:06 +02:00
|
|
|
from posthog.models.utils import sane_repr
|
2024-05-29 02:29:24 +02:00
|
|
|
from posthog.tasks.tasks import sync_all_organization_available_product_features
|
2021-05-24 22:17:01 +02:00
|
|
|
|
2020-08-14 11:23:55 +02:00
|
|
|
|
2021-04-14 17:45:39 +02:00
|
|
|
class LicenseError(exceptions.APIException):
|
|
|
|
"""
|
|
|
|
Exception raised for licensing errors.
|
2020-08-14 11:23:55 +02:00
|
|
|
"""
|
|
|
|
|
2021-04-14 17:45:39 +02:00
|
|
|
default_type = "license_error"
|
|
|
|
default_code = "license_error"
|
|
|
|
status_code = status.HTTP_400_BAD_REQUEST
|
|
|
|
default_detail = "There was a problem with your current license."
|
|
|
|
|
2020-08-14 11:23:55 +02:00
|
|
|
def __init__(self, code, detail):
|
|
|
|
self.code = code
|
2021-04-14 17:45:39 +02:00
|
|
|
self.detail = exceptions._get_error_details(detail, code)
|
2020-08-14 11:23:55 +02:00
|
|
|
|
|
|
|
|
|
|
|
class LicenseManager(models.Manager):
|
2020-12-04 19:54:27 +01:00
|
|
|
def first_valid(self) -> Optional["License"]:
|
2023-06-15 10:33:19 +02:00
|
|
|
"""Return the highest valid license or cloud licenses if any"""
|
|
|
|
valid_licenses = list(self.filter(Q(valid_until__gte=timezone.now()) | Q(plan="cloud")))
|
2022-04-20 13:33:41 +02:00
|
|
|
if not valid_licenses:
|
|
|
|
return None
|
2023-10-26 12:38:15 +02:00
|
|
|
return max(
|
|
|
|
valid_licenses,
|
|
|
|
key=lambda license: License.PLAN_TO_SORTING_VALUE.get(license.plan, 0),
|
|
|
|
)
|
2020-08-14 11:23:55 +02:00
|
|
|
|
|
|
|
|
|
|
|
class License(models.Model):
|
2021-05-24 22:17:01 +02:00
|
|
|
objects: LicenseManager = LicenseManager()
|
2020-08-26 10:34:57 +02:00
|
|
|
|
2024-08-22 11:42:25 +02:00
|
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
|
|
plan = models.CharField(max_length=200)
|
|
|
|
valid_until = models.DateTimeField()
|
|
|
|
key = models.CharField(max_length=200)
|
2022-10-19 08:58:36 +02:00
|
|
|
# DEPRECATED: This is no longer used
|
2024-08-22 11:42:25 +02:00
|
|
|
max_users = models.IntegerField(default=None, null=True) # None = no restriction
|
2020-08-14 11:23:55 +02:00
|
|
|
|
2022-11-10 17:37:56 +01:00
|
|
|
# NOTE: Remember to update the Billing Service as well. Long-term it will be the source of truth.
|
2021-10-14 14:13:37 +02:00
|
|
|
SCALE_PLAN = "scale"
|
|
|
|
SCALE_FEATURES = [
|
2021-09-09 12:19:55 +02:00
|
|
|
AvailableFeature.ZAPIER,
|
|
|
|
AvailableFeature.ORGANIZATIONS_PROJECTS,
|
2023-09-27 22:06:51 +02:00
|
|
|
AvailableFeature.SOCIAL_SSO,
|
2021-09-09 12:19:55 +02:00
|
|
|
AvailableFeature.INGESTION_TAXONOMY,
|
2021-10-18 09:17:54 +02:00
|
|
|
AvailableFeature.PATHS_ADVANCED,
|
2021-11-03 18:41:16 +01:00
|
|
|
AvailableFeature.CORRELATION_ANALYSIS,
|
2021-12-09 07:37:39 +01:00
|
|
|
AvailableFeature.GROUP_ANALYTICS,
|
2022-02-14 07:19:53 +01:00
|
|
|
AvailableFeature.TAGGING,
|
2022-05-17 19:51:26 +02:00
|
|
|
AvailableFeature.BEHAVIORAL_COHORT_FILTERING,
|
2022-05-19 18:04:41 +02:00
|
|
|
AvailableFeature.WHITE_LABELLING,
|
2022-06-22 14:26:13 +02:00
|
|
|
AvailableFeature.SUBSCRIPTIONS,
|
2022-10-12 10:30:58 +02:00
|
|
|
AvailableFeature.APP_METRICS,
|
2022-11-15 16:19:59 +01:00
|
|
|
AvailableFeature.RECORDINGS_PLAYLISTS,
|
2022-12-02 18:20:21 +01:00
|
|
|
AvailableFeature.RECORDINGS_FILE_EXPORT,
|
2023-01-06 09:51:51 +01:00
|
|
|
AvailableFeature.RECORDINGS_PERFORMANCE,
|
2021-10-14 14:13:37 +02:00
|
|
|
]
|
|
|
|
|
|
|
|
ENTERPRISE_PLAN = "enterprise"
|
2024-04-19 14:19:10 +02:00
|
|
|
ENTERPRISE_FEATURES = [
|
|
|
|
*SCALE_FEATURES,
|
2024-03-04 16:07:09 +01:00
|
|
|
AvailableFeature.ADVANCED_PERMISSIONS,
|
2021-11-17 15:08:31 +01:00
|
|
|
AvailableFeature.PROJECT_BASED_PERMISSIONING,
|
2021-10-14 14:13:37 +02:00
|
|
|
AvailableFeature.SAML,
|
2022-03-18 12:32:44 +01:00
|
|
|
AvailableFeature.SSO_ENFORCEMENT,
|
2022-11-24 02:36:29 +01:00
|
|
|
AvailableFeature.ROLE_BASED_ACCESS,
|
2021-10-14 14:13:37 +02:00
|
|
|
]
|
|
|
|
PLANS = {SCALE_PLAN: SCALE_FEATURES, ENTERPRISE_PLAN: ENTERPRISE_FEATURES}
|
2022-04-29 10:27:06 +02:00
|
|
|
# The higher the plan, the higher its sorting value - sync with front-end licenseLogic
|
|
|
|
PLAN_TO_SORTING_VALUE = {SCALE_PLAN: 10, ENTERPRISE_PLAN: 20}
|
2020-12-04 19:54:27 +01:00
|
|
|
|
|
|
|
@property
|
2024-04-25 09:22:28 +02:00
|
|
|
def available_features(self) -> list[AvailableFeature]:
|
2020-12-04 19:54:27 +01:00
|
|
|
return self.PLANS.get(self.plan, [])
|
2021-04-14 17:45:39 +02:00
|
|
|
|
2022-10-19 12:28:26 +02:00
|
|
|
@property
|
|
|
|
def is_v2_license(self) -> bool:
|
|
|
|
return self.key and len(self.key.split("::")) == 2
|
|
|
|
|
2022-04-29 10:27:06 +02:00
|
|
|
__repr__ = sane_repr("key", "plan", "valid_until")
|
|
|
|
|
2021-04-14 17:45:39 +02:00
|
|
|
|
|
|
|
def get_licensed_users_available() -> Optional[int]:
|
|
|
|
"""
|
|
|
|
Returns the number of user slots available that can be created based on the instance's current license.
|
|
|
|
Not relevant for cloud users.
|
|
|
|
`None` means unlimited users.
|
|
|
|
"""
|
|
|
|
|
|
|
|
license = License.objects.first_valid()
|
|
|
|
from posthog.models import OrganizationInvite
|
|
|
|
|
|
|
|
if license:
|
|
|
|
if license.max_users is None:
|
|
|
|
return None
|
|
|
|
|
|
|
|
users_left = license.max_users - get_user_model().objects.count() - OrganizationInvite.objects.count()
|
|
|
|
return max(users_left, 0)
|
|
|
|
|
|
|
|
return None
|
2021-05-24 22:17:01 +02:00
|
|
|
|
|
|
|
|
|
|
|
@receiver(post_save, sender=License)
|
|
|
|
def license_saved(sender, instance, created, raw, using, **kwargs):
|
2024-05-29 02:29:24 +02:00
|
|
|
sync_all_organization_available_product_features()
|