authentik.enterprise.api

Enterprise API Views

  1"""Enterprise API Views"""
  2
  3from collections.abc import Callable
  4from datetime import timedelta
  5from functools import wraps
  6
  7from django.utils.timezone import now
  8from django.utils.translation import gettext as _
  9from drf_spectacular.types import OpenApiTypes
 10from drf_spectacular.utils import OpenApiParameter, extend_schema, inline_serializer
 11from rest_framework.decorators import action
 12from rest_framework.exceptions import ValidationError
 13from rest_framework.fields import CharField, IntegerField
 14from rest_framework.permissions import IsAuthenticated
 15from rest_framework.request import Request
 16from rest_framework.response import Response
 17from rest_framework.validators import UniqueValidator
 18from rest_framework.viewsets import ModelViewSet
 19
 20from authentik.core.api.used_by import UsedByMixin
 21from authentik.core.api.utils import ModelSerializer, PassiveSerializer
 22from authentik.core.models import User, UserTypes
 23from authentik.enterprise.license import LicenseKey, LicenseSummarySerializer
 24from authentik.enterprise.models import License
 25from authentik.rbac.decorators import permission_required
 26from authentik.tenants.utils import get_unique_identifier
 27
 28
 29class EnterpriseRequiredMixin:
 30    """Mixin to validate that a valid enterprise license
 31    exists before allowing to save the object"""
 32
 33    def validate(self, attrs: dict) -> dict:
 34        """Check that a valid license exists"""
 35        if not LicenseKey.cached_summary().status.is_valid:
 36            raise ValidationError(_("Enterprise is required to create/update this object."))
 37        return super().validate(attrs)
 38
 39
 40def enterprise_action(func: Callable):
 41    """Check permissions for a single custom action"""
 42
 43    @wraps(func)
 44    def wrapper(*args, **kwargs) -> Response:
 45        if not LicenseKey.cached_summary().status.is_valid:
 46            raise ValidationError(_("Enterprise is required to use this endpoint."))
 47        return func(*args, **kwargs)
 48
 49    return wrapper
 50
 51
 52class LicenseSerializer(ModelSerializer):
 53    """License Serializer"""
 54
 55    def validate_key(self, key: str) -> str:
 56        """Validate the license key (install_id and signature)"""
 57        LicenseKey.validate(key)
 58        return key
 59
 60    class Meta:
 61        model = License
 62        fields = [
 63            "license_uuid",
 64            "name",
 65            "key",
 66            "expiry",
 67            "internal_users",
 68            "external_users",
 69        ]
 70        extra_kwargs = {
 71            "key": {"validators": [UniqueValidator(queryset=License.objects.all())]},
 72            "name": {"read_only": True},
 73            "expiry": {"read_only": True},
 74            "internal_users": {"read_only": True},
 75            "external_users": {"read_only": True},
 76        }
 77
 78
 79class LicenseForecastSerializer(PassiveSerializer):
 80    """Serializer for license forecast"""
 81
 82    internal_users = IntegerField(required=True)
 83    external_users = IntegerField(required=True)
 84    forecasted_internal_users = IntegerField(required=True)
 85    forecasted_external_users = IntegerField(required=True)
 86
 87
 88class LicenseViewSet(UsedByMixin, ModelViewSet):
 89    """License Viewset"""
 90
 91    queryset = License.objects.all()
 92    serializer_class = LicenseSerializer
 93    search_fields = ["name"]
 94    ordering = ["name"]
 95    filterset_fields = ["name"]
 96
 97    @permission_required(None, ["authentik_enterprise.view_license"])
 98    @extend_schema(
 99        request=OpenApiTypes.NONE,
100        responses={
101            200: inline_serializer("InstallIDSerializer", {"install_id": CharField(required=True)}),
102        },
103    )
104    @action(detail=False, methods=["GET"])
105    def install_id(self, request: Request) -> Response:
106        """Get install_id"""
107        return Response(
108            data={
109                "install_id": get_unique_identifier(),
110            }
111        )
112
113    @extend_schema(
114        request=OpenApiTypes.NONE,
115        responses={
116            200: LicenseSummarySerializer(),
117        },
118        parameters=[
119            OpenApiParameter(
120                name="cached",
121                location=OpenApiParameter.QUERY,
122                type=OpenApiTypes.BOOL,
123                default=True,
124            )
125        ],
126    )
127    @action(detail=False, methods=["GET"], permission_classes=[IsAuthenticated])
128    def summary(self, request: Request) -> Response:
129        """Get the total license status"""
130        summary = LicenseKey.cached_summary()
131        if request.query_params.get("cached", "true").lower() == "false":
132            summary = LicenseKey.get_total().summary()
133        response = LicenseSummarySerializer(instance=summary)
134        return Response(response.data)
135
136    @permission_required(None, ["authentik_enterprise.view_license"])
137    @extend_schema(
138        request=OpenApiTypes.NONE,
139        responses={
140            200: LicenseForecastSerializer(),
141        },
142    )
143    @action(detail=False, methods=["GET"])
144    def forecast(self, request: Request) -> Response:
145        """Forecast how many users will be required in a year"""
146        last_month = now() - timedelta(days=30)
147        # Forecast for internal users
148        internal_in_last_month = User.objects.filter(
149            type=UserTypes.INTERNAL, date_joined__gte=last_month
150        ).count()
151        # Forecast for external users
152        external_in_last_month = LicenseKey.get_external_user_count()
153        forecast_for_months = 12
154        response = LicenseForecastSerializer(
155            data={
156                "internal_users": LicenseKey.get_internal_user_count(),
157                "external_users": LicenseKey.get_external_user_count(),
158                "forecasted_internal_users": (internal_in_last_month * forecast_for_months),
159                "forecasted_external_users": (external_in_last_month * forecast_for_months),
160            }
161        )
162        response.is_valid(raise_exception=True)
163        return Response(response.data)
class EnterpriseRequiredMixin:
30class EnterpriseRequiredMixin:
31    """Mixin to validate that a valid enterprise license
32    exists before allowing to save the object"""
33
34    def validate(self, attrs: dict) -> dict:
35        """Check that a valid license exists"""
36        if not LicenseKey.cached_summary().status.is_valid:
37            raise ValidationError(_("Enterprise is required to create/update this object."))
38        return super().validate(attrs)

Mixin to validate that a valid enterprise license exists before allowing to save the object

def validate(self, attrs: dict) -> dict:
34    def validate(self, attrs: dict) -> dict:
35        """Check that a valid license exists"""
36        if not LicenseKey.cached_summary().status.is_valid:
37            raise ValidationError(_("Enterprise is required to create/update this object."))
38        return super().validate(attrs)

Check that a valid license exists

def enterprise_action(func: Callable):
41def enterprise_action(func: Callable):
42    """Check permissions for a single custom action"""
43
44    @wraps(func)
45    def wrapper(*args, **kwargs) -> Response:
46        if not LicenseKey.cached_summary().status.is_valid:
47            raise ValidationError(_("Enterprise is required to use this endpoint."))
48        return func(*args, **kwargs)
49
50    return wrapper

Check permissions for a single custom action

class LicenseSerializer(authentik.core.api.utils.ModelSerializer):
53class LicenseSerializer(ModelSerializer):
54    """License Serializer"""
55
56    def validate_key(self, key: str) -> str:
57        """Validate the license key (install_id and signature)"""
58        LicenseKey.validate(key)
59        return key
60
61    class Meta:
62        model = License
63        fields = [
64            "license_uuid",
65            "name",
66            "key",
67            "expiry",
68            "internal_users",
69            "external_users",
70        ]
71        extra_kwargs = {
72            "key": {"validators": [UniqueValidator(queryset=License.objects.all())]},
73            "name": {"read_only": True},
74            "expiry": {"read_only": True},
75            "internal_users": {"read_only": True},
76            "external_users": {"read_only": True},
77        }

License Serializer

def validate_key(self, key: str) -> str:
56    def validate_key(self, key: str) -> str:
57        """Validate the license key (install_id and signature)"""
58        LicenseKey.validate(key)
59        return key

Validate the license key (install_id and signature)

class LicenseSerializer.Meta:
61    class Meta:
62        model = License
63        fields = [
64            "license_uuid",
65            "name",
66            "key",
67            "expiry",
68            "internal_users",
69            "external_users",
70        ]
71        extra_kwargs = {
72            "key": {"validators": [UniqueValidator(queryset=License.objects.all())]},
73            "name": {"read_only": True},
74            "expiry": {"read_only": True},
75            "internal_users": {"read_only": True},
76            "external_users": {"read_only": True},
77        }
fields = ['license_uuid', 'name', 'key', 'expiry', 'internal_users', 'external_users']
extra_kwargs = {'key': {'validators': [<UniqueValidator(queryset=<QuerySet []>)>]}, 'name': {'read_only': True}, 'expiry': {'read_only': True}, 'internal_users': {'read_only': True}, 'external_users': {'read_only': True}}
class LicenseForecastSerializer(authentik.core.api.utils.PassiveSerializer):
80class LicenseForecastSerializer(PassiveSerializer):
81    """Serializer for license forecast"""
82
83    internal_users = IntegerField(required=True)
84    external_users = IntegerField(required=True)
85    forecasted_internal_users = IntegerField(required=True)
86    forecasted_external_users = IntegerField(required=True)

Serializer for license forecast

internal_users
external_users
forecasted_internal_users
forecasted_external_users
class LicenseViewSet(authentik.core.api.used_by.UsedByMixin, rest_framework.viewsets.ModelViewSet):
 89class LicenseViewSet(UsedByMixin, ModelViewSet):
 90    """License Viewset"""
 91
 92    queryset = License.objects.all()
 93    serializer_class = LicenseSerializer
 94    search_fields = ["name"]
 95    ordering = ["name"]
 96    filterset_fields = ["name"]
 97
 98    @permission_required(None, ["authentik_enterprise.view_license"])
 99    @extend_schema(
100        request=OpenApiTypes.NONE,
101        responses={
102            200: inline_serializer("InstallIDSerializer", {"install_id": CharField(required=True)}),
103        },
104    )
105    @action(detail=False, methods=["GET"])
106    def install_id(self, request: Request) -> Response:
107        """Get install_id"""
108        return Response(
109            data={
110                "install_id": get_unique_identifier(),
111            }
112        )
113
114    @extend_schema(
115        request=OpenApiTypes.NONE,
116        responses={
117            200: LicenseSummarySerializer(),
118        },
119        parameters=[
120            OpenApiParameter(
121                name="cached",
122                location=OpenApiParameter.QUERY,
123                type=OpenApiTypes.BOOL,
124                default=True,
125            )
126        ],
127    )
128    @action(detail=False, methods=["GET"], permission_classes=[IsAuthenticated])
129    def summary(self, request: Request) -> Response:
130        """Get the total license status"""
131        summary = LicenseKey.cached_summary()
132        if request.query_params.get("cached", "true").lower() == "false":
133            summary = LicenseKey.get_total().summary()
134        response = LicenseSummarySerializer(instance=summary)
135        return Response(response.data)
136
137    @permission_required(None, ["authentik_enterprise.view_license"])
138    @extend_schema(
139        request=OpenApiTypes.NONE,
140        responses={
141            200: LicenseForecastSerializer(),
142        },
143    )
144    @action(detail=False, methods=["GET"])
145    def forecast(self, request: Request) -> Response:
146        """Forecast how many users will be required in a year"""
147        last_month = now() - timedelta(days=30)
148        # Forecast for internal users
149        internal_in_last_month = User.objects.filter(
150            type=UserTypes.INTERNAL, date_joined__gte=last_month
151        ).count()
152        # Forecast for external users
153        external_in_last_month = LicenseKey.get_external_user_count()
154        forecast_for_months = 12
155        response = LicenseForecastSerializer(
156            data={
157                "internal_users": LicenseKey.get_internal_user_count(),
158                "external_users": LicenseKey.get_external_user_count(),
159                "forecasted_internal_users": (internal_in_last_month * forecast_for_months),
160                "forecasted_external_users": (external_in_last_month * forecast_for_months),
161            }
162        )
163        response.is_valid(raise_exception=True)
164        return Response(response.data)

License Viewset

queryset = <QuerySet []>
serializer_class = <class 'LicenseSerializer'>
search_fields = ['name']
ordering = ['name']
filterset_fields = ['name']
@permission_required(None, ['authentik_enterprise.view_license'])
@extend_schema(request=OpenApiTypes.NONE, responses={200: inline_serializer('InstallIDSerializer', {'install_id': CharField(required=True)})})
@action(detail=False, methods=['GET'])
def install_id( self, request: rest_framework.request.Request) -> rest_framework.response.Response:
 98    @permission_required(None, ["authentik_enterprise.view_license"])
 99    @extend_schema(
100        request=OpenApiTypes.NONE,
101        responses={
102            200: inline_serializer("InstallIDSerializer", {"install_id": CharField(required=True)}),
103        },
104    )
105    @action(detail=False, methods=["GET"])
106    def install_id(self, request: Request) -> Response:
107        """Get install_id"""
108        return Response(
109            data={
110                "install_id": get_unique_identifier(),
111            }
112        )

Get install_id

@extend_schema(request=OpenApiTypes.NONE, responses={200: LicenseSummarySerializer()}, parameters=[OpenApiParameter(name='cached', location=OpenApiParameter.QUERY, type=OpenApiTypes.BOOL, default=True)])
@action(detail=False, methods=['GET'], permission_classes=[IsAuthenticated])
def summary( self, request: rest_framework.request.Request) -> rest_framework.response.Response:
114    @extend_schema(
115        request=OpenApiTypes.NONE,
116        responses={
117            200: LicenseSummarySerializer(),
118        },
119        parameters=[
120            OpenApiParameter(
121                name="cached",
122                location=OpenApiParameter.QUERY,
123                type=OpenApiTypes.BOOL,
124                default=True,
125            )
126        ],
127    )
128    @action(detail=False, methods=["GET"], permission_classes=[IsAuthenticated])
129    def summary(self, request: Request) -> Response:
130        """Get the total license status"""
131        summary = LicenseKey.cached_summary()
132        if request.query_params.get("cached", "true").lower() == "false":
133            summary = LicenseKey.get_total().summary()
134        response = LicenseSummarySerializer(instance=summary)
135        return Response(response.data)

Get the total license status

@permission_required(None, ['authentik_enterprise.view_license'])
@extend_schema(request=OpenApiTypes.NONE, responses={200: LicenseForecastSerializer()})
@action(detail=False, methods=['GET'])
def forecast( self, request: rest_framework.request.Request) -> rest_framework.response.Response:
137    @permission_required(None, ["authentik_enterprise.view_license"])
138    @extend_schema(
139        request=OpenApiTypes.NONE,
140        responses={
141            200: LicenseForecastSerializer(),
142        },
143    )
144    @action(detail=False, methods=["GET"])
145    def forecast(self, request: Request) -> Response:
146        """Forecast how many users will be required in a year"""
147        last_month = now() - timedelta(days=30)
148        # Forecast for internal users
149        internal_in_last_month = User.objects.filter(
150            type=UserTypes.INTERNAL, date_joined__gte=last_month
151        ).count()
152        # Forecast for external users
153        external_in_last_month = LicenseKey.get_external_user_count()
154        forecast_for_months = 12
155        response = LicenseForecastSerializer(
156            data={
157                "internal_users": LicenseKey.get_internal_user_count(),
158                "external_users": LicenseKey.get_external_user_count(),
159                "forecasted_internal_users": (internal_in_last_month * forecast_for_months),
160                "forecasted_external_users": (external_in_last_month * forecast_for_months),
161            }
162        )
163        response.is_valid(raise_exception=True)
164        return Response(response.data)

Forecast how many users will be required in a year

name = None
description = None
suffix = None
detail = None
basename = None