Repository: goauthentik/authentik

Issue:
Using 'Have-I-been-pwned' policy breaks flows in Authentik 2022.4.1
**Describe the bug**
Using a 'Have-I-been-pwned' policy on a password prompt within a flow breaks the flow.

**To Reproduce**
Steps to reproduce the behavior:
1. Use Authentik 2022.3.3
2. Use all the default settings/flows, so a clean install
3. Add a have-i-been-pwned policy to the default-password-change flow on the default-password-change-prompt stage.
4. This stage binding has the following settings:
- _Evaluate on plan: True_
- _Re-evaluate policies: False_
- _Invalid respones action: RETRY returns the error message and a similar challenge to the executor._
- _Policy engine mode: ALL, all policies must match to include this stage access._
5. Go to the Flow Overview and Execute flow with current user, see that the have-i-been pwned policy works correctly.
6. Use Authentik 2022.4.1
7. Repeat steps 2 - 5 described above
8. See that you will receive an error message 'Password not set in context'.

**Expected behavior**
The password should be checked, and the flow should not crash with the error 'Password not set in context'.

**Version and Deployment (please complete the following information):**
 - authentik version: 2022.4.1
 - Deployment: tested both Docker & K8S

**Additional context**
I repeated these steps multiple times and I keep getting the same issue. Therefore I think it is safe to assume that this is a bug introduced in the update from version 2022.3.3 to version 2022.4.1



Discussion:

Resolving patch:
diff --git a/authentik/policies/hibp/models.py b/authentik/policies/hibp/models.py
--- a/authentik/policies/hibp/models.py
+++ b/authentik/policies/hibp/models.py
@@ -9,6 +9,7 @@
 from authentik.lib.utils.http import get_http_session
 from authentik.policies.models import Policy, PolicyResult
 from authentik.policies.types import PolicyRequest
+from authentik.stages.prompt.stage import PLAN_CONTEXT_PROMPT
 
 LOGGER = get_logger()
 
@@ -38,14 +39,17 @@ def passes(self, request: PolicyRequest) -> PolicyResult:
         """Check if password is in HIBP DB. Hashes given Password with SHA1, uses the first 5
         characters of Password in request and checks if full hash is in response. Returns 0
         if Password is not in result otherwise the count of how many times it was used."""
-        if self.password_field not in request.context:
+        password = request.context.get(PLAN_CONTEXT_PROMPT, {}).get(
+            self.password_field, request.context.get(self.password_field)
+        )
+        if not password:
             LOGGER.warning(
                 "Password field not set in Policy Request",
                 field=self.password_field,
                 fields=request.context.keys(),
             )
             return PolicyResult(False, _("Password not set in context"))
-        password = str(request.context[self.password_field])
+        password = str(password)
 
         pw_hash = sha1(password.encode("utf-8")).hexdigest()  # nosec
         url = f"https://api.pwnedpasswords.com/range/{pw_hash[:5]}"


Test patch:
diff --git a/authentik/policies/hibp/tests.py b/authentik/policies/hibp/tests.py
--- a/authentik/policies/hibp/tests.py
+++ b/authentik/policies/hibp/tests.py
@@ -5,6 +5,7 @@
 from authentik.lib.generators import generate_key
 from authentik.policies.hibp.models import HaveIBeenPwendPolicy
 from authentik.policies.types import PolicyRequest, PolicyResult
+from authentik.stages.prompt.stage import PLAN_CONTEXT_PROMPT
 
 
 class TestHIBPPolicy(TestCase):
@@ -26,7 +27,7 @@ def test_false(self):
             name="test_false",
         )
         request = PolicyRequest(get_anonymous_user())
-        request.context["password"] = "password"  # nosec
+        request.context[PLAN_CONTEXT_PROMPT] = {"password": "password"}  # nosec
         result: PolicyResult = policy.passes(request)
         self.assertFalse(result.passing)
         self.assertTrue(result.messages[0].startswith("Password exists on "))
@@ -37,7 +38,7 @@ def test_true(self):
             name="test_true",
         )
         request = PolicyRequest(get_anonymous_user())
-        request.context["password"] = generate_key()
+        request.context[PLAN_CONTEXT_PROMPT] = {"password": generate_key()}
         result: PolicyResult = policy.passes(request)
         self.assertTrue(result.passing)
         self.assertEqual(result.messages, tuple())