Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Validation Errors in authentik-client Python Library Due to API Returning Null Values for String Fields #12164

Open
isThatHim opened this issue Nov 22, 2024 · 3 comments
Labels
bug Something isn't working

Comments

@isThatHim
Copy link

Describe the bug

When using the authentik-client Python library to interact with the Authentik API, we encounter validation errors due to the API returning null values for certain fields that are expected to be non-null strings (StrictStr) in the client library's Pydantic models.

The problematic fields are:

  • assigned_application_slug
  • assigned_application_name
  • assigned_backchannel_application_slug
  • assigned_backchannel_application_name

These fields are defined as StrictStr in the Pydantic models, which do not accept None values. As a result, when the API returns null for these fields, deserialization fails with a ValidationError.

To Reproduce

Steps to reproduce the behavior:

  1. Set up an Authentik instance (version 2024.10.4) using Docker Compose.

  2. Install the authentik-client Python library and dependencies:

    pip install authentik-client==2024.10.4.post1732236734

    The pip output indicates the following versions for Pydantic and related packages:

    Using cached pydantic-2.10.1-py3-none-any.whl (455 kB)
    Using cached pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (2.1 MB)
    Using cached annotated_types-0.7.0-py3-none-any.whl (13 kB)
    
  3. Use the following script to retrieve OAuth2 providers:

    import authentik_client
    from authentik_client.api import ProvidersApi
    from authentik_client.exceptions import ApiException
    
    # Configure API client
    configuration = authentik_client.Configuration(
        host="https://your-authentik-instance",
        access_token="your_api_token"
    )
    api_client = authentik_client.ApiClient(configuration)
    providers_api = ProvidersApi(api_client)
    
    # Attempt to list OAuth2 providers
    try:
        providers = providers_api.providers_oauth2_list()
        for provider in providers.results:
            print(f"Provider Name: {provider.name}")
    except ApiException as e:
        print(f"An error occurred: {e}")
  4. Run the script.

Expected behavior

The script should successfully retrieve and print the names of the OAuth2 providers without any errors (even if they are not associated with any application).

Actual behavior

The script fails with a ValidationError from Pydantic, indicating that the fields received None values, which are not valid for StrictStr. The error occurs during deserialization of the API response.

Example error message:

ValidationError: 4 validation errors for OAuth2Provider
assigned_application_slug
  Input should be a valid string [type=string_type, input_value=None, input_type=NoneType]
assigned_application_name
  Input should be a valid string [type=string_type, input_value=None, input_type=NoneType]
assigned_backchannel_application_slug
  Input should be a valid string [type=string_type, input_value=None, input_type=NoneType]
assigned_backchannel_application_name
  Input should be a valid string [type=string_type, input_value=None, input_type=NoneType]

Logs

No additional logs are available beyond the error message provided above.

Version and Deployment

  • Authentik Version: 2024.10.4
  • Deployment Method: Docker Compose
  • authentik-client Library Version: 2024.10.4.post1732236734
  • Pydantic Version: 2.10.1
  • Pydantic Core Version: 2.27.1
  • Annotated Types Version: 0.7.0

Additional Context

  • The Authentik API returns null for the fields mentioned when they are not set.
  • The authentik-client library's Pydantic models define these fields as StrictStr, which does not accept None values.
  • This issue affects multiple models, including OAuth2Provider and Provider.
  • According to the API documentation, these fields are not optional and thus cannot be null.

Workaround

As a temporary workaround, we created a custom ApiClient subclass that preprocesses the API responses to replace null values with empty strings before deserialization. This approach avoids modifying the client library or Pydantic models directly.

Here is the code for the custom ApiClient:

import json
from authentik_client.api_client import ApiClient
import logging

logger = logging.getLogger(__name__)

class CustomApiClient(ApiClient):
    def deserialize(self, response_text, response_type):
        """Deserializes response into an object after preprocessing."""
        if response_text:
            try:
                data = json.loads(response_text)
            except ValueError:
                return super().deserialize(response_text, response_type)

            data = self.preprocess_data(data)
            logger.debug("Data after preprocessing: %s", data)
            response_text = json.dumps(data)

        return super().deserialize(response_text, response_type)

    def preprocess_data(self, data):
        """Recursively replace null or missing values with empty strings for specific fields."""
        if isinstance(data, dict):
            self.replace_null_fields(data)
            for key, value in data.items():
                if isinstance(value, (dict, list)):
                    data[key] = self.preprocess_data(value)
        elif isinstance(data, list):
            data = [self.preprocess_data(item) for item in data]
        return data

    def replace_null_fields(self, item):
        """Helper method to replace null or missing fields in a dictionary item."""
        for field in [
            'assigned_application_slug',
            'assigned_application_name',
            'assigned_backchannel_application_slug',
            'assigned_backchannel_application_name'
        ]:
            if item.get(field) is None:
                item[field] = ''
        return item

Usage of the Custom ApiClient:

# Configure API client with the custom ApiClient
configuration = authentik_client.Configuration(
    host="https://your-authentik-instance",
    access_token="your_api_token"
)
api_client = CustomApiClient(configuration)
providers_api = ProvidersApi(api_client)

# Proceed with the same code as before
try:
    providers = providers_api.providers_oauth2_list()
    for provider in providers.results:
        print(f"Provider Name: {provider.name}")
except ApiException as e:
    print(f"An error occurred: {e}")

Impact

  • This issue prevents users from programmatically interacting with the Authentik API using the official client library when these fields are null.
  • It affects automation scripts and integrations that rely on the authentik-client library.
  • The workaround adds complexity to the client code and may have performance implications due to recursive data processing.
  • It requires users to implement and maintain additional code to handle this inconsistency.

Possible Solution

  • Adjust the Pydantic models in the authentik-client library to accommodate None values for these fields.
  • Alternatively, modify the API to return empty strings or omit the fields when they are not set.
  • Modify the client library generation process to handle null values appropriately for these fields.

Request

  • Please consider updating the authentik-client library's Pydantic models or adjusting the API responses to resolve this inconsistency.
  • Any guidance on a recommended workaround that doesn't involve modifying the client library code or extensive preprocessing would also be appreciated.

Thank you for your attention to this matter.

@isThatHim isThatHim added the bug Something isn't working label Nov 22, 2024
@thcrt
Copy link

thcrt commented Dec 4, 2024

Seconding, I've also run into this issue through very basic use of the API. Without the workaround, this makes the Python API essentially unusable.

Thank you for sharing your workaround so I don't have to figure it out myself ;)

Unfortunately it seems the Python API is not a huge priority for the authentik team -- see #12170. Of course, this isn't a criticism of the team at all, authentik is a great piece of software provided for free under an open-source license on the basis of a great amount of work from both paid team members and volunteers. It would be nice, though, if the API could perhaps be officially spun off into a separate repository where changes and bug reports can more easily be maintained, tracked and worked on.

An open API is one of the best things that software can provide, and it would be really nice to see Authentik's Python API get a bit more love.

@authentik-automation
Copy link
Contributor

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.

@authentik-automation authentik-automation bot added the status/stale This issue has not been updated recently label Feb 3, 2025
@thcrt
Copy link

thcrt commented Feb 3, 2025

Issues don’t go away because people stop talking about them.

@authentik-automation authentik-automation bot removed the status/stale This issue has not been updated recently label Feb 4, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants