We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
OIDC Well-known URL won't configure endpoint URLs
**Describe the bug**
When entering the OIDC Well-known URL for an OpenID OAuth source, it won't automatically configure the endpoint URLs. Instead it prompts a "Bad Request" error and says "This field may not be blank." for every endpoint.

**To Reproduce**
Steps to reproduce the behavior:

1. Go to 'Directory > Federation and Social login'
2. Click on 'Create > OpenID OAuth Source > Next'
3. Enter your credentials (Name, Slug, Key, Secret and the Well-known URL)
4. Click on 'Finish'
5. See error

**Expected behavior**
Uppon clicking on 'Finish' authentik should configure the missing endpoints using the Well-known URL.

**Screenshots**

![grafik](https://github.com/goauthentik/authentik/assets/145431101/b7056922-b35f-44fb-a926-4e2ebf8b9041)

**Version and Deployment:**

-   authentik version: 2024.2.2 
-   Deployment: docker-compose

**Additional context**
I thought, it might have something to do with connectivity and proxy, since we are running authentik behind a corporate proxy. But I can curl the Well-known URL from inside the containers.


--- 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/oauth/api/source.py`
Content:
```
1 """OAuth Source Serializer"""
2 
3 from django.urls.base import reverse_lazy
4 from django_filters.filters import BooleanFilter
5 from django_filters.filterset import FilterSet
6 from drf_spectacular.types import OpenApiTypes
7 from drf_spectacular.utils import OpenApiParameter, extend_schema, extend_schema_field
8 from requests import RequestException
9 from rest_framework.decorators import action
10 from rest_framework.fields import BooleanField, CharField, ChoiceField, SerializerMethodField
11 from rest_framework.request import Request
12 from rest_framework.response import Response
13 from rest_framework.serializers import ValidationError
14 from rest_framework.viewsets import ModelViewSet
15 
16 from authentik.core.api.sources import SourceSerializer
17 from authentik.core.api.used_by import UsedByMixin
18 from authentik.core.api.utils import PassiveSerializer
19 from authentik.lib.utils.http import get_http_session
20 from authentik.sources.oauth.models import OAuthSource
21 from authentik.sources.oauth.types.registry import SourceType, registry
22 
23 
24 class SourceTypeSerializer(PassiveSerializer):
25     """Serializer for SourceType"""
26 
27     name = CharField(required=True)
28     verbose_name = CharField(required=True)
29     urls_customizable = BooleanField()
30     request_token_url = CharField(read_only=True, allow_null=True)
31     authorization_url = CharField(read_only=True, allow_null=True)
32     access_token_url = CharField(read_only=True, allow_null=True)
33     profile_url = CharField(read_only=True, allow_null=True)
34     oidc_well_known_url = CharField(read_only=True, allow_null=True)
35     oidc_jwks_url = CharField(read_only=True, allow_null=True)
36 
37 
38 class OAuthSourceSerializer(SourceSerializer):
39     """OAuth Source Serializer"""
40 
41     provider_type = ChoiceField(choices=registry.get_name_tuple())
42     callback_url = SerializerMethodField()
43     type = SerializerMethodField()
44 
45     def get_callback_url(self, instance: OAuthSource) -> str:
46         """Get OAuth Callback URL"""
47         relative_url = reverse_lazy(
48             "authentik_sources_oauth:oauth-client-callback",
49             kwargs={"source_slug": instance.slug},
50         )
51         if "request" not in self.context:
52             return relative_url
53         return self.context["request"].build_absolute_uri(relative_url)
54 
55     @extend_schema_field(SourceTypeSerializer)
56     def get_type(self, instance: OAuthSource) -> SourceTypeSerializer:
57         """Get source's type configuration"""
58         return SourceTypeSerializer(instance.source_type).data
59 
60     def validate(self, attrs: dict) -> dict:
61         session = get_http_session()
62         source_type = registry.find_type(attrs["provider_type"])
63 
64         well_known = attrs.get("oidc_well_known_url") or source_type.oidc_well_known_url
65         inferred_oidc_jwks_url = None
66 
67         if well_known and well_known != "":
68             try:
69                 well_known_config = session.get(well_known)
70                 well_known_config.raise_for_status()
71             except RequestException as exc:
72                 text = exc.response.text if exc.response else str(exc)
73                 raise ValidationError({"oidc_well_known_url": text}) from None
74             config = well_known_config.json()
75             if "issuer" not in config:
76                 raise ValidationError({"oidc_well_known_url": "Invalid well-known configuration"})
77             field_map = {
78                 # authentik field to oidc field
79                 "authorization_url": "authorization_endpoint",
80                 "access_token_url": "token_endpoint",
81                 "profile_url": "userinfo_endpoint",
82             }
83             for ak_key, oidc_key in field_map.items():
84                 # Don't overwrite user-set values
85                 if ak_key in attrs and attrs[ak_key]:
86                     continue
87                 attrs[ak_key] = config.get(oidc_key, "")
88             inferred_oidc_jwks_url = config.get("jwks_uri", "")
89 
90         # Prefer user-entered URL to inferred URL to default URL
91         jwks_url = attrs.get("oidc_jwks_url") or inferred_oidc_jwks_url or source_type.oidc_jwks_url
92         if jwks_url and jwks_url != "":
93             attrs["oidc_jwks_url"] = jwks_url
94             try:
95                 jwks_config = session.get(jwks_url)
96                 jwks_config.raise_for_status()
97             except RequestException as exc:
98                 text = exc.response.text if exc.response else str(exc)
99                 raise ValidationError({"oidc_jwks_url": text}) from None
100             config = jwks_config.json()
101             attrs["oidc_jwks"] = config
102 
103         provider_type = registry.find_type(attrs.get("provider_type", ""))
104         for url in [
105             "authorization_url",
106             "access_token_url",
107             "profile_url",
108         ]:
109             if getattr(provider_type, url, None) is None:
110                 if url not in attrs:
111                     raise ValidationError(
112                         f"{url} is required for provider {provider_type.verbose_name}"
113                     )
114         return attrs
115 
116     class Meta:
117         model = OAuthSource
118         fields = SourceSerializer.Meta.fields + [
119             "provider_type",
120             "request_token_url",
121             "authorization_url",
122             "access_token_url",
123             "profile_url",
124             "consumer_key",
125             "consumer_secret",
126             "callback_url",
127             "additional_scopes",
128             "type",
129             "oidc_well_known_url",
130             "oidc_jwks_url",
131             "oidc_jwks",
132         ]
133         extra_kwargs = {"consumer_secret": {"write_only": True}}
134 
135 
136 class OAuthSourceFilter(FilterSet):
137     """OAuth Source filter set"""
138 
139     has_jwks = BooleanFilter(label="Only return sources with JWKS data", method="filter_has_jwks")
140 
141     def filter_has_jwks(self, queryset, name, value):  # pragma: no cover
142         """Only return sources with JWKS data"""
143         return queryset.exclude(oidc_jwks__iexact="{}")
144 
145     class Meta:
146         model = OAuthSource
147         fields = [
148             "name",
149             "slug",
150             "enabled",
151             "authentication_flow",
152             "enrollment_flow",
153             "policy_engine_mode",
154             "user_matching_mode",
155             "provider_type",
156             "request_token_url",
157             "authorization_url",
158             "access_token_url",
159             "profile_url",
160             "consumer_key",
161             "additional_scopes",
162         ]
163 
164 
165 class OAuthSourceViewSet(UsedByMixin, ModelViewSet):
166     """Source Viewset"""
167 
168     queryset = OAuthSource.objects.all()
169     serializer_class = OAuthSourceSerializer
170     lookup_field = "slug"
171     filterset_class = OAuthSourceFilter
172     search_fields = ["name", "slug"]
173     ordering = ["name"]
174 
175     @extend_schema(
176         responses={200: SourceTypeSerializer(many=True)},
177         parameters=[
178             OpenApiParameter(
179                 name="name",
180                 location=OpenApiParameter.QUERY,
181                 type=OpenApiTypes.STR,
182             )
183         ],
184     )
185     @action(detail=False, pagination_class=None, filter_backends=[])
186     def source_types(self, request: Request) -> Response:
187         """Get all creatable source types. If ?name is set, only returns the type for <name>.
188         If <name> isn't found, returns the default type."""
189         data = []
190         if "name" in request.query_params:
191             source_type = registry.find_type(request.query_params.get("name"))
192             if source_type.__class__ != SourceType:
193                 data.append(SourceTypeSerializer(source_type).data)
194         else:
195             for source_type in registry.get():
196                 data.append(SourceTypeSerializer(source_type).data)
197         return Response(data)
198
```
--- 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/sources/oauth/api/source.py b/authentik/sources/oauth/api/source.py
--- a/authentik/sources/oauth/api/source.py
+++ b/authentik/sources/oauth/api/source.py
@@ -130,7 +130,13 @@
             "oidc_jwks_url",
             "oidc_jwks",
         ]
-        extra_kwargs = {"consumer_secret": {"write_only": True}}
+        extra_kwargs = {
+            "consumer_secret": {"write_only": True},
+            "request_token_url": {"allow_blank": True},
+            "authorization_url": {"allow_blank": True},
+            "access_token_url": {"allow_blank": True},
+            "profile_url": {"allow_blank": True},
+        }
 
 
 class OAuthSourceFilter(FilterSet):

{"golden_diff": "diff --git a/authentik/sources/oauth/api/source.py b/authentik/sources/oauth/api/source.py\n--- a/authentik/sources/oauth/api/source.py\n+++ b/authentik/sources/oauth/api/source.py\n@@ -130,7 +130,13 @@\n             \"oidc_jwks_url\",\n             \"oidc_jwks\",\n         ]\n-        extra_kwargs = {\"consumer_secret\": {\"write_only\": True}}\n+        extra_kwargs = {\n+            \"consumer_secret\": {\"write_only\": True},\n+            \"request_token_url\": {\"allow_blank\": True},\n+            \"authorization_url\": {\"allow_blank\": True},\n+            \"access_token_url\": {\"allow_blank\": True},\n+            \"profile_url\": {\"allow_blank\": True},\n+        }\n \n \n class OAuthSourceFilter(FilterSet):\n", "issue": "OIDC Well-known URL won't configure endpoint URLs\n**Describe the bug**\r\nWhen entering the OIDC Well-known URL for an OpenID OAuth source, it won't automatically configure the endpoint URLs. Instead it prompts a \"Bad Request\" error and says \"This field may not be blank.\" for every endpoint.\r\n\r\n**To Reproduce**\r\nSteps to reproduce the behavior:\r\n\r\n1. Go to 'Directory > Federation and Social login'\r\n2. Click on 'Create > OpenID OAuth Source > Next'\r\n3. Enter your credentials (Name, Slug, Key, Secret and the Well-known URL)\r\n4. Click on 'Finish'\r\n5. See error\r\n\r\n**Expected behavior**\r\nUppon clicking on 'Finish' authentik should configure the missing endpoints using the Well-known URL.\r\n\r\n**Screenshots**\r\n\r\n![grafik](https://github.com/goauthentik/authentik/assets/145431101/b7056922-b35f-44fb-a926-4e2ebf8b9041)\r\n\r\n**Version and Deployment:**\r\n\r\n-   authentik version: 2024.2.2 \r\n-   Deployment: docker-compose\r\n\r\n**Additional context**\r\nI thought, it might have something to do with connectivity and proxy, since we are running authentik behind a corporate proxy. But I can curl the Well-known URL from inside the containers.\r\n\n", "before_files": [{"content": "\"\"\"OAuth Source Serializer\"\"\"\n\nfrom django.urls.base import reverse_lazy\nfrom django_filters.filters import BooleanFilter\nfrom django_filters.filterset import FilterSet\nfrom drf_spectacular.types import OpenApiTypes\nfrom drf_spectacular.utils import OpenApiParameter, extend_schema, extend_schema_field\nfrom requests import RequestException\nfrom rest_framework.decorators import action\nfrom rest_framework.fields import BooleanField, CharField, ChoiceField, SerializerMethodField\nfrom rest_framework.request import Request\nfrom rest_framework.response import Response\nfrom rest_framework.serializers import ValidationError\nfrom rest_framework.viewsets import ModelViewSet\n\nfrom authentik.core.api.sources import SourceSerializer\nfrom authentik.core.api.used_by import UsedByMixin\nfrom authentik.core.api.utils import PassiveSerializer\nfrom authentik.lib.utils.http import get_http_session\nfrom authentik.sources.oauth.models import OAuthSource\nfrom authentik.sources.oauth.types.registry import SourceType, registry\n\n\nclass SourceTypeSerializer(PassiveSerializer):\n    \"\"\"Serializer for SourceType\"\"\"\n\n    name = CharField(required=True)\n    verbose_name = CharField(required=True)\n    urls_customizable = BooleanField()\n    request_token_url = CharField(read_only=True, allow_null=True)\n    authorization_url = CharField(read_only=True, allow_null=True)\n    access_token_url = CharField(read_only=True, allow_null=True)\n    profile_url = CharField(read_only=True, allow_null=True)\n    oidc_well_known_url = CharField(read_only=True, allow_null=True)\n    oidc_jwks_url = CharField(read_only=True, allow_null=True)\n\n\nclass OAuthSourceSerializer(SourceSerializer):\n    \"\"\"OAuth Source Serializer\"\"\"\n\n    provider_type = ChoiceField(choices=registry.get_name_tuple())\n    callback_url = SerializerMethodField()\n    type = SerializerMethodField()\n\n    def get_callback_url(self, instance: OAuthSource) -> str:\n        \"\"\"Get OAuth Callback URL\"\"\"\n        relative_url = reverse_lazy(\n            \"authentik_sources_oauth:oauth-client-callback\",\n            kwargs={\"source_slug\": instance.slug},\n        )\n        if \"request\" not in self.context:\n            return relative_url\n        return self.context[\"request\"].build_absolute_uri(relative_url)\n\n    @extend_schema_field(SourceTypeSerializer)\n    def get_type(self, instance: OAuthSource) -> SourceTypeSerializer:\n        \"\"\"Get source's type configuration\"\"\"\n        return SourceTypeSerializer(instance.source_type).data\n\n    def validate(self, attrs: dict) -> dict:\n        session = get_http_session()\n        source_type = registry.find_type(attrs[\"provider_type\"])\n\n        well_known = attrs.get(\"oidc_well_known_url\") or source_type.oidc_well_known_url\n        inferred_oidc_jwks_url = None\n\n        if well_known and well_known != \"\":\n            try:\n                well_known_config = session.get(well_known)\n                well_known_config.raise_for_status()\n            except RequestException as exc:\n                text = exc.response.text if exc.response else str(exc)\n                raise ValidationError({\"oidc_well_known_url\": text}) from None\n            config = well_known_config.json()\n            if \"issuer\" not in config:\n                raise ValidationError({\"oidc_well_known_url\": \"Invalid well-known configuration\"})\n            field_map = {\n                # authentik field to oidc field\n                \"authorization_url\": \"authorization_endpoint\",\n                \"access_token_url\": \"token_endpoint\",\n                \"profile_url\": \"userinfo_endpoint\",\n            }\n            for ak_key, oidc_key in field_map.items():\n                # Don't overwrite user-set values\n                if ak_key in attrs and attrs[ak_key]:\n                    continue\n                attrs[ak_key] = config.get(oidc_key, \"\")\n            inferred_oidc_jwks_url = config.get(\"jwks_uri\", \"\")\n\n        # Prefer user-entered URL to inferred URL to default URL\n        jwks_url = attrs.get(\"oidc_jwks_url\") or inferred_oidc_jwks_url or source_type.oidc_jwks_url\n        if jwks_url and jwks_url != \"\":\n            attrs[\"oidc_jwks_url\"] = jwks_url\n            try:\n                jwks_config = session.get(jwks_url)\n                jwks_config.raise_for_status()\n            except RequestException as exc:\n                text = exc.response.text if exc.response else str(exc)\n                raise ValidationError({\"oidc_jwks_url\": text}) from None\n            config = jwks_config.json()\n            attrs[\"oidc_jwks\"] = config\n\n        provider_type = registry.find_type(attrs.get(\"provider_type\", \"\"))\n        for url in [\n            \"authorization_url\",\n            \"access_token_url\",\n            \"profile_url\",\n        ]:\n            if getattr(provider_type, url, None) is None:\n                if url not in attrs:\n                    raise ValidationError(\n                        f\"{url} is required for provider {provider_type.verbose_name}\"\n                    )\n        return attrs\n\n    class Meta:\n        model = OAuthSource\n        fields = SourceSerializer.Meta.fields + [\n            \"provider_type\",\n            \"request_token_url\",\n            \"authorization_url\",\n            \"access_token_url\",\n            \"profile_url\",\n            \"consumer_key\",\n            \"consumer_secret\",\n            \"callback_url\",\n            \"additional_scopes\",\n            \"type\",\n            \"oidc_well_known_url\",\n            \"oidc_jwks_url\",\n            \"oidc_jwks\",\n        ]\n        extra_kwargs = {\"consumer_secret\": {\"write_only\": True}}\n\n\nclass OAuthSourceFilter(FilterSet):\n    \"\"\"OAuth Source filter set\"\"\"\n\n    has_jwks = BooleanFilter(label=\"Only return sources with JWKS data\", method=\"filter_has_jwks\")\n\n    def filter_has_jwks(self, queryset, name, value):  # pragma: no cover\n        \"\"\"Only return sources with JWKS data\"\"\"\n        return queryset.exclude(oidc_jwks__iexact=\"{}\")\n\n    class Meta:\n        model = OAuthSource\n        fields = [\n            \"name\",\n            \"slug\",\n            \"enabled\",\n            \"authentication_flow\",\n            \"enrollment_flow\",\n            \"policy_engine_mode\",\n            \"user_matching_mode\",\n            \"provider_type\",\n            \"request_token_url\",\n            \"authorization_url\",\n            \"access_token_url\",\n            \"profile_url\",\n            \"consumer_key\",\n            \"additional_scopes\",\n        ]\n\n\nclass OAuthSourceViewSet(UsedByMixin, ModelViewSet):\n    \"\"\"Source Viewset\"\"\"\n\n    queryset = OAuthSource.objects.all()\n    serializer_class = OAuthSourceSerializer\n    lookup_field = \"slug\"\n    filterset_class = OAuthSourceFilter\n    search_fields = [\"name\", \"slug\"]\n    ordering = [\"name\"]\n\n    @extend_schema(\n        responses={200: SourceTypeSerializer(many=True)},\n        parameters=[\n            OpenApiParameter(\n                name=\"name\",\n                location=OpenApiParameter.QUERY,\n                type=OpenApiTypes.STR,\n            )\n        ],\n    )\n    @action(detail=False, pagination_class=None, filter_backends=[])\n    def source_types(self, request: Request) -> Response:\n        \"\"\"Get all creatable source types. If ?name is set, only returns the type for <name>.\n        If <name> isn't found, returns the default type.\"\"\"\n        data = []\n        if \"name\" in request.query_params:\n            source_type = registry.find_type(request.query_params.get(\"name\"))\n            if source_type.__class__ != SourceType:\n                data.append(SourceTypeSerializer(source_type).data)\n        else:\n            for source_type in registry.get():\n                data.append(SourceTypeSerializer(source_type).data)\n        return Response(data)\n", "path": "authentik/sources/oauth/api/source.py"}], "after_files": [{"content": "\"\"\"OAuth Source Serializer\"\"\"\n\nfrom django.urls.base import reverse_lazy\nfrom django_filters.filters import BooleanFilter\nfrom django_filters.filterset import FilterSet\nfrom drf_spectacular.types import OpenApiTypes\nfrom drf_spectacular.utils import OpenApiParameter, extend_schema, extend_schema_field\nfrom requests import RequestException\nfrom rest_framework.decorators import action\nfrom rest_framework.fields import BooleanField, CharField, ChoiceField, SerializerMethodField\nfrom rest_framework.request import Request\nfrom rest_framework.response import Response\nfrom rest_framework.serializers import ValidationError\nfrom rest_framework.viewsets import ModelViewSet\n\nfrom authentik.core.api.sources import SourceSerializer\nfrom authentik.core.api.used_by import UsedByMixin\nfrom authentik.core.api.utils import PassiveSerializer\nfrom authentik.lib.utils.http import get_http_session\nfrom authentik.sources.oauth.models import OAuthSource\nfrom authentik.sources.oauth.types.registry import SourceType, registry\n\n\nclass SourceTypeSerializer(PassiveSerializer):\n    \"\"\"Serializer for SourceType\"\"\"\n\n    name = CharField(required=True)\n    verbose_name = CharField(required=True)\n    urls_customizable = BooleanField()\n    request_token_url = CharField(read_only=True, allow_null=True)\n    authorization_url = CharField(read_only=True, allow_null=True)\n    access_token_url = CharField(read_only=True, allow_null=True)\n    profile_url = CharField(read_only=True, allow_null=True)\n    oidc_well_known_url = CharField(read_only=True, allow_null=True)\n    oidc_jwks_url = CharField(read_only=True, allow_null=True)\n\n\nclass OAuthSourceSerializer(SourceSerializer):\n    \"\"\"OAuth Source Serializer\"\"\"\n\n    provider_type = ChoiceField(choices=registry.get_name_tuple())\n    callback_url = SerializerMethodField()\n    type = SerializerMethodField()\n\n    def get_callback_url(self, instance: OAuthSource) -> str:\n        \"\"\"Get OAuth Callback URL\"\"\"\n        relative_url = reverse_lazy(\n            \"authentik_sources_oauth:oauth-client-callback\",\n            kwargs={\"source_slug\": instance.slug},\n        )\n        if \"request\" not in self.context:\n            return relative_url\n        return self.context[\"request\"].build_absolute_uri(relative_url)\n\n    @extend_schema_field(SourceTypeSerializer)\n    def get_type(self, instance: OAuthSource) -> SourceTypeSerializer:\n        \"\"\"Get source's type configuration\"\"\"\n        return SourceTypeSerializer(instance.source_type).data\n\n    def validate(self, attrs: dict) -> dict:\n        session = get_http_session()\n        source_type = registry.find_type(attrs[\"provider_type\"])\n\n        well_known = attrs.get(\"oidc_well_known_url\") or source_type.oidc_well_known_url\n        inferred_oidc_jwks_url = None\n\n        if well_known and well_known != \"\":\n            try:\n                well_known_config = session.get(well_known)\n                well_known_config.raise_for_status()\n            except RequestException as exc:\n                text = exc.response.text if exc.response else str(exc)\n                raise ValidationError({\"oidc_well_known_url\": text}) from None\n            config = well_known_config.json()\n            if \"issuer\" not in config:\n                raise ValidationError({\"oidc_well_known_url\": \"Invalid well-known configuration\"})\n            field_map = {\n                # authentik field to oidc field\n                \"authorization_url\": \"authorization_endpoint\",\n                \"access_token_url\": \"token_endpoint\",\n                \"profile_url\": \"userinfo_endpoint\",\n            }\n            for ak_key, oidc_key in field_map.items():\n                # Don't overwrite user-set values\n                if ak_key in attrs and attrs[ak_key]:\n                    continue\n                attrs[ak_key] = config.get(oidc_key, \"\")\n            inferred_oidc_jwks_url = config.get(\"jwks_uri\", \"\")\n\n        # Prefer user-entered URL to inferred URL to default URL\n        jwks_url = attrs.get(\"oidc_jwks_url\") or inferred_oidc_jwks_url or source_type.oidc_jwks_url\n        if jwks_url and jwks_url != \"\":\n            attrs[\"oidc_jwks_url\"] = jwks_url\n            try:\n                jwks_config = session.get(jwks_url)\n                jwks_config.raise_for_status()\n            except RequestException as exc:\n                text = exc.response.text if exc.response else str(exc)\n                raise ValidationError({\"oidc_jwks_url\": text}) from None\n            config = jwks_config.json()\n            attrs[\"oidc_jwks\"] = config\n\n        provider_type = registry.find_type(attrs.get(\"provider_type\", \"\"))\n        for url in [\n            \"authorization_url\",\n            \"access_token_url\",\n            \"profile_url\",\n        ]:\n            if getattr(provider_type, url, None) is None:\n                if url not in attrs:\n                    raise ValidationError(\n                        f\"{url} is required for provider {provider_type.verbose_name}\"\n                    )\n        return attrs\n\n    class Meta:\n        model = OAuthSource\n        fields = SourceSerializer.Meta.fields + [\n            \"provider_type\",\n            \"request_token_url\",\n            \"authorization_url\",\n            \"access_token_url\",\n            \"profile_url\",\n            \"consumer_key\",\n            \"consumer_secret\",\n            \"callback_url\",\n            \"additional_scopes\",\n            \"type\",\n            \"oidc_well_known_url\",\n            \"oidc_jwks_url\",\n            \"oidc_jwks\",\n        ]\n        extra_kwargs = {\n            \"consumer_secret\": {\"write_only\": True},\n            \"request_token_url\": {\"allow_blank\": True},\n            \"authorization_url\": {\"allow_blank\": True},\n            \"access_token_url\": {\"allow_blank\": True},\n            \"profile_url\": {\"allow_blank\": True},\n        }\n\n\nclass OAuthSourceFilter(FilterSet):\n    \"\"\"OAuth Source filter set\"\"\"\n\n    has_jwks = BooleanFilter(label=\"Only return sources with JWKS data\", method=\"filter_has_jwks\")\n\n    def filter_has_jwks(self, queryset, name, value):  # pragma: no cover\n        \"\"\"Only return sources with JWKS data\"\"\"\n        return queryset.exclude(oidc_jwks__iexact=\"{}\")\n\n    class Meta:\n        model = OAuthSource\n        fields = [\n            \"name\",\n            \"slug\",\n            \"enabled\",\n            \"authentication_flow\",\n            \"enrollment_flow\",\n            \"policy_engine_mode\",\n            \"user_matching_mode\",\n            \"provider_type\",\n            \"request_token_url\",\n            \"authorization_url\",\n            \"access_token_url\",\n            \"profile_url\",\n            \"consumer_key\",\n            \"additional_scopes\",\n        ]\n\n\nclass OAuthSourceViewSet(UsedByMixin, ModelViewSet):\n    \"\"\"Source Viewset\"\"\"\n\n    queryset = OAuthSource.objects.all()\n    serializer_class = OAuthSourceSerializer\n    lookup_field = \"slug\"\n    filterset_class = OAuthSourceFilter\n    search_fields = [\"name\", \"slug\"]\n    ordering = [\"name\"]\n\n    @extend_schema(\n        responses={200: SourceTypeSerializer(many=True)},\n        parameters=[\n            OpenApiParameter(\n                name=\"name\",\n                location=OpenApiParameter.QUERY,\n                type=OpenApiTypes.STR,\n            )\n        ],\n    )\n    @action(detail=False, pagination_class=None, filter_backends=[])\n    def source_types(self, request: Request) -> Response:\n        \"\"\"Get all creatable source types. If ?name is set, only returns the type for <name>.\n        If <name> isn't found, returns the default type.\"\"\"\n        data = []\n        if \"name\" in request.query_params:\n            source_type = registry.find_type(request.query_params.get(\"name\"))\n            if source_type.__class__ != SourceType:\n                data.append(SourceTypeSerializer(source_type).data)\n        else:\n            for source_type in registry.get():\n                data.append(SourceTypeSerializer(source_type).data)\n        return Response(data)\n", "path": "authentik/sources/oauth/api/source.py"}]}