From ec823aebedc889320256bd245c21fb3b1001615b Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Mon, 29 Jun 2020 16:19:39 +0200 Subject: [PATCH] flows: update migrations to use update_or_create --- .../flows/migrations/0002_default_flows.py | 75 ++++++++---------- .../flows/migrations/0004_source_flows.py | 76 ++++++++++--------- .../flows/migrations/0005_provider_flows.py | 18 +++-- passbook/sources/saml/processors/base.py | 2 +- passbook/stages/identification/forms.py | 10 +++ passbook/stages/prompt/forms.py | 3 + 6 files changed, 97 insertions(+), 87 deletions(-) diff --git a/passbook/flows/migrations/0002_default_flows.py b/passbook/flows/migrations/0002_default_flows.py index 5977cc8ee..15b45625c 100644 --- a/passbook/flows/migrations/0002_default_flows.py +++ b/passbook/flows/migrations/0002_default_flows.py @@ -20,42 +20,38 @@ def create_default_authentication_flow( ) db_alias = schema_editor.connection.alias - if ( - Flow.objects.using(db_alias) - .filter(designation=FlowDesignation.AUTHENTICATION) - .exists() - ): - # Only create default flow when none exist - return + identification_stage, _ = IdentificationStage.objects.using( + db_alias + ).update_or_create( + name="default-authentication-identification", + defaults={ + "user_fields": [UserFields.E_MAIL, UserFields.USERNAME], + "template": Templates.DEFAULT_LOGIN, + }, + ) - if not IdentificationStage.objects.using(db_alias).exists(): - IdentificationStage.objects.using(db_alias).create( - name="identification", - user_fields=[UserFields.E_MAIL, UserFields.USERNAME], - template=Templates.DEFAULT_LOGIN, - ) + password_stage, _ = PasswordStage.objects.using(db_alias).update_or_create( + name="default-authentication-password", + defaults={"backends": ["django.contrib.auth.backends.ModelBackend"]}, + ) - if not PasswordStage.objects.using(db_alias).exists(): - PasswordStage.objects.using(db_alias).create( - name="password", backends=["django.contrib.auth.backends.ModelBackend"], - ) + login_stage, _ = UserLoginStage.objects.using(db_alias).update_or_create( + name="default-authentication-login" + ) - if not UserLoginStage.objects.using(db_alias).exists(): - UserLoginStage.objects.using(db_alias).create(name="authentication") - - flow = Flow.objects.using(db_alias).create( - name="Welcome to passbook!", + flow, _ = Flow.objects.using(db_alias).update_or_create( slug="default-authentication-flow", designation=FlowDesignation.AUTHENTICATION, + defaults={"name": "Welcome to passbook!",}, ) - FlowStageBinding.objects.using(db_alias).create( - flow=flow, stage=IdentificationStage.objects.using(db_alias).first(), order=0, + FlowStageBinding.objects.using(db_alias).update_or_create( + flow=flow, stage=identification_stage, defaults={"order": 0,}, ) - FlowStageBinding.objects.using(db_alias).create( - flow=flow, stage=PasswordStage.objects.using(db_alias).first(), order=1, + FlowStageBinding.objects.using(db_alias).update_or_create( + flow=flow, stage=password_stage, defaults={"order": 1,}, ) - FlowStageBinding.objects.using(db_alias).create( - flow=flow, stage=UserLoginStage.objects.using(db_alias).first(), order=2, + FlowStageBinding.objects.using(db_alias).update_or_create( + flow=flow, stage=login_stage, defaults={"order": 2,}, ) @@ -67,24 +63,19 @@ def create_default_invalidation_flow( UserLogoutStage = apps.get_model("passbook_stages_user_logout", "UserLogoutStage") db_alias = schema_editor.connection.alias - if ( - Flow.objects.using(db_alias) - .filter(designation=FlowDesignation.INVALIDATION) - .exists() - ): - # Only create default flow when none exist - return + UserLogoutStage.objects.using(db_alias).update_or_create( + name="default-invalidation-logout" + ) - if not UserLogoutStage.objects.using(db_alias).exists(): - UserLogoutStage.objects.using(db_alias).create(name="logout") - - flow = Flow.objects.using(db_alias).create( - name="default-invalidation-flow", + flow, _ = Flow.objects.using(db_alias).update_or_create( slug="default-invalidation-flow", designation=FlowDesignation.INVALIDATION, + defaults={"name": "Logout",}, ) - FlowStageBinding.objects.using(db_alias).create( - flow=flow, stage=UserLogoutStage.objects.using(db_alias).first(), order=0, + FlowStageBinding.objects.using(db_alias).update_or_create( + flow=flow, + stage=UserLogoutStage.objects.using(db_alias).first(), + defaults={"order": 0,}, ) diff --git a/passbook/flows/migrations/0004_source_flows.py b/passbook/flows/migrations/0004_source_flows.py index 4a2f04432..933f986b5 100644 --- a/passbook/flows/migrations/0004_source_flows.py +++ b/passbook/flows/migrations/0004_source_flows.py @@ -34,60 +34,63 @@ def create_default_source_enrollment_flow( db_alias = schema_editor.connection.alias # Create a policy that only allows this flow when doing an SSO Request - flow_policy = ExpressionPolicy.objects.using(db_alias).create( - name="default-source-enrollment-if-sso", expression=FLOW_POLICY_EXPRESSION + flow_policy, _ = ExpressionPolicy.objects.using(db_alias).update_or_create( + name="default-source-enrollment-if-sso", + defaults={"expression": FLOW_POLICY_EXPRESSION}, ) # This creates a Flow used by sources to enroll users # It makes sure that a username is set, and if not, prompts the user for a Username - flow = Flow.objects.using(db_alias).create( - name="default-source-enrollment", + flow, _ = Flow.objects.using(db_alias).update_or_create( slug="default-source-enrollment", designation=FlowDesignation.ENROLLMENT, + defaults={"name": "Welcome to passbook!",}, ) - PolicyBinding.objects.using(db_alias).create( - policy=flow_policy, target=flow, order=0 + PolicyBinding.objects.using(db_alias).update_or_create( + policy=flow_policy, target=flow, defaults={"order": 0} ) # PromptStage to ask user for their username - prompt_stage = PromptStage.objects.using(db_alias).create( + prompt_stage, _ = PromptStage.objects.using(db_alias).update_or_create( name="default-source-enrollment-username-prompt", ) - prompt_stage.fields.add( - Prompt.objects.using(db_alias).create( - field_key="username", - label="Username", - type=FieldTypes.TEXT, - required=True, - placeholder="Username", - ) + prompt, _ = Prompt.objects.using(db_alias).update_or_create( + field_key="username", + defaults={ + "label": "Username", + "type": FieldTypes.TEXT, + "required": True, + "placeholder": "Username", + }, ) + prompt_stage.fields.add(prompt) + # Policy to only trigger prompt when no username is given - prompt_policy = ExpressionPolicy.objects.using(db_alias).create( + prompt_policy, _ = ExpressionPolicy.objects.using(db_alias).update_or_create( name="default-source-enrollment-if-username", - expression=PROMPT_POLICY_EXPRESSION, + defaults={"expression": PROMPT_POLICY_EXPRESSION}, ) # UserWrite stage to create the user, and login stage to log user in - user_write = UserWriteStage.objects.using(db_alias).create( + user_write, _ = UserWriteStage.objects.using(db_alias).update_or_create( name="default-source-enrollment-write" ) - user_login = UserLoginStage.objects.using(db_alias).create( + user_login, _ = UserLoginStage.objects.using(db_alias).update_or_create( name="default-source-enrollment-login" ) - binding = FlowStageBinding.objects.using(db_alias).create( - flow=flow, stage=prompt_stage, order=0 + binding, _ = FlowStageBinding.objects.using(db_alias).update_or_create( + flow=flow, stage=prompt_stage, defaults={"order": 0} ) - PolicyBinding.objects.using(db_alias).create( - policy=prompt_policy, target=binding, order=0 + PolicyBinding.objects.using(db_alias).update_or_create( + policy=prompt_policy, target=binding, defaults={"order": 0} ) - FlowStageBinding.objects.using(db_alias).create( - flow=flow, stage=user_write, order=1 + FlowStageBinding.objects.using(db_alias).update_or_create( + flow=flow, stage=user_write, defaults={"order": 1} ) - FlowStageBinding.objects.using(db_alias).create( - flow=flow, stage=user_login, order=2 + FlowStageBinding.objects.using(db_alias).update_or_create( + flow=flow, stage=user_login, defaults={"order": 2} ) @@ -107,25 +110,26 @@ def create_default_source_authentication_flow( db_alias = schema_editor.connection.alias # Create a policy that only allows this flow when doing an SSO Request - flow_policy = ExpressionPolicy.objects.using(db_alias).create( - name="default-source-authentication-if-sso", expression=FLOW_POLICY_EXPRESSION + flow_policy, _ = ExpressionPolicy.objects.using(db_alias).update_or_create( + name="default-source-authentication-if-sso", + defaults={"expression": FLOW_POLICY_EXPRESSION,}, ) # This creates a Flow used by sources to authenticate users - flow = Flow.objects.using(db_alias).create( - name="default-source-authentication", + flow, _ = Flow.objects.using(db_alias).update_or_create( slug="default-source-authentication", designation=FlowDesignation.AUTHENTICATION, + defaults={"name": "Welcome to passbook!",}, ) - PolicyBinding.objects.using(db_alias).create( - policy=flow_policy, target=flow, order=0 + PolicyBinding.objects.using(db_alias).update_or_create( + policy=flow_policy, target=flow, defaults={"order": 0} ) - user_login = UserLoginStage.objects.using(db_alias).create( + user_login, _ = UserLoginStage.objects.using(db_alias).update_or_create( name="default-source-authentication-login" ) - FlowStageBinding.objects.using(db_alias).create( - flow=flow, stage=user_login, order=0 + FlowStageBinding.objects.using(db_alias).update_or_create( + flow=flow, stage=user_login, defaults={"order": 0} ) diff --git a/passbook/flows/migrations/0005_provider_flows.py b/passbook/flows/migrations/0005_provider_flows.py index 6fa5fa48e..764bf826d 100644 --- a/passbook/flows/migrations/0005_provider_flows.py +++ b/passbook/flows/migrations/0005_provider_flows.py @@ -7,7 +7,7 @@ from django.db.backends.base.schema import BaseDatabaseSchemaEditor from passbook.flows.models import FlowDesignation -def create_default_provider_authz_flow( +def create_default_provider_authorization_flow( apps: Apps, schema_editor: BaseDatabaseSchemaEditor ): Flow = apps.get_model("passbook_flows", "Flow") @@ -18,22 +18,24 @@ def create_default_provider_authz_flow( db_alias = schema_editor.connection.alias # Empty flow for providers where consent is implicitly given - Flow.objects.using(db_alias).create( - name="Authorize Application", + Flow.objects.using(db_alias).update_or_create( slug="default-provider-authorization-implicit-consent", designation=FlowDesignation.AUTHORIZATION, + defaults={"name": "Authorize Application"}, ) # Flow with consent form to obtain explicit user consent - flow = Flow.objects.using(db_alias).create( - name="Authorize Application", + flow, _ = Flow.objects.using(db_alias).update_or_create( slug="default-provider-authorization-explicit-consent", designation=FlowDesignation.AUTHORIZATION, + defaults={"name": "Authorize Application"}, ) - stage = ConsentStage.objects.using(db_alias).create( + stage, _ = ConsentStage.objects.using(db_alias).update_or_create( name="default-provider-authorization-consent" ) - FlowStageBinding.objects.using(db_alias).create(flow=flow, stage=stage, order=0) + FlowStageBinding.objects.using(db_alias).update_or_create( + flow=flow, stage=stage, defaults={"order": 0} + ) class Migration(migrations.Migration): @@ -43,4 +45,4 @@ class Migration(migrations.Migration): ("passbook_stages_consent", "0001_initial"), ] - operations = [migrations.RunPython(create_default_provider_authz_flow)] + operations = [migrations.RunPython(create_default_provider_authorization_flow)] diff --git a/passbook/sources/saml/processors/base.py b/passbook/sources/saml/processors/base.py index 996d1d7c5..036c034f5 100644 --- a/passbook/sources/saml/processors/base.py +++ b/passbook/sources/saml/processors/base.py @@ -153,7 +153,7 @@ class Processor: self, request: HttpRequest, flow: Flow, **kwargs ) -> HttpResponse: kwargs[PLAN_CONTEXT_SSO] = True - request.session[SESSION_KEY_PLAN] = FlowPlanner(flow).plan(request, kwargs,) + request.session[SESSION_KEY_PLAN] = FlowPlanner(flow).plan(request, kwargs) return redirect_with_qs( "passbook_flows:flow-executor-shell", request.GET, flow_slug=flow.slug, ) diff --git a/passbook/stages/identification/forms.py b/passbook/stages/identification/forms.py index e99292640..faf8389e6 100644 --- a/passbook/stages/identification/forms.py +++ b/passbook/stages/identification/forms.py @@ -5,6 +5,7 @@ from django.core.validators import validate_email from django.utils.translation import gettext_lazy as _ from structlog import get_logger +from passbook.flows.models import Flow, FlowDesignation from passbook.lib.utils.ui import human_list from passbook.stages.identification.models import IdentificationStage, UserFields @@ -14,6 +15,15 @@ LOGGER = get_logger() class IdentificationStageForm(forms.ModelForm): """Form to create/edit IdentificationStage instances""" + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.fields["enrollment_flow"].queryset = Flow.objects.filter( + designation=FlowDesignation.ENROLLMENT + ) + self.fields["recovery_flow"].queryset = Flow.objects.filter( + designation=FlowDesignation.RECOVERY + ) + class Meta: model = IdentificationStage diff --git a/passbook/stages/prompt/forms.py b/passbook/stages/prompt/forms.py index e9157d198..15bb648ae 100644 --- a/passbook/stages/prompt/forms.py +++ b/passbook/stages/prompt/forms.py @@ -1,5 +1,7 @@ """Prompt forms""" from django import forms +from django.contrib.admin.widgets import FilteredSelectMultiple +from django.utils.translation import gettext_lazy as _ from guardian.shortcuts import get_anonymous_user from passbook.flows.planner import PLAN_CONTEXT_PENDING_USER, FlowPlan @@ -16,6 +18,7 @@ class PromptStageForm(forms.ModelForm): fields = ["name", "fields"] widgets = { "name": forms.TextInput(), + "fields": FilteredSelectMultiple(_("prompts"), False), }