authentik icon indicating copy to clipboard operation
authentik copied to clipboard

Creating application or provider via core API causes validation errors

Open cadeParade opened this issue 1 year ago • 8 comments

Describe the bug I am trying to set up a clean authentik install with a script in my FastAPI app, including an application and an oauth2/OIDC provider. I am using authentik-client. So I need to create both an application and a provider object, but creating either one causes validation errors. It seems like the create call does work, ultimately, but the terminal output shows errors and I'm not sure it should. Additionally, the properties it complains about are not listed as required (or even mentioned at all?) on the core API doc pages (application and provider)

To Reproduce

I run this:

    import authentik_client
    from authentik_client.rest import ApiException

    authentik_admin_access_token = settings.AUTHENTIK_BOOTSTRAP_TOKEN
    authentik_configuration = authentik_client.Configuration(
        host="http://localhost:9000/api/v3",
        access_token=authentik_admin_access_token,
    )

    with authentik_client.ApiClient(authentik_configuration) as api_client:
        application_api_instance = authentik_client.CoreApi(api_client)
        try:
            application_data = {
                "name": "Foo",
                "slug": "foo",
                "meta_launch_url": "http://127.0.0.1:3000",
            }
            api_application_response = application_api_instance.core_applications_create(
                application_data
            )
            print("The response of ApplicationsAPI->core_applications_create:\n")
            pprint(api_application_response)
            return api_application_response
        except ApiException as e:
            print("Exception when calling ApplicationsAPI->core_applications_create: %s\n" % e)

I get

pydantic_core._pydantic_core.ValidationError: 1 validation error for Application provider_obj Input should be a valid dictionary or instance of Provider [type=model_type, input_value=None, input_type=NoneType] For further information visit https://errors.pydantic.dev/2.7/v/model_type

Stack trace
Traceback (most recent call last):
  File "/Users/lc/projects/foo/api/bin/first_time_setup", line 38, in <module>
    setup_authentik.do_it()
  File "/Users/lc/projects/foo/api/bin/setup_authentik.py", line 43, in do_it
    create_foo_application()
  File "/Users/lc/projects/foo/api/bin/setup_authentik.py", line 56, in create_foo_application
    api_application_response = application_api_instance.core_applications_create(
                               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/lc/projects/foo/api/.venv/lib/python3.12/site-packages/pydantic/validate_call_decorator.py", line 59, in wrapper_function
    return validate_call_wrapper(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/lc/projects/foo/api/.venv/lib/python3.12/site-packages/pydantic/_internal/_validate_call.py", line 81, in __call__
    res = self.__pydantic_validator__.validate_python(pydantic_core.ArgsKwargs(args, kwargs))
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/lc/projects/foo/api/.venv/lib/python3.12/site-packages/authentik_client/api/core_api.py", line 428, in core_applications_create
    return self.api_client.response_deserialize(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/lc/projects/foo/api/.venv/lib/python3.12/site-packages/authentik_client/api_client.py", line 316, in response_deserialize
    return_data = self.deserialize(response_text, response_type)
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/lc/projects/foo/api/.venv/lib/python3.12/site-packages/authentik_client/api_client.py", line 392, in deserialize
    return self.__deserialize(data, response_type)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/lc/projects/foo/api/.venv/lib/python3.12/site-packages/authentik_client/api_client.py", line 439, in __deserialize
    return self.__deserialize_model(data, klass)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/lc/projects/foo/api/.venv/lib/python3.12/site-packages/authentik_client/api_client.py", line 761, in __deserialize_model
    return klass.from_dict(data)
           ^^^^^^^^^^^^^^^^^^^^^
  File "/Users/lc/projects/foo/api/.venv/lib/python3.12/site-packages/authentik_client/models/application.py", line 148, in from_dict
    _obj = cls.model_validate({
           ^^^^^^^^^^^^^^^^^^^^
  File "/Users/lc/projects/foo/api/.venv/lib/python3.12/site-packages/pydantic/main.py", line 551, in model_validate
    return cls.__pydantic_validator__.validate_python(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
pydantic_core._pydantic_core.ValidationError: 1 validation error for Application
provider_obj
  Input should be a valid dictionary or instance of Provider [type=model_type, input_value=None, input_type=NoneType]
    For further information visit https://errors.pydantic.dev/2.7/v/model_type

Ok fine so maybe I need a provider first. So I run (after deleting the application):

    with authentik_client.ApiClient(authentik_configuration) as api_client:
        provider_api_instance = authentik_client.ProvidersApi(api_client)
        flows_api_instance = authentik_client.FlowsApi(api_client)

        try:
            authorization_flow = flows_api_instance.flows_instances_retrieve(
                "default-provider-authorization-implicit-consent"
            )
            authetication_flow = flows_api_instance.flows_instances_retrieve(
                "default-authentication-flow"
            )
            provider_data = {
                "name": "FooProvider",
                "authorization_flow": authorization_flow.pk,
                "authentication_flow": authetication_flow.pk,
                "client_id": "authentik_client_id2",
                "client_secret": "authentik_client_secret",
                "redirect_uris": "http://127.0.0.1:9090/auth/authentik/callback",
                "assigned_application_slug": "foo",
                "assigned_application_name": "Foo",
                "assigned_backchannel_application_slug": "foo",
                "assigned_backchannel_application_name": "foo",
            }

            api_provider_response = provider_api_instance.providers_oauth2_create(provider_data)
            print("The response of ProvidersApi->core_providers_create:\n")
            pprint(api_provider_response)
            return api_provider_response
        except ApiException as e:
            print("Exception when calling ProvidersApi->core_providers_create: %s\n" % e)

And again I get validation errors:

pydantic_core._pydantic_core.ValidationError: 4 validation errors for OAuth2Provider
assigned_application_slug
  Input should be a valid string [type=string_type, input_value=None, input_type=NoneType]
    For further information visit https://errors.pydantic.dev/2.7/v/string_type
assigned_application_name
  Input should be a valid string [type=string_type, input_value=None, input_type=NoneType]
    For further information visit https://errors.pydantic.dev/2.7/v/string_type
assigned_backchannel_application_slug
  Input should be a valid string [type=string_type, input_value=None, input_type=NoneType]
    For further information visit https://errors.pydantic.dev/2.7/v/string_type
assigned_backchannel_application_name
  Input should be a valid string [type=string_type, input_value=None, input_type=NoneType]
    For further information visit https://errors.pydantic.dev/2.7/v/string_type

First of all -- all the validation error properties have a string provided, so I'm pretty confused about this error. Second, none of these properties are mentioned in the request_body section of the providers_oauth2_create documentation.

It seems like there are two issues:

  1. Some type validation is acting weird or incorrect -- I am providing strings for properties but it is saying it is wrong because it expects a string
  2. Creating a provider requires an application. Creating an application requires a provider. So I can't create either.

Expected behavior Ability to create an application or a provider.

Version and Deployment (please complete the following information):

  • authentik version: 2024.4.2.post1715271061
  • Deployment: docker-compose

Thanks for any advice or help!

cadeParade avatar May 20 '24 17:05 cadeParade

The intended use for the generated API client would be using this

from authentik_client.models.application import Application
from authentik_client.models.o_auth2_provider import OAuth2Provider

Application()

instead of just passing a dictionary to the API calls

BeryJu avatar May 21 '24 11:05 BeryJu

Thanks for your reply. I have been continuing to run into this problem after using authentik_client provided models. I think I have narrowed down the problem. When I create an OAuth Provider via the API, it throws validation errors for assigned_application_slug, assigned_application_name, assigned_backchannel_application_slug, assigned_backchannel_application_name.

I am using this package, as linked from the authentik documentation. FYI, the links to the documentation on the pypi page go to 404s (ex: https://pypi.org/project/authentik-client/docs/ProvidersApi.md#providers_oauth2_create). I cannot find a python version of authentik_client open source on github, although maybe it is all generated by this schema.yml?)

To create a new provider, we call: provider_api_instance.providers_oauth2_create([Instance of OAuth2ProviderRequest]).

providers_oauth2_create expects an argument of type OAuth2ProviderRequest (maybe defined here).

An OAuth2ProviderRequest has these properties, which does not include assigned_application_slug, assigned_application_name, assigned_backchannel_application_slug, assigned_backchannel_application_name. So to me, it seems impossible to send the properties an OAuth2Provider model is expecting since even if you put the assigned_application_slug etc in the data object to construct OAuth2ProviderRequest, properties that aren't in the OAuth2ProviderRequest schema are ignored.

Please let me know if I am misunderstanding something and how I can successfully create an OAuth provider with authentik-client

Here is the actual code I am using if it helpful:

expand...
    with ApiClient(authentik_configuration) as api_client:
        provider_api_instance = ProvidersApi(api_client)
        flows_api_instance = FlowsApi(api_client)
        property_mappings_api_instance = PropertymappingsApi(api_client)

        try:
            print("[authentik_setup] Creating oauth provider...")
            authorization_flow = flows_api_instance.flows_instances_retrieve(
                "default-provider-authorization-implicit-consent"
            )
            authetication_flow = flows_api_instance.flows_instances_retrieve(
                "default-authentication-flow"
            )

            mappings = property_mappings_api_instance.propertymappings_scope_list()
            scope_mappings = [
                mapping.pk
                for mapping in mappings.results
                if mapping.managed
                in [
                    "goauthentik.io/providers/oauth2/scope-openid",
                    "goauthentik.io/providers/oauth2/scope-profile",
                    "goauthentik.io/providers/oauth2/scope-email",
                ]
            ]

            provider_data = {
                "name": PROVIDER_NAME,
                "authorization_flow": authorization_flow.pk,
                "authentication_flow": authetication_flow.pk,
                "client_id": settings.AUTHENTIK_CLIENT_ID,
                "client_secret": settings.AUTHENTIK_CLIENT_SECRET,
                "redirect_uris": "http://127.0.0.1:9090/auth/authentik/callback",
                "property_mappings": scope_mappings,
            }

            provider_request = OAuth2ProviderRequest.model_construct(**provider_data)
            api_provider_response = provider_api_instance.providers_oauth2_create(provider_request)

cadeParade avatar May 23 '24 17:05 cadeParade

The assigned_* properties are read_only and are mainly used by the frontend when the provider is connected with an application. In the backend this is defined correctly (and in theory so is it in the schema), however some client generators don't interpret this correctly

The python client is indeed generated from that schema (https://github.com/goauthentik/authentik/blob/main/.github/workflows/api-py-publish.yml) hence there currently isn't a source for it available.

Which line exactly is throwing the exception you posted above?

BeryJu avatar May 23 '24 22:05 BeryJu

Thanks for your reply.

The lines are hard to share since I can't link to the generated python code, but here's some more details.

Backtrace
  File "/Users/lc/projects/foo/api/bin/first_time_setup", line 307, in <module>
    setup_authentik.do_it()
  File "/Users/lc/projects/foo/api/bin/setup_authentik.py", line 30, in do_it
    create_oauth_provider()
  File "/Users/lc/projects/foo/api/bin/setup_authentik.py", line 110, in create_oauth_provider
    api_provider_response = provider_api_instance.providers_oauth2_create(provider_request)
                            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/lc/projects/foo/api/.venv/lib/python3.12/site-packages/pydantic/validate_call_decorator.py", line 59, in wrapper_function
    return validate_call_wrapper(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/lc/projects/foo/api/.venv/lib/python3.12/site-packages/pydantic/_internal/_validate_call.py", line 81, in __call__
    res = self.__pydantic_validator__.validate_python(pydantic_core.ArgsKwargs(args, kwargs))
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/lc/projects/foo/api/.venv/lib/python3.12/site-packages/authentik_client/api/providers_api.py", line 16090, in providers_oauth2_create
    foo = self.api_client.response_deserialize(
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/lc/projects/foo/api/.venv/lib/python3.12/site-packages/authentik_client/api_client.py", line 316, in response_deserialize
    return_data = self.deserialize(response_text, response_type)
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/lc/projects/foo/api/.venv/lib/python3.12/site-packages/authentik_client/api_client.py", line 392, in deserialize
    return self.__deserialize(data, response_type)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/lc/projects/foo/api/.venv/lib/python3.12/site-packages/authentik_client/api_client.py", line 437, in __deserialize
    return self.__deserialize_model(data, klass)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/lc/projects/foo/api/.venv/lib/python3.12/site-packages/authentik_client/api_client.py", line 761, in __deserialize_model
    return klass.from_dict(data)
           ^^^^^^^^^^^^^^^^^^^^^
  File "/Users/lc/projects/foo/api/.venv/lib/python3.12/site-packages/authentik_client/models/o_auth2_provider.py", line 163, in from_dict
    _obj = cls.model_validate({
           ^^^^^^^^^^^^^^^^^^^^
  File "/Users/lc/projects/foo/api/.venv/lib/python3.12/site-packages/pydantic/main.py", line 551, in model_validate
    return cls.__pydantic_validator__.validate_python(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
pydantic_core._pydantic_core.ValidationError: 4 validation errors for OAuth2Provider
assigned_application_slug
  Input should be a valid string [type=string_type, input_value=None, input_type=NoneType]
    For further information visit https://errors.pydantic.dev/2.7/v/string_type
assigned_application_name
  Input should be a valid string [type=string_type, input_value=None, input_type=NoneType]
    For further information visit https://errors.pydantic.dev/2.7/v/string_type
assigned_backchannel_application_slug
  Input should be a valid string [type=string_type, input_value=None, input_type=NoneType]
    For further information visit https://errors.pydantic.dev/2.7/v/string_type
assigned_backchannel_application_name
  Input should be a valid string [type=string_type, input_value=None, input_type=NoneType]
    For further information visit https://errors.pydantic.dev/2.7/v/string_type

So what is happening as far as I understand is:

I call `providers_oauth2_create`
# definition from generated authentik-client -> `providers_api.py:16027`

@validate_call
def providers_oauth2_create(
    self,
    o_auth2_provider_request: OAuth2ProviderRequest,
    _request_timeout: Union[
        None,
        Annotated[StrictFloat, Field(gt=0)],
        Tuple[
            Annotated[StrictFloat, Field(gt=0)],
            Annotated[StrictFloat, Field(gt=0)]
        ]
    ] = None,
    _request_auth: Optional[Dict[StrictStr, Any]] = None,
    _content_type: Optional[StrictStr] = None,
    _headers: Optional[Dict[StrictStr, Any]] = None,
    _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0,
) -> OAuth2Provider:
    """providers_oauth2_create

    OAuth2Provider Viewset

    :param o_auth2_provider_request: (required)
    :type o_auth2_provider_request: OAuth2ProviderRequest
    :param _request_timeout: timeout setting for this request. If one
                             number provided, it will be total request
                             timeout. It can also be a pair (tuple) of
                             (connection, read) timeouts.
    :type _request_timeout: int, tuple(int, int), optional
    :param _request_auth: set to override the auth_settings for an a single
                          request; this effectively ignores the
                          authentication in the spec for a single request.
    :type _request_auth: dict, optional
    :param _content_type: force content-type for the request.
    :type _content_type: str, Optional
    :param _headers: set to override the headers for a single
                     request; this effectively ignores the headers
                     in the spec for a single request.
    :type _headers: dict, optional
    :param _host_index: set to override the host_index for a single
                        request; this effectively ignores the host_index
                        in the spec for a single request.
    :type _host_index: int, optional
    :return: Returns the result object.
    """ # noqa: E501

    _param = self._providers_oauth2_create_serialize(
        o_auth2_provider_request=o_auth2_provider_request,
        _request_auth=_request_auth,
        _content_type=_content_type,
        _headers=_headers,
        _host_index=_host_index
    )

    _response_types_map: Dict[str, Optional[str]] = {
        '201': "OAuth2Provider",
        '400': "ValidationError",
        '403': "GenericError",
    }
    response_data = self.api_client.call_api(
        *_param,
        _request_timeout=_request_timeout
    )
    response_data.read()
    return self.api_client.response_deserialize(
        response_data=response_data,
        response_types_map=_response_types_map,
    ).data
We get some response_data back at the end of `providers_oauth2_create`.
# `response_data` content

{
  "pk": 1,
  "name": "Foo OAuth/OIDC provider",
  "authentication_flow": "feb4c4d7-14fe-44e4-8036-c43c9af69a93",
  "authorization_flow": "5dbc2d24-2417-43f3-ac98-16336fba7968",
  "property_mappings": [
    "89cb76b9-3874-4bbe-9d04-4fedd482a787",
    "bbc36495-5293-436e-960e-f0745a4ee542",
    "db7d0b95-2d85-4894-a0dc-4d398f9076b6"
  ],
  "component": "ak-provider-oauth2-form",
  "assigned_application_slug": null,
  "assigned_application_name": null,
  "verbose_name": "OAuth2/OpenID Provider",
  "verbose_name_plural": "OAuth2/OpenID Providers",
  "meta_model_name": "authentik_providers_oauth2.oauth2provider",
  "client_type": "confidential",
  "client_id": "foobarbaz",
  "client_secret": "blablabla",
  "access_code_validity": "minutes=1",
  "access_token_validity": "hours=1",
  "refresh_token_validity": "days=30",
  "include_claims_in_id_token": true,
  "signing_key": null,
  "redirect_uris": "http://127.0.0.1:9090/auth/authentik/callback",
  "sub_mode": "hashed_user_id",
  "issuer_mode": "per_provider",
  "jwks_sources": []
}

As you can see, there are no assigned_backchannel_application_name, assigned_backchannel_application_slug, assigned_application_name, or assigned_application_slug in this response. We get this response back, and then providers_oauth2_create calls api_client.response_deserialize which then calls api_client.__deserialize, which eventually calls api_client.__deserialize_model.

return self.api_client.response_deserialize(
            response_data=response_data,
            response_types_map=_response_types_map,
        ).data
definition of `api_client.response_deserialize`, `api_client.__deserialize`, and `api_client.__deserialize_model` -> api_client.py: 376 - 437, api_client.py:751

    def deserialize(self, response_text, response_type):
        """Deserializes response into an object.

        :param response: RESTResponse object to be deserialized.
        :param response_type: class literal for
            deserialized object, or string of class name.

        :return: deserialized object.
        """

        # fetch data from response object
        try:
            data = json.loads(response_text)
        except ValueError:
            data = response_text

        return self.__deserialize(data, response_type)

    def __deserialize(self, data, klass):
        """Deserializes dict, list, str into an object.

        :param data: dict, list or str.
        :param klass: class literal, or string of class name.

        :return: object.
        """
        if data is None:
            return None

        if isinstance(klass, str):
            if klass.startswith('List['):
                m = re.match(r'List\[(.*)]', klass)
                assert m is not None, "Malformed List type definition"
                sub_kls = m.group(1)
                return [self.__deserialize(sub_data, sub_kls)
                        for sub_data in data]

            if klass.startswith('Dict['):
                m = re.match(r'Dict\[([^,]*), (.*)]', klass)
                assert m is not None, "Malformed Dict type definition"
                sub_kls = m.group(2)
                return {k: self.__deserialize(v, sub_kls)
                        for k, v in data.items()}

            # convert str to class
            if klass in self.NATIVE_TYPES_MAPPING:
                klass = self.NATIVE_TYPES_MAPPING[klass]
            else:
                klass = getattr(authentik_client.models, klass)

        if klass in self.PRIMITIVE_TYPES:
            return self.__deserialize_primitive(data, klass)
        elif klass == object:
            return self.__deserialize_object(data)
        elif klass == datetime.date:
            return self.__deserialize_date(data)
        elif klass == datetime.datetime:
            return self.__deserialize_datetime(data)
        elif issubclass(klass, Enum):
            return self.__deserialize_enum(data, klass)
        else:
            return self.__deserialize_model(data, klass)

...

    def __deserialize_model(self, data, klass):
        """Deserializes list or dict to model.

        :param data: dict, list.
        :param klass: class literal.
        :return: model object.
        """
        return klass.from_dict(data)
`__deserialize_model` calls `klass.from_dict` which in our case is `OAuth2Provider` class. So it calls `from_dict` method on `OAuth2Provider` model which calls `model_validate` requiring `assigned_application_slug`, etc. which do not exist, and therefore fails validation
models/o_auth2_provider.py:151

@classmethod
def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
    """Create an instance of OAuth2Provider from a dict"""
    if obj is None:
        return None

    if not isinstance(obj, dict):
        return cls.model_validate(obj)

    _obj = cls.model_validate({
        "pk": obj.get("pk"),
        "name": obj.get("name"),
        "authentication_flow": obj.get("authentication_flow"),
        "authorization_flow": obj.get("authorization_flow"),
        "property_mappings": obj.get("property_mappings"),
        "component": obj.get("component"),
        "assigned_application_slug": obj.get("assigned_application_slug"),
        "assigned_application_name": obj.get("assigned_application_name"),
        "assigned_backchannel_application_slug": obj.get("assigned_backchannel_application_slug"),
        "assigned_backchannel_application_name": obj.get("assigned_backchannel_application_name"),
        "verbose_name": obj.get("verbose_name"),
        "verbose_name_plural": obj.get("verbose_name_plural"),
        "meta_model_name": obj.get("meta_model_name"),
        "client_type": obj.get("client_type"),
        "client_id": obj.get("client_id"),
        "client_secret": obj.get("client_secret"),
        "access_code_validity": obj.get("access_code_validity"),
        "access_token_validity": obj.get("access_token_validity"),
        "refresh_token_validity": obj.get("refresh_token_validity"),
        "include_claims_in_id_token": obj.get("include_claims_in_id_token"),
        "signing_key": obj.get("signing_key"),
        "redirect_uris": obj.get("redirect_uris"),
        "sub_mode": obj.get("sub_mode"),
        "issuer_mode": obj.get("issuer_mode"),
        "jwks_sources": obj.get("jwks_sources")
    })
    return _obj

So it seems like

  1. The authentik API is not sending back required properties assigned_application_slug, assigned_application_name, assigned_backchannel_application_name, and assigned_backchannel_application_slug from a create call and therefore failing the de-serialization step.
  2. It is impossible for me to create a provider with those properties because the request object to create a provider does not accept those properties.

cadeParade avatar May 24 '24 18:05 cadeParade

The generated API docs show assigned_application_name, assigned_application_slug, etc as "required" in the response, so I think the problem may actually be with the schema.yml file (or whatever generates it) rather than the openapi generator.

ekoyle avatar Jun 28 '24 20:06 ekoyle

(edit: these were for the provider creation endpoint rather than the application creation endpoint)

For reference, adding nullable to these fields in schema.yml and regenerating the python client bindings was enough to get past this (not sure whether that is correct, I didn't look at the returned json to determine whether these were actually null values or just not present):

diff --git a/schema.yml b/schema.yml
index baa970150..8b301609b 100644
--- a/schema.yml
+++ b/schema.yml
@@ -45767,18 +45767,22 @@ components:
         assigned_application_slug:
           type: string
           description: Internal application name, used in URLs.
+          nullable: true
           readOnly: true
         assigned_application_name:
           type: string
           description: Application's display Name.
+          nullable: true
           readOnly: true
         assigned_backchannel_application_slug:
           type: string
           description: Internal application name, used in URLs.
+          nullable: true
           readOnly: true
         assigned_backchannel_application_name:
           type: string
           description: Application's display Name.
+          nullable: true
           readOnly: true
         verbose_name:
           type: string

ekoyle avatar Jun 28 '24 20:06 ekoyle

I am also seeing similar issues trying to create a provider via the API.

It looks like DRF doesn't honor required=False for ReadOnlyFields. Even though these fields have required=False in their ModelSerializer class, they are still showing up under the required: field list for the response object in schema.yml . There was a similar problem with allow_null which appears to have been resolved by https://github.com/encode/django-rest-framework/pull/8536 . It doesn't seem like adding "required": False for the field in extra_kwargs in the serializer Meta makes any difference on these. I think changes may be needed in DRF for required so that drf_spectacular gets the metadata it needs.

A possible workaround would be to set allow_null=True for these fields. That solves the python openapi binding issue, at least (not sure whether other libraries/bindings validate the responses the same way... it still seems like having the field not be required in the spec would be ideal).

See also: https://github.com/tfranzel/drf-spectacular/issues/383

ekoyle avatar Jun 29 '24 21:06 ekoyle

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

Can we bump this

kettleofketchup avatar Nov 22 '24 14:11 kettleofketchup

following for updates

txj-xyz avatar Jan 21 '25 22:01 txj-xyz

Any update on this? It seems to not be possible to get even a list of providers in ´2025.2.1.post1740858304´

pydantic_core._pydantic_core.ValidationError: 4 validation errors for ProxyProvider assigned_application_slug Input should be a valid string [type=string_type, input_value=None, input_type=NoneType] For further information visit https://errors.pydantic.dev/2.10/v/string_type assigned_application_name Input should be a valid string [type=string_type, input_value=None, input_type=NoneType] For further information visit https://errors.pydantic.dev/2.10/v/string_type assigned_backchannel_application_slug Input should be a valid string [type=string_type, input_value=None, input_type=NoneType] For further information visit https://errors.pydantic.dev/2.10/v/string_type assigned_backchannel_application_name Input should be a valid string [type=string_type, input_value=None, input_type=NoneType] For further information visit https://errors.pydantic.dev/2.10/v/string_type

File "/app/packages/automation/common/helper/authentik.py", line 274, in create_or_update_proxy_provider api_response = providers_api.providers_proxy_list(name__iexact=provider_request.name) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/app/.venv/lib/python3.12/site-packages/pydantic/_internal/_validate_call.py", line 38, in wrapper_function return wrapper(*args, **kwargs) ^^^^^^^^^^^^^^^^^^^^^^^^ File "/app/.venv/lib/python3.12/site-packages/pydantic/_internal/_validate_call.py", line 111, in call res = self.pydantic_validator.validate_python(pydantic_core.ArgsKwargs(args, kwargs)) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/app/.venv/lib/python3.12/site-packages/authentik_client/api/providers_api.py", line 17966, in providers_proxy_list return self.api_client.response_deserialize( ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/app/.venv/lib/python3.12/site-packages/authentik_client/api_client.py", line 316, in response_deserialize return_data = self.deserialize(response_text, response_type) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/app/.venv/lib/python3.12/site-packages/authentik_client/api_client.py", line 392, in deserialize return self.__deserialize(data, response_type) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/app/.venv/lib/python3.12/site-packages/authentik_client/api_client.py", line 437, in __deserialize return self.__deserialize_model(data, klass) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/app/.venv/lib/python3.12/site-packages/authentik_client/api_client.py", line 759, in __deserialize_model return klass.from_dict(data) ^^^^^^^^^^^^^^^^^^^^^ File "/app/.venv/lib/python3.12/site-packages/authentik_client/models/paginated_proxy_provider_list.py", line 98, in from_dict "results": [ProxyProvider.from_dict(_item) for _item in obj["results"]] if obj.get("results") is not None else None ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/app/.venv/lib/python3.12/site-packages/authentik_client/models/proxy_provider.py", line 157, in from_dict _obj = cls.model_validate({ ^^^^^^^^^^^^^^^^^^^^ File "/app/.venv/lib/python3.12/site-packages/pydantic/main.py", line 627, in model_validate return cls.pydantic_validator.validate_python( ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

dtomasi avatar Mar 10 '25 09:03 dtomasi