stages/authenticator_duo: add API to "import" devices from duo

closes #1371

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
Jens Langhammer 2021-09-10 11:35:04 +02:00
parent 5fd4f56fa2
commit 7dfbcdbb81
4 changed files with 95 additions and 4 deletions

View file

@ -31,7 +31,7 @@ VALIDATION_ERROR = build_object_type(
"non_field_errors": build_array_type(build_standard_type(OpenApiTypes.STR)),
"code": build_standard_type(OpenApiTypes.STR),
},
required=["detail"],
required=[],
additionalProperties={},
)

View file

@ -1,7 +1,8 @@
"""AuthenticatorDuoStage API Views"""
from django_filters.rest_framework.backends import DjangoFilterBackend
from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import OpenApiResponse, extend_schema
from drf_spectacular.utils import OpenApiParameter, OpenApiResponse, extend_schema
from guardian.shortcuts import get_objects_for_user
from rest_framework import mixins
from rest_framework.decorators import action
from rest_framework.filters import OrderingFilter, SearchFilter
@ -12,6 +13,7 @@ from rest_framework.serializers import ModelSerializer
from rest_framework.viewsets import GenericViewSet, ModelViewSet
from authentik.api.authorization import OwnerFilter, OwnerPermissions
from authentik.api.decorators import permission_required
from authentik.core.api.used_by import UsedByMixin
from authentik.flows.api.stages import StageSerializer
from authentik.stages.authenticator_duo.models import AuthenticatorDuoStage, DuoDevice
@ -71,6 +73,43 @@ class AuthenticatorDuoStageViewSet(UsedByMixin, ModelViewSet):
return Response(status=204)
return Response(status=420)
@permission_required(
"", ["authentik_stages_authenticator_duo.add_duodevice", "authentik_core.view_user"]
)
@extend_schema(
parameters=[
OpenApiParameter(
name="duo_user_id", type=OpenApiTypes.STR, location=OpenApiParameter.QUERY
),
OpenApiParameter(
name="username", type=OpenApiTypes.STR, location=OpenApiParameter.QUERY
),
],
responses={
204: OpenApiResponse(description="Enrollment successful"),
400: OpenApiResponse(description="Device exists already"),
},
)
@action(methods=["POST"], detail=True)
# pylint: disable=invalid-name,unused-argument
def import_devices(self, request: Request, pk: str) -> Response:
"""Import duo devices into authentik"""
stage: AuthenticatorDuoStage = self.get_object()
users = get_objects_for_user(request.user, "authentik_core.view_user").filter(
username=request.query_params.get("username", "")
)
if not users.exists():
return Response(data={"non_field_errors": ["user does not exist"]}, status=400)
devices = DuoDevice.objects.filter(
duo_user_id=request.query_params.get("duo_user_id"), user=users.first(), stage=stage
)
if devices.exists():
return Response(data={"non_field_errors": ["device exists already"]}, status=400)
DuoDevice.objects.create(
duo_user_id=request.query_params.get("duo_user_id"), user=users.first(), stage=stage
)
return Response(status=204)
class DuoDeviceSerializer(ModelSerializer):
"""Serializer for Duo authenticator devices"""

View file

@ -13585,6 +13585,43 @@ paths:
$ref: '#/components/schemas/ValidationError'
'403':
$ref: '#/components/schemas/GenericError'
/stages/authenticator/duo/{stage_uuid}/import_devices/:
post:
operationId: stages_authenticator_duo_import_devices_create
description: Import duo devices into authentik
parameters:
- in: query
name: duo_user_id
schema:
type: string
- in: path
name: stage_uuid
schema:
type: string
format: uuid
description: A UUID string identifying this Duo Authenticator Setup Stage.
required: true
- in: query
name: username
schema:
type: string
tags:
- stages
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/AuthenticatorDuoStageRequest'
required: true
security:
- authentik: []
responses:
'204':
description: Enrollment successful
'400':
description: Device exists already
'403':
$ref: '#/components/schemas/GenericError'
/stages/authenticator/duo/{stage_uuid}/used_by/:
get:
operationId: stages_authenticator_duo_used_by_list
@ -29004,8 +29041,6 @@ components:
code:
type: string
additionalProperties: {}
required:
- detail
Version:
type: object
description: Get running and latest version.

View file

@ -9,3 +9,20 @@ Go to Applications, click on Protect an Application and search for "Auth API". C
Copy all of the integration key, secret key and API hostname, and paste them in the Stage form.
Devices created reference the stage they were created with, since the API credentials are needed to authenticate. This also means when the stage is deleted, all devices are removed.
## Importing users
:::info
Due to the way the Duo API works, authentik cannot automatically import existing Duo users.
:::
:::info
This API requires version 2021.10.1 or later
:::
You can call the `/api/v3/stages/authenticator/duo/{stage_uuid}/import_devices/` endpoint ([see here](https://goauthentik.io/api/#post-/stages/authenticator/duo/-stage_uuid-/import_devices/)) using the following parameters:
- `duo_user_id`: The Duo User's ID. This can be found in the Duo Admin Portal, navigating to the user list and clicking on a single user. Their ID is shown in th URL.
- `username`: The authentik user's username to assign the device to.
Additionally, you need to pass `stage_uuid` which is the `authenticator_duo` stage, in which you entered your API credentials.