fix stuff

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
This commit is contained in:
Jens Langhammer 2023-07-25 14:18:15 +02:00
parent ada24a609a
commit 31f869c2e0
No known key found for this signature in database
9 changed files with 135 additions and 54 deletions

View File

@ -130,7 +130,10 @@ SPECTACULAR_SETTINGS = {
"CONTACT": {
"email": "hello@goauthentik.io",
},
"AUTHENTICATION_WHITELIST": ["authentik.api.authentication.TokenAuthentication"],
"AUTHENTICATION_WHITELIST": [
"authentik.stages.authenticator_mobile.api.auth.MobileDeviceTokenAuthentication",
"authentik.api.authentication.TokenAuthentication",
],
"LICENSE": {
"name": "MIT",
"url": "https://github.com/goauthentik/authentik/blob/main/LICENSE",

View File

@ -0,0 +1,40 @@
"""Mobile device token authentication"""
from typing import Any
from drf_spectacular.extensions import OpenApiAuthenticationExtension
from rest_framework.authentication import BaseAuthentication, get_authorization_header
from rest_framework.request import Request
from authentik.api.authentication import validate_auth
from authentik.core.models import User
from authentik.stages.authenticator_mobile.models import MobileDeviceToken
class MobileDeviceTokenAuthentication(BaseAuthentication):
"""Mobile device token authentication"""
def authenticate(self, request: Request) -> tuple[User, Any] | None:
"""Token-based authentication using HTTP Bearer authentication"""
auth = get_authorization_header(request)
raw_token = validate_auth(auth)
device_token: MobileDeviceToken = MobileDeviceToken.objects.filter(token=raw_token).first()
if not device_token:
return None
return (device_token.user, None)
class TokenSchema(OpenApiAuthenticationExtension):
"""Auth schema"""
target_class = MobileDeviceTokenAuthentication
name = "mobile_device_token"
def get_security_definition(self, auto_schema):
"""Auth schema"""
return {
"type": "apiKey",
"in": "header",
"name": "Authorization",
"scheme": "bearer",
}

View File

@ -1,60 +1,14 @@
"""AuthenticatorMobileStage API Views"""
from django_filters.rest_framework.backends import DjangoFilterBackend
from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import extend_schema, inline_serializer
from rest_framework import mixins
from rest_framework.decorators import action
from rest_framework.fields import CharField
from rest_framework.filters import OrderingFilter, SearchFilter
from rest_framework.permissions import IsAdminUser
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.serializers import ModelSerializer
from rest_framework.viewsets import GenericViewSet, ModelViewSet
from authentik.api.authorization import OwnerFilter, OwnerPermissions
from authentik.core.api.used_by import UsedByMixin
from authentik.flows.api.stages import StageSerializer
from authentik.stages.authenticator_mobile.models import AuthenticatorMobileStage, MobileDevice
class AuthenticatorMobileStageSerializer(StageSerializer):
"""AuthenticatorMobileStage Serializer"""
class Meta:
model = AuthenticatorMobileStage
fields = StageSerializer.Meta.fields + [
"configure_flow",
"friendly_name",
]
class AuthenticatorMobileStageViewSet(UsedByMixin, ModelViewSet):
"""AuthenticatorMobileStage Viewset"""
queryset = AuthenticatorMobileStage.objects.all()
serializer_class = AuthenticatorMobileStageSerializer
filterset_fields = [
"name",
"configure_flow",
]
search_fields = ["name"]
ordering = ["name"]
@extend_schema(
request=OpenApiTypes.NONE,
responses={
200: inline_serializer(
"MobileDeviceEnrollmentCallbackSerializer",
{
"device_token": CharField(required=True),
},
),
},
)
@action(methods=["POST"], detail=True, permission_classes=[])
def enrollment_callback(self, request: Request, pk: str) -> Response:
"""Enrollment callback"""
from authentik.stages.authenticator_mobile.models import MobileDevice
class MobileDeviceSerializer(ModelSerializer):

View File

@ -0,0 +1,63 @@
"""AuthenticatorMobileStage API Views"""
from drf_spectacular.utils import extend_schema, inline_serializer
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.viewsets import ModelViewSet
from authentik.core.api.used_by import UsedByMixin
from authentik.flows.api.stages import StageSerializer
from authentik.stages.authenticator_mobile.api.auth import MobileDeviceTokenAuthentication
from authentik.stages.authenticator_mobile.models import AuthenticatorMobileStage
class AuthenticatorMobileStageSerializer(StageSerializer):
"""AuthenticatorMobileStage Serializer"""
class Meta:
model = AuthenticatorMobileStage
fields = StageSerializer.Meta.fields + [
"configure_flow",
"friendly_name",
]
class AuthenticatorMobileStageViewSet(UsedByMixin, ModelViewSet):
"""AuthenticatorMobileStage Viewset"""
queryset = AuthenticatorMobileStage.objects.all()
serializer_class = AuthenticatorMobileStageSerializer
filterset_fields = [
"name",
"configure_flow",
]
search_fields = ["name"]
ordering = ["name"]
@extend_schema(
responses={
200: inline_serializer(
"MobileDeviceEnrollmentCallbackSerializer",
{
"device_token": CharField(required=True),
},
),
},
request=inline_serializer(
"MobileDeviceEnrollmentSerializer",
{
"device_token": CharField(required=True),
},
),
)
@action(
methods=["POST"],
detail=True,
permission_classes=[],
authentication_classes=[MobileDeviceTokenAuthentication],
)
def enrollment_callback(self, request: Request, pk: str) -> Response:
"""Enrollment callback"""
print(request.data)
return Response(status=204)

View File

@ -25,7 +25,9 @@ class AuthenticatorMobileStage(ConfigurableStage, FriendlyNamedStage, Stage):
@property
def serializer(self) -> type[BaseSerializer]:
from authentik.stages.authenticator_mobile.api import AuthenticatorMobileStageSerializer
from authentik.stages.authenticator_mobile.api.stage import (
AuthenticatorMobileStageSerializer,
)
return AuthenticatorMobileStageSerializer
@ -67,7 +69,7 @@ class MobileDevice(SerializerModel, Device):
@property
def serializer(self) -> Serializer:
from authentik.stages.authenticator_mobile.api import MobileDeviceSerializer
from authentik.stages.authenticator_mobile.api.device import MobileDeviceSerializer
return MobileDeviceSerializer

View File

@ -56,7 +56,7 @@ class AuthenticatorMobileStageView(ChallengeStageView):
payload = AuthenticatorMobilePayloadChallenge(
data={
# TODO: use cloud gateway?
"u": self.request.get_host(),
"u": self.request.build_absolute_uri("/"),
"s": str(stage.stage_uuid),
"t": self.executor.plan.context[FLOW_PLAN_MOBILE_ENROLL].token,
}

View File

@ -1,9 +1,9 @@
"""API URLs"""
from authentik.stages.authenticator_mobile.api import (
from authentik.stages.authenticator_mobile.api.device import (
AdminMobileDeviceViewSet,
AuthenticatorMobileStageViewSet,
MobileDeviceViewSet,
)
from authentik.stages.authenticator_mobile.api.stage import AuthenticatorMobileStageViewSet
api_urlpatterns = [
("authenticators/mobile", MobileDeviceViewSet),

View File

@ -23435,8 +23435,14 @@ paths:
required: true
tags:
- stages
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/MobileDeviceEnrollmentRequest'
required: true
security:
- authentik: []
- mobile_device_token: []
responses:
'200':
content:
@ -35129,6 +35135,14 @@ components:
type: string
required:
- device_token
MobileDeviceEnrollmentRequest:
type: object
properties:
device_token:
type: string
minLength: 1
required:
- device_token
MobileDeviceRequest:
type: object
description: Serializer for Mobile authenticator devices
@ -45091,5 +45105,10 @@ components:
in: header
name: Authorization
scheme: bearer
mobile_device_token:
type: apiKey
in: header
name: Authorization
scheme: bearer
servers:
- url: /api/v3/