goauthentik / goauthentik/authentik
Validation Errors in authentik-client Python Library Due to API Returning Null Values for String Fields
- Dominant language
- Python
- Stars
- 25.6k
- Forks
- 2k
- Avg merge
- 1d 2h
- Merged PRs (30d)
- 651
Description
**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**:
```bash
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**:
```python
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`:
```python
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`**:
```python
# 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.**
Contributor guide
Research direction
Start at the authentik-client Pydantic models for OAuth2Provider and Provider and the providers_oauth2_list() entry point; no repository file or test path is specified in the issue. Reproduce the response containing null values, then trace the API schema and client generation path. Done means the official client deserializes unassigned providers without custom response preprocessing, with regression coverage for these fields.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- api, authentication
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 45/100