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
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)
Inherited Members
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 }
model =
<class 'authentik.enterprise.models.License'>
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
Inherited Members
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
serializer_class =
<class 'LicenseSerializer'>
@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
@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