Skip to content

fix: Handle unknown Application signOnMode values gracefully#511

Open
BinoyOza-okta wants to merge 1 commit intomasterfrom
OKTA-1127597
Open

fix: Handle unknown Application signOnMode values gracefully#511
BinoyOza-okta wants to merge 1 commit intomasterfrom
OKTA-1127597

Conversation

@BinoyOza-okta
Copy link
Contributor

Handle Unknown Application SignOnMode Values

Problem

SDK throws deserialization errors when applications have unknown signOnMode values (e.g., MFA_AS_SERVICE).

Solution

Implements post-processing using OpenAPI Generator vendor extensions to preserve unknown signOnMode values.

Key Changes

  1. post_process_application.py - Custom deserialization logic
  2. api.yaml - Added x-post-process-function vendor extension
  3. model_generic.mustache - Invokes post-processing when defined
  4. ApplicationSignOnMode - Added OTHER enum value

How It Works

  • Unknown signOnMode values fall back to OTHER in discriminator
  • Post-processing preserves original API value
  • SDK remains forward-compatible with new Okta features

Benefits

✅ Forward compatibility
✅ Application-specific solution
✅ Preserves data integrity

Testing

Verified list_applications() works with MFA_AS_SERVICE and other unknown modes.
Commit: bef284d

This commit introduces a post-processing mechanism to handle Application
objects with signOnMode values that are not defined in the ApplicationSignOnMode
enum, preventing deserialization errors when new sign-on modes are introduced
by Okta.

Changes:
- Added post_process_application.py script with custom deserialization logic
- Implements x-post-process-function vendor extension support
- Updated api.yaml to reference post-processing for Application schema
- Modified model_generic.mustache to invoke post-processing when defined
- Added OTHER enum value to ApplicationSignOnMode for unknown modes

The solution uses OpenAPI Generator's vendor extension mechanism (x-post-process-function)
to apply custom deserialization logic specifically to the Application model without
affecting other models in the SDK.

When an Application response contains an unknown signOnMode:
1. Discriminator resolution falls back to 'OTHER' mapping
2. Post-processing preserves the original signOnMode value
3. Application object is returned with the actual value from API response
4. SDK remains forward-compatible with new Okta sign-on modes

This prevents breaking changes when Okta introduces new application types
like MFA_AS_SERVICE while maintaining type safety for known values.

Fixes: Deserialization errors for applications with unknown signOnMode values
aniket-okta

This comment was marked as outdated.

'BasicAuthApplication',
'BookmarkApplication',
'BrowserPluginApplication',
'Application'

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing trailing comma — Python silently concatenates adjacent string literals, so this becomes 'ApplicationOpenIdConnectApplication' as a single list element. OpenIdConnectApplication is silently dropped from the preload group, which can break discriminator resolution for OIDC apps.

Suggested change
'Application'
'Application',

from okta.models.browser_plugin_application import BrowserPluginApplication
from okta.models.application import Application
from okta.models.open_id_connect_application import OpenIdConnectApplication
from okta.models.application import Application

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicate import — from okta.models.application import Application is already imported on line 53. Remove this duplicate.

embedded: Optional[ApplicationEmbedded] = Field(default=None, alias="_embedded")
links: Optional[ApplicationLinks] = Field(default=None, alias="_links")
# Store the original sign-on mode value when it's not in the enum
_original_sign_on_mode: Optional[str] = None

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not idiomatic Pydantic v2 — underscore-prefixed fields are silently ignored by Pydantic (not in model_fields, not validated, not serialized), but the intent is unclear to readers. Use PrivateAttr to make this explicit:

Suggested change
_original_sign_on_mode: Optional[str] = None
_original_sign_on_mode: Optional[str] = PrivateAttr(default=None)

Also add PrivateAttr to the pydantic import at the top of the file.

if object_type == cls.__name__:
return cls.model_validate(obj)
return models.OpenIdConnectApplication.from_dict(obj)
if object_type == "Application":

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unreachable dead code — the first if object_type == "Application": block (line 391) always returns, so this second block can never be reached. This is a copy-paste artifact from adding the OpenIdConnectApplication entry. Remove lines 415–419 entirely.

mapped_class = cls.__discriminator_value_class_map.get(discriminator_value)
if mapped_class:
return mapped_class
# If not in mapping, return base class (will be handled by post-processing for Application)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Incorrect comment — post-processing only applies to Application, not ActionProvider (or any of the other 21 models with this same change). Also, silently swallowing unknown discriminator values here could mask real API schema changes. Consider logging a warning instead of silently falling back.

Suggested change
# If not in mapping, return base class (will be handled by post-processing for Application)
# Unknown discriminator value — fall back to base class to avoid crashing

SECURE_PASSWORD_STORE = "SECURE_PASSWORD_STORE"
WS_FEDERATION = "WS_FEDERATION"
MFA_AS_SERVICE = "MFA_AS_SERVICE"
OTHER = "OTHER"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OTHER is an SDK-internal sentinel — Okta's API never returns this value. Exposing it in the public enum misleads consumers and could cause failures if passed in write operations (e.g. PUT /api/v1/apps/{id}). Add a docstring note making the internal-only nature clear:

Suggested change
OTHER = "OTHER"
OTHER = "OTHER" # SDK-internal sentinel for unknown sign-on modes; never send this value to the Okta API

echo "========================================="
echo "Post-processing Application model..."
echo "========================================="
python3 post_process_application.py

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The changes this script applies are already manually committed to application.py in this PR. The script's own check_if_already_processed() guard will detect this and skip — making it a no-op on every generate.sh run. Additionally, the regex patterns are brittle; any generator formatting change will cause silent partial failure.

Pick one source of truth:

  • If manual edits are canonical → remove the script invocation from generate.sh
  • If the script is canonical → remove the manual edits from the committed application.py and harden the script with strict exit codes

Copy link

@aniket-okta aniket-okta left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review Summary

The core roundtrip logic (unknown signOnMode → store original → restore on serialise) works correctly per my local testing. However, there are two blocking bugs and several quality issues that need to be addressed before this can merge.

7 inline comments have been posted covering:

# Severity File Issue
1 🔴 Critical okta/models/__init__.py:44 Missing comma — Python silently concatenates 'Application' + 'OpenIdConnectApplication' into one string
2 🔴 Critical okta/models/application.py:55 Duplicate self-import — from okta.models.application import Application already imported above
3 🟡 Major okta/models/application.py:113 _original_sign_on_mode should use Pydantic PrivateAttr(default=None) instead of a public field
4 🟡 Major okta/models/application.py:415 Unreachable dead code — second if object_type == "Application": block never executes
5 🟡 Major okta/models/action_provider.py:76 Comment says "post-processing for Application" but this pattern was applied to all 22 discriminator models
6 🟡 Major okta/models/application_sign_on_mode.py:56 OTHER is SDK-internal; it must not round-trip as a literal string to the Okta API
7 🟡 Major openapi/generate.sh:15 post_process_application.py is a no-op (changes already manually applied); its regex patterns are also brittle

Please address the two 🔴 critical bugs at minimum before re-requesting a review.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants