*: add new base class for non-model serializers

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
Jens Langhammer 2021-03-30 15:50:00 +02:00
parent c7dcf92a2e
commit b1214f6c35
16 changed files with 69 additions and 248 deletions

View File

@ -3,7 +3,7 @@ import time
from collections import Counter
from datetime import timedelta
from django.db.models import Count, ExpressionWrapper, F, Model
from django.db.models import Count, ExpressionWrapper, F
from django.db.models.fields import DurationField
from django.db.models.functions import ExtractHour
from django.utils.timezone import now
@ -12,9 +12,9 @@ from rest_framework.fields import IntegerField, SerializerMethodField
from rest_framework.permissions import IsAdminUser
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.serializers import Serializer
from rest_framework.viewsets import ViewSet
from authentik.core.api.utils import PassiveSerializer
from authentik.events.models import Event, EventAction
@ -45,20 +45,14 @@ def get_events_per_1h(**filter_kwargs) -> list[dict[str, int]]:
return results
class CoordinateSerializer(Serializer):
class CoordinateSerializer(PassiveSerializer):
"""Coordinates for diagrams"""
x_cord = IntegerField(read_only=True)
y_cord = IntegerField(read_only=True)
def create(self, validated_data: dict) -> Model:
raise NotImplementedError
def update(self, instance: Model, validated_data: dict) -> Model:
raise NotImplementedError
class LoginMetricsSerializer(Serializer):
class LoginMetricsSerializer(PassiveSerializer):
"""Login Metrics per 1h"""
logins_per_1h = SerializerMethodField()
@ -74,12 +68,6 @@ class LoginMetricsSerializer(Serializer):
"""Get failed logins per hour for the last 24 hours"""
return get_events_per_1h(action=EventAction.LOGIN_FAILED)
def create(self, validated_data: dict) -> Model:
raise NotImplementedError
def update(self, instance: Model, validated_data: dict) -> Model:
raise NotImplementedError
class AdministrationMetricsViewSet(ViewSet):
"""Login Metrics per 1h"""

View File

@ -2,7 +2,6 @@
from importlib import import_module
from django.contrib import messages
from django.db.models import Model
from django.http.response import Http404
from django.utils.translation import gettext_lazy as _
from drf_yasg.utils import swagger_auto_schema
@ -11,13 +10,13 @@ from rest_framework.fields import CharField, ChoiceField, DateTimeField, ListFie
from rest_framework.permissions import IsAdminUser
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.serializers import Serializer
from rest_framework.viewsets import ViewSet
from authentik.core.api.utils import PassiveSerializer
from authentik.events.monitored_tasks import TaskInfo, TaskResultStatus
class TaskSerializer(Serializer):
class TaskSerializer(PassiveSerializer):
"""Serialize TaskInfo and TaskResult"""
task_name = CharField()
@ -30,12 +29,6 @@ class TaskSerializer(Serializer):
)
messages = ListField(source="result.messages")
def create(self, validated_data: dict) -> Model:
raise NotImplementedError
def update(self, instance: Model, validated_data: dict) -> Model:
raise NotImplementedError
class TaskViewSet(ViewSet):
"""Read-only view set that returns all background tasks"""

View File

@ -2,7 +2,6 @@
from os import environ
from django.core.cache import cache
from django.db.models import Model
from drf_yasg.utils import swagger_auto_schema
from packaging.version import parse
from rest_framework.fields import SerializerMethodField
@ -10,14 +9,14 @@ from rest_framework.mixins import ListModelMixin
from rest_framework.permissions import IsAdminUser
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.serializers import Serializer
from rest_framework.viewsets import GenericViewSet
from authentik import ENV_GIT_HASH_KEY, __version__
from authentik.admin.tasks import VERSION_CACHE_KEY, update_latest_version
from authentik.core.api.utils import PassiveSerializer
class VersionSerializer(Serializer):
class VersionSerializer(PassiveSerializer):
"""Get running and latest version."""
version_current = SerializerMethodField()
@ -47,12 +46,6 @@ class VersionSerializer(Serializer):
self.get_version_latest(instance)
)
def create(self, validated_data: dict) -> Model:
raise NotImplementedError
def update(self, instance: Model, validated_data: dict) -> Model:
raise NotImplementedError
class VersionViewSet(ListModelMixin, GenericViewSet):
"""Get running and latest version."""

View File

@ -1,30 +1,23 @@
"""core Configs API"""
from django.db.models import Model
from drf_yasg.utils import swagger_auto_schema
from rest_framework.fields import BooleanField, CharField, ListField
from rest_framework.permissions import AllowAny
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.serializers import Serializer
from rest_framework.viewsets import ViewSet
from authentik.core.api.utils import PassiveSerializer
from authentik.lib.config import CONFIG
class FooterLinkSerializer(Serializer):
class FooterLinkSerializer(PassiveSerializer):
"""Links returned in Config API"""
href = CharField(read_only=True)
name = CharField(read_only=True)
def create(self, validated_data: dict) -> Model:
raise NotImplementedError
def update(self, instance: Model, validated_data: dict) -> Model:
raise NotImplementedError
class ConfigSerializer(Serializer):
class ConfigSerializer(PassiveSerializer):
"""Serialize authentik Config into DRF Object"""
branding_logo = CharField(read_only=True)
@ -35,12 +28,6 @@ class ConfigSerializer(Serializer):
error_reporting_environment = CharField(read_only=True)
error_reporting_send_pii = BooleanField(read_only=True)
def create(self, validated_data: dict) -> Model:
raise NotImplementedError
def update(self, instance: Model, validated_data: dict) -> Model:
raise NotImplementedError
class ConfigsViewSet(ViewSet):
"""Read-only view set that returns the current session's Configs"""

View File

@ -1,15 +1,15 @@
"""Tokens API Viewset"""
from django.db.models.base import Model
from django.http.response import Http404
from drf_yasg.utils import swagger_auto_schema
from rest_framework.decorators import action
from rest_framework.fields import CharField
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.serializers import ModelSerializer, Serializer
from rest_framework.serializers import ModelSerializer
from rest_framework.viewsets import ModelViewSet
from authentik.core.api.users import UserSerializer
from authentik.core.api.utils import PassiveSerializer
from authentik.core.models import Token
from authentik.events.models import Event, EventAction
@ -34,17 +34,11 @@ class TokenSerializer(ModelSerializer):
depth = 2
class TokenViewSerializer(Serializer):
class TokenViewSerializer(PassiveSerializer):
"""Show token's current key"""
key = CharField(read_only=True)
def create(self, validated_data: dict) -> Model:
raise NotImplementedError
def update(self, instance: Model, validated_data: dict) -> Model:
raise NotImplementedError
class TokenViewSet(ModelViewSet):
"""Token Viewset"""

View File

@ -1,5 +1,4 @@
"""User API Views"""
from django.db.models.base import Model
from django.urls import reverse_lazy
from django.utils.http import urlencode
from drf_yasg.utils import swagger_auto_schema, swagger_serializer_method
@ -8,12 +7,12 @@ from rest_framework.decorators import action
from rest_framework.fields import CharField, SerializerMethodField
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.serializers import BooleanField, ModelSerializer, Serializer
from rest_framework.serializers import BooleanField, ModelSerializer
from rest_framework.viewsets import ModelViewSet
from authentik.admin.api.metrics import CoordinateSerializer, get_events_per_1h
from authentik.api.decorators import permission_required
from authentik.core.api.utils import LinkSerializer
from authentik.core.api.utils import LinkSerializer, PassiveSerializer
from authentik.core.middleware import (
SESSION_IMPERSONATE_ORIGINAL_USER,
SESSION_IMPERSONATE_USER,
@ -44,33 +43,15 @@ class UserSerializer(ModelSerializer):
]
class SessionUserSerializer(Serializer):
class SessionUserSerializer(PassiveSerializer):
"""Response for the /user/me endpoint, returns the currently active user (as `user` property)
and, if this user is being impersonated, the original user in the `original` property."""
user = UserSerializer()
original = UserSerializer(required=False)
def create(self, validated_data: dict) -> Model:
raise NotImplementedError
def update(self, instance: Model, validated_data: dict) -> Model:
raise NotImplementedError
class UserRecoverySerializer(Serializer):
"""Recovery link for a user to reset their password"""
link = CharField()
def create(self, validated_data: dict) -> Model:
raise NotImplementedError
def update(self, instance: Model, validated_data: dict) -> Model:
raise NotImplementedError
class UserMetricsSerializer(Serializer):
class UserMetricsSerializer(PassiveSerializer):
"""User Metrics"""
logins_per_1h = SerializerMethodField()
@ -99,12 +80,6 @@ class UserMetricsSerializer(Serializer):
action=EventAction.AUTHORIZE_APPLICATION, user__pk=request.user.pk
)
def create(self, validated_data: dict) -> Model:
raise NotImplementedError
def update(self, instance: Model, validated_data: dict) -> Model:
raise NotImplementedError
class UserViewSet(ModelViewSet):
"""User Viewset"""

View File

@ -4,18 +4,22 @@ from rest_framework.fields import CharField, IntegerField
from rest_framework.serializers import Serializer, SerializerMethodField
class MetaNameSerializer(Serializer):
class PassiveSerializer(Serializer):
"""Base serializer class which doesn't implement create/update methods"""
def create(self, validated_data: dict) -> Model:
return Model()
def update(self, instance: Model, validated_data: dict) -> Model:
return Model()
class MetaNameSerializer(PassiveSerializer):
"""Add verbose names to response"""
verbose_name = SerializerMethodField()
verbose_name_plural = SerializerMethodField()
def create(self, validated_data: dict) -> Model:
raise NotImplementedError
def update(self, instance: Model, validated_data: dict) -> Model:
raise NotImplementedError
def get_verbose_name(self, obj: Model) -> str:
"""Return object's verbose_name"""
return obj._meta.verbose_name
@ -25,39 +29,21 @@ class MetaNameSerializer(Serializer):
return obj._meta.verbose_name_plural
class TypeCreateSerializer(Serializer):
class TypeCreateSerializer(PassiveSerializer):
"""Types of an object that can be created"""
name = CharField(required=True)
description = CharField(required=True)
link = CharField(required=True)
def create(self, validated_data: dict) -> Model:
raise NotImplementedError
def update(self, instance: Model, validated_data: dict) -> Model:
raise NotImplementedError
class CacheSerializer(Serializer):
class CacheSerializer(PassiveSerializer):
"""Generic cache stats for an object"""
count = IntegerField(read_only=True)
def create(self, validated_data: dict) -> Model:
raise NotImplementedError
def update(self, instance: Model, validated_data: dict) -> Model:
raise NotImplementedError
class LinkSerializer(Serializer):
class LinkSerializer(PassiveSerializer):
"""Returns a single link"""
link = CharField()
def create(self, validated_data: dict) -> Model:
raise NotImplementedError
def update(self, instance: Model, validated_data: dict) -> Model:
raise NotImplementedError

View File

@ -2,9 +2,9 @@
from dataclasses import dataclass
from typing import Optional
from django.db.models.base import Model
from rest_framework.fields import CharField
from rest_framework.serializers import Serializer
from authentik.core.api.utils import PassiveSerializer
@dataclass
@ -21,29 +21,17 @@ class UILoginButton:
icon_url: Optional[str] = None
class UILoginButtonSerializer(Serializer):
class UILoginButtonSerializer(PassiveSerializer):
"""Serializer for Login buttons of sources"""
name = CharField()
url = CharField()
icon_url = CharField(required=False)
def create(self, validated_data: dict) -> Model:
return Model()
def update(self, instance: Model, validated_data: dict) -> Model:
return Model()
class UserSettingSerializer(Serializer):
class UserSettingSerializer(PassiveSerializer):
"""Serializer for User settings for stages and sources"""
object_uid = CharField()
component = CharField()
title = CharField()
def create(self, validated_data: dict) -> Model:
return Model()
def update(self, instance: Model, validated_data: dict) -> Model:
return Model()

View File

@ -2,7 +2,6 @@
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.serialization import load_pem_private_key
from cryptography.x509 import load_pem_x509_certificate
from django.db.models import Model
from django.utils.translation import gettext_lazy as _
from drf_yasg.utils import swagger_auto_schema
from rest_framework.decorators import action
@ -14,10 +13,11 @@ from rest_framework.fields import (
)
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.serializers import ModelSerializer, Serializer, ValidationError
from rest_framework.serializers import ModelSerializer, ValidationError
from rest_framework.viewsets import ModelViewSet
from authentik.api.decorators import permission_required
from authentik.core.api.utils import PassiveSerializer
from authentik.crypto.builder import CertificateBuilder
from authentik.crypto.models import CertificateKeyPair
from authentik.events.models import Event, EventAction
@ -79,19 +79,13 @@ class CertificateKeyPairSerializer(ModelSerializer):
}
class CertificateDataSerializer(Serializer):
class CertificateDataSerializer(PassiveSerializer):
"""Get CertificateKeyPair's data"""
data = CharField(read_only=True)
def create(self, validated_data: dict) -> Model:
raise NotImplementedError
def update(self, instance: Model, validated_data: dict) -> Model:
raise NotImplementedError
class CertificateGenerationSerializer(Serializer):
class CertificateGenerationSerializer(PassiveSerializer):
"""Certificate generation parameters"""
common_name = CharField()
@ -100,12 +94,6 @@ class CertificateGenerationSerializer(Serializer):
)
validity_days = IntegerField(initial=365)
def create(self, validated_data: dict) -> Model:
raise NotImplementedError
def update(self, instance: Model, validated_data: dict) -> Model:
raise NotImplementedError
class CertificateKeyPairViewSet(ModelViewSet):
"""CertificateKeyPair Viewset"""

View File

@ -2,11 +2,11 @@
from enum import Enum
from typing import TYPE_CHECKING, Optional
from django.db.models.base import Model
from django.http import JsonResponse
from rest_framework.fields import ChoiceField, DictField
from rest_framework.serializers import CharField, Serializer
from rest_framework.serializers import CharField
from authentik.core.api.utils import PassiveSerializer
from authentik.flows.transfer.common import DataclassEncoder
if TYPE_CHECKING:
@ -21,20 +21,14 @@ class ChallengeTypes(Enum):
REDIRECT = "redirect"
class ErrorDetailSerializer(Serializer):
class ErrorDetailSerializer(PassiveSerializer):
"""Serializer for rest_framework's error messages"""
string = CharField()
code = CharField()
def create(self, validated_data: dict) -> Model:
return Model()
def update(self, instance: Model, validated_data: dict) -> Model:
return Model()
class Challenge(Serializer):
class Challenge(PassiveSerializer):
"""Challenge that gets sent to the client based on which stage
is currently active"""
@ -49,12 +43,6 @@ class Challenge(Serializer):
child=ErrorDetailSerializer(many=True), allow_empty=True, required=False
)
def create(self, validated_data: dict) -> Model:
return Model()
def update(self, instance: Model, validated_data: dict) -> Model:
return Model()
class RedirectChallenge(Challenge):
"""Challenge type to redirect the client"""
@ -81,20 +69,14 @@ class AccessDeniedChallenge(Challenge):
error_message = CharField(required=False)
class PermissionSerializer(Serializer):
class PermissionSerializer(PassiveSerializer):
"""Permission used for consent"""
name = CharField()
id = CharField()
def create(self, validated_data: dict) -> Model:
return Model()
def update(self, instance: Model, validated_data: dict) -> Model:
return Model()
class ChallengeResponse(Serializer):
class ChallengeResponse(PassiveSerializer):
"""Base class for all challenge responses"""
stage: Optional["StageView"]
@ -103,12 +85,6 @@ class ChallengeResponse(Serializer):
self.stage = kwargs.pop("stage", None)
super().__init__(instance=instance, data=data, **kwargs)
def create(self, validated_data: dict) -> Model:
return Model()
def update(self, instance: Model, validated_data: dict) -> Model:
return Model()
class HttpChallengeResponse(JsonResponse):
"""Subclass of JsonResponse that uses the `DataclassEncoder`"""

View File

@ -1,17 +1,20 @@
"""Outpost API Views"""
from dataclasses import asdict
from django.db.models.base import Model
from django.urls import reverse
from drf_yasg.utils import swagger_auto_schema
from rest_framework.decorators import action
from rest_framework.fields import BooleanField, CharField, SerializerMethodField
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.serializers import ModelSerializer, Serializer
from rest_framework.serializers import ModelSerializer
from rest_framework.viewsets import ModelViewSet
from authentik.core.api.utils import MetaNameSerializer, TypeCreateSerializer
from authentik.core.api.utils import (
MetaNameSerializer,
PassiveSerializer,
TypeCreateSerializer,
)
from authentik.lib.templatetags.authentik_utils import verbose_name
from authentik.lib.utils.reflection import all_subclasses
from authentik.outposts.models import (
@ -43,18 +46,12 @@ class ServiceConnectionSerializer(ModelSerializer, MetaNameSerializer):
]
class ServiceConnectionStateSerializer(Serializer):
class ServiceConnectionStateSerializer(PassiveSerializer):
"""Serializer for Service connection state"""
healthy = BooleanField(read_only=True)
version = CharField(read_only=True)
def create(self, validated_data: dict) -> Model:
raise NotImplementedError
def update(self, instance: Model, validated_data: dict) -> Model:
raise NotImplementedError
class ServiceConnectionViewSet(ModelViewSet):
"""ServiceConnection Viewset"""

View File

@ -1,14 +1,14 @@
"""Outpost API Views"""
from django.db.models import Model
from drf_yasg.utils import swagger_auto_schema
from rest_framework.decorators import action
from rest_framework.fields import BooleanField, CharField, DateTimeField
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.serializers import JSONField, ModelSerializer, Serializer
from rest_framework.serializers import JSONField, ModelSerializer
from rest_framework.viewsets import ModelViewSet
from authentik.core.api.providers import ProviderSerializer
from authentik.core.api.utils import PassiveSerializer
from authentik.outposts.models import Outpost, default_outpost_config
@ -32,19 +32,13 @@ class OutpostSerializer(ModelSerializer):
]
class OutpostDefaultConfigSerializer(Serializer):
class OutpostDefaultConfigSerializer(PassiveSerializer):
"""Global default outpost config"""
config = JSONField(read_only=True)
def create(self, validated_data: dict) -> Model:
raise NotImplementedError
def update(self, instance: Model, validated_data: dict) -> Model:
raise NotImplementedError
class OutpostHealthSerializer(Serializer):
class OutpostHealthSerializer(PassiveSerializer):
"""Outpost health status"""
last_seen = DateTimeField(read_only=True)
@ -52,12 +46,6 @@ class OutpostHealthSerializer(Serializer):
version_should = CharField(read_only=True)
version_outdated = BooleanField(read_only=True)
def create(self, validated_data: dict) -> Model:
raise NotImplementedError
def update(self, instance: Model, validated_data: dict) -> Model:
raise NotImplementedError
class OutpostViewSet(ModelViewSet):
"""Outpost Viewset"""

View File

@ -1,33 +1,20 @@
"""Serializer for policy execution"""
from django.db.models import Model
from rest_framework.fields import BooleanField, CharField, JSONField, ListField
from rest_framework.relations import PrimaryKeyRelatedField
from rest_framework.serializers import Serializer
from authentik.core.api.utils import PassiveSerializer
from authentik.core.models import User
class PolicyTestSerializer(Serializer):
class PolicyTestSerializer(PassiveSerializer):
"""Test policy execution for a user with context"""
user = PrimaryKeyRelatedField(queryset=User.objects.all())
context = JSONField(required=False)
def create(self, validated_data: dict) -> Model:
raise NotImplementedError
def update(self, instance: Model, validated_data: dict) -> Model:
raise NotImplementedError
class PolicyTestResultSerializer(Serializer):
class PolicyTestResultSerializer(PassiveSerializer):
"""result of a policy test"""
passing = BooleanField()
messages = ListField(child=CharField(), read_only=True)
def create(self, validated_data: dict) -> Model:
raise NotImplementedError
def update(self, instance: Model, validated_data: dict) -> Model:
raise NotImplementedError

View File

@ -3,17 +3,16 @@ from datetime import datetime
from time import time
from django.core.cache import cache
from django.db.models.base import Model
from drf_yasg.utils import swagger_auto_schema
from rest_framework.decorators import action
from rest_framework.fields import DateTimeField
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.serializers import ModelSerializer, Serializer
from rest_framework.serializers import ModelSerializer
from rest_framework.viewsets import ModelViewSet
from authentik.core.api.sources import SourceSerializer
from authentik.core.api.utils import MetaNameSerializer
from authentik.core.api.utils import MetaNameSerializer, PassiveSerializer
from authentik.sources.ldap.models import LDAPPropertyMapping, LDAPSource
@ -44,17 +43,11 @@ class LDAPSourceSerializer(SourceSerializer):
extra_kwargs = {"bind_password": {"write_only": True}}
class LDAPSourceSyncStatusSerializer(Serializer):
class LDAPSourceSyncStatusSerializer(PassiveSerializer):
"""LDAP Sync status"""
last_sync = DateTimeField(read_only=True)
def create(self, validated_data: dict) -> Model:
raise NotImplementedError
def update(self, instance: Model, validated_data: dict) -> Model:
raise NotImplementedError
class LDAPSourceViewSet(ModelViewSet):
"""LDAP Source Viewset"""

View File

@ -1,5 +1,4 @@
"""Validation stage challenge checking"""
from django.db.models import Model
from django.http import HttpRequest
from django.utils.translation import gettext_lazy as _
from django_otp import match_token
@ -7,7 +6,7 @@ from django_otp.models import Device
from django_otp.plugins.otp_static.models import StaticDevice
from django_otp.plugins.otp_totp.models import TOTPDevice
from rest_framework.fields import CharField, JSONField
from rest_framework.serializers import Serializer, ValidationError
from rest_framework.serializers import ValidationError
from webauthn import WebAuthnAssertionOptions, WebAuthnAssertionResponse, WebAuthnUser
from webauthn.webauthn import (
AuthenticationRejectedException,
@ -15,24 +14,19 @@ from webauthn.webauthn import (
WebAuthnUserDataMissing,
)
from authentik.core.api.utils import PassiveSerializer
from authentik.core.models import User
from authentik.stages.authenticator_webauthn.models import WebAuthnDevice
from authentik.stages.authenticator_webauthn.utils import generate_challenge, get_origin
class DeviceChallenge(Serializer):
class DeviceChallenge(PassiveSerializer):
"""Single device challenge"""
device_class = CharField()
device_uid = CharField()
challenge = JSONField()
def create(self, validated_data: dict) -> Model:
raise NotImplementedError
def update(self, instance: Model, validated_data: dict) -> Model:
raise NotImplementedError
def get_challenge_for_device(request: HttpRequest, device: Device) -> dict:
"""Generate challenge for a single device"""

View File

@ -3,16 +3,16 @@ from email.policy import Policy
from types import MethodType
from typing import Any, Callable, Iterator
from django.db.models.base import Model
from django.db.models.query import QuerySet
from django.http import HttpRequest, HttpResponse
from django.http.request import QueryDict
from django.utils.translation import gettext_lazy as _
from guardian.shortcuts import get_anonymous_user
from rest_framework.fields import BooleanField, CharField, IntegerField
from rest_framework.serializers import Serializer, ValidationError
from rest_framework.serializers import ValidationError
from structlog.stdlib import get_logger
from authentik.core.api.utils import PassiveSerializer
from authentik.core.models import User
from authentik.flows.challenge import Challenge, ChallengeResponse, ChallengeTypes
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER, FlowPlan
@ -26,7 +26,7 @@ LOGGER = get_logger()
PLAN_CONTEXT_PROMPT = "prompt_data"
class PromptSerializer(Serializer):
class PromptSerializer(PassiveSerializer):
"""Serializer for a single Prompt field"""
field_key = CharField()
@ -36,12 +36,6 @@ class PromptSerializer(Serializer):
placeholder = CharField()
order = IntegerField()
def create(self, validated_data: dict) -> Model:
return Model()
def update(self, instance: Model, validated_data: dict) -> Model:
return Model()
class PromptChallenge(Challenge):
"""Initial challenge being sent, define fields"""