stages/otp_static: start implementing static stage
This commit is contained in:
parent
3716bda76e
commit
d2bf579ff6
|
@ -37,6 +37,8 @@ from passbook.stages.dummy.api import DummyStageViewSet
|
||||||
from passbook.stages.email.api import EmailStageViewSet
|
from passbook.stages.email.api import EmailStageViewSet
|
||||||
from passbook.stages.identification.api import IdentificationStageViewSet
|
from passbook.stages.identification.api import IdentificationStageViewSet
|
||||||
from passbook.stages.invitation.api import InvitationStageViewSet, InvitationViewSet
|
from passbook.stages.invitation.api import InvitationStageViewSet, InvitationViewSet
|
||||||
|
from passbook.stages.otp_static.api import OTPStaticStageViewSet
|
||||||
|
from passbook.stages.otp_time.api import OTPTimeStageViewSet
|
||||||
from passbook.stages.otp_validate.api import OTPValidateStageViewSet
|
from passbook.stages.otp_validate.api import OTPValidateStageViewSet
|
||||||
from passbook.stages.password.api import PasswordStageViewSet
|
from passbook.stages.password.api import PasswordStageViewSet
|
||||||
from passbook.stages.prompt.api import PromptStageViewSet, PromptViewSet
|
from passbook.stages.prompt.api import PromptStageViewSet, PromptViewSet
|
||||||
|
@ -91,10 +93,12 @@ router.register("stages/email", EmailStageViewSet)
|
||||||
router.register("stages/identification", IdentificationStageViewSet)
|
router.register("stages/identification", IdentificationStageViewSet)
|
||||||
router.register("stages/invitation", InvitationStageViewSet)
|
router.register("stages/invitation", InvitationStageViewSet)
|
||||||
router.register("stages/invitation/invitations", InvitationViewSet)
|
router.register("stages/invitation/invitations", InvitationViewSet)
|
||||||
|
router.register("stages/otp_static", OTPStaticStageViewSet)
|
||||||
|
router.register("stages/otp_time", OTPTimeStageViewSet)
|
||||||
router.register("stages/otp_validate", OTPValidateStageViewSet)
|
router.register("stages/otp_validate", OTPValidateStageViewSet)
|
||||||
router.register("stages/password", PasswordStageViewSet)
|
router.register("stages/password", PasswordStageViewSet)
|
||||||
router.register("stages/prompt/stages", PromptStageViewSet)
|
|
||||||
router.register("stages/prompt/prompts", PromptViewSet)
|
router.register("stages/prompt/prompts", PromptViewSet)
|
||||||
|
router.register("stages/prompt/stages", PromptStageViewSet)
|
||||||
router.register("stages/user_delete", UserDeleteStageViewSet)
|
router.register("stages/user_delete", UserDeleteStageViewSet)
|
||||||
router.register("stages/user_login", UserLoginStageViewSet)
|
router.register("stages/user_login", UserLoginStageViewSet)
|
||||||
router.register("stages/user_logout", UserLogoutStageViewSet)
|
router.register("stages/user_logout", UserLogoutStageViewSet)
|
||||||
|
|
|
@ -107,6 +107,7 @@ INSTALLED_APPS = [
|
||||||
"passbook.stages.user_login.apps.PassbookStageUserLoginConfig",
|
"passbook.stages.user_login.apps.PassbookStageUserLoginConfig",
|
||||||
"passbook.stages.user_logout.apps.PassbookStageUserLogoutConfig",
|
"passbook.stages.user_logout.apps.PassbookStageUserLogoutConfig",
|
||||||
"passbook.stages.user_write.apps.PassbookStageUserWriteConfig",
|
"passbook.stages.user_write.apps.PassbookStageUserWriteConfig",
|
||||||
|
"passbook.stages.otp_static.apps.PassbookStageOTPStaticConfig",
|
||||||
"passbook.stages.otp_time.apps.PassbookStageOTPTimeConfig",
|
"passbook.stages.otp_time.apps.PassbookStageOTPTimeConfig",
|
||||||
"passbook.stages.otp_validate.apps.PassbookStageOTPValidateConfig",
|
"passbook.stages.otp_validate.apps.PassbookStageOTPValidateConfig",
|
||||||
"passbook.stages.password.apps.PassbookStagePasswordConfig",
|
"passbook.stages.password.apps.PassbookStagePasswordConfig",
|
||||||
|
|
21
passbook/stages/otp_static/api.py
Normal file
21
passbook/stages/otp_static/api.py
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
"""OTPStaticStage API Views"""
|
||||||
|
from rest_framework.serializers import ModelSerializer
|
||||||
|
from rest_framework.viewsets import ModelViewSet
|
||||||
|
|
||||||
|
from passbook.stages.otp_static.models import OTPStaticStage
|
||||||
|
|
||||||
|
|
||||||
|
class OTPStaticStageSerializer(ModelSerializer):
|
||||||
|
"""OTPStaticStage Serializer"""
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
|
||||||
|
model = OTPStaticStage
|
||||||
|
fields = ["pk", "name", "token_count"]
|
||||||
|
|
||||||
|
|
||||||
|
class OTPStaticStageViewSet(ModelViewSet):
|
||||||
|
"""OTPStaticStage Viewset"""
|
||||||
|
|
||||||
|
queryset = OTPStaticStage.objects.all()
|
||||||
|
serializer_class = OTPStaticStageSerializer
|
28
passbook/stages/otp_static/forms.py
Normal file
28
passbook/stages/otp_static/forms.py
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
"""OTP Static forms"""
|
||||||
|
from django import forms
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from passbook.stages.otp_static.models import OTPStaticStage
|
||||||
|
|
||||||
|
|
||||||
|
class SetupForm(forms.Form):
|
||||||
|
"""Form to setup Static OTP"""
|
||||||
|
|
||||||
|
tokens = forms.MultipleChoiceField(disabled=True, required=False)
|
||||||
|
|
||||||
|
def __init__(self, tokens, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
print(tokens)
|
||||||
|
self.fields['tokens'].choices = [(x.token, x.token) for x in tokens]
|
||||||
|
|
||||||
|
|
||||||
|
class OTPStaticStageForm(forms.ModelForm):
|
||||||
|
"""OTP Static Stage setup form"""
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
|
||||||
|
model = OTPStaticStage
|
||||||
|
fields = ["name", "token_count"]
|
||||||
|
|
||||||
|
widgets = {
|
||||||
|
"name": forms.TextInput(),
|
||||||
|
}
|
38
passbook/stages/otp_static/migrations/0001_initial.py
Normal file
38
passbook/stages/otp_static/migrations/0001_initial.py
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
# Generated by Django 3.0.7 on 2020-06-30 11:43
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("passbook_flows", "0006_auto_20200629_0857"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="OTPStaticStage",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"stage_ptr",
|
||||||
|
models.OneToOneField(
|
||||||
|
auto_created=True,
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
parent_link=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
to="passbook_flows.Stage",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("token_count", models.IntegerField(default=6)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
"verbose_name": "OTP Static Setup Stage",
|
||||||
|
"verbose_name_plural": "OTP Static Setup Stages",
|
||||||
|
},
|
||||||
|
bases=("passbook_flows.stage",),
|
||||||
|
),
|
||||||
|
]
|
0
passbook/stages/otp_static/migrations/__init__.py
Normal file
0
passbook/stages/otp_static/migrations/__init__.py
Normal file
33
passbook/stages/otp_static/models.py
Normal file
33
passbook/stages/otp_static/models.py
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
"""OTP Static-based models"""
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from django.db import models
|
||||||
|
from django.shortcuts import reverse
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
from passbook.core.types import UIUserSettings
|
||||||
|
from passbook.flows.models import Stage
|
||||||
|
|
||||||
|
|
||||||
|
class OTPStaticStage(Stage):
|
||||||
|
"""Generate static tokens for the user as a backup"""
|
||||||
|
|
||||||
|
token_count = models.IntegerField(default=6)
|
||||||
|
|
||||||
|
type = "passbook.stages.otp_static.stage.OTPStaticStageView"
|
||||||
|
form = "passbook.stages.otp_static.forms.OTPStaticStageForm"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def ui_user_settings(self) -> Optional[UIUserSettings]:
|
||||||
|
return UIUserSettings(
|
||||||
|
name="Static-based OTP",
|
||||||
|
url=reverse("passbook_stages_otp_static:user-settings"),
|
||||||
|
)
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return f"OTP Static Stage {self.name}"
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
|
||||||
|
verbose_name = _("OTP Static Setup Stage")
|
||||||
|
verbose_name_plural = _("OTP Static Setup Stages")
|
5
passbook/stages/otp_static/settings.py
Normal file
5
passbook/stages/otp_static/settings.py
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
"""OTP Static settings"""
|
||||||
|
|
||||||
|
INSTALLED_APPS = [
|
||||||
|
"django_otp.plugins.otp_static",
|
||||||
|
]
|
57
passbook/stages/otp_static/stage.py
Normal file
57
passbook/stages/otp_static/stage.py
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
"""Static OTP Setup stage"""
|
||||||
|
from typing import Any, Dict
|
||||||
|
|
||||||
|
from django.http import HttpRequest, HttpResponse
|
||||||
|
from django.views.generic import FormView
|
||||||
|
from django_otp.plugins.otp_static.models import StaticDevice, StaticToken
|
||||||
|
from structlog import get_logger
|
||||||
|
|
||||||
|
from passbook.flows.planner import PLAN_CONTEXT_PENDING_USER
|
||||||
|
from passbook.flows.stage import StageView
|
||||||
|
from passbook.stages.otp_static.forms import SetupForm
|
||||||
|
from passbook.stages.otp_static.models import OTPStaticStage
|
||||||
|
|
||||||
|
LOGGER = get_logger()
|
||||||
|
SESSION_STATIC_DEVICE = "static_device"
|
||||||
|
SESSION_STATIC_TOKENS = "static_device_tokens"
|
||||||
|
|
||||||
|
|
||||||
|
class OTPStaticStageView(FormView, StageView):
|
||||||
|
"""Static OTP Setup stage"""
|
||||||
|
|
||||||
|
form_class = SetupForm
|
||||||
|
|
||||||
|
def get_form_kwargs(self, **kwargs) -> Dict[str, Any]:
|
||||||
|
kwargs = super().get_form_kwargs(**kwargs)
|
||||||
|
tokens = self.request.session[SESSION_STATIC_TOKENS]
|
||||||
|
kwargs["tokens"] = tokens
|
||||||
|
return kwargs
|
||||||
|
|
||||||
|
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
|
||||||
|
user = self.executor.plan.context.get(PLAN_CONTEXT_PENDING_USER)
|
||||||
|
if not user:
|
||||||
|
LOGGER.debug("No pending user, continuing")
|
||||||
|
return self.executor.stage_ok()
|
||||||
|
|
||||||
|
# Currently, this stage only supports one device per user. If the user already
|
||||||
|
# has a device, just skip to the next stage
|
||||||
|
if StaticDevice.objects.filter(user=user).exists():
|
||||||
|
return self.executor.stage_ok()
|
||||||
|
|
||||||
|
stage: OTPStaticStage = self.executor.current_stage
|
||||||
|
|
||||||
|
if SESSION_STATIC_DEVICE not in self.request.session:
|
||||||
|
device = StaticDevice(user=user, confirmed=True)
|
||||||
|
tokens = [StaticToken(device=device, token=StaticToken.random_token()) for _ in range(0, stage.token_count)]
|
||||||
|
self.request.session[SESSION_STATIC_DEVICE] = device
|
||||||
|
self.request.session[SESSION_STATIC_TOKENS] = tokens
|
||||||
|
return super().get(request, *args, **kwargs)
|
||||||
|
|
||||||
|
def form_valid(self, form: SetupForm) -> HttpResponse:
|
||||||
|
"""Verify OTP Token"""
|
||||||
|
device: StaticDevice = self.request.session[SESSION_STATIC_DEVICE]
|
||||||
|
device.save()
|
||||||
|
[x.save() for x in self.request.session[SESSION_STATIC_TOKENS]]
|
||||||
|
del self.request.session[SESSION_STATIC_DEVICE]
|
||||||
|
del self.request.session[SESSION_STATIC_TOKENS]
|
||||||
|
return self.executor.stage_ok()
|
|
@ -0,0 +1,31 @@
|
||||||
|
{% extends "user/base.html" %}
|
||||||
|
|
||||||
|
{% load passbook_utils %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block page %}
|
||||||
|
<div class="pf-c-card">
|
||||||
|
<div class="pf-c-card__header pf-c-title pf-m-md">
|
||||||
|
{% trans "Time-based One-Time Passwords" %}
|
||||||
|
</div>
|
||||||
|
<div class="pf-c-card__body">
|
||||||
|
<p>
|
||||||
|
{% blocktrans with state=state|yesno:"Enabled,Disabled" %}
|
||||||
|
Status: {{ state }}
|
||||||
|
{% endblocktrans %}
|
||||||
|
{% if state %}
|
||||||
|
<i class="pf-icon pf-icon-ok"></i>
|
||||||
|
{% else %}
|
||||||
|
<i class="pf-icon pf-icon-error-circle-o"></i>
|
||||||
|
{% endif %}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
{% if not state %}
|
||||||
|
<a href="{% url 'passbook_stages_otp_time:otp-enable' %}" class="btn btn-success btn-sm">{% trans "Enable Time-based OTP" %}</a>
|
||||||
|
{% else %}
|
||||||
|
<a href="{% url 'passbook_stages_otp_time:disable' %}" class="btn btn-danger btn-sm">{% trans "Disable Time-based OTP" %}</a>
|
||||||
|
{% endif %}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
9
passbook/stages/otp_static/urls.py
Normal file
9
passbook/stages/otp_static/urls.py
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
"""OTP static urls"""
|
||||||
|
from django.urls import path
|
||||||
|
|
||||||
|
from passbook.stages.otp_static.views import DisableView, UserSettingsView
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path("settings", UserSettingsView.as_view(), name="user-settings"),
|
||||||
|
path("disable", DisableView.as_view(), name="disable"),
|
||||||
|
]
|
40
passbook/stages/otp_static/views.py
Normal file
40
passbook/stages/otp_static/views.py
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
"""otp Static view Tokens"""
|
||||||
|
from django.contrib import messages
|
||||||
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
|
from django.http import HttpRequest, HttpResponse
|
||||||
|
from django.shortcuts import redirect
|
||||||
|
from django.views import View
|
||||||
|
from django.views.generic import TemplateView
|
||||||
|
from django_otp.plugins.otp_static.models import StaticDevice
|
||||||
|
|
||||||
|
from passbook.audit.models import Event, EventAction
|
||||||
|
|
||||||
|
|
||||||
|
class UserSettingsView(LoginRequiredMixin, TemplateView):
|
||||||
|
"""View for user settings to control OTP"""
|
||||||
|
|
||||||
|
template_name = "stages/otp_static/user_settings.html"
|
||||||
|
|
||||||
|
# TODO: Check if OTP Stage exists and applies to user
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
kwargs = super().get_context_data(**kwargs)
|
||||||
|
static_devices = StaticDevice.objects.filter(
|
||||||
|
user=self.request.user, confirmed=True
|
||||||
|
)
|
||||||
|
kwargs["state"] = static_devices.exists()
|
||||||
|
return kwargs
|
||||||
|
|
||||||
|
|
||||||
|
class DisableView(LoginRequiredMixin, View):
|
||||||
|
"""Disable Static Tokens for user"""
|
||||||
|
|
||||||
|
def get(self, request: HttpRequest) -> HttpResponse:
|
||||||
|
"""Delete all the devices for user"""
|
||||||
|
devices = StaticDevice.objects.filter(user=request.user, confirmed=True)
|
||||||
|
devices.delete()
|
||||||
|
messages.success(request, "Successfully disabled Static OTP Tokens")
|
||||||
|
# Create event with email notification
|
||||||
|
Event.new(
|
||||||
|
EventAction.CUSTOM, message="User disabled Static OTP Tokens."
|
||||||
|
).from_http(request)
|
||||||
|
return redirect("passbook_stages_otp:otp-user-settings")
|
|
@ -18,7 +18,6 @@ class PictureWidget(forms.widgets.Widget):
|
||||||
class SetupForm(forms.Form):
|
class SetupForm(forms.Form):
|
||||||
"""Form to setup Time-based OTP"""
|
"""Form to setup Time-based OTP"""
|
||||||
|
|
||||||
title = _("Set up OTP")
|
|
||||||
device: Device = None
|
device: Device = None
|
||||||
|
|
||||||
qr_code = forms.CharField(
|
qr_code = forms.CharField(
|
||||||
|
|
|
@ -9,6 +9,7 @@ from lxml.etree import tostring # nosec
|
||||||
from qrcode import QRCode
|
from qrcode import QRCode
|
||||||
from qrcode.image.svg import SvgFillImage
|
from qrcode.image.svg import SvgFillImage
|
||||||
from structlog import get_logger
|
from structlog import get_logger
|
||||||
|
from django_otp.plugins.otp_totp.models import TOTPDevice
|
||||||
|
|
||||||
from passbook.flows.planner import PLAN_CONTEXT_PENDING_USER
|
from passbook.flows.planner import PLAN_CONTEXT_PENDING_USER
|
||||||
from passbook.flows.stage import StageView
|
from passbook.flows.stage import StageView
|
||||||
|
@ -43,6 +44,11 @@ class OTPTimeStageView(FormView, StageView):
|
||||||
LOGGER.debug("No pending user, continuing")
|
LOGGER.debug("No pending user, continuing")
|
||||||
return self.executor.stage_ok()
|
return self.executor.stage_ok()
|
||||||
|
|
||||||
|
# Currently, this stage only supports one device per user. If the user already
|
||||||
|
# has a device, just skip to the next stage
|
||||||
|
if TOTPDevice.objects.filter(user=user).exists():
|
||||||
|
return self.executor.stage_ok()
|
||||||
|
|
||||||
stage: OTPTimeStage = self.executor.current_stage
|
stage: OTPTimeStage = self.executor.current_stage
|
||||||
|
|
||||||
if SESSION_TOTP_DEVICE not in self.request.session:
|
if SESSION_TOTP_DEVICE not in self.request.session:
|
||||||
|
|
|
@ -9,10 +9,6 @@ from django_otp.plugins.otp_totp.models import TOTPDevice
|
||||||
|
|
||||||
from passbook.audit.models import Event, EventAction
|
from passbook.audit.models import Event, EventAction
|
||||||
|
|
||||||
# from passbook.flows.planner import PLAN_CONTEXT_PENDING_USER, FlowPlan
|
|
||||||
# from passbook.flows.views import SESSION_KEY_PLAN
|
|
||||||
# from passbook.stages.otp_time.models import OTPTimeStage
|
|
||||||
|
|
||||||
|
|
||||||
class UserSettingsView(LoginRequiredMixin, TemplateView):
|
class UserSettingsView(LoginRequiredMixin, TemplateView):
|
||||||
"""View for user settings to control OTP"""
|
"""View for user settings to control OTP"""
|
||||||
|
|
294
swagger.yaml
294
swagger.yaml
|
@ -4037,6 +4037,260 @@ paths:
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
format: uuid
|
format: uuid
|
||||||
|
/stages/otp_static/:
|
||||||
|
get:
|
||||||
|
operationId: stages_otp_static_list
|
||||||
|
description: OTPStaticStage Viewset
|
||||||
|
parameters:
|
||||||
|
- name: ordering
|
||||||
|
in: query
|
||||||
|
description: Which field to use when ordering the results.
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
- name: search
|
||||||
|
in: query
|
||||||
|
description: A search term.
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
- name: limit
|
||||||
|
in: query
|
||||||
|
description: Number of results to return per page.
|
||||||
|
required: false
|
||||||
|
type: integer
|
||||||
|
- name: offset
|
||||||
|
in: query
|
||||||
|
description: The initial index from which to return the results.
|
||||||
|
required: false
|
||||||
|
type: integer
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: ''
|
||||||
|
schema:
|
||||||
|
required:
|
||||||
|
- count
|
||||||
|
- results
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
count:
|
||||||
|
type: integer
|
||||||
|
next:
|
||||||
|
type: string
|
||||||
|
format: uri
|
||||||
|
x-nullable: true
|
||||||
|
previous:
|
||||||
|
type: string
|
||||||
|
format: uri
|
||||||
|
x-nullable: true
|
||||||
|
results:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/OTPStaticStage'
|
||||||
|
tags:
|
||||||
|
- stages
|
||||||
|
post:
|
||||||
|
operationId: stages_otp_static_create
|
||||||
|
description: OTPStaticStage Viewset
|
||||||
|
parameters:
|
||||||
|
- name: data
|
||||||
|
in: body
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/OTPStaticStage'
|
||||||
|
responses:
|
||||||
|
'201':
|
||||||
|
description: ''
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/OTPStaticStage'
|
||||||
|
tags:
|
||||||
|
- stages
|
||||||
|
parameters: []
|
||||||
|
/stages/otp_static/{stage_uuid}/:
|
||||||
|
get:
|
||||||
|
operationId: stages_otp_static_read
|
||||||
|
description: OTPStaticStage Viewset
|
||||||
|
parameters: []
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: ''
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/OTPStaticStage'
|
||||||
|
tags:
|
||||||
|
- stages
|
||||||
|
put:
|
||||||
|
operationId: stages_otp_static_update
|
||||||
|
description: OTPStaticStage Viewset
|
||||||
|
parameters:
|
||||||
|
- name: data
|
||||||
|
in: body
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/OTPStaticStage'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: ''
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/OTPStaticStage'
|
||||||
|
tags:
|
||||||
|
- stages
|
||||||
|
patch:
|
||||||
|
operationId: stages_otp_static_partial_update
|
||||||
|
description: OTPStaticStage Viewset
|
||||||
|
parameters:
|
||||||
|
- name: data
|
||||||
|
in: body
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/OTPStaticStage'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: ''
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/OTPStaticStage'
|
||||||
|
tags:
|
||||||
|
- stages
|
||||||
|
delete:
|
||||||
|
operationId: stages_otp_static_delete
|
||||||
|
description: OTPStaticStage Viewset
|
||||||
|
parameters: []
|
||||||
|
responses:
|
||||||
|
'204':
|
||||||
|
description: ''
|
||||||
|
tags:
|
||||||
|
- stages
|
||||||
|
parameters:
|
||||||
|
- name: stage_uuid
|
||||||
|
in: path
|
||||||
|
description: A UUID string identifying this OTP Static Setup Stage.
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
/stages/otp_time/:
|
||||||
|
get:
|
||||||
|
operationId: stages_otp_time_list
|
||||||
|
description: OTPTimeStage Viewset
|
||||||
|
parameters:
|
||||||
|
- name: ordering
|
||||||
|
in: query
|
||||||
|
description: Which field to use when ordering the results.
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
- name: search
|
||||||
|
in: query
|
||||||
|
description: A search term.
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
- name: limit
|
||||||
|
in: query
|
||||||
|
description: Number of results to return per page.
|
||||||
|
required: false
|
||||||
|
type: integer
|
||||||
|
- name: offset
|
||||||
|
in: query
|
||||||
|
description: The initial index from which to return the results.
|
||||||
|
required: false
|
||||||
|
type: integer
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: ''
|
||||||
|
schema:
|
||||||
|
required:
|
||||||
|
- count
|
||||||
|
- results
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
count:
|
||||||
|
type: integer
|
||||||
|
next:
|
||||||
|
type: string
|
||||||
|
format: uri
|
||||||
|
x-nullable: true
|
||||||
|
previous:
|
||||||
|
type: string
|
||||||
|
format: uri
|
||||||
|
x-nullable: true
|
||||||
|
results:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/OTPTimeStage'
|
||||||
|
tags:
|
||||||
|
- stages
|
||||||
|
post:
|
||||||
|
operationId: stages_otp_time_create
|
||||||
|
description: OTPTimeStage Viewset
|
||||||
|
parameters:
|
||||||
|
- name: data
|
||||||
|
in: body
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/OTPTimeStage'
|
||||||
|
responses:
|
||||||
|
'201':
|
||||||
|
description: ''
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/OTPTimeStage'
|
||||||
|
tags:
|
||||||
|
- stages
|
||||||
|
parameters: []
|
||||||
|
/stages/otp_time/{stage_uuid}/:
|
||||||
|
get:
|
||||||
|
operationId: stages_otp_time_read
|
||||||
|
description: OTPTimeStage Viewset
|
||||||
|
parameters: []
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: ''
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/OTPTimeStage'
|
||||||
|
tags:
|
||||||
|
- stages
|
||||||
|
put:
|
||||||
|
operationId: stages_otp_time_update
|
||||||
|
description: OTPTimeStage Viewset
|
||||||
|
parameters:
|
||||||
|
- name: data
|
||||||
|
in: body
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/OTPTimeStage'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: ''
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/OTPTimeStage'
|
||||||
|
tags:
|
||||||
|
- stages
|
||||||
|
patch:
|
||||||
|
operationId: stages_otp_time_partial_update
|
||||||
|
description: OTPTimeStage Viewset
|
||||||
|
parameters:
|
||||||
|
- name: data
|
||||||
|
in: body
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/OTPTimeStage'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: ''
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/OTPTimeStage'
|
||||||
|
tags:
|
||||||
|
- stages
|
||||||
|
delete:
|
||||||
|
operationId: stages_otp_time_delete
|
||||||
|
description: OTPTimeStage Viewset
|
||||||
|
parameters: []
|
||||||
|
responses:
|
||||||
|
'204':
|
||||||
|
description: ''
|
||||||
|
tags:
|
||||||
|
- stages
|
||||||
|
parameters:
|
||||||
|
- name: stage_uuid
|
||||||
|
in: path
|
||||||
|
description: A UUID string identifying this OTP Time (TOTP) Setup Stage.
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
/stages/otp_validate/:
|
/stages/otp_validate/:
|
||||||
get:
|
get:
|
||||||
operationId: stages_otp_validate_list
|
operationId: stages_otp_validate_list
|
||||||
|
@ -6350,6 +6604,46 @@ definitions:
|
||||||
fixed_data:
|
fixed_data:
|
||||||
title: Fixed data
|
title: Fixed data
|
||||||
type: object
|
type: object
|
||||||
|
OTPStaticStage:
|
||||||
|
required:
|
||||||
|
- name
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
pk:
|
||||||
|
title: Stage uuid
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
readOnly: true
|
||||||
|
name:
|
||||||
|
title: Name
|
||||||
|
type: string
|
||||||
|
minLength: 1
|
||||||
|
token_count:
|
||||||
|
title: Token count
|
||||||
|
type: integer
|
||||||
|
maximum: 2147483647
|
||||||
|
minimum: -2147483648
|
||||||
|
OTPTimeStage:
|
||||||
|
required:
|
||||||
|
- name
|
||||||
|
- digits
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
pk:
|
||||||
|
title: Stage uuid
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
readOnly: true
|
||||||
|
name:
|
||||||
|
title: Name
|
||||||
|
type: string
|
||||||
|
minLength: 1
|
||||||
|
digits:
|
||||||
|
title: Digits
|
||||||
|
type: integer
|
||||||
|
enum:
|
||||||
|
- 6
|
||||||
|
- 8
|
||||||
OTPValidateStage:
|
OTPValidateStage:
|
||||||
required:
|
required:
|
||||||
- name
|
- name
|
||||||
|
|
Reference in a new issue