stages/authenticator_duo: revamp duo enroll status API
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> #3288
This commit is contained in:
parent
2858682866
commit
54c16129ea
|
@ -1,10 +1,16 @@
|
||||||
"""AuthenticatorDuoStage API Views"""
|
"""AuthenticatorDuoStage 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.types import OpenApiTypes
|
||||||
from drf_spectacular.utils import OpenApiParameter, OpenApiResponse, extend_schema
|
from drf_spectacular.utils import (
|
||||||
|
OpenApiParameter,
|
||||||
|
OpenApiResponse,
|
||||||
|
extend_schema,
|
||||||
|
inline_serializer,
|
||||||
|
)
|
||||||
from guardian.shortcuts import get_objects_for_user
|
from guardian.shortcuts import get_objects_for_user
|
||||||
from rest_framework import mixins
|
from rest_framework import mixins
|
||||||
from rest_framework.decorators import action
|
from rest_framework.decorators import action
|
||||||
|
from rest_framework.fields import ChoiceField
|
||||||
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.request import Request
|
||||||
|
@ -57,8 +63,18 @@ class AuthenticatorDuoStageViewSet(UsedByMixin, ModelViewSet):
|
||||||
@extend_schema(
|
@extend_schema(
|
||||||
request=OpenApiTypes.NONE,
|
request=OpenApiTypes.NONE,
|
||||||
responses={
|
responses={
|
||||||
204: OpenApiResponse(description="Enrollment successful"),
|
200: inline_serializer(
|
||||||
420: OpenApiResponse(description="Enrollment pending/failed"),
|
"DuoDeviceEnrollmentStatusSerializer",
|
||||||
|
{
|
||||||
|
"duo_response": ChoiceField(
|
||||||
|
(
|
||||||
|
("success", "Success"),
|
||||||
|
("waiting", "Waiting"),
|
||||||
|
("invalid", "Invalid"),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@action(methods=["POST"], detail=True, permission_classes=[])
|
@action(methods=["POST"], detail=True, permission_classes=[])
|
||||||
|
@ -70,11 +86,9 @@ class AuthenticatorDuoStageViewSet(UsedByMixin, ModelViewSet):
|
||||||
user_id = self.request.session.get(SESSION_KEY_DUO_USER_ID)
|
user_id = self.request.session.get(SESSION_KEY_DUO_USER_ID)
|
||||||
activation_code = self.request.session.get(SESSION_KEY_DUO_ACTIVATION_CODE)
|
activation_code = self.request.session.get(SESSION_KEY_DUO_ACTIVATION_CODE)
|
||||||
if not user_id or not activation_code:
|
if not user_id or not activation_code:
|
||||||
return Response(status=420)
|
return Response(status=400)
|
||||||
status = client.enroll_status(user_id, activation_code)
|
status = client.enroll_status(user_id, activation_code)
|
||||||
if status == "success":
|
return Response({"duo_response": status})
|
||||||
return Response(status=204)
|
|
||||||
return Response(status=420)
|
|
||||||
|
|
||||||
@permission_required(
|
@permission_required(
|
||||||
"", ["authentik_stages_authenticator_duo.add_duodevice", "authentik_core.view_user"]
|
"", ["authentik_stages_authenticator_duo.add_duodevice", "authentik_core.view_user"]
|
||||||
|
|
|
@ -94,5 +94,5 @@ class AuthenticatorDuoStageView(ChallengeStageView):
|
||||||
return self.executor.stage_ok()
|
return self.executor.stage_ok()
|
||||||
|
|
||||||
def cleanup(self):
|
def cleanup(self):
|
||||||
self.request.session.pop(SESSION_KEY_DUO_USER_ID)
|
self.request.session.pop(SESSION_KEY_DUO_USER_ID, None)
|
||||||
self.request.session.pop(SESSION_KEY_DUO_ACTIVATION_CODE)
|
self.request.session.pop(SESSION_KEY_DUO_ACTIVATION_CODE, None)
|
||||||
|
|
23
schema.yml
23
schema.yml
|
@ -15076,10 +15076,12 @@ paths:
|
||||||
security:
|
security:
|
||||||
- authentik: []
|
- authentik: []
|
||||||
responses:
|
responses:
|
||||||
'204':
|
'200':
|
||||||
description: Enrollment successful
|
content:
|
||||||
'420':
|
application/json:
|
||||||
description: Enrollment pending/failed
|
schema:
|
||||||
|
$ref: '#/components/schemas/DuoDeviceEnrollmentStatus'
|
||||||
|
description: ''
|
||||||
'400':
|
'400':
|
||||||
$ref: '#/components/schemas/ValidationError'
|
$ref: '#/components/schemas/ValidationError'
|
||||||
'403':
|
'403':
|
||||||
|
@ -21877,6 +21879,13 @@ components:
|
||||||
required:
|
required:
|
||||||
- name
|
- name
|
||||||
- pk
|
- pk
|
||||||
|
DuoDeviceEnrollmentStatus:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
duo_response:
|
||||||
|
$ref: '#/components/schemas/DuoResponseEnum'
|
||||||
|
required:
|
||||||
|
- duo_response
|
||||||
DuoDeviceRequest:
|
DuoDeviceRequest:
|
||||||
type: object
|
type: object
|
||||||
description: Serializer for Duo authenticator devices
|
description: Serializer for Duo authenticator devices
|
||||||
|
@ -21888,6 +21897,12 @@ components:
|
||||||
maxLength: 64
|
maxLength: 64
|
||||||
required:
|
required:
|
||||||
- name
|
- name
|
||||||
|
DuoResponseEnum:
|
||||||
|
enum:
|
||||||
|
- success
|
||||||
|
- waiting
|
||||||
|
- invalid
|
||||||
|
type: string
|
||||||
EmailChallenge:
|
EmailChallenge:
|
||||||
type: object
|
type: object
|
||||||
description: Email challenge
|
description: Email challenge
|
||||||
|
|
|
@ -21,6 +21,7 @@ import PFBase from "@patternfly/patternfly/patternfly-base.css";
|
||||||
import {
|
import {
|
||||||
AuthenticatorDuoChallenge,
|
AuthenticatorDuoChallenge,
|
||||||
AuthenticatorDuoChallengeResponseRequest,
|
AuthenticatorDuoChallengeResponseRequest,
|
||||||
|
DuoResponseEnum,
|
||||||
StagesApi,
|
StagesApi,
|
||||||
} from "@goauthentik/api";
|
} from "@goauthentik/api";
|
||||||
|
|
||||||
|
@ -35,23 +36,29 @@ export class AuthenticatorDuoStage extends BaseStage<
|
||||||
|
|
||||||
firstUpdated(): void {
|
firstUpdated(): void {
|
||||||
const i = setInterval(() => {
|
const i = setInterval(() => {
|
||||||
this.checkEnrollStatus().then(() => {
|
this.checkEnrollStatus().then((shouldStop) => {
|
||||||
clearInterval(i);
|
if (shouldStop) {
|
||||||
|
clearInterval(i);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}, 3000);
|
}, 3000);
|
||||||
}
|
}
|
||||||
|
|
||||||
checkEnrollStatus(): Promise<void> {
|
async checkEnrollStatus(): Promise<boolean> {
|
||||||
return new StagesApi(DEFAULT_CONFIG)
|
const status = await new StagesApi(
|
||||||
.stagesAuthenticatorDuoEnrollmentStatusCreate({
|
DEFAULT_CONFIG,
|
||||||
stageUuid: this.challenge?.stageUuid || "",
|
).stagesAuthenticatorDuoEnrollmentStatusCreate({
|
||||||
})
|
stageUuid: this.challenge?.stageUuid || "",
|
||||||
.then(() => {
|
});
|
||||||
|
console.debug(`authentik/flows/duo: Enrollment status: ${status.duoResponse}`);
|
||||||
|
switch (status.duoResponse) {
|
||||||
|
case DuoResponseEnum.Success:
|
||||||
this.host?.submit({});
|
this.host?.submit({});
|
||||||
})
|
return true;
|
||||||
.catch(() => {
|
case DuoResponseEnum.Waiting:
|
||||||
console.debug("authentik/flows/duo: Waiting for auth status");
|
break;
|
||||||
});
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
render(): TemplateResult {
|
render(): TemplateResult {
|
||||||
|
|
Reference in New Issue