Skip to content

Commit b8382ad

Browse files
committed
fix: Handle unknown Application signOnMode values gracefully
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
1 parent 2e30816 commit b8382ad

36 files changed

Lines changed: 382 additions & 31 deletions

docs/ApplicationSignOnMode.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# ApplicationSignOnMode
22

3-
Authentication mode for the app | signOnMode | Description | | ---------- | ----------- | | AUTO_LOGIN | Secure Web Authentication (SWA) | | BASIC_AUTH | HTTP Basic Authentication with Okta Browser Plugin | | BOOKMARK | Just a bookmark (no-authentication) | | BROWSER_PLUGIN | Secure Web Authentication (SWA) with Okta Browser Plugin | | OPENID_CONNECT | Federated Authentication with OpenID Connect (OIDC) | | SAML_1_1 | Federated Authentication with SAML 1.1 WebSSO (not supported for custom apps) | | SAML_2_0 | Federated Authentication with SAML 2.0 WebSSO | | SECURE_PASSWORD_STORE | Secure Web Authentication (SWA) with POST (plugin not required) | | WS_FEDERATION | Federated Authentication with WS-Federation Passive Requestor Profile | | MFA_AS_SERVICE | Application to use Okta's MFA as a service for RDP | Select the `signOnMode` for your custom app:
3+
Authentication mode for the app | signOnMode | Description | | ---------- | ----------- | | AUTO_LOGIN | Secure Web Authentication (SWA) | | BASIC_AUTH | HTTP Basic Authentication with Okta Browser Plugin | | BOOKMARK | Just a bookmark (no-authentication) | | BROWSER_PLUGIN | Secure Web Authentication (SWA) with Okta Browser Plugin | | OPENID_CONNECT | Federated Authentication with OpenID Connect (OIDC) | | SAML_1_1 | Federated Authentication with SAML 1.1 WebSSO (not supported for custom apps) | | SAML_2_0 | Federated Authentication with SAML 2.0 WebSSO | | SECURE_PASSWORD_STORE | Secure Web Authentication (SWA) with POST (plugin not required) | | WS_FEDERATION | Federated Authentication with WS-Federation Passive Requestor Profile | | MFA_AS_SERVICE | Application to use Okta's MFA as a service for RDP | | OTHER | Unknown Sign on mode | Select the `signOnMode` for your custom app:
44

55
## Properties
66

okta/models/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
'BasicAuthApplication',
4242
'BookmarkApplication',
4343
'BrowserPluginApplication',
44+
'Application'
4445
'OpenIdConnectApplication',
4546
'Saml11Application',
4647
'SamlApplication',

okta/models/action_provider.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,11 @@ def get_discriminator_value(cls, obj: Dict[str, Any]) -> Optional[str]:
7070
"""Returns the discriminator value (object type) of the data"""
7171
discriminator_value = obj[cls.__discriminator_property_name]
7272
if discriminator_value:
73-
return cls.__discriminator_value_class_map.get(discriminator_value)
73+
mapped_class = cls.__discriminator_value_class_map.get(discriminator_value)
74+
if mapped_class:
75+
return mapped_class
76+
# If not in mapping, return base class (will be handled by post-processing for Application)
77+
return "ActionProvider"
7478
else:
7579
return None
7680

okta/models/app_config.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,11 @@ def get_discriminator_value(cls, obj: Dict[str, Any]) -> Optional[str]:
6363
"""Returns the discriminator value (object type) of the data"""
6464
discriminator_value = obj[cls.__discriminator_property_name]
6565
if discriminator_value:
66-
return cls.__discriminator_value_class_map.get(discriminator_value)
66+
mapped_class = cls.__discriminator_value_class_map.get(discriminator_value)
67+
if mapped_class:
68+
return mapped_class
69+
# If not in mapping, return base class (will be handled by post-processing for Application)
70+
return "AppConfig"
6771
else:
6872
return None
6973

okta/models/application.py

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
from okta.models.browser_plugin_application import BrowserPluginApplication
5353
from okta.models.application import Application
5454
from okta.models.open_id_connect_application import OpenIdConnectApplication
55+
from okta.models.application import Application
5556
from okta.models.saml11_application import Saml11Application
5657
from okta.models.saml_application import SamlApplication
5758
from okta.models.secure_password_store_application import (
@@ -108,6 +109,8 @@ class Application(BaseModel): # noqa: F811
108109
visibility: Optional[ApplicationVisibility] = None
109110
embedded: Optional[ApplicationEmbedded] = Field(default=None, alias="_embedded")
110111
links: Optional[ApplicationLinks] = Field(default=None, alias="_links")
112+
# Store the original sign-on mode value when it's not in the enum
113+
_original_sign_on_mode: Optional[str] = None
111114
__properties: ClassVar[List[str]] = [
112115
"accessibility",
113116
"created",
@@ -212,6 +215,7 @@ def features_validate_enum(cls, value):
212215
"BROWSER_PLUGIN": "BrowserPluginApplication",
213216
"MFA_AS_SERVICE": "Application",
214217
"OPENID_CONNECT": "OpenIdConnectApplication",
218+
"OTHER": "Application",
215219
"SAML_1_1": "Saml11Application",
216220
"SAML_2_0": "SamlApplication",
217221
"SECURE_PASSWORD_STORE": "SecurePasswordStoreApplication",
@@ -223,7 +227,11 @@ def get_discriminator_value(cls, obj: Dict[str, Any]) -> Optional[str]:
223227
"""Returns the discriminator value (object type) of the data"""
224228
discriminator_value = obj[cls.__discriminator_property_name]
225229
if discriminator_value:
226-
return cls.__discriminator_value_class_map.get(discriminator_value)
230+
mapped_class = cls.__discriminator_value_class_map.get(discriminator_value)
231+
if mapped_class:
232+
return mapped_class
233+
# If not in mapping, return base class (will be handled by post-processing for Application)
234+
return "Application"
227235
else:
228236
return None
229237

@@ -245,6 +253,7 @@ def from_json(cls, json_str: str) -> Optional[
245253
BrowserPluginApplication,
246254
Application,
247255
OpenIdConnectApplication,
256+
Application,
248257
Saml11Application,
249258
SamlApplication,
250259
SecurePasswordStoreApplication,
@@ -333,6 +342,9 @@ def to_dict(self) -> Dict[str, Any]:
333342
else:
334343
_dict["_links"] = self.links
335344

345+
# If we have an original sign-on mode (was unknown), return it instead of OTHER
346+
if self._original_sign_on_mode:
347+
_dict["signOnMode"] = self._original_sign_on_mode
336348
return _dict
337349

338350
@classmethod
@@ -344,6 +356,7 @@ def from_dict(cls, obj: Dict[str, Any]) -> Optional[
344356
BrowserPluginApplication,
345357
Application,
346358
OpenIdConnectApplication,
359+
Application,
347360
Saml11Application,
348361
SamlApplication,
349362
SecurePasswordStoreApplication,
@@ -378,13 +391,32 @@ def from_dict(cls, obj: Dict[str, Any]) -> Optional[
378391
if object_type == "Application":
379392
# Check if the discriminator maps to the same class to avoid infinite recursion
380393
if object_type == cls.__name__:
394+
# Handle unknown sign-on modes
395+
original_discriminator = obj.get(cls.__discriminator_property_name)
396+
if (
397+
original_discriminator
398+
and original_discriminator
399+
not in cls.__discriminator_value_class_map
400+
):
401+
# Store the original value and replace with OTHER
402+
obj_copy = obj.copy()
403+
# Note: This assumes an OTHER value exists in the discriminator enum
404+
obj_copy[cls.__discriminator_property_name] = "OTHER"
405+
instance = cls.model_validate(obj_copy)
406+
instance._original_sign_on_mode = original_discriminator
407+
return instance
381408
return cls.model_validate(obj)
382409
return models.Application.from_dict(obj)
383410
if object_type == "OpenIdConnectApplication":
384411
# Check if the discriminator maps to the same class to avoid infinite recursion
385412
if object_type == cls.__name__:
386413
return cls.model_validate(obj)
387414
return models.OpenIdConnectApplication.from_dict(obj)
415+
if object_type == "Application":
416+
# Check if the discriminator maps to the same class to avoid infinite recursion
417+
if object_type == cls.__name__:
418+
return cls.model_validate(obj)
419+
return models.Application.from_dict(obj)
388420
if object_type == "Saml11Application":
389421
# Check if the discriminator maps to the same class to avoid infinite recursion
390422
if object_type == cls.__name__:

okta/models/application_feature.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,11 @@ def get_discriminator_value(cls, obj: Dict[str, Any]) -> Optional[str]:
7878
"""Returns the discriminator value (object type) of the data"""
7979
discriminator_value = obj[cls.__discriminator_property_name]
8080
if discriminator_value:
81-
return cls.__discriminator_value_class_map.get(discriminator_value)
81+
mapped_class = cls.__discriminator_value_class_map.get(discriminator_value)
82+
if mapped_class:
83+
return mapped_class
84+
# If not in mapping, return base class (will be handled by post-processing for Application)
85+
return "ApplicationFeature"
8286
else:
8387
return None
8488

okta/models/application_sign_on_mode.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ class ApplicationSignOnMode(str, Enum):
5353
SECURE_PASSWORD_STORE = "SECURE_PASSWORD_STORE"
5454
WS_FEDERATION = "WS_FEDERATION"
5555
MFA_AS_SERVICE = "MFA_AS_SERVICE"
56+
OTHER = "OTHER"
5657

5758
@classmethod
5859
def from_json(cls, json_str: str) -> Self:

okta/models/authenticator_base.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,11 @@ def get_discriminator_value(cls, obj: Dict[str, Any]) -> Optional[str]:
129129
"""Returns the discriminator value (object type) of the data"""
130130
discriminator_value = obj[cls.__discriminator_property_name]
131131
if discriminator_value:
132-
return cls.__discriminator_value_class_map.get(discriminator_value)
132+
mapped_class = cls.__discriminator_value_class_map.get(discriminator_value)
133+
if mapped_class:
134+
return mapped_class
135+
# If not in mapping, return base class (will be handled by post-processing for Application)
136+
return "AuthenticatorBase"
133137
else:
134138
return None
135139

okta/models/authenticator_method_base.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,11 @@ def get_discriminator_value(cls, obj: Dict[str, Any]) -> Optional[str]:
9393
"""Returns the discriminator value (object type) of the data"""
9494
discriminator_value = obj[cls.__discriminator_property_name]
9595
if discriminator_value:
96-
return cls.__discriminator_value_class_map.get(discriminator_value)
96+
mapped_class = cls.__discriminator_value_class_map.get(discriminator_value)
97+
if mapped_class:
98+
return mapped_class
99+
# If not in mapping, return base class (will be handled by post-processing for Application)
100+
return "AuthenticatorMethodBase"
97101
else:
98102
return None
99103

okta/models/authenticator_method_with_verifiable_properties.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,11 @@ def get_discriminator_value(cls, obj: Dict[str, Any]) -> Optional[str]:
9797
"""Returns the discriminator value (object type) of the data"""
9898
discriminator_value = obj[cls.__discriminator_property_name]
9999
if discriminator_value:
100-
return cls.__discriminator_value_class_map.get(discriminator_value)
100+
mapped_class = cls.__discriminator_value_class_map.get(discriminator_value)
101+
if mapped_class:
102+
return mapped_class
103+
# If not in mapping, return base class (will be handled by post-processing for Application)
104+
return "AuthenticatorMethodWithVerifiableProperties"
101105
else:
102106
return None
103107

0 commit comments

Comments
 (0)