factors: -> stage
This commit is contained in:
@ -1,8 +1,8 @@
|
||||
"""Flow API Views"""
|
||||
from rest_framework.serializers import ModelSerializer
|
||||
from rest_framework.viewsets import ModelViewSet
|
||||
from rest_framework.serializers import ModelSerializer, SerializerMethodField
|
||||
from rest_framework.viewsets import ModelViewSet, ReadOnlyModelViewSet
|
||||
|
||||
from passbook.flows.models import Flow, FlowFactorBinding
|
||||
from passbook.flows.models import Flow, FlowStageBinding, Stage
|
||||
|
||||
|
||||
class FlowSerializer(ModelSerializer):
|
||||
@ -11,7 +11,7 @@ class FlowSerializer(ModelSerializer):
|
||||
class Meta:
|
||||
|
||||
model = Flow
|
||||
fields = ["pk", "name", "slug", "designation", "factors", "policies"]
|
||||
fields = ["pk", "name", "slug", "designation", "stages", "policies"]
|
||||
|
||||
|
||||
class FlowViewSet(ModelViewSet):
|
||||
@ -21,17 +21,42 @@ class FlowViewSet(ModelViewSet):
|
||||
serializer_class = FlowSerializer
|
||||
|
||||
|
||||
class FlowFactorBindingSerializer(ModelSerializer):
|
||||
"""FlowFactorBinding Serializer"""
|
||||
class FlowStageBindingSerializer(ModelSerializer):
|
||||
"""FlowStageBinding Serializer"""
|
||||
|
||||
class Meta:
|
||||
|
||||
model = FlowFactorBinding
|
||||
fields = ["pk", "flow", "factor", "re_evaluate_policies", "order", "policies"]
|
||||
model = FlowStageBinding
|
||||
fields = ["pk", "flow", "stage", "re_evaluate_policies", "order", "policies"]
|
||||
|
||||
|
||||
class FlowFactorBindingViewSet(ModelViewSet):
|
||||
"""FlowFactorBinding Viewset"""
|
||||
class FlowStageBindingViewSet(ModelViewSet):
|
||||
"""FlowStageBinding Viewset"""
|
||||
|
||||
queryset = FlowFactorBinding.objects.all()
|
||||
serializer_class = FlowFactorBindingSerializer
|
||||
queryset = FlowStageBinding.objects.all()
|
||||
serializer_class = FlowStageBindingSerializer
|
||||
|
||||
|
||||
class StageSerializer(ModelSerializer):
|
||||
"""Stage Serializer"""
|
||||
|
||||
__type__ = SerializerMethodField(method_name="get_type")
|
||||
|
||||
def get_type(self, obj):
|
||||
"""Get object type so that we know which API Endpoint to use to get the full object"""
|
||||
return obj._meta.object_name.lower().replace("stage", "")
|
||||
|
||||
class Meta:
|
||||
|
||||
model = Stage
|
||||
fields = ["pk", "name", "__type__"]
|
||||
|
||||
|
||||
class StageViewSet(ReadOnlyModelViewSet):
|
||||
"""Stage Viewset"""
|
||||
|
||||
queryset = Stage.objects.all()
|
||||
serializer_class = StageSerializer
|
||||
|
||||
def get_queryset(self):
|
||||
return Stage.objects.select_subclasses()
|
||||
|
@ -1,12 +1,10 @@
|
||||
"""factor forms"""
|
||||
"""Flow and Stage forms"""
|
||||
|
||||
from django import forms
|
||||
from django.contrib.admin.widgets import FilteredSelectMultiple
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from passbook.flows.models import Flow, FlowFactorBinding
|
||||
|
||||
GENERAL_FIELDS = ["name", "slug", "order", "policies", "enabled"]
|
||||
from passbook.flows.models import Flow, FlowStageBinding
|
||||
|
||||
|
||||
class FlowForm(forms.ModelForm):
|
||||
@ -19,29 +17,30 @@ class FlowForm(forms.ModelForm):
|
||||
"name",
|
||||
"slug",
|
||||
"designation",
|
||||
"factors",
|
||||
"stages",
|
||||
"policies",
|
||||
]
|
||||
widgets = {
|
||||
"name": forms.TextInput(),
|
||||
"factors": FilteredSelectMultiple(_("policies"), False),
|
||||
"stages": FilteredSelectMultiple(_("stages"), False),
|
||||
"policies": FilteredSelectMultiple(_("policies"), False),
|
||||
}
|
||||
|
||||
|
||||
class FlowFactorBindingForm(forms.ModelForm):
|
||||
"""FlowFactorBinding Form"""
|
||||
class FlowStageBindingForm(forms.ModelForm):
|
||||
"""FlowStageBinding Form"""
|
||||
|
||||
class Meta:
|
||||
|
||||
model = FlowFactorBinding
|
||||
model = FlowStageBinding
|
||||
fields = [
|
||||
"flow",
|
||||
"factor",
|
||||
"stage",
|
||||
"re_evaluate_policies",
|
||||
"order",
|
||||
"policies",
|
||||
]
|
||||
widgets = {
|
||||
"name": forms.TextInput(),
|
||||
"factors": FilteredSelectMultiple(_("policies"), False),
|
||||
"policies": FilteredSelectMultiple(_("policies"), False),
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Generated by Django 3.0.3 on 2020-05-07 18:35
|
||||
# Generated by Django 3.0.3 on 2020-05-08 18:27
|
||||
|
||||
import uuid
|
||||
|
||||
@ -11,8 +11,7 @@ class Migration(migrations.Migration):
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
("passbook_policies", "0001_initial"),
|
||||
("passbook_core", "0011_auto_20200222_1822"),
|
||||
("passbook_policies", "0003_auto_20200508_1642"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
@ -37,6 +36,7 @@ class Migration(migrations.Migration):
|
||||
("AUTHENTICATION", "authentication"),
|
||||
("ENROLLMENT", "enrollment"),
|
||||
("RECOVERY", "recovery"),
|
||||
("PASSWORD_CHANGE", "password_change"),
|
||||
],
|
||||
max_length=100,
|
||||
),
|
||||
@ -55,7 +55,23 @@ class Migration(migrations.Migration):
|
||||
bases=("passbook_policies.policybindingmodel", models.Model),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="FlowFactorBinding",
|
||||
name="Stage",
|
||||
fields=[
|
||||
(
|
||||
"uuid",
|
||||
models.UUIDField(
|
||||
default=uuid.uuid4,
|
||||
editable=False,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
),
|
||||
),
|
||||
("name", models.TextField()),
|
||||
],
|
||||
options={"abstract": False,},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="FlowStageBinding",
|
||||
fields=[
|
||||
(
|
||||
"policybindingmodel_ptr",
|
||||
@ -75,14 +91,14 @@ class Migration(migrations.Migration):
|
||||
serialize=False,
|
||||
),
|
||||
),
|
||||
("order", models.IntegerField()),
|
||||
(
|
||||
"factor",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to="passbook_core.Factor",
|
||||
"re_evaluate_policies",
|
||||
models.BooleanField(
|
||||
default=False,
|
||||
help_text="When this option is enabled, the planner will re-evaluate policies bound to this.",
|
||||
),
|
||||
),
|
||||
("order", models.IntegerField()),
|
||||
(
|
||||
"flow",
|
||||
models.ForeignKey(
|
||||
@ -90,19 +106,29 @@ class Migration(migrations.Migration):
|
||||
to="passbook_flows.Flow",
|
||||
),
|
||||
),
|
||||
(
|
||||
"stage",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to="passbook_flows.Stage",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "Flow Factor Binding",
|
||||
"verbose_name_plural": "Flow Factor Bindings",
|
||||
"unique_together": {("flow", "factor", "order")},
|
||||
"verbose_name": "Flow Stage Binding",
|
||||
"verbose_name_plural": "Flow Stage Bindings",
|
||||
"ordering": ["order", "flow"],
|
||||
"unique_together": {("flow", "stage", "order")},
|
||||
},
|
||||
bases=("passbook_policies.policybindingmodel", models.Model),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="flow",
|
||||
name="factors",
|
||||
name="stages",
|
||||
field=models.ManyToManyField(
|
||||
through="passbook_flows.FlowFactorBinding", to="passbook_core.Factor"
|
||||
blank=True,
|
||||
through="passbook_flows.FlowStageBinding",
|
||||
to="passbook_flows.Stage",
|
||||
),
|
||||
),
|
||||
]
|
||||
|
@ -9,29 +9,35 @@ from passbook.flows.models import FlowDesignation
|
||||
|
||||
def create_default_flow(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
|
||||
Flow = apps.get_model("passbook_flows", "Flow")
|
||||
FlowFactorBinding = apps.get_model("passbook_flows", "FlowFactorBinding")
|
||||
PasswordFactor = apps.get_model("passbook_factors_password", "PasswordFactor")
|
||||
FlowStageBinding = apps.get_model("passbook_flows", "FlowStageBinding")
|
||||
PasswordStage = apps.get_model("passbook_stages_password", "PasswordStage")
|
||||
db_alias = schema_editor.connection.alias
|
||||
|
||||
if Flow.objects.using(db_alias).all().exists():
|
||||
# Only create default flow when none exist
|
||||
return
|
||||
|
||||
pw_factor = PasswordFactor.objects.using(db_alias).first()
|
||||
if not PasswordStage.objects.using(db_alias).exists():
|
||||
PasswordStage.objects.using(db_alias).create(
|
||||
name="password", backends=["django.contrib.auth.backends.ModelBackend"],
|
||||
)
|
||||
|
||||
pw_stage = PasswordStage.objects.using(db_alias).first()
|
||||
flow = Flow.objects.using(db_alias).create(
|
||||
name="default-authentication-flow",
|
||||
slug="default-authentication-flow",
|
||||
designation=FlowDesignation.AUTHENTICATION,
|
||||
)
|
||||
FlowFactorBinding.objects.using(db_alias).create(
|
||||
flow=flow, factor=pw_factor, order=0,
|
||||
FlowStageBinding.objects.using(db_alias).create(
|
||||
flow=flow, stage=pw_stage, order=0,
|
||||
)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("passbook_flows", "0003_auto_20200508_1230"),
|
||||
("passbook_flows", "0001_initial"),
|
||||
("passbook_stages_password", "0001_initial"),
|
||||
]
|
||||
|
||||
operations = [migrations.RunPython(create_default_flow)]
|
@ -1,21 +0,0 @@
|
||||
# Generated by Django 3.0.3 on 2020-05-07 19:18
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("passbook_flows", "0001_initial"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="flowfactorbinding",
|
||||
name="re_evaluate_policies",
|
||||
field=models.BooleanField(
|
||||
default=False,
|
||||
help_text="When this option is enabled, the planner will re-evaluate policies bound to this.",
|
||||
),
|
||||
),
|
||||
]
|
@ -1,21 +0,0 @@
|
||||
# Generated by Django 3.0.3 on 2020-05-08 12:30
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("passbook_flows", "0002_flowfactorbinding_re_evaluate_policies"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name="flowfactorbinding",
|
||||
options={
|
||||
"ordering": ["order", "flow"],
|
||||
"verbose_name": "Flow Factor Binding",
|
||||
"verbose_name_plural": "Flow Factor Bindings",
|
||||
},
|
||||
),
|
||||
]
|
@ -1,23 +0,0 @@
|
||||
# Generated by Django 3.0.3 on 2020-05-08 16:42
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("passbook_core", "0011_auto_20200222_1822"),
|
||||
("passbook_flows", "0004_default_flows"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="flow",
|
||||
name="factors",
|
||||
field=models.ManyToManyField(
|
||||
blank=True,
|
||||
through="passbook_flows.FlowFactorBinding",
|
||||
to="passbook_core.Factor",
|
||||
),
|
||||
),
|
||||
]
|
@ -1,11 +1,12 @@
|
||||
"""Flow models"""
|
||||
from enum import Enum
|
||||
from typing import Tuple
|
||||
from typing import Optional, Tuple
|
||||
|
||||
from django.db import models
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from model_utils.managers import InheritanceManager
|
||||
|
||||
from passbook.core.models import Factor
|
||||
from passbook.core.types import UIUserSettings
|
||||
from passbook.lib.models import UUIDModel
|
||||
from passbook.policies.models import PolicyBindingModel
|
||||
|
||||
@ -17,6 +18,7 @@ class FlowDesignation(Enum):
|
||||
AUTHENTICATION = "authentication"
|
||||
ENROLLMENT = "enrollment"
|
||||
RECOVERY = "recovery"
|
||||
PASSWORD_CHANGE = "password_change" # nosec # noqa
|
||||
|
||||
@staticmethod
|
||||
def as_choices() -> Tuple[Tuple[str, str]]:
|
||||
@ -26,8 +28,28 @@ class FlowDesignation(Enum):
|
||||
)
|
||||
|
||||
|
||||
class Stage(UUIDModel):
|
||||
"""Stage is an instance of a component used in a flow. This can verify the user,
|
||||
enroll the user or offer a way of recovery"""
|
||||
|
||||
name = models.TextField()
|
||||
|
||||
objects = InheritanceManager()
|
||||
type = ""
|
||||
form = ""
|
||||
|
||||
@property
|
||||
def ui_user_settings(self) -> Optional[UIUserSettings]:
|
||||
"""Entrypoint to integrate with User settings. Can either return None if no
|
||||
user settings are available, or an instanace of UIUserSettings."""
|
||||
return None
|
||||
|
||||
def __str__(self):
|
||||
return f"Stage {self.name}"
|
||||
|
||||
|
||||
class Flow(PolicyBindingModel, UUIDModel):
|
||||
"""Flow describes how a series of Factors should be executed to authenticate/enroll/recover
|
||||
"""Flow describes how a series of Stages should be executed to authenticate/enroll/recover
|
||||
a user. Additionally, policies can be applied, to specify which users
|
||||
have access to this flow."""
|
||||
|
||||
@ -36,7 +58,7 @@ class Flow(PolicyBindingModel, UUIDModel):
|
||||
|
||||
designation = models.CharField(max_length=100, choices=FlowDesignation.as_choices())
|
||||
|
||||
factors = models.ManyToManyField(Factor, through="FlowFactorBinding", blank=True)
|
||||
stages = models.ManyToManyField(Stage, through="FlowStageBinding", blank=True)
|
||||
|
||||
pbm = models.OneToOneField(
|
||||
PolicyBindingModel, parent_link=True, on_delete=models.CASCADE, related_name="+"
|
||||
@ -51,13 +73,13 @@ class Flow(PolicyBindingModel, UUIDModel):
|
||||
verbose_name_plural = _("Flows")
|
||||
|
||||
|
||||
class FlowFactorBinding(PolicyBindingModel, UUIDModel):
|
||||
"""Relationship between Flow and Factor. Order is required and unique for
|
||||
each flow-factor Binding. Additionally, policies can be specified, which determine if
|
||||
class FlowStageBinding(PolicyBindingModel, UUIDModel):
|
||||
"""Relationship between Flow and Stage. Order is required and unique for
|
||||
each flow-stage Binding. Additionally, policies can be specified, which determine if
|
||||
this Binding applies to the current user"""
|
||||
|
||||
flow = models.ForeignKey("Flow", on_delete=models.CASCADE)
|
||||
factor = models.ForeignKey(Factor, on_delete=models.CASCADE)
|
||||
stage = models.ForeignKey(Stage, on_delete=models.CASCADE)
|
||||
|
||||
re_evaluate_policies = models.BooleanField(
|
||||
default=False,
|
||||
@ -69,12 +91,12 @@ class FlowFactorBinding(PolicyBindingModel, UUIDModel):
|
||||
order = models.IntegerField()
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"Flow Factor Binding #{self.order} {self.flow} -> {self.factor}"
|
||||
return f"Flow Stage Binding #{self.order} {self.flow} -> {self.stage}"
|
||||
|
||||
class Meta:
|
||||
|
||||
ordering = ["order", "flow"]
|
||||
|
||||
verbose_name = _("Flow Factor Binding")
|
||||
verbose_name_plural = _("Flow Factor Bindings")
|
||||
unique_together = (("flow", "factor", "order"),)
|
||||
verbose_name = _("Flow Stage Binding")
|
||||
verbose_name_plural = _("Flow Stage Bindings")
|
||||
unique_together = (("flow", "stage", "order"),)
|
||||
|
@ -7,7 +7,7 @@ from django.http import HttpRequest
|
||||
from structlog import get_logger
|
||||
|
||||
from passbook.flows.exceptions import FlowNonApplicableError
|
||||
from passbook.flows.models import Factor, Flow
|
||||
from passbook.flows.models import Flow, Stage
|
||||
from passbook.policies.engine import PolicyEngine
|
||||
|
||||
LOGGER = get_logger()
|
||||
@ -19,19 +19,19 @@ PLAN_CONTEXT_SSO = "is_sso"
|
||||
@dataclass
|
||||
class FlowPlan:
|
||||
"""This data-class is the output of a FlowPlanner. It holds a flat list
|
||||
of all Factors that should be run."""
|
||||
of all Stages that should be run."""
|
||||
|
||||
factors: List[Factor] = field(default_factory=list)
|
||||
stages: List[Stage] = field(default_factory=list)
|
||||
context: Dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
def next(self) -> Factor:
|
||||
"""Return next pending factor from the bottom of the list"""
|
||||
factor_cls = self.factors.pop(0)
|
||||
return factor_cls
|
||||
def next(self) -> Stage:
|
||||
"""Return next pending stage from the bottom of the list"""
|
||||
stage_cls = self.stages.pop(0)
|
||||
return stage_cls
|
||||
|
||||
|
||||
class FlowPlanner:
|
||||
"""Execute all policies to plan out a flat list of all Factors
|
||||
"""Execute all policies to plan out a flat list of all Stages
|
||||
that should be applied."""
|
||||
|
||||
flow: Flow
|
||||
@ -45,7 +45,7 @@ class FlowPlanner:
|
||||
return engine.result
|
||||
|
||||
def plan(self, request: HttpRequest) -> FlowPlan:
|
||||
"""Check each of the flows' policies, check policies for each factor with PolicyBinding
|
||||
"""Check each of the flows' policies, check policies for each stage with PolicyBinding
|
||||
and return ordered list"""
|
||||
LOGGER.debug("Starting planning process", flow=self.flow)
|
||||
start_time = time()
|
||||
@ -56,13 +56,18 @@ class FlowPlanner:
|
||||
if not root_passing:
|
||||
raise FlowNonApplicableError(root_passing_messages)
|
||||
# Check Flow policies
|
||||
for factor in self.flow.factors.order_by("order").select_subclasses():
|
||||
engine = PolicyEngine(factor.policies.all(), request.user, request)
|
||||
for stage in (
|
||||
self.flow.stages.order_by("flowstagebinding__order")
|
||||
.select_subclasses()
|
||||
.select_related()
|
||||
):
|
||||
binding = stage.flowstagebinding_set.get(flow__pk=self.flow.pk)
|
||||
engine = PolicyEngine(binding.policies.all(), request.user, request)
|
||||
engine.build()
|
||||
passing, _ = engine.result
|
||||
if passing:
|
||||
LOGGER.debug("Factor passing", factor=factor)
|
||||
plan.factors.append(factor)
|
||||
LOGGER.debug("Stage passing", stage=stage)
|
||||
plan.stages.append(stage)
|
||||
end_time = time()
|
||||
LOGGER.debug(
|
||||
"Finished planning", flow=self.flow, duration_s=end_time - start_time
|
||||
|
@ -1,4 +1,4 @@
|
||||
"""passbook multi-factor authentication engine"""
|
||||
"""passbook stage Base view"""
|
||||
from typing import Any, Dict
|
||||
|
||||
from django.forms import ModelForm
|
||||
@ -11,8 +11,8 @@ from passbook.flows.views import FlowExecutorView
|
||||
from passbook.lib.config import CONFIG
|
||||
|
||||
|
||||
class AuthenticationFactor(TemplateView):
|
||||
"""Abstract Authentication factor, inherits TemplateView but can be combined with FormView"""
|
||||
class AuthenticationStage(TemplateView):
|
||||
"""Abstract Authentication stage, inherits TemplateView but can be combined with FormView"""
|
||||
|
||||
form: ModelForm = None
|
||||
|
@ -1,4 +1,4 @@
|
||||
"""passbook multi-factor authentication engine"""
|
||||
"""passbook multi-stage authentication engine"""
|
||||
from typing import Optional
|
||||
|
||||
from django.contrib.auth import login
|
||||
@ -7,10 +7,9 @@ from django.shortcuts import get_object_or_404, redirect
|
||||
from django.views.generic import View
|
||||
from structlog import get_logger
|
||||
|
||||
from passbook.core.models import Factor
|
||||
from passbook.core.views.utils import PermissionDeniedView
|
||||
from passbook.flows.exceptions import FlowNonApplicableError
|
||||
from passbook.flows.models import Flow
|
||||
from passbook.flows.models import Flow, Stage
|
||||
from passbook.flows.planner import PLAN_CONTEXT_PENDING_USER, FlowPlan, FlowPlanner
|
||||
from passbook.lib.config import CONFIG
|
||||
from passbook.lib.utils.reflection import class_to_path, path_to_class
|
||||
@ -24,13 +23,13 @@ SESSION_KEY_PLAN = "passbook_flows_plan"
|
||||
|
||||
|
||||
class FlowExecutorView(View):
|
||||
"""Stage 1 Flow executor, passing requests to Factor Views"""
|
||||
"""Stage 1 Flow executor, passing requests to Stage Views"""
|
||||
|
||||
flow: Flow
|
||||
|
||||
plan: FlowPlan
|
||||
current_factor: Factor
|
||||
current_factor_view: View
|
||||
current_stage: Stage
|
||||
current_stage_view: View
|
||||
|
||||
def setup(self, request: HttpRequest, flow_slug: str):
|
||||
super().setup(request, flow_slug=flow_slug)
|
||||
@ -77,36 +76,34 @@ class FlowExecutorView(View):
|
||||
else:
|
||||
LOGGER.debug("Continuing existing plan", flow_slug=flow_slug)
|
||||
self.plan = self.request.session[SESSION_KEY_PLAN]
|
||||
# We don't save the Plan after getting the next factor
|
||||
# We don't save the Plan after getting the next stage
|
||||
# as it hasn't been successfully passed yet
|
||||
self.current_factor = self.plan.next()
|
||||
self.current_stage = self.plan.next()
|
||||
LOGGER.debug(
|
||||
"Current factor",
|
||||
current_factor=self.current_factor,
|
||||
flow_slug=self.flow.slug,
|
||||
"Current stage", current_stage=self.current_stage, flow_slug=self.flow.slug,
|
||||
)
|
||||
factor_cls = path_to_class(self.current_factor.type)
|
||||
self.current_factor_view = factor_cls(self)
|
||||
self.current_factor_view.request = request
|
||||
stage_cls = path_to_class(self.current_stage.type)
|
||||
self.current_stage_view = stage_cls(self)
|
||||
self.current_stage_view.request = request
|
||||
return super().dispatch(request)
|
||||
|
||||
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
|
||||
"""pass get request to current factor"""
|
||||
"""pass get request to current stage"""
|
||||
LOGGER.debug(
|
||||
"Passing GET",
|
||||
view_class=class_to_path(self.current_factor_view.__class__),
|
||||
view_class=class_to_path(self.current_stage_view.__class__),
|
||||
flow_slug=self.flow.slug,
|
||||
)
|
||||
return self.current_factor_view.get(request, *args, **kwargs)
|
||||
return self.current_stage_view.get(request, *args, **kwargs)
|
||||
|
||||
def post(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
|
||||
"""pass post request to current factor"""
|
||||
"""pass post request to current stage"""
|
||||
LOGGER.debug(
|
||||
"Passing POST",
|
||||
view_class=class_to_path(self.current_factor_view.__class__),
|
||||
view_class=class_to_path(self.current_stage_view.__class__),
|
||||
flow_slug=self.flow.slug,
|
||||
)
|
||||
return self.current_factor_view.post(request, *args, **kwargs)
|
||||
return self.current_stage_view.post(request, *args, **kwargs)
|
||||
|
||||
def _initiate_plan(self) -> FlowPlan:
|
||||
planner = FlowPlanner(self.flow)
|
||||
@ -115,7 +112,7 @@ class FlowExecutorView(View):
|
||||
return plan
|
||||
|
||||
def _flow_done(self) -> HttpResponse:
|
||||
"""User Successfully passed all factors"""
|
||||
"""User Successfully passed all stages"""
|
||||
backend = self.plan.context[PLAN_CONTEXT_PENDING_USER].backend
|
||||
login(
|
||||
self.request, self.plan.context[PLAN_CONTEXT_PENDING_USER], backend=backend
|
||||
@ -131,34 +128,34 @@ class FlowExecutorView(View):
|
||||
return redirect(next_param)
|
||||
return redirect_with_qs("passbook_core:overview")
|
||||
|
||||
def factor_ok(self) -> HttpResponse:
|
||||
"""Callback called by factors upon successful completion.
|
||||
def stage_ok(self) -> HttpResponse:
|
||||
"""Callback called by stages upon successful completion.
|
||||
Persists updated plan and context to session."""
|
||||
LOGGER.debug(
|
||||
"Factor ok",
|
||||
factor_class=class_to_path(self.current_factor_view.__class__),
|
||||
"Stage ok",
|
||||
stage_class=class_to_path(self.current_stage_view.__class__),
|
||||
flow_slug=self.flow.slug,
|
||||
)
|
||||
self.request.session[SESSION_KEY_PLAN] = self.plan
|
||||
if self.plan.factors:
|
||||
if self.plan.stages:
|
||||
LOGGER.debug(
|
||||
"Continuing with next factor",
|
||||
reamining=len(self.plan.factors),
|
||||
"Continuing with next stage",
|
||||
reamining=len(self.plan.stages),
|
||||
flow_slug=self.flow.slug,
|
||||
)
|
||||
return redirect_with_qs(
|
||||
"passbook_flows:flow-executor", self.request.GET, **self.kwargs
|
||||
)
|
||||
# User passed all factors
|
||||
# User passed all stages
|
||||
LOGGER.debug(
|
||||
"User passed all factors",
|
||||
"User passed all stages",
|
||||
user=self.plan.context[PLAN_CONTEXT_PENDING_USER],
|
||||
flow_slug=self.flow.slug,
|
||||
)
|
||||
return self._flow_done()
|
||||
|
||||
def factor_invalid(self) -> HttpResponse:
|
||||
"""Callback used factor when data is correct but a policy denies access
|
||||
def stage_invalid(self) -> HttpResponse:
|
||||
"""Callback used stage when data is correct but a policy denies access
|
||||
or the user account is disabled."""
|
||||
LOGGER.debug("User invalid", flow_slug=self.flow.slug)
|
||||
self.cancel()
|
||||
|
Reference in New Issue
Block a user