fix stuff
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
This commit is contained in:
parent
4813bd033e
commit
130ec2128d
|
@ -130,7 +130,10 @@ SPECTACULAR_SETTINGS = {
|
||||||
"CONTACT": {
|
"CONTACT": {
|
||||||
"email": "hello@goauthentik.io",
|
"email": "hello@goauthentik.io",
|
||||||
},
|
},
|
||||||
"AUTHENTICATION_WHITELIST": ["authentik.api.authentication.TokenAuthentication"],
|
"AUTHENTICATION_WHITELIST": [
|
||||||
|
"authentik.stages.authenticator_mobile.api.auth.MobileDeviceTokenAuthentication",
|
||||||
|
"authentik.api.authentication.TokenAuthentication",
|
||||||
|
],
|
||||||
"LICENSE": {
|
"LICENSE": {
|
||||||
"name": "MIT",
|
"name": "MIT",
|
||||||
"url": "https://github.com/goauthentik/authentik/blob/main/LICENSE",
|
"url": "https://github.com/goauthentik/authentik/blob/main/LICENSE",
|
||||||
|
|
|
@ -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",
|
||||||
|
}
|
|
@ -1,60 +1,14 @@
|
||||||
"""AuthenticatorMobileStage API Views"""
|
"""AuthenticatorMobileStage API Views"""
|
||||||
from django_filters.rest_framework.backends import DjangoFilterBackend
|
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 import mixins
|
||||||
from rest_framework.decorators import action
|
|
||||||
from rest_framework.fields import CharField
|
|
||||||
from rest_framework.filters import OrderingFilter, SearchFilter
|
from rest_framework.filters import OrderingFilter, SearchFilter
|
||||||
from rest_framework.permissions import IsAdminUser
|
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.serializers import ModelSerializer
|
||||||
from rest_framework.viewsets import GenericViewSet, ModelViewSet
|
from rest_framework.viewsets import GenericViewSet, ModelViewSet
|
||||||
|
|
||||||
from authentik.api.authorization import OwnerFilter, OwnerPermissions
|
from authentik.api.authorization import OwnerFilter, OwnerPermissions
|
||||||
from authentik.core.api.used_by import UsedByMixin
|
from authentik.core.api.used_by import UsedByMixin
|
||||||
from authentik.flows.api.stages import StageSerializer
|
from authentik.stages.authenticator_mobile.models import MobileDevice
|
||||||
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"""
|
|
||||||
|
|
||||||
|
|
||||||
class MobileDeviceSerializer(ModelSerializer):
|
class MobileDeviceSerializer(ModelSerializer):
|
|
@ -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)
|
|
@ -25,7 +25,9 @@ class AuthenticatorMobileStage(ConfigurableStage, FriendlyNamedStage, Stage):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def serializer(self) -> type[BaseSerializer]:
|
def serializer(self) -> type[BaseSerializer]:
|
||||||
from authentik.stages.authenticator_mobile.api import AuthenticatorMobileStageSerializer
|
from authentik.stages.authenticator_mobile.api.stage import (
|
||||||
|
AuthenticatorMobileStageSerializer,
|
||||||
|
)
|
||||||
|
|
||||||
return AuthenticatorMobileStageSerializer
|
return AuthenticatorMobileStageSerializer
|
||||||
|
|
||||||
|
@ -67,7 +69,7 @@ class MobileDevice(SerializerModel, Device):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def serializer(self) -> Serializer:
|
def serializer(self) -> Serializer:
|
||||||
from authentik.stages.authenticator_mobile.api import MobileDeviceSerializer
|
from authentik.stages.authenticator_mobile.api.device import MobileDeviceSerializer
|
||||||
|
|
||||||
return MobileDeviceSerializer
|
return MobileDeviceSerializer
|
||||||
|
|
||||||
|
|
|
@ -56,7 +56,7 @@ class AuthenticatorMobileStageView(ChallengeStageView):
|
||||||
payload = AuthenticatorMobilePayloadChallenge(
|
payload = AuthenticatorMobilePayloadChallenge(
|
||||||
data={
|
data={
|
||||||
# TODO: use cloud gateway?
|
# TODO: use cloud gateway?
|
||||||
"u": self.request.get_host(),
|
"u": self.request.build_absolute_uri("/"),
|
||||||
"s": str(stage.stage_uuid),
|
"s": str(stage.stage_uuid),
|
||||||
"t": self.executor.plan.context[FLOW_PLAN_MOBILE_ENROLL].token,
|
"t": self.executor.plan.context[FLOW_PLAN_MOBILE_ENROLL].token,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
"""API URLs"""
|
"""API URLs"""
|
||||||
from authentik.stages.authenticator_mobile.api import (
|
from authentik.stages.authenticator_mobile.api.device import (
|
||||||
AdminMobileDeviceViewSet,
|
AdminMobileDeviceViewSet,
|
||||||
AuthenticatorMobileStageViewSet,
|
|
||||||
MobileDeviceViewSet,
|
MobileDeviceViewSet,
|
||||||
)
|
)
|
||||||
|
from authentik.stages.authenticator_mobile.api.stage import AuthenticatorMobileStageViewSet
|
||||||
|
|
||||||
api_urlpatterns = [
|
api_urlpatterns = [
|
||||||
("authenticators/mobile", MobileDeviceViewSet),
|
("authenticators/mobile", MobileDeviceViewSet),
|
||||||
|
|
21
schema.yml
21
schema.yml
|
@ -22611,8 +22611,14 @@ paths:
|
||||||
required: true
|
required: true
|
||||||
tags:
|
tags:
|
||||||
- stages
|
- stages
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/MobileDeviceEnrollmentRequest'
|
||||||
|
required: true
|
||||||
security:
|
security:
|
||||||
- authentik: []
|
- mobile_device_token: []
|
||||||
responses:
|
responses:
|
||||||
'200':
|
'200':
|
||||||
content:
|
content:
|
||||||
|
@ -34141,6 +34147,14 @@ components:
|
||||||
type: string
|
type: string
|
||||||
required:
|
required:
|
||||||
- device_token
|
- device_token
|
||||||
|
MobileDeviceEnrollmentRequest:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
device_token:
|
||||||
|
type: string
|
||||||
|
minLength: 1
|
||||||
|
required:
|
||||||
|
- device_token
|
||||||
MobileDeviceRequest:
|
MobileDeviceRequest:
|
||||||
type: object
|
type: object
|
||||||
description: Serializer for Mobile authenticator devices
|
description: Serializer for Mobile authenticator devices
|
||||||
|
@ -43692,5 +43706,10 @@ components:
|
||||||
in: header
|
in: header
|
||||||
name: Authorization
|
name: Authorization
|
||||||
scheme: bearer
|
scheme: bearer
|
||||||
|
mobile_device_token:
|
||||||
|
type: apiKey
|
||||||
|
in: header
|
||||||
|
name: Authorization
|
||||||
|
scheme: bearer
|
||||||
servers:
|
servers:
|
||||||
- url: /api/v3/
|
- url: /api/v3/
|
||||||
|
|
Reference in New Issue