Repository: goauthentik/authentik

Issue:
Backchannel LDAP providers missing from outpost configuration page
**Description**
When creating an LDAP Provider, it will appear in the Outpost create/configuration page if it is assigned an application, but not if it's assigned as an application backchannel.

This means we're unable to associate an LDAP backchannel to an outpost.

**To Reproduce**
Steps to reproduce the behavior:

1. Create a demo Application and an LDAP Provider
2. Assign the LDAP Provider as a _backchannel provider_ to the application.
3. Attempt to create an LDAP outpost. Notice the LDAP provider does not appear on the selectable list.

**Expected behavior**
The backchannel should appear the outposts "applications" list, so that it may be added to the outpost.

**Version and Deployment (please complete the following information):**
-   authentik version: 2023.5.2
-   Deployment: docker-compose



Discussion:

Resolving patch:
diff --git a/authentik/core/api/providers.py b/authentik/core/api/providers.py
--- a/authentik/core/api/providers.py
+++ b/authentik/core/api/providers.py
@@ -1,4 +1,6 @@
 """Provider API Views"""
+from django.db.models import QuerySet
+from django.db.models.query import Q
 from django.utils.translation import gettext_lazy as _
 from django_filters.filters import BooleanFilter
 from django_filters.filterset import FilterSet
@@ -56,17 +58,22 @@ class Meta:
 
 
 class ProviderFilter(FilterSet):
-    """Filter for groups"""
+    """Filter for providers"""
 
-    application__isnull = BooleanFilter(
-        field_name="application",
-        lookup_expr="isnull",
-    )
+    application__isnull = BooleanFilter(method="filter_application__isnull")
     backchannel_only = BooleanFilter(
         method="filter_backchannel_only",
     )
 
-    def filter_backchannel_only(self, queryset, name, value):
+    def filter_application__isnull(self, queryset: QuerySet, name, value):
+        """Only return providers that are neither assigned to application,
+        both as provider or application provider"""
+        return queryset.filter(
+            Q(backchannel_application__isnull=value, is_backchannel=True)
+            | Q(application__isnull=value)
+        )
+
+    def filter_backchannel_only(self, queryset: QuerySet, name, value):
         """Only return backchannel providers"""
         return queryset.filter(is_backchannel=value)
 
diff --git a/authentik/providers/ldap/api.py b/authentik/providers/ldap/api.py
--- a/authentik/providers/ldap/api.py
+++ b/authentik/providers/ldap/api.py
@@ -1,4 +1,8 @@
 """LDAPProvider API Views"""
+from django.db.models import QuerySet
+from django.db.models.query import Q
+from django_filters.filters import BooleanFilter
+from django_filters.filterset import FilterSet
 from rest_framework.fields import CharField, ListField, SerializerMethodField
 from rest_framework.serializers import ModelSerializer
 from rest_framework.viewsets import ModelViewSet, ReadOnlyModelViewSet
@@ -29,24 +33,41 @@ class Meta:
         extra_kwargs = ProviderSerializer.Meta.extra_kwargs
 
 
+class LDAPProviderFilter(FilterSet):
+    """LDAP Provider filters"""
+
+    application__isnull = BooleanFilter(method="filter_application__isnull")
+
+    def filter_application__isnull(self, queryset: QuerySet, name, value):
+        """Only return providers that are neither assigned to application,
+        both as provider or application provider"""
+        return queryset.filter(
+            Q(backchannel_application__isnull=value) | Q(application__isnull=value)
+        )
+
+    class Meta:
+        model = LDAPProvider
+        fields = {
+            "application": ["isnull"],
+            "name": ["iexact"],
+            "authorization_flow__slug": ["iexact"],
+            "base_dn": ["iexact"],
+            "search_group__group_uuid": ["iexact"],
+            "search_group__name": ["iexact"],
+            "certificate__kp_uuid": ["iexact"],
+            "certificate__name": ["iexact"],
+            "tls_server_name": ["iexact"],
+            "uid_start_number": ["iexact"],
+            "gid_start_number": ["iexact"],
+        }
+
+
 class LDAPProviderViewSet(UsedByMixin, ModelViewSet):
     """LDAPProvider Viewset"""
 
     queryset = LDAPProvider.objects.all()
     serializer_class = LDAPProviderSerializer
-    filterset_fields = {
-        "application": ["isnull"],
-        "name": ["iexact"],
-        "authorization_flow__slug": ["iexact"],
-        "base_dn": ["iexact"],
-        "search_group__group_uuid": ["iexact"],
-        "search_group__name": ["iexact"],
-        "certificate__kp_uuid": ["iexact"],
-        "certificate__name": ["iexact"],
-        "tls_server_name": ["iexact"],
-        "uid_start_number": ["iexact"],
-        "gid_start_number": ["iexact"],
-    }
+    filterset_class = LDAPProviderFilter
     search_fields = ["name"]
     ordering = ["name"]
 


Test patch: