We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Inactive user can't pass identification stage
**Describe the bug**
I hope I'm not completely wrong, but inactive users should also be able to pass an identification stage.
In my case it should be possible to request a password reset even with an inactive user account. 

Like in the default-recovery-flow which is delivered as a blueprint example with Authentik and looks like this:

![image](https://user-images.githubusercontent.com/18358381/216637203-26f9b110-31cd-4afc-9597-c73fd7c74087.png)

The "default-recovery-email" also has an option to activate inactive users with a normal password reset:

![image](https://user-images.githubusercontent.com/18358381/216637227-6787f663-531b-4e73-b061-1e01d402f1b6.png)

However, in my opinion, this interferes with the following query, which I have adapted for my purposes:

/authentics/authentics/stages/identification/stage.py

changed line 175:
        user = User.objects.filter(query, is_active=True).first()
to 
        user = User.objects.filter(query).first()

Now even inactive users are able to pass the Identification Stage and get to the EMail Stage.
Please let me know if this customization is a safety issue or if it can be done this way.

**To Reproduce**
Steps to reproduce the behavior:
1. fresh Authentik installation
2. created a normal user and set it to inactive
3. created a new Blueprint instance from examples/flows-recovery-email-verification.yaml
4. executed the blueprint instance
5. now I have my recovery flow "default-recovery-flow"
6. to make the flow work I made a small correction to on of the expressions "default-recoery-skip-if-restored" as stated in this Github post: https://github.com/goauthentik/authentik/issues/3297
7. after that I deleted the flow cache and opened the flow in an incognito tab: "/if/flow/default-recovery-flow"
8. try to trigger the passwort reset function

**Expected behavior**
Also an inactive user should be able pass the identification stage to reach the email stage.

**Version and Deployment (please complete the following information):**
 - authentik version: 2023.1.2
 - Deployment: docker-compose

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `authentik/stages/identification/stage.py`
Content:
```
1 """Identification stage logic"""
2 from dataclasses import asdict
3 from random import SystemRandom
4 from time import sleep
5 from typing import Any, Optional
6 
7 from django.core.exceptions import PermissionDenied
8 from django.db.models import Q
9 from django.http import HttpResponse
10 from django.utils.translation import gettext as _
11 from drf_spectacular.utils import PolymorphicProxySerializer, extend_schema_field
12 from rest_framework.fields import BooleanField, CharField, DictField, ListField
13 from rest_framework.serializers import ValidationError
14 from sentry_sdk.hub import Hub
15 
16 from authentik.core.api.utils import PassiveSerializer
17 from authentik.core.models import Application, Source, User
18 from authentik.events.utils import sanitize_item
19 from authentik.flows.challenge import (
20     Challenge,
21     ChallengeResponse,
22     ChallengeTypes,
23     RedirectChallenge,
24 )
25 from authentik.flows.models import FlowDesignation
26 from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER
27 from authentik.flows.stage import PLAN_CONTEXT_PENDING_USER_IDENTIFIER, ChallengeStageView
28 from authentik.flows.views.executor import SESSION_KEY_APPLICATION_PRE, SESSION_KEY_GET
29 from authentik.lib.utils.http import get_client_ip
30 from authentik.lib.utils.urls import reverse_with_qs
31 from authentik.sources.oauth.types.apple import AppleLoginChallenge
32 from authentik.sources.plex.models import PlexAuthenticationChallenge
33 from authentik.stages.identification.models import IdentificationStage
34 from authentik.stages.identification.signals import identification_failed
35 from authentik.stages.password.stage import authenticate
36 
37 
38 @extend_schema_field(
39     PolymorphicProxySerializer(
40         component_name="LoginChallengeTypes",
41         serializers={
42             RedirectChallenge().fields["component"].default: RedirectChallenge,
43             PlexAuthenticationChallenge().fields["component"].default: PlexAuthenticationChallenge,
44             AppleLoginChallenge().fields["component"].default: AppleLoginChallenge,
45         },
46         resource_type_field_name="component",
47     )
48 )
49 class ChallengeDictWrapper(DictField):
50     """Wrapper around DictField that annotates itself as challenge proxy"""
51 
52 
53 class LoginSourceSerializer(PassiveSerializer):
54     """Serializer for Login buttons of sources"""
55 
56     name = CharField()
57     icon_url = CharField(required=False, allow_null=True)
58 
59     challenge = ChallengeDictWrapper()
60 
61 
62 class IdentificationChallenge(Challenge):
63     """Identification challenges with all UI elements"""
64 
65     user_fields = ListField(child=CharField(), allow_empty=True, allow_null=True)
66     password_fields = BooleanField()
67     application_pre = CharField(required=False)
68 
69     enroll_url = CharField(required=False)
70     recovery_url = CharField(required=False)
71     passwordless_url = CharField(required=False)
72     primary_action = CharField()
73     sources = LoginSourceSerializer(many=True, required=False)
74     show_source_labels = BooleanField()
75 
76     component = CharField(default="ak-stage-identification")
77 
78 
79 class IdentificationChallengeResponse(ChallengeResponse):
80     """Identification challenge"""
81 
82     uid_field = CharField()
83     password = CharField(required=False, allow_blank=True, allow_null=True)
84     component = CharField(default="ak-stage-identification")
85 
86     pre_user: Optional[User] = None
87 
88     def validate(self, attrs: dict[str, Any]) -> dict[str, Any]:
89         """Validate that user exists, and optionally their password"""
90         uid_field = attrs["uid_field"]
91         current_stage: IdentificationStage = self.stage.executor.current_stage
92 
93         pre_user = self.stage.get_user(uid_field)
94         if not pre_user:
95             with Hub.current.start_span(
96                 op="authentik.stages.identification.validate_invalid_wait",
97                 description="Sleep random time on invalid user identifier",
98             ):
99                 # Sleep a random time (between 90 and 210ms) to "prevent" user enumeration attacks
100                 sleep(0.030 * SystemRandom().randint(3, 7))
101             # Log in a similar format to Event.new(), but we don't want to create an event here
102             # as this stage is mostly used by unauthenticated users with very high rate limits
103             self.stage.logger.info(
104                 "invalid_login",
105                 identifier=uid_field,
106                 client_ip=get_client_ip(self.stage.request),
107                 action="invalid_identifier",
108                 context={
109                     "stage": sanitize_item(self.stage),
110                 },
111             )
112             identification_failed.send(sender=self, request=self.stage.request, uid_field=uid_field)
113             # We set the pending_user even on failure so it's part of the context, even
114             # when the input is invalid
115             # This is so its part of the current flow plan, and on flow restart can be kept, and
116             # policies can be applied.
117             self.stage.executor.plan.context[PLAN_CONTEXT_PENDING_USER] = User(
118                 username=uid_field,
119                 email=uid_field,
120             )
121             if not current_stage.show_matched_user:
122                 self.stage.executor.plan.context[PLAN_CONTEXT_PENDING_USER_IDENTIFIER] = uid_field
123             raise ValidationError("Failed to authenticate.")
124         self.pre_user = pre_user
125         if not current_stage.password_stage:
126             # No password stage select, don't validate the password
127             return attrs
128 
129         password = attrs.get("password", None)
130         if not password:
131             self.stage.logger.warning("Password not set for ident+auth attempt")
132         try:
133             with Hub.current.start_span(
134                 op="authentik.stages.identification.authenticate",
135                 description="User authenticate call (combo stage)",
136             ):
137                 user = authenticate(
138                     self.stage.request,
139                     current_stage.password_stage.backends,
140                     current_stage,
141                     username=self.pre_user.username,
142                     password=password,
143                 )
144             if not user:
145                 raise ValidationError("Failed to authenticate.")
146             self.pre_user = user
147         except PermissionDenied as exc:
148             raise ValidationError(str(exc)) from exc
149         return attrs
150 
151 
152 class IdentificationStageView(ChallengeStageView):
153     """Form to identify the user"""
154 
155     response_class = IdentificationChallengeResponse
156 
157     def get_user(self, uid_value: str) -> Optional[User]:
158         """Find user instance. Returns None if no user was found."""
159         current_stage: IdentificationStage = self.executor.current_stage
160         query = Q()
161         for search_field in current_stage.user_fields:
162             model_field = {
163                 "email": "email",
164                 "username": "username",
165                 "upn": "attributes__upn",
166             }[search_field]
167             if current_stage.case_insensitive_matching:
168                 model_field += "__iexact"
169             else:
170                 model_field += "__exact"
171             query |= Q(**{model_field: uid_value})
172         if not query:
173             self.logger.debug("Empty user query", query=query)
174             return None
175         user = User.objects.filter(query, is_active=True).first()
176         if user:
177             self.logger.debug("Found user", user=user.username, query=query)
178             return user
179         return None
180 
181     def get_primary_action(self) -> str:
182         """Get the primary action label for this stage"""
183         if self.executor.flow.designation == FlowDesignation.AUTHENTICATION:
184             return _("Log in")
185         return _("Continue")
186 
187     def get_challenge(self) -> Challenge:
188         current_stage: IdentificationStage = self.executor.current_stage
189         challenge = IdentificationChallenge(
190             data={
191                 "type": ChallengeTypes.NATIVE.value,
192                 "primary_action": self.get_primary_action(),
193                 "component": "ak-stage-identification",
194                 "user_fields": current_stage.user_fields,
195                 "password_fields": bool(current_stage.password_stage),
196                 "show_source_labels": current_stage.show_source_labels,
197             }
198         )
199         # If the user has been redirected to us whilst trying to access an
200         # application, SESSION_KEY_APPLICATION_PRE is set in the session
201         if SESSION_KEY_APPLICATION_PRE in self.request.session:
202             challenge.initial_data["application_pre"] = self.request.session.get(
203                 SESSION_KEY_APPLICATION_PRE, Application()
204             ).name
205         get_qs = self.request.session.get(SESSION_KEY_GET, self.request.GET)
206         # Check for related enrollment and recovery flow, add URL to view
207         if current_stage.enrollment_flow:
208             challenge.initial_data["enroll_url"] = reverse_with_qs(
209                 "authentik_core:if-flow",
210                 query=get_qs,
211                 kwargs={"flow_slug": current_stage.enrollment_flow.slug},
212             )
213         if current_stage.recovery_flow:
214             challenge.initial_data["recovery_url"] = reverse_with_qs(
215                 "authentik_core:if-flow",
216                 query=get_qs,
217                 kwargs={"flow_slug": current_stage.recovery_flow.slug},
218             )
219         if current_stage.passwordless_flow:
220             challenge.initial_data["passwordless_url"] = reverse_with_qs(
221                 "authentik_core:if-flow",
222                 query=get_qs,
223                 kwargs={"flow_slug": current_stage.passwordless_flow.slug},
224             )
225 
226         # Check all enabled source, add them if they have a UI Login button.
227         ui_sources = []
228         sources: list[Source] = (
229             current_stage.sources.filter(enabled=True).order_by("name").select_subclasses()
230         )
231         for source in sources:
232             ui_login_button = source.ui_login_button(self.request)
233             if ui_login_button:
234                 button = asdict(ui_login_button)
235                 button["challenge"] = ui_login_button.challenge.data
236                 ui_sources.append(button)
237         challenge.initial_data["sources"] = ui_sources
238         return challenge
239 
240     def challenge_valid(self, response: IdentificationChallengeResponse) -> HttpResponse:
241         self.executor.plan.context[PLAN_CONTEXT_PENDING_USER] = response.pre_user
242         current_stage: IdentificationStage = self.executor.current_stage
243         if not current_stage.show_matched_user:
244             self.executor.plan.context[
245                 PLAN_CONTEXT_PENDING_USER_IDENTIFIER
246             ] = response.validated_data.get("uid_field")
247         return self.executor.stage_ok()
248
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/authentik/stages/identification/stage.py b/authentik/stages/identification/stage.py
--- a/authentik/stages/identification/stage.py
+++ b/authentik/stages/identification/stage.py
@@ -172,7 +172,7 @@
         if not query:
             self.logger.debug("Empty user query", query=query)
             return None
-        user = User.objects.filter(query, is_active=True).first()
+        user = User.objects.filter(query).first()
         if user:
             self.logger.debug("Found user", user=user.username, query=query)
             return user

{"golden_diff": "diff --git a/authentik/stages/identification/stage.py b/authentik/stages/identification/stage.py\n--- a/authentik/stages/identification/stage.py\n+++ b/authentik/stages/identification/stage.py\n@@ -172,7 +172,7 @@\n         if not query:\n             self.logger.debug(\"Empty user query\", query=query)\n             return None\n-        user = User.objects.filter(query, is_active=True).first()\n+        user = User.objects.filter(query).first()\n         if user:\n             self.logger.debug(\"Found user\", user=user.username, query=query)\n             return user\n", "issue": "Inactive user can't pass identification stage\n**Describe the bug**\r\nI hope I'm not completely wrong, but inactive users should also be able to pass an identification stage.\r\nIn my case it should be possible to request a password reset even with an inactive user account. \r\n\r\nLike in the default-recovery-flow which is delivered as a blueprint example with Authentik and looks like this:\r\n\r\n![image](https://user-images.githubusercontent.com/18358381/216637203-26f9b110-31cd-4afc-9597-c73fd7c74087.png)\r\n\r\nThe \"default-recovery-email\" also has an option to activate inactive users with a normal password reset:\r\n\r\n![image](https://user-images.githubusercontent.com/18358381/216637227-6787f663-531b-4e73-b061-1e01d402f1b6.png)\r\n\r\nHowever, in my opinion, this interferes with the following query, which I have adapted for my purposes:\r\n\r\n/authentics/authentics/stages/identification/stage.py\r\n\r\nchanged line 175:\r\n        user = User.objects.filter(query, is_active=True).first()\r\nto \r\n        user = User.objects.filter(query).first()\r\n\r\nNow even inactive users are able to pass the Identification Stage and get to the EMail Stage.\r\nPlease let me know if this customization is a safety issue or if it can be done this way.\r\n\r\n**To Reproduce**\r\nSteps to reproduce the behavior:\r\n1. fresh Authentik installation\r\n2. created a normal user and set it to inactive\r\n3. created a new Blueprint instance from examples/flows-recovery-email-verification.yaml\r\n4. executed the blueprint instance\r\n5. now I have my recovery flow \"default-recovery-flow\"\r\n6. to make the flow work I made a small correction to on of the expressions \"default-recoery-skip-if-restored\" as stated in this Github post: https://github.com/goauthentik/authentik/issues/3297\r\n7. after that I deleted the flow cache and opened the flow in an incognito tab: \"/if/flow/default-recovery-flow\"\r\n8. try to trigger the passwort reset function\r\n\r\n**Expected behavior**\r\nAlso an inactive user should be able pass the identification stage to reach the email stage.\r\n\r\n**Version and Deployment (please complete the following information):**\r\n - authentik version: 2023.1.2\r\n - Deployment: docker-compose\n", "before_files": [{"content": "\"\"\"Identification stage logic\"\"\"\nfrom dataclasses import asdict\nfrom random import SystemRandom\nfrom time import sleep\nfrom typing import Any, Optional\n\nfrom django.core.exceptions import PermissionDenied\nfrom django.db.models import Q\nfrom django.http import HttpResponse\nfrom django.utils.translation import gettext as _\nfrom drf_spectacular.utils import PolymorphicProxySerializer, extend_schema_field\nfrom rest_framework.fields import BooleanField, CharField, DictField, ListField\nfrom rest_framework.serializers import ValidationError\nfrom sentry_sdk.hub import Hub\n\nfrom authentik.core.api.utils import PassiveSerializer\nfrom authentik.core.models import Application, Source, User\nfrom authentik.events.utils import sanitize_item\nfrom authentik.flows.challenge import (\n    Challenge,\n    ChallengeResponse,\n    ChallengeTypes,\n    RedirectChallenge,\n)\nfrom authentik.flows.models import FlowDesignation\nfrom authentik.flows.planner import PLAN_CONTEXT_PENDING_USER\nfrom authentik.flows.stage import PLAN_CONTEXT_PENDING_USER_IDENTIFIER, ChallengeStageView\nfrom authentik.flows.views.executor import SESSION_KEY_APPLICATION_PRE, SESSION_KEY_GET\nfrom authentik.lib.utils.http import get_client_ip\nfrom authentik.lib.utils.urls import reverse_with_qs\nfrom authentik.sources.oauth.types.apple import AppleLoginChallenge\nfrom authentik.sources.plex.models import PlexAuthenticationChallenge\nfrom authentik.stages.identification.models import IdentificationStage\nfrom authentik.stages.identification.signals import identification_failed\nfrom authentik.stages.password.stage import authenticate\n\n\n@extend_schema_field(\n    PolymorphicProxySerializer(\n        component_name=\"LoginChallengeTypes\",\n        serializers={\n            RedirectChallenge().fields[\"component\"].default: RedirectChallenge,\n            PlexAuthenticationChallenge().fields[\"component\"].default: PlexAuthenticationChallenge,\n            AppleLoginChallenge().fields[\"component\"].default: AppleLoginChallenge,\n        },\n        resource_type_field_name=\"component\",\n    )\n)\nclass ChallengeDictWrapper(DictField):\n    \"\"\"Wrapper around DictField that annotates itself as challenge proxy\"\"\"\n\n\nclass LoginSourceSerializer(PassiveSerializer):\n    \"\"\"Serializer for Login buttons of sources\"\"\"\n\n    name = CharField()\n    icon_url = CharField(required=False, allow_null=True)\n\n    challenge = ChallengeDictWrapper()\n\n\nclass IdentificationChallenge(Challenge):\n    \"\"\"Identification challenges with all UI elements\"\"\"\n\n    user_fields = ListField(child=CharField(), allow_empty=True, allow_null=True)\n    password_fields = BooleanField()\n    application_pre = CharField(required=False)\n\n    enroll_url = CharField(required=False)\n    recovery_url = CharField(required=False)\n    passwordless_url = CharField(required=False)\n    primary_action = CharField()\n    sources = LoginSourceSerializer(many=True, required=False)\n    show_source_labels = BooleanField()\n\n    component = CharField(default=\"ak-stage-identification\")\n\n\nclass IdentificationChallengeResponse(ChallengeResponse):\n    \"\"\"Identification challenge\"\"\"\n\n    uid_field = CharField()\n    password = CharField(required=False, allow_blank=True, allow_null=True)\n    component = CharField(default=\"ak-stage-identification\")\n\n    pre_user: Optional[User] = None\n\n    def validate(self, attrs: dict[str, Any]) -> dict[str, Any]:\n        \"\"\"Validate that user exists, and optionally their password\"\"\"\n        uid_field = attrs[\"uid_field\"]\n        current_stage: IdentificationStage = self.stage.executor.current_stage\n\n        pre_user = self.stage.get_user(uid_field)\n        if not pre_user:\n            with Hub.current.start_span(\n                op=\"authentik.stages.identification.validate_invalid_wait\",\n                description=\"Sleep random time on invalid user identifier\",\n            ):\n                # Sleep a random time (between 90 and 210ms) to \"prevent\" user enumeration attacks\n                sleep(0.030 * SystemRandom().randint(3, 7))\n            # Log in a similar format to Event.new(), but we don't want to create an event here\n            # as this stage is mostly used by unauthenticated users with very high rate limits\n            self.stage.logger.info(\n                \"invalid_login\",\n                identifier=uid_field,\n                client_ip=get_client_ip(self.stage.request),\n                action=\"invalid_identifier\",\n                context={\n                    \"stage\": sanitize_item(self.stage),\n                },\n            )\n            identification_failed.send(sender=self, request=self.stage.request, uid_field=uid_field)\n            # We set the pending_user even on failure so it's part of the context, even\n            # when the input is invalid\n            # This is so its part of the current flow plan, and on flow restart can be kept, and\n            # policies can be applied.\n            self.stage.executor.plan.context[PLAN_CONTEXT_PENDING_USER] = User(\n                username=uid_field,\n                email=uid_field,\n            )\n            if not current_stage.show_matched_user:\n                self.stage.executor.plan.context[PLAN_CONTEXT_PENDING_USER_IDENTIFIER] = uid_field\n            raise ValidationError(\"Failed to authenticate.\")\n        self.pre_user = pre_user\n        if not current_stage.password_stage:\n            # No password stage select, don't validate the password\n            return attrs\n\n        password = attrs.get(\"password\", None)\n        if not password:\n            self.stage.logger.warning(\"Password not set for ident+auth attempt\")\n        try:\n            with Hub.current.start_span(\n                op=\"authentik.stages.identification.authenticate\",\n                description=\"User authenticate call (combo stage)\",\n            ):\n                user = authenticate(\n                    self.stage.request,\n                    current_stage.password_stage.backends,\n                    current_stage,\n                    username=self.pre_user.username,\n                    password=password,\n                )\n            if not user:\n                raise ValidationError(\"Failed to authenticate.\")\n            self.pre_user = user\n        except PermissionDenied as exc:\n            raise ValidationError(str(exc)) from exc\n        return attrs\n\n\nclass IdentificationStageView(ChallengeStageView):\n    \"\"\"Form to identify the user\"\"\"\n\n    response_class = IdentificationChallengeResponse\n\n    def get_user(self, uid_value: str) -> Optional[User]:\n        \"\"\"Find user instance. Returns None if no user was found.\"\"\"\n        current_stage: IdentificationStage = self.executor.current_stage\n        query = Q()\n        for search_field in current_stage.user_fields:\n            model_field = {\n                \"email\": \"email\",\n                \"username\": \"username\",\n                \"upn\": \"attributes__upn\",\n            }[search_field]\n            if current_stage.case_insensitive_matching:\n                model_field += \"__iexact\"\n            else:\n                model_field += \"__exact\"\n            query |= Q(**{model_field: uid_value})\n        if not query:\n            self.logger.debug(\"Empty user query\", query=query)\n            return None\n        user = User.objects.filter(query, is_active=True).first()\n        if user:\n            self.logger.debug(\"Found user\", user=user.username, query=query)\n            return user\n        return None\n\n    def get_primary_action(self) -> str:\n        \"\"\"Get the primary action label for this stage\"\"\"\n        if self.executor.flow.designation == FlowDesignation.AUTHENTICATION:\n            return _(\"Log in\")\n        return _(\"Continue\")\n\n    def get_challenge(self) -> Challenge:\n        current_stage: IdentificationStage = self.executor.current_stage\n        challenge = IdentificationChallenge(\n            data={\n                \"type\": ChallengeTypes.NATIVE.value,\n                \"primary_action\": self.get_primary_action(),\n                \"component\": \"ak-stage-identification\",\n                \"user_fields\": current_stage.user_fields,\n                \"password_fields\": bool(current_stage.password_stage),\n                \"show_source_labels\": current_stage.show_source_labels,\n            }\n        )\n        # If the user has been redirected to us whilst trying to access an\n        # application, SESSION_KEY_APPLICATION_PRE is set in the session\n        if SESSION_KEY_APPLICATION_PRE in self.request.session:\n            challenge.initial_data[\"application_pre\"] = self.request.session.get(\n                SESSION_KEY_APPLICATION_PRE, Application()\n            ).name\n        get_qs = self.request.session.get(SESSION_KEY_GET, self.request.GET)\n        # Check for related enrollment and recovery flow, add URL to view\n        if current_stage.enrollment_flow:\n            challenge.initial_data[\"enroll_url\"] = reverse_with_qs(\n                \"authentik_core:if-flow\",\n                query=get_qs,\n                kwargs={\"flow_slug\": current_stage.enrollment_flow.slug},\n            )\n        if current_stage.recovery_flow:\n            challenge.initial_data[\"recovery_url\"] = reverse_with_qs(\n                \"authentik_core:if-flow\",\n                query=get_qs,\n                kwargs={\"flow_slug\": current_stage.recovery_flow.slug},\n            )\n        if current_stage.passwordless_flow:\n            challenge.initial_data[\"passwordless_url\"] = reverse_with_qs(\n                \"authentik_core:if-flow\",\n                query=get_qs,\n                kwargs={\"flow_slug\": current_stage.passwordless_flow.slug},\n            )\n\n        # Check all enabled source, add them if they have a UI Login button.\n        ui_sources = []\n        sources: list[Source] = (\n            current_stage.sources.filter(enabled=True).order_by(\"name\").select_subclasses()\n        )\n        for source in sources:\n            ui_login_button = source.ui_login_button(self.request)\n            if ui_login_button:\n                button = asdict(ui_login_button)\n                button[\"challenge\"] = ui_login_button.challenge.data\n                ui_sources.append(button)\n        challenge.initial_data[\"sources\"] = ui_sources\n        return challenge\n\n    def challenge_valid(self, response: IdentificationChallengeResponse) -> HttpResponse:\n        self.executor.plan.context[PLAN_CONTEXT_PENDING_USER] = response.pre_user\n        current_stage: IdentificationStage = self.executor.current_stage\n        if not current_stage.show_matched_user:\n            self.executor.plan.context[\n                PLAN_CONTEXT_PENDING_USER_IDENTIFIER\n            ] = response.validated_data.get(\"uid_field\")\n        return self.executor.stage_ok()\n", "path": "authentik/stages/identification/stage.py"}], "after_files": [{"content": "\"\"\"Identification stage logic\"\"\"\nfrom dataclasses import asdict\nfrom random import SystemRandom\nfrom time import sleep\nfrom typing import Any, Optional\n\nfrom django.core.exceptions import PermissionDenied\nfrom django.db.models import Q\nfrom django.http import HttpResponse\nfrom django.utils.translation import gettext as _\nfrom drf_spectacular.utils import PolymorphicProxySerializer, extend_schema_field\nfrom rest_framework.fields import BooleanField, CharField, DictField, ListField\nfrom rest_framework.serializers import ValidationError\nfrom sentry_sdk.hub import Hub\n\nfrom authentik.core.api.utils import PassiveSerializer\nfrom authentik.core.models import Application, Source, User\nfrom authentik.events.utils import sanitize_item\nfrom authentik.flows.challenge import (\n    Challenge,\n    ChallengeResponse,\n    ChallengeTypes,\n    RedirectChallenge,\n)\nfrom authentik.flows.models import FlowDesignation\nfrom authentik.flows.planner import PLAN_CONTEXT_PENDING_USER\nfrom authentik.flows.stage import PLAN_CONTEXT_PENDING_USER_IDENTIFIER, ChallengeStageView\nfrom authentik.flows.views.executor import SESSION_KEY_APPLICATION_PRE, SESSION_KEY_GET\nfrom authentik.lib.utils.http import get_client_ip\nfrom authentik.lib.utils.urls import reverse_with_qs\nfrom authentik.sources.oauth.types.apple import AppleLoginChallenge\nfrom authentik.sources.plex.models import PlexAuthenticationChallenge\nfrom authentik.stages.identification.models import IdentificationStage\nfrom authentik.stages.identification.signals import identification_failed\nfrom authentik.stages.password.stage import authenticate\n\n\n@extend_schema_field(\n    PolymorphicProxySerializer(\n        component_name=\"LoginChallengeTypes\",\n        serializers={\n            RedirectChallenge().fields[\"component\"].default: RedirectChallenge,\n            PlexAuthenticationChallenge().fields[\"component\"].default: PlexAuthenticationChallenge,\n            AppleLoginChallenge().fields[\"component\"].default: AppleLoginChallenge,\n        },\n        resource_type_field_name=\"component\",\n    )\n)\nclass ChallengeDictWrapper(DictField):\n    \"\"\"Wrapper around DictField that annotates itself as challenge proxy\"\"\"\n\n\nclass LoginSourceSerializer(PassiveSerializer):\n    \"\"\"Serializer for Login buttons of sources\"\"\"\n\n    name = CharField()\n    icon_url = CharField(required=False, allow_null=True)\n\n    challenge = ChallengeDictWrapper()\n\n\nclass IdentificationChallenge(Challenge):\n    \"\"\"Identification challenges with all UI elements\"\"\"\n\n    user_fields = ListField(child=CharField(), allow_empty=True, allow_null=True)\n    password_fields = BooleanField()\n    application_pre = CharField(required=False)\n\n    enroll_url = CharField(required=False)\n    recovery_url = CharField(required=False)\n    passwordless_url = CharField(required=False)\n    primary_action = CharField()\n    sources = LoginSourceSerializer(many=True, required=False)\n    show_source_labels = BooleanField()\n\n    component = CharField(default=\"ak-stage-identification\")\n\n\nclass IdentificationChallengeResponse(ChallengeResponse):\n    \"\"\"Identification challenge\"\"\"\n\n    uid_field = CharField()\n    password = CharField(required=False, allow_blank=True, allow_null=True)\n    component = CharField(default=\"ak-stage-identification\")\n\n    pre_user: Optional[User] = None\n\n    def validate(self, attrs: dict[str, Any]) -> dict[str, Any]:\n        \"\"\"Validate that user exists, and optionally their password\"\"\"\n        uid_field = attrs[\"uid_field\"]\n        current_stage: IdentificationStage = self.stage.executor.current_stage\n\n        pre_user = self.stage.get_user(uid_field)\n        if not pre_user:\n            with Hub.current.start_span(\n                op=\"authentik.stages.identification.validate_invalid_wait\",\n                description=\"Sleep random time on invalid user identifier\",\n            ):\n                # Sleep a random time (between 90 and 210ms) to \"prevent\" user enumeration attacks\n                sleep(0.030 * SystemRandom().randint(3, 7))\n            # Log in a similar format to Event.new(), but we don't want to create an event here\n            # as this stage is mostly used by unauthenticated users with very high rate limits\n            self.stage.logger.info(\n                \"invalid_login\",\n                identifier=uid_field,\n                client_ip=get_client_ip(self.stage.request),\n                action=\"invalid_identifier\",\n                context={\n                    \"stage\": sanitize_item(self.stage),\n                },\n            )\n            identification_failed.send(sender=self, request=self.stage.request, uid_field=uid_field)\n            # We set the pending_user even on failure so it's part of the context, even\n            # when the input is invalid\n            # This is so its part of the current flow plan, and on flow restart can be kept, and\n            # policies can be applied.\n            self.stage.executor.plan.context[PLAN_CONTEXT_PENDING_USER] = User(\n                username=uid_field,\n                email=uid_field,\n            )\n            if not current_stage.show_matched_user:\n                self.stage.executor.plan.context[PLAN_CONTEXT_PENDING_USER_IDENTIFIER] = uid_field\n            raise ValidationError(\"Failed to authenticate.\")\n        self.pre_user = pre_user\n        if not current_stage.password_stage:\n            # No password stage select, don't validate the password\n            return attrs\n\n        password = attrs.get(\"password\", None)\n        if not password:\n            self.stage.logger.warning(\"Password not set for ident+auth attempt\")\n        try:\n            with Hub.current.start_span(\n                op=\"authentik.stages.identification.authenticate\",\n                description=\"User authenticate call (combo stage)\",\n            ):\n                user = authenticate(\n                    self.stage.request,\n                    current_stage.password_stage.backends,\n                    current_stage,\n                    username=self.pre_user.username,\n                    password=password,\n                )\n            if not user:\n                raise ValidationError(\"Failed to authenticate.\")\n            self.pre_user = user\n        except PermissionDenied as exc:\n            raise ValidationError(str(exc)) from exc\n        return attrs\n\n\nclass IdentificationStageView(ChallengeStageView):\n    \"\"\"Form to identify the user\"\"\"\n\n    response_class = IdentificationChallengeResponse\n\n    def get_user(self, uid_value: str) -> Optional[User]:\n        \"\"\"Find user instance. Returns None if no user was found.\"\"\"\n        current_stage: IdentificationStage = self.executor.current_stage\n        query = Q()\n        for search_field in current_stage.user_fields:\n            model_field = {\n                \"email\": \"email\",\n                \"username\": \"username\",\n                \"upn\": \"attributes__upn\",\n            }[search_field]\n            if current_stage.case_insensitive_matching:\n                model_field += \"__iexact\"\n            else:\n                model_field += \"__exact\"\n            query |= Q(**{model_field: uid_value})\n        if not query:\n            self.logger.debug(\"Empty user query\", query=query)\n            return None\n        user = User.objects.filter(query).first()\n        if user:\n            self.logger.debug(\"Found user\", user=user.username, query=query)\n            return user\n        return None\n\n    def get_primary_action(self) -> str:\n        \"\"\"Get the primary action label for this stage\"\"\"\n        if self.executor.flow.designation == FlowDesignation.AUTHENTICATION:\n            return _(\"Log in\")\n        return _(\"Continue\")\n\n    def get_challenge(self) -> Challenge:\n        current_stage: IdentificationStage = self.executor.current_stage\n        challenge = IdentificationChallenge(\n            data={\n                \"type\": ChallengeTypes.NATIVE.value,\n                \"primary_action\": self.get_primary_action(),\n                \"component\": \"ak-stage-identification\",\n                \"user_fields\": current_stage.user_fields,\n                \"password_fields\": bool(current_stage.password_stage),\n                \"show_source_labels\": current_stage.show_source_labels,\n            }\n        )\n        # If the user has been redirected to us whilst trying to access an\n        # application, SESSION_KEY_APPLICATION_PRE is set in the session\n        if SESSION_KEY_APPLICATION_PRE in self.request.session:\n            challenge.initial_data[\"application_pre\"] = self.request.session.get(\n                SESSION_KEY_APPLICATION_PRE, Application()\n            ).name\n        get_qs = self.request.session.get(SESSION_KEY_GET, self.request.GET)\n        # Check for related enrollment and recovery flow, add URL to view\n        if current_stage.enrollment_flow:\n            challenge.initial_data[\"enroll_url\"] = reverse_with_qs(\n                \"authentik_core:if-flow\",\n                query=get_qs,\n                kwargs={\"flow_slug\": current_stage.enrollment_flow.slug},\n            )\n        if current_stage.recovery_flow:\n            challenge.initial_data[\"recovery_url\"] = reverse_with_qs(\n                \"authentik_core:if-flow\",\n                query=get_qs,\n                kwargs={\"flow_slug\": current_stage.recovery_flow.slug},\n            )\n        if current_stage.passwordless_flow:\n            challenge.initial_data[\"passwordless_url\"] = reverse_with_qs(\n                \"authentik_core:if-flow\",\n                query=get_qs,\n                kwargs={\"flow_slug\": current_stage.passwordless_flow.slug},\n            )\n\n        # Check all enabled source, add them if they have a UI Login button.\n        ui_sources = []\n        sources: list[Source] = (\n            current_stage.sources.filter(enabled=True).order_by(\"name\").select_subclasses()\n        )\n        for source in sources:\n            ui_login_button = source.ui_login_button(self.request)\n            if ui_login_button:\n                button = asdict(ui_login_button)\n                button[\"challenge\"] = ui_login_button.challenge.data\n                ui_sources.append(button)\n        challenge.initial_data[\"sources\"] = ui_sources\n        return challenge\n\n    def challenge_valid(self, response: IdentificationChallengeResponse) -> HttpResponse:\n        self.executor.plan.context[PLAN_CONTEXT_PENDING_USER] = response.pre_user\n        current_stage: IdentificationStage = self.executor.current_stage\n        if not current_stage.show_matched_user:\n            self.executor.plan.context[\n                PLAN_CONTEXT_PENDING_USER_IDENTIFIER\n            ] = response.validated_data.get(\"uid_field\")\n        return self.executor.stage_ok()\n", "path": "authentik/stages/identification/stage.py"}]}