prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  ldap_sync_single ignores "ldap.task_timeout_hours" settings
  **Describe the bug**
  The "ldap_sync_single" task is ignoring the "ldap.task_timeout_hours" setting as set with the `AUTHENTIK_LDAP__TASK_TIMEOUT_HOURS` environment variable.
  
  **To Reproduce**
  Steps to reproduce the behavior:
  
  1. configure AUTHENTIK_LDAP__TASK_TIMEOUT_HOURS to be too short to synchronize a target ldap source
  2. configure an LDAP source
  3. on the LDAP source details page, click on "Run sync now"
  4. wait 10 minutes
  
  **Expected behavior**
  The task is given the specified amount of time and not cancelled after 10 minutes.
  
  **Screenshots**
  ![Flower](https://github.com/goauthentik/authentik/assets/986924/02ae8800-3258-4f87-b0c9-cf6993d6b1a0)
  
  **Logs**
  Output of docker-compose logs or kubectl logs respectively
  
  **Version and Deployment (please complete the following information):**
  
  -   authentik version: [e.g. [2023.8.2](https://goauthentik.io/docs/releases/2023.8)]
  -   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/sources/ldap/tasks.py`
  Content:
  ```
  1 """LDAP Sync tasks"""
  2 from uuid import uuid4
  3 
  4 from celery import chain, group
  5 from django.core.cache import cache
  6 from ldap3.core.exceptions import LDAPException
  7 from structlog.stdlib import get_logger
  8 
  9 from authentik.events.monitored_tasks import MonitoredTask, TaskResult, TaskResultStatus
  10 from authentik.lib.config import CONFIG
  11 from authentik.lib.utils.errors import exception_to_string
  12 from authentik.lib.utils.reflection import class_to_path, path_to_class
  13 from authentik.root.celery import CELERY_APP
  14 from authentik.sources.ldap.models import LDAPSource
  15 from authentik.sources.ldap.sync.base import BaseLDAPSynchronizer
  16 from authentik.sources.ldap.sync.groups import GroupLDAPSynchronizer
  17 from authentik.sources.ldap.sync.membership import MembershipLDAPSynchronizer
  18 from authentik.sources.ldap.sync.users import UserLDAPSynchronizer
  19 
  20 LOGGER = get_logger()
  21 SYNC_CLASSES = [
  22     UserLDAPSynchronizer,
  23     GroupLDAPSynchronizer,
  24     MembershipLDAPSynchronizer,
  25 ]
  26 CACHE_KEY_PREFIX = "goauthentik.io/sources/ldap/page/"
  27 
  28 
  29 @CELERY_APP.task()
  30 def ldap_sync_all():
  31     """Sync all sources"""
  32     for source in LDAPSource.objects.filter(enabled=True):
  33         ldap_sync_single(source.pk)
  34 
  35 
  36 @CELERY_APP.task()
  37 def ldap_sync_single(source_pk: str):
  38     """Sync a single source"""
  39     source: LDAPSource = LDAPSource.objects.filter(pk=source_pk).first()
  40     if not source:
  41         return
  42     task = chain(
  43         # User and group sync can happen at once, they have no dependencies on each other
  44         group(
  45             ldap_sync_paginator(source, UserLDAPSynchronizer)
  46             + ldap_sync_paginator(source, GroupLDAPSynchronizer),
  47         ),
  48         # Membership sync needs to run afterwards
  49         group(
  50             ldap_sync_paginator(source, MembershipLDAPSynchronizer),
  51         ),
  52     )
  53     task()
  54 
  55 
  56 def ldap_sync_paginator(source: LDAPSource, sync: type[BaseLDAPSynchronizer]) -> list:
  57     """Return a list of task signatures with LDAP pagination data"""
  58     sync_inst: BaseLDAPSynchronizer = sync(source)
  59     signatures = []
  60     for page in sync_inst.get_objects():
  61         page_cache_key = CACHE_KEY_PREFIX + str(uuid4())
  62         cache.set(page_cache_key, page, 60 * 60 * CONFIG.get_int("ldap.task_timeout_hours"))
  63         page_sync = ldap_sync.si(source.pk, class_to_path(sync), page_cache_key)
  64         signatures.append(page_sync)
  65     return signatures
  66 
  67 
  68 @CELERY_APP.task(
  69     bind=True,
  70     base=MonitoredTask,
  71     soft_time_limit=60 * 60 * CONFIG.get_int("ldap.task_timeout_hours"),
  72     task_time_limit=60 * 60 * CONFIG.get_int("ldap.task_timeout_hours"),
  73 )
  74 def ldap_sync(self: MonitoredTask, source_pk: str, sync_class: str, page_cache_key: str):
  75     """Synchronization of an LDAP Source"""
  76     self.result_timeout_hours = CONFIG.get_int("ldap.task_timeout_hours")
  77     source: LDAPSource = LDAPSource.objects.filter(pk=source_pk).first()
  78     if not source:
  79         # Because the source couldn't be found, we don't have a UID
  80         # to set the state with
  81         return
  82     sync: type[BaseLDAPSynchronizer] = path_to_class(sync_class)
  83     uid = page_cache_key.replace(CACHE_KEY_PREFIX, "")
  84     self.set_uid(f"{source.slug}:{sync.name()}:{uid}")
  85     try:
  86         sync_inst: BaseLDAPSynchronizer = sync(source)
  87         page = cache.get(page_cache_key)
  88         if not page:
  89             error_message = (
  90                 f"Could not find page in cache: {page_cache_key}. "
  91                 + "Try increasing ldap.task_timeout_hours"
  92             )
  93             LOGGER.warning(error_message)
  94             self.set_status(TaskResult(TaskResultStatus.ERROR, [error_message]))
  95             return
  96         cache.touch(page_cache_key)
  97         count = sync_inst.sync(page)
  98         messages = sync_inst.messages
  99         messages.append(f"Synced {count} objects.")
  100         self.set_status(
  101             TaskResult(
  102                 TaskResultStatus.SUCCESSFUL,
  103                 messages,
  104             )
  105         )
  106         cache.delete(page_cache_key)
  107     except LDAPException as exc:
  108         # No explicit event is created here as .set_status with an error will do that
  109         LOGGER.warning(exception_to_string(exc))
  110         self.set_status(TaskResult(TaskResultStatus.ERROR).with_error(exc))
  111
  ```
  --- 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()
  
  ```
  
golden_diff: diff --git a/authentik/sources/ldap/tasks.py b/authentik/sources/ldap/tasks.py
  --- a/authentik/sources/ldap/tasks.py
  +++ b/authentik/sources/ldap/tasks.py
  @@ -33,7 +33,13 @@
           ldap_sync_single(source.pk)
   
   
  -@CELERY_APP.task()
  +@CELERY_APP.task(
  +    # We take the configured hours timeout time by 2.5 as we run user and
  +    # group in parallel and then membership, so 2x is to cover the serial tasks,
  +    # and 0.5x on top of that to give some more leeway
  +    soft_time_limit=(60 * 60 * CONFIG.get_int("ldap.task_timeout_hours")) * 2.5,
  +    task_time_limit=(60 * 60 * CONFIG.get_int("ldap.task_timeout_hours")) * 2.5,
  +)
   def ldap_sync_single(source_pk: str):
       """Sync a single source"""
       source: LDAPSource = LDAPSource.objects.filter(pk=source_pk).first()
  
verification_info: {"golden_diff": "diff --git a/authentik/sources/ldap/tasks.py b/authentik/sources/ldap/tasks.py\n--- a/authentik/sources/ldap/tasks.py\n+++ b/authentik/sources/ldap/tasks.py\n@@ -33,7 +33,13 @@\n         ldap_sync_single(source.pk)\n \n \n-@CELERY_APP.task()\n+@CELERY_APP.task(\n+    # We take the configured hours timeout time by 2.5 as we run user and\n+    # group in parallel and then membership, so 2x is to cover the serial tasks,\n+    # and 0.5x on top of that to give some more leeway\n+    soft_time_limit=(60 * 60 * CONFIG.get_int(\"ldap.task_timeout_hours\")) * 2.5,\n+    task_time_limit=(60 * 60 * CONFIG.get_int(\"ldap.task_timeout_hours\")) * 2.5,\n+)\n def ldap_sync_single(source_pk: str):\n     \"\"\"Sync a single source\"\"\"\n     source: LDAPSource = LDAPSource.objects.filter(pk=source_pk).first()\n", "issue": "ldap_sync_single ignores \"ldap.task_timeout_hours\" settings\n**Describe the bug**\r\nThe \"ldap_sync_single\" task is ignoring the \"ldap.task_timeout_hours\" setting as set with the `AUTHENTIK_LDAP__TASK_TIMEOUT_HOURS` environment variable.\r\n\r\n**To Reproduce**\r\nSteps to reproduce the behavior:\r\n\r\n1. configure AUTHENTIK_LDAP__TASK_TIMEOUT_HOURS to be too short to synchronize a target ldap source\r\n2. configure an LDAP source\r\n3. on the LDAP source details page, click on \"Run sync now\"\r\n4. wait 10 minutes\r\n\r\n**Expected behavior**\r\nThe task is given the specified amount of time and not cancelled after 10 minutes.\r\n\r\n**Screenshots**\r\n![Flower](https://github.com/goauthentik/authentik/assets/986924/02ae8800-3258-4f87-b0c9-cf6993d6b1a0)\r\n\r\n**Logs**\r\nOutput of docker-compose logs or kubectl logs respectively\r\n\r\n**Version and Deployment (please complete the following information):**\r\n\r\n-   authentik version: [e.g. [2023.8.2](https://goauthentik.io/docs/releases/2023.8)]\r\n-   Deployment: docker-compose\r\n\r\n\r\n\n", "before_files": [{"content": "\"\"\"LDAP Sync tasks\"\"\"\nfrom uuid import uuid4\n\nfrom celery import chain, group\nfrom django.core.cache import cache\nfrom ldap3.core.exceptions import LDAPException\nfrom structlog.stdlib import get_logger\n\nfrom authentik.events.monitored_tasks import MonitoredTask, TaskResult, TaskResultStatus\nfrom authentik.lib.config import CONFIG\nfrom authentik.lib.utils.errors import exception_to_string\nfrom authentik.lib.utils.reflection import class_to_path, path_to_class\nfrom authentik.root.celery import CELERY_APP\nfrom authentik.sources.ldap.models import LDAPSource\nfrom authentik.sources.ldap.sync.base import BaseLDAPSynchronizer\nfrom authentik.sources.ldap.sync.groups import GroupLDAPSynchronizer\nfrom authentik.sources.ldap.sync.membership import MembershipLDAPSynchronizer\nfrom authentik.sources.ldap.sync.users import UserLDAPSynchronizer\n\nLOGGER = get_logger()\nSYNC_CLASSES = [\n    UserLDAPSynchronizer,\n    GroupLDAPSynchronizer,\n    MembershipLDAPSynchronizer,\n]\nCACHE_KEY_PREFIX = \"goauthentik.io/sources/ldap/page/\"\n\n\n@CELERY_APP.task()\ndef ldap_sync_all():\n    \"\"\"Sync all sources\"\"\"\n    for source in LDAPSource.objects.filter(enabled=True):\n        ldap_sync_single(source.pk)\n\n\n@CELERY_APP.task()\ndef ldap_sync_single(source_pk: str):\n    \"\"\"Sync a single source\"\"\"\n    source: LDAPSource = LDAPSource.objects.filter(pk=source_pk).first()\n    if not source:\n        return\n    task = chain(\n        # User and group sync can happen at once, they have no dependencies on each other\n        group(\n            ldap_sync_paginator(source, UserLDAPSynchronizer)\n            + ldap_sync_paginator(source, GroupLDAPSynchronizer),\n        ),\n        # Membership sync needs to run afterwards\n        group(\n            ldap_sync_paginator(source, MembershipLDAPSynchronizer),\n        ),\n    )\n    task()\n\n\ndef ldap_sync_paginator(source: LDAPSource, sync: type[BaseLDAPSynchronizer]) -> list:\n    \"\"\"Return a list of task signatures with LDAP pagination data\"\"\"\n    sync_inst: BaseLDAPSynchronizer = sync(source)\n    signatures = []\n    for page in sync_inst.get_objects():\n        page_cache_key = CACHE_KEY_PREFIX + str(uuid4())\n        cache.set(page_cache_key, page, 60 * 60 * CONFIG.get_int(\"ldap.task_timeout_hours\"))\n        page_sync = ldap_sync.si(source.pk, class_to_path(sync), page_cache_key)\n        signatures.append(page_sync)\n    return signatures\n\n\n@CELERY_APP.task(\n    bind=True,\n    base=MonitoredTask,\n    soft_time_limit=60 * 60 * CONFIG.get_int(\"ldap.task_timeout_hours\"),\n    task_time_limit=60 * 60 * CONFIG.get_int(\"ldap.task_timeout_hours\"),\n)\ndef ldap_sync(self: MonitoredTask, source_pk: str, sync_class: str, page_cache_key: str):\n    \"\"\"Synchronization of an LDAP Source\"\"\"\n    self.result_timeout_hours = CONFIG.get_int(\"ldap.task_timeout_hours\")\n    source: LDAPSource = LDAPSource.objects.filter(pk=source_pk).first()\n    if not source:\n        # Because the source couldn't be found, we don't have a UID\n        # to set the state with\n        return\n    sync: type[BaseLDAPSynchronizer] = path_to_class(sync_class)\n    uid = page_cache_key.replace(CACHE_KEY_PREFIX, \"\")\n    self.set_uid(f\"{source.slug}:{sync.name()}:{uid}\")\n    try:\n        sync_inst: BaseLDAPSynchronizer = sync(source)\n        page = cache.get(page_cache_key)\n        if not page:\n            error_message = (\n                f\"Could not find page in cache: {page_cache_key}. \"\n                + \"Try increasing ldap.task_timeout_hours\"\n            )\n            LOGGER.warning(error_message)\n            self.set_status(TaskResult(TaskResultStatus.ERROR, [error_message]))\n            return\n        cache.touch(page_cache_key)\n        count = sync_inst.sync(page)\n        messages = sync_inst.messages\n        messages.append(f\"Synced {count} objects.\")\n        self.set_status(\n            TaskResult(\n                TaskResultStatus.SUCCESSFUL,\n                messages,\n            )\n        )\n        cache.delete(page_cache_key)\n    except LDAPException as exc:\n        # No explicit event is created here as .set_status with an error will do that\n        LOGGER.warning(exception_to_string(exc))\n        self.set_status(TaskResult(TaskResultStatus.ERROR).with_error(exc))\n", "path": "authentik/sources/ldap/tasks.py"}], "after_files": [{"content": "\"\"\"LDAP Sync tasks\"\"\"\nfrom uuid import uuid4\n\nfrom celery import chain, group\nfrom django.core.cache import cache\nfrom ldap3.core.exceptions import LDAPException\nfrom structlog.stdlib import get_logger\n\nfrom authentik.events.monitored_tasks import MonitoredTask, TaskResult, TaskResultStatus\nfrom authentik.lib.config import CONFIG\nfrom authentik.lib.utils.errors import exception_to_string\nfrom authentik.lib.utils.reflection import class_to_path, path_to_class\nfrom authentik.root.celery import CELERY_APP\nfrom authentik.sources.ldap.models import LDAPSource\nfrom authentik.sources.ldap.sync.base import BaseLDAPSynchronizer\nfrom authentik.sources.ldap.sync.groups import GroupLDAPSynchronizer\nfrom authentik.sources.ldap.sync.membership import MembershipLDAPSynchronizer\nfrom authentik.sources.ldap.sync.users import UserLDAPSynchronizer\n\nLOGGER = get_logger()\nSYNC_CLASSES = [\n    UserLDAPSynchronizer,\n    GroupLDAPSynchronizer,\n    MembershipLDAPSynchronizer,\n]\nCACHE_KEY_PREFIX = \"goauthentik.io/sources/ldap/page/\"\n\n\n@CELERY_APP.task()\ndef ldap_sync_all():\n    \"\"\"Sync all sources\"\"\"\n    for source in LDAPSource.objects.filter(enabled=True):\n        ldap_sync_single(source.pk)\n\n\n@CELERY_APP.task(\n    # We take the configured hours timeout time by 2.5 as we run user and\n    # group in parallel and then membership, so 2x is to cover the serial tasks,\n    # and 0.5x on top of that to give some more leeway\n    soft_time_limit=(60 * 60 * CONFIG.get_int(\"ldap.task_timeout_hours\")) * 2.5,\n    task_time_limit=(60 * 60 * CONFIG.get_int(\"ldap.task_timeout_hours\")) * 2.5,\n)\ndef ldap_sync_single(source_pk: str):\n    \"\"\"Sync a single source\"\"\"\n    source: LDAPSource = LDAPSource.objects.filter(pk=source_pk).first()\n    if not source:\n        return\n    task = chain(\n        # User and group sync can happen at once, they have no dependencies on each other\n        group(\n            ldap_sync_paginator(source, UserLDAPSynchronizer)\n            + ldap_sync_paginator(source, GroupLDAPSynchronizer),\n        ),\n        # Membership sync needs to run afterwards\n        group(\n            ldap_sync_paginator(source, MembershipLDAPSynchronizer),\n        ),\n    )\n    task()\n\n\ndef ldap_sync_paginator(source: LDAPSource, sync: type[BaseLDAPSynchronizer]) -> list:\n    \"\"\"Return a list of task signatures with LDAP pagination data\"\"\"\n    sync_inst: BaseLDAPSynchronizer = sync(source)\n    signatures = []\n    for page in sync_inst.get_objects():\n        page_cache_key = CACHE_KEY_PREFIX + str(uuid4())\n        cache.set(page_cache_key, page, 60 * 60 * CONFIG.get_int(\"ldap.task_timeout_hours\"))\n        page_sync = ldap_sync.si(source.pk, class_to_path(sync), page_cache_key)\n        signatures.append(page_sync)\n    return signatures\n\n\n@CELERY_APP.task(\n    bind=True,\n    base=MonitoredTask,\n    soft_time_limit=60 * 60 * CONFIG.get_int(\"ldap.task_timeout_hours\"),\n    task_time_limit=60 * 60 * CONFIG.get_int(\"ldap.task_timeout_hours\"),\n)\ndef ldap_sync(self: MonitoredTask, source_pk: str, sync_class: str, page_cache_key: str):\n    \"\"\"Synchronization of an LDAP Source\"\"\"\n    self.result_timeout_hours = CONFIG.get_int(\"ldap.task_timeout_hours\")\n    source: LDAPSource = LDAPSource.objects.filter(pk=source_pk).first()\n    if not source:\n        # Because the source couldn't be found, we don't have a UID\n        # to set the state with\n        return\n    sync: type[BaseLDAPSynchronizer] = path_to_class(sync_class)\n    uid = page_cache_key.replace(CACHE_KEY_PREFIX, \"\")\n    self.set_uid(f\"{source.slug}:{sync.name()}:{uid}\")\n    try:\n        sync_inst: BaseLDAPSynchronizer = sync(source)\n        page = cache.get(page_cache_key)\n        if not page:\n            error_message = (\n                f\"Could not find page in cache: {page_cache_key}. \"\n                + \"Try increasing ldap.task_timeout_hours\"\n            )\n            LOGGER.warning(error_message)\n            self.set_status(TaskResult(TaskResultStatus.ERROR, [error_message]))\n            return\n        cache.touch(page_cache_key)\n        count = sync_inst.sync(page)\n        messages = sync_inst.messages\n        messages.append(f\"Synced {count} objects.\")\n        self.set_status(\n            TaskResult(\n                TaskResultStatus.SUCCESSFUL,\n                messages,\n            )\n        )\n        cache.delete(page_cache_key)\n    except LDAPException as exc:\n        # No explicit event is created here as .set_status with an error will do that\n        LOGGER.warning(exception_to_string(exc))\n        self.set_status(TaskResult(TaskResultStatus.ERROR).with_error(exc))\n", "path": "authentik/sources/ldap/tasks.py"}]}