Repository: goauthentik/authentik

Issue:
webauthn MFA not working with TOTP. 'dict' object is not callable
**Describe the bug**
I was able to setup the TOTP setup stage correctly, but as soon as I add Webauthn and select both TOTP and Webauthn, I start receiving a general system exception saying `'dict' object is not callable` [as you can see in the logs below]
I also keep seeing this error in the logs 
```
{"body":"{\"error\": \"invalid_grant\", \"error_description\": \"The provided authorization grant or refresh token is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client\"}
```

**To Reproduce**
Steps to reproduce the behavior:
1. Go to Flows -> default-authentication-flow -> Stage Bindings -> default-authentication-mfa-validation -> Edit Stage 
2. Select `WebAuthn Authenticator` under Device classes. Select `default-authenticator-webauthn-setup` under configuration stage along with the corresponding TOTP device and config stage.
3. Select `Force the user to configure an authenticator`
4. Try to create a new user and use any MFA setup method and see `Something went wrong! Please try again later.`

Note: TOTP alone works just fine.

**Expected behavior**
MFA setup upon new login using TOTP as well as WebAuthn.

**Logs**
<details>
    <summary>Stacktrace from authentik</summary>

```
{
    "geo": {<data>},
    "message": "Traceback (most recent call last):\n  File \"/authentik/flows/views/executor.py\", line 342, in post\n    stage_response = self.current_stage_view.post(request, *args, **kwargs)\n                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File \"/authentik/stages/authenticator_validate/stage.py\", line 285, in post\n    stage_pk = self.executor.plan.context(PLAN_CONTEXT_SELECTED_STAGE)\n               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\nbuiltins.TypeError: 'dict' object is not callable",
    "http_request": {
        "args": {
            "next": "/"
        },
        "path": "/api/v3/flows/executor/default-authentication-flow/",
        "method": "POST"
    }
}
User
{
    "pk": 2,
    "email": "",
    "username": "AnonymousUser"
}

```
</details>


**Version and Deployment (please complete the following information):**
- authentik version: 2023.5.4
- Deployment: helm
        


Discussion:

Resolving patch:
diff --git a/authentik/stages/authenticator_validate/stage.py b/authentik/stages/authenticator_validate/stage.py
--- a/authentik/stages/authenticator_validate/stage.py
+++ b/authentik/stages/authenticator_validate/stage.py
@@ -282,7 +282,7 @@ def post(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
             and self.executor.current_stage.not_configured_action == NotConfiguredAction.CONFIGURE
         ):
             self.logger.debug("Got selected stage in context, running that")
-            stage_pk = self.executor.plan.context(PLAN_CONTEXT_SELECTED_STAGE)
+            stage_pk = self.executor.plan.context.get(PLAN_CONTEXT_SELECTED_STAGE)
             # Because the foreign key to stage.configuration_stage points to
             # a base stage class, we need to do another lookup
             stage = Stage.objects.get_subclass(pk=stage_pk)


Test patch:
diff --git a/authentik/stages/authenticator_validate/tests/test_stage.py b/authentik/stages/authenticator_validate/tests/test_stage.py
--- a/authentik/stages/authenticator_validate/tests/test_stage.py
+++ b/authentik/stages/authenticator_validate/tests/test_stage.py
@@ -41,6 +41,10 @@ def test_not_configured_action(self):
         FlowStageBinding.objects.create(target=flow, stage=conf_stage, order=0)
         FlowStageBinding.objects.create(target=flow, stage=stage, order=1)
 
+        response = self.client.get(
+            reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
+        )
+        self.assertEqual(response.status_code, 200)
         response = self.client.post(
             reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
             {"uid_field": self.user.username},
@@ -61,6 +65,67 @@ def test_not_configured_action(self):
             show_source_labels=False,
         )
 
+    def test_not_configured_action_multiple(self):
+        """Test not_configured_action"""
+        conf_stage = IdentificationStage.objects.create(
+            name=generate_id(),
+            user_fields=[
+                UserFields.USERNAME,
+            ],
+        )
+        conf_stage2 = IdentificationStage.objects.create(
+            name=generate_id(),
+            user_fields=[
+                UserFields.USERNAME,
+            ],
+        )
+        stage = AuthenticatorValidateStage.objects.create(
+            name=generate_id(),
+            not_configured_action=NotConfiguredAction.CONFIGURE,
+        )
+        stage.configuration_stages.set([conf_stage, conf_stage2])
+        flow = create_test_flow()
+        FlowStageBinding.objects.create(target=flow, stage=conf_stage, order=0)
+        FlowStageBinding.objects.create(target=flow, stage=stage, order=1)
+
+        # Get initial identification stage
+        response = self.client.get(
+            reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
+        )
+        self.assertEqual(response.status_code, 200)
+        # Answer initial identification stage
+        response = self.client.post(
+            reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
+            {"uid_field": self.user.username},
+        )
+        self.assertEqual(response.status_code, 302)
+        # Get list of all configuration stages
+        response = self.client.get(
+            reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
+        )
+        self.assertEqual(response.status_code, 200)
+        # Select stage
+        response = self.client.post(
+            reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
+            {"selected_stage": conf_stage.pk},
+        )
+        self.assertEqual(response.status_code, 302)
+        # get actual identification stage response
+        response = self.client.get(
+            reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
+        )
+        self.assertEqual(response.status_code, 200)
+        self.assertStageResponse(
+            response,
+            flow,
+            component="ak-stage-identification",
+            password_fields=False,
+            primary_action="Continue",
+            user_fields=["username"],
+            sources=[],
+            show_source_labels=False,
+        )
+
     def test_stage_validation(self):
         """Test serializer validation"""
         self.client.force_login(self.user)