Repository: goauthentik/authentik

Issue:
Using `ak_create_event` in an `event policy`
**Describe the bug**
A clear and concise description of what the bug is.

**To Reproduce**
Steps to reproduce the behavior:
1. Create an `event policy` with `ak_create_event`
ex:
```python
ak_create_event("return_true_event")
return True
```
2. Trigger policy
3. Error

**Expected behavior**
An event should be created in the log without triggering a `General system exception` or `Policy exception`

**Logs**
<details>
    <summary>Stacktrace from authentik</summary>

```
Traceback (most recent call last):
  File "/ak-root/venv/lib/python3.11/site-packages/asgiref/sync.py", line 534, in thread_handler
    raise exc_info[1]
  File "/ak-root/venv/lib/python3.11/site-packages/django/core/handlers/base.py", line 284, in _get_response_async
    response = await sync_to_async(
               ^^^^^^^^^^^^^^^^^^^^
  File "/ak-root/venv/lib/python3.11/site-packages/asgiref/sync.py", line 479, in __call__
    ret: _R = await loop.run_in_executor(
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/ak-root/venv/lib/python3.11/site-packages/asgiref/current_thread_executor.py", line 40, in run
    result = self.fn(*self.args, **self.kwargs)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/ak-root/venv/lib/python3.11/site-packages/asgiref/sync.py", line 538, in thread_handler
    return func(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^
  File "/ak-root/venv/lib/python3.11/site-packages/django/template/response.py", line 114, in render
    self.content = self.rendered_content
                   ^^^^^^^^^^^^^^^^^^^^^
  File "/ak-root/venv/lib/python3.11/site-packages/rest_framework/response.py", line 70, in rendered_content
    ret = renderer.render(self.data, accepted_media_type, context)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/ak-root/venv/lib/python3.11/site-packages/rest_framework/renderers.py", line 99, in render
    ret = json.dumps(
          ^^^^^^^^^^^
  File "/ak-root/venv/lib/python3.11/site-packages/rest_framework/utils/json.py", line 25, in dumps
    return json.dumps(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/json/__init__.py", line 238, in dumps
    **kw).encode(obj)
          ^^^^^^^^^^^
  File "/usr/local/lib/python3.11/json/encoder.py", line 200, in encode
    chunks = self.iterencode(o, _one_shot=True)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/json/encoder.py", line 258, in iterencode
    return _iterencode(o, 0)
           ^^^^^^^^^^^^^^^^^
  File "/ak-root/venv/lib/python3.11/site-packages/rest_framework/utils/encoders.py", line 67, in default
    return super().default(obj)
           ^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/json/encoder.py", line 180, in default
    raise TypeError(f'Object of type {o.__class__.__name__} '
builtins.TypeError: Object of type IPv4Address is not JSON serializable
```
</details>


**Version and Deployment (please complete the following information):**
- authentik version: 2023.10.4
- Deployment: docker-compose
        


Discussion:

Resolving patch:
diff --git a/authentik/events/utils.py b/authentik/events/utils.py
--- a/authentik/events/utils.py
+++ b/authentik/events/utils.py
@@ -5,12 +5,13 @@
 from datetime import date, datetime, time, timedelta
 from enum import Enum
 from pathlib import Path
-from types import GeneratorType
+from types import GeneratorType, NoneType
 from typing import Any, Optional
 from uuid import UUID
 
 from django.contrib.auth.models import AnonymousUser
 from django.core.handlers.wsgi import WSGIRequest
+from django.core.serializers.json import DjangoJSONEncoder
 from django.db import models
 from django.db.models.base import Model
 from django.http.request import HttpRequest
@@ -159,7 +160,13 @@ def sanitize_item(value: Any) -> Any:
             "name": value.__name__,
             "module": value.__module__,
         }
-    return value
+    # List taken from the stdlib's JSON encoder (_make_iterencode, encoder.py:415)
+    if isinstance(value, (bool, int, float, NoneType, list, tuple, dict)):
+        return value
+    try:
+        return DjangoJSONEncoder.default(value)
+    finally:
+        return str(value)
 
 
 def sanitize_dict(source: dict[Any, Any]) -> dict[Any, Any]:
diff --git a/authentik/stages/authenticator_sms/stage.py b/authentik/stages/authenticator_sms/stage.py
--- a/authentik/stages/authenticator_sms/stage.py
+++ b/authentik/stages/authenticator_sms/stage.py
@@ -69,7 +69,6 @@ def validate_and_send(self, phone_number: str):
         stage: AuthenticatorSMSStage = self.executor.current_stage
         hashed_number = hash_phone_number(phone_number)
         query = Q(phone_number=hashed_number) | Q(phone_number=phone_number)
-        print(SMSDevice.objects.filter(query, stage=stage.pk))
         if SMSDevice.objects.filter(query, stage=stage.pk).exists():
             raise ValidationError(_("Invalid phone number"))
         # No code yet, but we have a phone number, so send a verification message
diff --git a/authentik/stages/email/stage.py b/authentik/stages/email/stage.py
--- a/authentik/stages/email/stage.py
+++ b/authentik/stages/email/stage.py
@@ -59,7 +59,6 @@ def get_full_url(self, **kwargs) -> str:
         query_params = QueryDict(self.request.GET.get(QS_QUERY), mutable=True)
         query_params.pop(QS_KEY_TOKEN, None)
         query_params.update(kwargs)
-        print(query_params)
         full_url = base_url
         if len(query_params) > 0:
             full_url = f"{full_url}?{query_params.urlencode()}"


Test patch:
diff --git a/authentik/stages/authenticator_sms/tests.py b/authentik/stages/authenticator_sms/tests.py
--- a/authentik/stages/authenticator_sms/tests.py
+++ b/authentik/stages/authenticator_sms/tests.py
@@ -199,11 +199,9 @@ def test_stage_context_data_duplicate(self):
                 sms_send_mock,
             ),
         ):
-            print(self.client.session[SESSION_KEY_PLAN])
             response = self.client.get(
                 reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}),
             )
-        print(response.content.decode())
         self.assertStageResponse(
             response,
             self.flow,