From b8382ad9b9d778306bcea494d97b44ee1e1904b3 Mon Sep 17 00:00:00 2001 From: BinoyOza-okta Date: Thu, 12 Mar 2026 21:44:49 +0530 Subject: [PATCH 1/4] 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 --- docs/ApplicationSignOnMode.md | 2 +- okta/models/__init__.py | 1 + okta/models/action_provider.py | 6 +- okta/models/app_config.py | 6 +- okta/models/application.py | 34 +++- okta/models/application_feature.py | 6 +- okta/models/application_sign_on_mode.py | 1 + okta/models/authenticator_base.py | 6 +- okta/models/authenticator_method_base.py | 6 +- ...cator_method_with_verifiable_properties.py | 6 +- okta/models/authenticator_simple.py | 6 +- okta/models/available_action_provider.py | 6 +- okta/models/behavior_rule.py | 6 +- okta/models/device_assurance.py | 6 +- ...lment_policy_authenticator_grace_period.py | 6 +- okta/models/inline_hook_channel.py | 6 +- okta/models/inline_hook_channel_create.py | 6 +- okta/models/log_stream.py | 6 +- okta/models/log_stream_put_schema.py | 6 +- okta/models/network_zone.py | 6 +- ..._auth2_client_json_signing_key_response.py | 6 +- okta/models/policy.py | 6 +- okta/models/policy_rule.py | 6 +- okta/models/privileged_resource.py | 6 +- okta/models/push_provider.py | 6 +- .../registration_inline_hook_request.py | 6 +- okta/models/service_account.py | 6 +- okta/models/user_factor.py | 6 +- okta/models/user_factor_push_transaction.py | 6 +- okta/models/user_risk_get_response.py | 6 +- okta/models/validation_detail_provider.py | 6 +- okta/models/verification_method.py | 6 +- openapi/api.yaml | 4 + openapi/generate.sh | 8 + openapi/post_process_application.py | 189 ++++++++++++++++++ openapi/templates/model_generic.mustache | 6 +- 36 files changed, 382 insertions(+), 31 deletions(-) create mode 100644 openapi/post_process_application.py diff --git a/docs/ApplicationSignOnMode.md b/docs/ApplicationSignOnMode.md index 505d1bd2..049cf336 100644 --- a/docs/ApplicationSignOnMode.md +++ b/docs/ApplicationSignOnMode.md @@ -1,6 +1,6 @@ # ApplicationSignOnMode -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: +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: ## Properties diff --git a/okta/models/__init__.py b/okta/models/__init__.py index 1a3ed8b6..db7133a5 100644 --- a/okta/models/__init__.py +++ b/okta/models/__init__.py @@ -41,6 +41,7 @@ 'BasicAuthApplication', 'BookmarkApplication', 'BrowserPluginApplication', + 'Application' 'OpenIdConnectApplication', 'Saml11Application', 'SamlApplication', diff --git a/okta/models/action_provider.py b/okta/models/action_provider.py index 657b6fd5..79c2d926 100644 --- a/okta/models/action_provider.py +++ b/okta/models/action_provider.py @@ -70,7 +70,11 @@ def get_discriminator_value(cls, obj: Dict[str, Any]) -> Optional[str]: """Returns the discriminator value (object type) of the data""" discriminator_value = obj[cls.__discriminator_property_name] if discriminator_value: - return cls.__discriminator_value_class_map.get(discriminator_value) + 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) + return "ActionProvider" else: return None diff --git a/okta/models/app_config.py b/okta/models/app_config.py index d8d01076..55000d67 100644 --- a/okta/models/app_config.py +++ b/okta/models/app_config.py @@ -63,7 +63,11 @@ def get_discriminator_value(cls, obj: Dict[str, Any]) -> Optional[str]: """Returns the discriminator value (object type) of the data""" discriminator_value = obj[cls.__discriminator_property_name] if discriminator_value: - return cls.__discriminator_value_class_map.get(discriminator_value) + 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) + return "AppConfig" else: return None diff --git a/okta/models/application.py b/okta/models/application.py index 5c3641b7..52a14008 100644 --- a/okta/models/application.py +++ b/okta/models/application.py @@ -52,6 +52,7 @@ 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 from okta.models.saml11_application import Saml11Application from okta.models.saml_application import SamlApplication from okta.models.secure_password_store_application import ( @@ -108,6 +109,8 @@ class Application(BaseModel): # noqa: F811 visibility: Optional[ApplicationVisibility] = None 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 __properties: ClassVar[List[str]] = [ "accessibility", "created", @@ -212,6 +215,7 @@ def features_validate_enum(cls, value): "BROWSER_PLUGIN": "BrowserPluginApplication", "MFA_AS_SERVICE": "Application", "OPENID_CONNECT": "OpenIdConnectApplication", + "OTHER": "Application", "SAML_1_1": "Saml11Application", "SAML_2_0": "SamlApplication", "SECURE_PASSWORD_STORE": "SecurePasswordStoreApplication", @@ -223,7 +227,11 @@ def get_discriminator_value(cls, obj: Dict[str, Any]) -> Optional[str]: """Returns the discriminator value (object type) of the data""" discriminator_value = obj[cls.__discriminator_property_name] if discriminator_value: - return cls.__discriminator_value_class_map.get(discriminator_value) + 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) + return "Application" else: return None @@ -245,6 +253,7 @@ def from_json(cls, json_str: str) -> Optional[ BrowserPluginApplication, Application, OpenIdConnectApplication, + Application, Saml11Application, SamlApplication, SecurePasswordStoreApplication, @@ -333,6 +342,9 @@ def to_dict(self) -> Dict[str, Any]: else: _dict["_links"] = self.links + # If we have an original sign-on mode (was unknown), return it instead of OTHER + if self._original_sign_on_mode: + _dict["signOnMode"] = self._original_sign_on_mode return _dict @classmethod @@ -344,6 +356,7 @@ def from_dict(cls, obj: Dict[str, Any]) -> Optional[ BrowserPluginApplication, Application, OpenIdConnectApplication, + Application, Saml11Application, SamlApplication, SecurePasswordStoreApplication, @@ -378,6 +391,20 @@ def from_dict(cls, obj: Dict[str, Any]) -> Optional[ if object_type == "Application": # Check if the discriminator maps to the same class to avoid infinite recursion if object_type == cls.__name__: + # Handle unknown sign-on modes + original_discriminator = obj.get(cls.__discriminator_property_name) + if ( + original_discriminator + and original_discriminator + not in cls.__discriminator_value_class_map + ): + # Store the original value and replace with OTHER + obj_copy = obj.copy() + # Note: This assumes an OTHER value exists in the discriminator enum + obj_copy[cls.__discriminator_property_name] = "OTHER" + instance = cls.model_validate(obj_copy) + instance._original_sign_on_mode = original_discriminator + return instance return cls.model_validate(obj) return models.Application.from_dict(obj) if object_type == "OpenIdConnectApplication": @@ -385,6 +412,11 @@ def from_dict(cls, obj: Dict[str, Any]) -> Optional[ if object_type == cls.__name__: return cls.model_validate(obj) return models.OpenIdConnectApplication.from_dict(obj) + if object_type == "Application": + # Check if the discriminator maps to the same class to avoid infinite recursion + if object_type == cls.__name__: + return cls.model_validate(obj) + return models.Application.from_dict(obj) if object_type == "Saml11Application": # Check if the discriminator maps to the same class to avoid infinite recursion if object_type == cls.__name__: diff --git a/okta/models/application_feature.py b/okta/models/application_feature.py index 456ca92f..573c46e6 100644 --- a/okta/models/application_feature.py +++ b/okta/models/application_feature.py @@ -78,7 +78,11 @@ def get_discriminator_value(cls, obj: Dict[str, Any]) -> Optional[str]: """Returns the discriminator value (object type) of the data""" discriminator_value = obj[cls.__discriminator_property_name] if discriminator_value: - return cls.__discriminator_value_class_map.get(discriminator_value) + 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) + return "ApplicationFeature" else: return None diff --git a/okta/models/application_sign_on_mode.py b/okta/models/application_sign_on_mode.py index 011925f7..a1b8376f 100644 --- a/okta/models/application_sign_on_mode.py +++ b/okta/models/application_sign_on_mode.py @@ -53,6 +53,7 @@ class ApplicationSignOnMode(str, Enum): SECURE_PASSWORD_STORE = "SECURE_PASSWORD_STORE" WS_FEDERATION = "WS_FEDERATION" MFA_AS_SERVICE = "MFA_AS_SERVICE" + OTHER = "OTHER" @classmethod def from_json(cls, json_str: str) -> Self: diff --git a/okta/models/authenticator_base.py b/okta/models/authenticator_base.py index 1519eecc..4f9ec4f9 100644 --- a/okta/models/authenticator_base.py +++ b/okta/models/authenticator_base.py @@ -129,7 +129,11 @@ def get_discriminator_value(cls, obj: Dict[str, Any]) -> Optional[str]: """Returns the discriminator value (object type) of the data""" discriminator_value = obj[cls.__discriminator_property_name] if discriminator_value: - return cls.__discriminator_value_class_map.get(discriminator_value) + 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) + return "AuthenticatorBase" else: return None diff --git a/okta/models/authenticator_method_base.py b/okta/models/authenticator_method_base.py index 1bdb93c1..de0626ce 100644 --- a/okta/models/authenticator_method_base.py +++ b/okta/models/authenticator_method_base.py @@ -93,7 +93,11 @@ def get_discriminator_value(cls, obj: Dict[str, Any]) -> Optional[str]: """Returns the discriminator value (object type) of the data""" discriminator_value = obj[cls.__discriminator_property_name] if discriminator_value: - return cls.__discriminator_value_class_map.get(discriminator_value) + 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) + return "AuthenticatorMethodBase" else: return None diff --git a/okta/models/authenticator_method_with_verifiable_properties.py b/okta/models/authenticator_method_with_verifiable_properties.py index 4bc2f639..8c83016f 100644 --- a/okta/models/authenticator_method_with_verifiable_properties.py +++ b/okta/models/authenticator_method_with_verifiable_properties.py @@ -97,7 +97,11 @@ def get_discriminator_value(cls, obj: Dict[str, Any]) -> Optional[str]: """Returns the discriminator value (object type) of the data""" discriminator_value = obj[cls.__discriminator_property_name] if discriminator_value: - return cls.__discriminator_value_class_map.get(discriminator_value) + 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) + return "AuthenticatorMethodWithVerifiableProperties" else: return None diff --git a/okta/models/authenticator_simple.py b/okta/models/authenticator_simple.py index 84c98d9a..283259a8 100644 --- a/okta/models/authenticator_simple.py +++ b/okta/models/authenticator_simple.py @@ -105,7 +105,11 @@ def get_discriminator_value(cls, obj: Dict[str, Any]) -> Optional[str]: """Returns the discriminator value (object type) of the data""" discriminator_value = obj[cls.__discriminator_property_name] if discriminator_value: - return cls.__discriminator_value_class_map.get(discriminator_value) + 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) + return "AuthenticatorSimple" else: return None diff --git a/okta/models/available_action_provider.py b/okta/models/available_action_provider.py index 13640be0..78813125 100644 --- a/okta/models/available_action_provider.py +++ b/okta/models/available_action_provider.py @@ -75,7 +75,11 @@ def get_discriminator_value(cls, obj: Dict[str, Any]) -> Optional[str]: """Returns the discriminator value (object type) of the data""" discriminator_value = obj[cls.__discriminator_property_name] if discriminator_value: - return cls.__discriminator_value_class_map.get(discriminator_value) + 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) + return "AvailableActionProvider" else: return None diff --git a/okta/models/behavior_rule.py b/okta/models/behavior_rule.py index 644a887b..347268a0 100644 --- a/okta/models/behavior_rule.py +++ b/okta/models/behavior_rule.py @@ -103,7 +103,11 @@ def get_discriminator_value(cls, obj: Dict[str, Any]) -> Optional[str]: """Returns the discriminator value (object type) of the data""" discriminator_value = obj[cls.__discriminator_property_name] if discriminator_value: - return cls.__discriminator_value_class_map.get(discriminator_value) + 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) + return "BehaviorRule" else: return None diff --git a/okta/models/device_assurance.py b/okta/models/device_assurance.py index 8521543e..364c43fc 100644 --- a/okta/models/device_assurance.py +++ b/okta/models/device_assurance.py @@ -126,7 +126,11 @@ def get_discriminator_value(cls, obj: Dict[str, Any]) -> Optional[str]: """Returns the discriminator value (object type) of the data""" discriminator_value = obj[cls.__discriminator_property_name] if discriminator_value: - return cls.__discriminator_value_class_map.get(discriminator_value) + 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) + return "DeviceAssurance" else: return None diff --git a/okta/models/enrollment_policy_authenticator_grace_period.py b/okta/models/enrollment_policy_authenticator_grace_period.py index f15ff4d4..8d70481b 100644 --- a/okta/models/enrollment_policy_authenticator_grace_period.py +++ b/okta/models/enrollment_policy_authenticator_grace_period.py @@ -76,7 +76,11 @@ def get_discriminator_value(cls, obj: Dict[str, Any]) -> Optional[str]: """Returns the discriminator value (object type) of the data""" discriminator_value = obj[cls.__discriminator_property_name] if discriminator_value: - return cls.__discriminator_value_class_map.get(discriminator_value) + 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) + return "EnrollmentPolicyAuthenticatorGracePeriod" else: return None diff --git a/okta/models/inline_hook_channel.py b/okta/models/inline_hook_channel.py index 974c6793..43de5420 100644 --- a/okta/models/inline_hook_channel.py +++ b/okta/models/inline_hook_channel.py @@ -71,7 +71,11 @@ def get_discriminator_value(cls, obj: Dict[str, Any]) -> Optional[str]: """Returns the discriminator value (object type) of the data""" discriminator_value = obj[cls.__discriminator_property_name] if discriminator_value: - return cls.__discriminator_value_class_map.get(discriminator_value) + 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) + return "InlineHookChannel" else: return None diff --git a/okta/models/inline_hook_channel_create.py b/okta/models/inline_hook_channel_create.py index 584f1632..ce146c0c 100644 --- a/okta/models/inline_hook_channel_create.py +++ b/okta/models/inline_hook_channel_create.py @@ -73,7 +73,11 @@ def get_discriminator_value(cls, obj: Dict[str, Any]) -> Optional[str]: """Returns the discriminator value (object type) of the data""" discriminator_value = obj[cls.__discriminator_property_name] if discriminator_value: - return cls.__discriminator_value_class_map.get(discriminator_value) + 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) + return "InlineHookChannelCreate" else: return None diff --git a/okta/models/log_stream.py b/okta/models/log_stream.py index 3c31a514..1a88392a 100644 --- a/okta/models/log_stream.py +++ b/okta/models/log_stream.py @@ -97,7 +97,11 @@ def get_discriminator_value(cls, obj: Dict[str, Any]) -> Optional[str]: """Returns the discriminator value (object type) of the data""" discriminator_value = obj[cls.__discriminator_property_name] if discriminator_value: - return cls.__discriminator_value_class_map.get(discriminator_value) + 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) + return "LogStream" else: return None diff --git a/okta/models/log_stream_put_schema.py b/okta/models/log_stream_put_schema.py index 7093deae..9dab2cdd 100644 --- a/okta/models/log_stream_put_schema.py +++ b/okta/models/log_stream_put_schema.py @@ -68,7 +68,11 @@ def get_discriminator_value(cls, obj: Dict[str, Any]) -> Optional[str]: """Returns the discriminator value (object type) of the data""" discriminator_value = obj[cls.__discriminator_property_name] if discriminator_value: - return cls.__discriminator_value_class_map.get(discriminator_value) + 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) + return "LogStreamPutSchema" else: return None diff --git a/okta/models/network_zone.py b/okta/models/network_zone.py index d05a1810..821c14c0 100644 --- a/okta/models/network_zone.py +++ b/okta/models/network_zone.py @@ -108,7 +108,11 @@ def get_discriminator_value(cls, obj: Dict[str, Any]) -> Optional[str]: """Returns the discriminator value (object type) of the data""" discriminator_value = obj[cls.__discriminator_property_name] if discriminator_value: - return cls.__discriminator_value_class_map.get(discriminator_value) + 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) + return "NetworkZone" else: return None diff --git a/okta/models/o_auth2_client_json_signing_key_response.py b/okta/models/o_auth2_client_json_signing_key_response.py index f5b939eb..57040696 100644 --- a/okta/models/o_auth2_client_json_signing_key_response.py +++ b/okta/models/o_auth2_client_json_signing_key_response.py @@ -127,7 +127,11 @@ def get_discriminator_value(cls, obj: Dict[str, Any]) -> Optional[str]: """Returns the discriminator value (object type) of the data""" discriminator_value = obj[cls.__discriminator_property_name] if discriminator_value: - return cls.__discriminator_value_class_map.get(discriminator_value) + 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) + return "OAuth2ClientJsonSigningKeyResponse" else: return None diff --git a/okta/models/policy.py b/okta/models/policy.py index dc508702..01004ed7 100644 --- a/okta/models/policy.py +++ b/okta/models/policy.py @@ -125,7 +125,11 @@ def get_discriminator_value(cls, obj: Dict[str, Any]) -> Optional[str]: """Returns the discriminator value (object type) of the data""" discriminator_value = obj[cls.__discriminator_property_name] if discriminator_value: - return cls.__discriminator_value_class_map.get(discriminator_value) + 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) + return "Policy" else: return None diff --git a/okta/models/policy_rule.py b/okta/models/policy_rule.py index 5a208a1e..d44ec6a0 100644 --- a/okta/models/policy_rule.py +++ b/okta/models/policy_rule.py @@ -118,7 +118,11 @@ def get_discriminator_value(cls, obj: Dict[str, Any]) -> Optional[str]: """Returns the discriminator value (object type) of the data""" discriminator_value = obj[cls.__discriminator_property_name] if discriminator_value: - return cls.__discriminator_value_class_map.get(discriminator_value) + 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) + return "PolicyRule" else: return None diff --git a/okta/models/privileged_resource.py b/okta/models/privileged_resource.py index 65cca4e2..a5e1ff15 100644 --- a/okta/models/privileged_resource.py +++ b/okta/models/privileged_resource.py @@ -100,7 +100,11 @@ def get_discriminator_value(cls, obj: Dict[str, Any]) -> Optional[str]: """Returns the discriminator value (object type) of the data""" discriminator_value = obj[cls.__discriminator_property_name] if discriminator_value: - return cls.__discriminator_value_class_map.get(discriminator_value) + 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) + return "PrivilegedResource" else: return None diff --git a/okta/models/push_provider.py b/okta/models/push_provider.py index 01d53612..181e78f1 100644 --- a/okta/models/push_provider.py +++ b/okta/models/push_provider.py @@ -86,7 +86,11 @@ def get_discriminator_value(cls, obj: Dict[str, Any]) -> Optional[str]: """Returns the discriminator value (object type) of the data""" discriminator_value = obj[cls.__discriminator_property_name] if discriminator_value: - return cls.__discriminator_value_class_map.get(discriminator_value) + 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) + return "PushProvider" else: return None diff --git a/okta/models/registration_inline_hook_request.py b/okta/models/registration_inline_hook_request.py index 365054b3..07d0dd15 100644 --- a/okta/models/registration_inline_hook_request.py +++ b/okta/models/registration_inline_hook_request.py @@ -83,7 +83,11 @@ def get_discriminator_value(cls, obj: Dict[str, Any]) -> Optional[str]: """Returns the discriminator value (object type) of the data""" discriminator_value = obj[cls.__discriminator_property_name] if discriminator_value: - return cls.__discriminator_value_class_map.get(discriminator_value) + 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) + return "RegistrationInlineHookRequest" else: return None diff --git a/okta/models/service_account.py b/okta/models/service_account.py index 1a5cd48d..f5aba212 100644 --- a/okta/models/service_account.py +++ b/okta/models/service_account.py @@ -144,7 +144,11 @@ def get_discriminator_value(cls, obj: Dict[str, Any]) -> Optional[str]: """Returns the discriminator value (object type) of the data""" discriminator_value = obj[cls.__discriminator_property_name] if discriminator_value: - return cls.__discriminator_value_class_map.get(discriminator_value) + 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) + return "ServiceAccount" else: return None diff --git a/okta/models/user_factor.py b/okta/models/user_factor.py index 05911c07..351987ff 100644 --- a/okta/models/user_factor.py +++ b/okta/models/user_factor.py @@ -128,7 +128,11 @@ def get_discriminator_value(cls, obj: Dict[str, Any]) -> Optional[str]: """Returns the discriminator value (object type) of the data""" discriminator_value = obj[cls.__discriminator_property_name] if discriminator_value: - return cls.__discriminator_value_class_map.get(discriminator_value) + 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) + return "UserFactor" else: return None diff --git a/okta/models/user_factor_push_transaction.py b/okta/models/user_factor_push_transaction.py index c5172c7a..5af838e2 100644 --- a/okta/models/user_factor_push_transaction.py +++ b/okta/models/user_factor_push_transaction.py @@ -104,7 +104,11 @@ def get_discriminator_value(cls, obj: Dict[str, Any]) -> Optional[str]: """Returns the discriminator value (object type) of the data""" discriminator_value = obj[cls.__discriminator_property_name] if discriminator_value: - return cls.__discriminator_value_class_map.get(discriminator_value) + 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) + return "UserFactorPushTransaction" else: return None diff --git a/okta/models/user_risk_get_response.py b/okta/models/user_risk_get_response.py index 58c87f78..70b3cdc9 100644 --- a/okta/models/user_risk_get_response.py +++ b/okta/models/user_risk_get_response.py @@ -71,7 +71,11 @@ def get_discriminator_value(cls, obj: Dict[str, Any]) -> Optional[str]: """Returns the discriminator value (object type) of the data""" discriminator_value = obj[cls.__discriminator_property_name] if discriminator_value: - return cls.__discriminator_value_class_map.get(discriminator_value) + 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) + return "UserRiskGetResponse" else: return None diff --git a/okta/models/validation_detail_provider.py b/okta/models/validation_detail_provider.py index 9ebbac15..4ac7627d 100644 --- a/okta/models/validation_detail_provider.py +++ b/okta/models/validation_detail_provider.py @@ -71,7 +71,11 @@ def get_discriminator_value(cls, obj: Dict[str, Any]) -> Optional[str]: """Returns the discriminator value (object type) of the data""" discriminator_value = obj[cls.__discriminator_property_name] if discriminator_value: - return cls.__discriminator_value_class_map.get(discriminator_value) + 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) + return "ValidationDetailProvider" else: return None diff --git a/okta/models/verification_method.py b/okta/models/verification_method.py index d5e8edcb..ecec0e9c 100644 --- a/okta/models/verification_method.py +++ b/okta/models/verification_method.py @@ -73,7 +73,11 @@ def get_discriminator_value(cls, obj: Dict[str, Any]) -> Optional[str]: """Returns the discriminator value (object type) of the data""" discriminator_value = obj[cls.__discriminator_property_name] if discriminator_value: - return cls.__discriminator_value_class_map.get(discriminator_value) + 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) + return "VerificationMethod" else: return None diff --git a/openapi/api.yaml b/openapi/api.yaml index 2fe8bda9..4cb03def 100644 --- a/openapi/api.yaml +++ b/openapi/api.yaml @@ -59103,6 +59103,7 @@ components: maxLength: 1024 example: test team id Application: + x-handle-unknown-discriminator: true type: object properties: accessibility: @@ -59272,6 +59273,7 @@ components: SECURE_PASSWORD_STORE: '#/components/schemas/SecurePasswordStoreApplication' WS_FEDERATION: '#/components/schemas/WsFederationApplication' MFA_AS_SERVICE: '#/components/schemas/Application' + OTHER: '#/components/schemas/Application' ApplicationAccessibility: description: Specifies access settings for the app type: object @@ -59735,6 +59737,7 @@ components: | 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: type: string @@ -59749,6 +59752,7 @@ components: - SECURE_PASSWORD_STORE - WS_FEDERATION - MFA_AS_SERVICE + - OTHER ApplicationType: description: 'The type of client application. Default value: `web`.' type: string diff --git a/openapi/generate.sh b/openapi/generate.sh index 989e85d7..c63c32c6 100755 --- a/openapi/generate.sh +++ b/openapi/generate.sh @@ -6,3 +6,11 @@ export JAVA_TOOL_OPTIONS="-Dswagger.v3.parser.max_depth=5000 -Dsnakeyaml.maxAliasesForCollections=10000" java -Xmx12g -DmaxYamlCodePoints=10000000 -Dsnakeyaml.codepoint.limit=10000000 -jar openapi-generator-cli-7.7.0.jar generate -g python -c config.yaml -i api.yaml --skip-validate-spec #openapi-generator-cli generate -g python -c config.yaml -i api.yaml --skip-validate-spec + +# Post-process Application model to add unknown sign-on mode handling +echo "" +echo "=========================================" +echo "Post-processing Application model..." +echo "=========================================" +python3 post_process_application.py + diff --git a/openapi/post_process_application.py b/openapi/post_process_application.py new file mode 100644 index 00000000..c45faf08 --- /dev/null +++ b/openapi/post_process_application.py @@ -0,0 +1,189 @@ +#!/usr/bin/env python3 +""" +Post-processing script to add unknown discriminator handling ONLY to Application model. +This runs after SDK generation to add the specific logic without affecting other models. + +Usage: python3 post_process_application.py +""" + +import re +import sys +from pathlib import Path + +# Paths +APPLICATION_FILE = Path(__file__).parent.parent / 'okta' / 'models' / 'application.py' + +def check_if_already_processed(): + """Check if the file has already been processed""" + content = APPLICATION_FILE.read_text() + return '_original_sign_on_mode: Optional[str] = None' in content + +def add_original_field(): + """Add the _original_sign_on_mode field to the Application class""" + content = APPLICATION_FILE.read_text() + + # Pattern: after links field, before __properties + pattern = r'( links: Optional\[ApplicationLinks\] = Field\(default=None, alias="_links"\)\n)( __properties: ClassVar)' + + if not re.search(pattern, content): + print("⚠️ Warning: Could not find insertion point for _original_sign_on_mode field") + return False + + replacement = r"\1 # Store the original sign-on mode value when it's not in the enum\n _original_sign_on_mode: Optional[str] = None\n\2" + content = re.sub(pattern, replacement, content) + + APPLICATION_FILE.write_text(content) + print("✓ Added _original_sign_on_mode field") + return True + +def add_to_dict_logic(): + """Add logic to return original sign-on mode in to_dict method""" + content = APPLICATION_FILE.read_text() + + # Pattern: before "return _dict" in to_dict method + pattern = r"( # override the default output from pydantic by calling `to_dict\(\)` of links\n if self\.links:\n if not isinstance\(self\.links, dict\):\n _dict\['_links'\] = self\.links\.to_dict\(\)\n else:\n _dict\['_links'\] = self\.links\n\n)( return _dict)" + + if not re.search(pattern, content): + print("⚠️ Warning: Could not find insertion point for to_dict logic") + return False + + replacement = r"\1 # If we have an original sign-on mode (was unknown), return it instead of OTHER\n if self._original_sign_on_mode:\n _dict['signOnMode'] = self._original_sign_on_mode\n\2" + content = re.sub(pattern, replacement, content) + + APPLICATION_FILE.write_text(content) + print("✓ Added to_dict logic for original sign-on mode") + return True + +def add_from_dict_logic(): + """Add logic to handle unknown sign-on modes in from_dict method""" + content = APPLICATION_FILE.read_text() + + # Pattern: Find where Application class processes itself (first occurrence in the mappedModels loop) + # We need to find: if object_type == 'Application': ... if object_type == cls.__name__: return cls.model_validate(obj) + # Use a more specific pattern that only matches the first occurrence + pattern = r"( if object_type == 'Application':\n # Check if the discriminator maps to the same class to avoid infinite recursion\n if object_type == cls\.__name__:\n)( return cls\.model_validate\(obj\)\n)( return models\.Application\.from_dict\(obj\))" + + # Check if already processed + if "# Handle unknown sign-on modes" in content: + print(" → Already processed (skipping duplicate addition)") + return True + + if not re.search(pattern, content): + print("⚠️ Warning: Could not find insertion point for from_dict logic") + return False + + replacement = r'''\1 # Handle unknown sign-on modes + original_discriminator = obj.get(cls.__discriminator_property_name) + if original_discriminator and original_discriminator not in cls.__discriminator_value_class_map: + # Store the original value and replace with OTHER + obj_copy = obj.copy() + # Note: This assumes an OTHER value exists in the discriminator enum + obj_copy[cls.__discriminator_property_name] = "OTHER" + instance = cls.model_validate(obj_copy) + instance._original_sign_on_mode = original_discriminator + return instance +\2\3''' + + # Only replace the FIRST occurrence + content = re.sub(pattern, replacement, content, count=1) + + APPLICATION_FILE.write_text(content) + print("✓ Added from_dict logic for unknown sign-on mode handling") + return True + +def remove_discriminator_logic_from_other_models(): + """Remove the _original_ field from other discriminator-based models""" + models_dir = APPLICATION_FILE.parent + + # Find all Python files with discriminators (except Application) + count_fixed = 0 + for py_file in models_dir.glob('*.py'): + if py_file.name == 'application.py' or py_file.name == '__init__.py': + continue + + content = py_file.read_text() + + # Check if this file has the _original_ field pattern + if re.search(r'_original_\w+: Optional\[str\] = None', content): + # Remove the field + content = re.sub( + r' # Store the original discriminator value.*?\n _original_\w+: Optional\[str\] = None\n', + '', + content + ) + + # Remove the to_dict logic + content = re.sub( + r' # If we have an original discriminator value.*?\n.*?_dict\[.*?\] = self\._original_.*?\n', + '', + content + ) + + # Remove the from_dict logic (more complex pattern) + content = re.sub( + r' # Handle unknown discriminator values\n.*?instance\._original_.*?\n.*?return instance\n', + '', + content, + flags=re.DOTALL + ) + + py_file.write_text(content) + count_fixed += 1 + print(f" ✓ Cleaned up {py_file.name}") + + if count_fixed > 0: + print(f"✓ Removed discriminator logic from {count_fixed} other model(s)") + + return count_fixed + +def main(): + """Main function to apply Application-specific modifications""" + print("="*70) + print("Post-Processing: Adding Unknown Discriminator Handling to Application") + print("="*70) + + if not APPLICATION_FILE.exists(): + print(f"❌ Error: Application file not found at {APPLICATION_FILE}") + sys.exit(1) + + # Check if already processed + if check_if_already_processed(): + print("\n⚠️ Application.py appears to already be processed.") + print(" Skipping modifications to avoid duplication.") + print(" If you need to reprocess, regenerate the SDK first.") + return + + try: + success = True + success &= add_original_field() + success &= add_to_dict_logic() + success &= add_from_dict_logic() + + # Clean up other models + print("\nCleaning up other models...") + remove_discriminator_logic_from_other_models() + + if success: + print("\n" + "="*70) + print("✓ Post-processing completed successfully!") + print(" Application.py now handles unknown sign-on modes") + print(" Other models remain unaffected") + print("="*70) + else: + print("\n⚠️ Post-processing completed with warnings") + sys.exit(1) + + except Exception as e: + print(f"\n❌ Post-processing failed: {e}") + import traceback + traceback.print_exc() + sys.exit(1) + +if __name__ == "__main__": + main() + + + + + + diff --git a/openapi/templates/model_generic.mustache b/openapi/templates/model_generic.mustache index 68fc3743..35198fe3 100644 --- a/openapi/templates/model_generic.mustache +++ b/openapi/templates/model_generic.mustache @@ -107,7 +107,11 @@ class {{classname}}({{#parent}}{{{.}}}{{/parent}}{{^parent}}BaseModel{{/parent}} """Returns the discriminator value (object type) of the data""" discriminator_value = obj[cls.__discriminator_property_name] if discriminator_value: - return cls.__discriminator_value_class_map.get(discriminator_value) + 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) + return '{{classname}}' else: return None From 026e93ec0f1771410a1b9ab259af1a335165ae32 Mon Sep 17 00:00:00 2001 From: BinoyOza-okta Date: Fri, 13 Mar 2026 14:51:27 +0530 Subject: [PATCH 2/4] - Removed the OTHER enum type from api.yaml. - Removed vendor extension support from the model_generic.mustache. - Regenerated SDK for the templated changes under model_generic.mustache. --- docs/ApplicationSignOnMode.md | 2 +- okta/models/__init__.py | 1 - okta/models/action_provider.py | 6 +-- okta/models/app_config.py | 6 +-- okta/models/application.py | 37 +++---------------- okta/models/application_feature.py | 6 +-- okta/models/application_sign_on_mode.py | 1 - okta/models/authenticator_base.py | 6 +-- okta/models/authenticator_method_base.py | 6 +-- ...cator_method_with_verifiable_properties.py | 6 +-- okta/models/authenticator_simple.py | 6 +-- okta/models/available_action_provider.py | 6 +-- okta/models/behavior_rule.py | 6 +-- okta/models/device_assurance.py | 6 +-- ...lment_policy_authenticator_grace_period.py | 6 +-- okta/models/inline_hook_channel.py | 6 +-- okta/models/inline_hook_channel_create.py | 6 +-- okta/models/log_stream.py | 6 +-- okta/models/log_stream_put_schema.py | 6 +-- okta/models/network_zone.py | 6 +-- ..._auth2_client_json_signing_key_response.py | 6 +-- okta/models/policy.py | 6 +-- okta/models/policy_rule.py | 6 +-- okta/models/privileged_resource.py | 6 +-- okta/models/push_provider.py | 6 +-- .../registration_inline_hook_request.py | 6 +-- okta/models/service_account.py | 6 +-- okta/models/user_factor.py | 6 +-- okta/models/user_factor_push_transaction.py | 6 +-- okta/models/user_risk_get_response.py | 6 +-- okta/models/validation_detail_provider.py | 6 +-- okta/models/verification_method.py | 6 +-- openapi/api.yaml | 4 -- openapi/templates/model_generic.mustache | 6 +-- 34 files changed, 36 insertions(+), 183 deletions(-) diff --git a/docs/ApplicationSignOnMode.md b/docs/ApplicationSignOnMode.md index 049cf336..505d1bd2 100644 --- a/docs/ApplicationSignOnMode.md +++ b/docs/ApplicationSignOnMode.md @@ -1,6 +1,6 @@ # ApplicationSignOnMode -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: +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: ## Properties diff --git a/okta/models/__init__.py b/okta/models/__init__.py index db7133a5..1a3ed8b6 100644 --- a/okta/models/__init__.py +++ b/okta/models/__init__.py @@ -41,7 +41,6 @@ 'BasicAuthApplication', 'BookmarkApplication', 'BrowserPluginApplication', - 'Application' 'OpenIdConnectApplication', 'Saml11Application', 'SamlApplication', diff --git a/okta/models/action_provider.py b/okta/models/action_provider.py index 79c2d926..657b6fd5 100644 --- a/okta/models/action_provider.py +++ b/okta/models/action_provider.py @@ -70,11 +70,7 @@ def get_discriminator_value(cls, obj: Dict[str, Any]) -> Optional[str]: """Returns the discriminator value (object type) of the data""" discriminator_value = obj[cls.__discriminator_property_name] if discriminator_value: - 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) - return "ActionProvider" + return cls.__discriminator_value_class_map.get(discriminator_value) else: return None diff --git a/okta/models/app_config.py b/okta/models/app_config.py index 55000d67..d8d01076 100644 --- a/okta/models/app_config.py +++ b/okta/models/app_config.py @@ -63,11 +63,7 @@ def get_discriminator_value(cls, obj: Dict[str, Any]) -> Optional[str]: """Returns the discriminator value (object type) of the data""" discriminator_value = obj[cls.__discriminator_property_name] if discriminator_value: - 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) - return "AppConfig" + return cls.__discriminator_value_class_map.get(discriminator_value) else: return None diff --git a/okta/models/application.py b/okta/models/application.py index 52a14008..dee2536a 100644 --- a/okta/models/application.py +++ b/okta/models/application.py @@ -30,6 +30,11 @@ from typing import Any, ClassVar, Dict, List, Union from typing import Optional, Set from typing import TYPE_CHECKING +from okta.application_converter import ( + handle_unknown_sign_on_mode, + get_discriminator_value_safe, + restore_original_sign_on_mode, +) from pydantic import BaseModel, ConfigDict, Field, StrictStr, field_validator @@ -52,7 +57,6 @@ 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 from okta.models.saml11_application import Saml11Application from okta.models.saml_application import SamlApplication from okta.models.secure_password_store_application import ( @@ -215,7 +219,6 @@ def features_validate_enum(cls, value): "BROWSER_PLUGIN": "BrowserPluginApplication", "MFA_AS_SERVICE": "Application", "OPENID_CONNECT": "OpenIdConnectApplication", - "OTHER": "Application", "SAML_1_1": "Saml11Application", "SAML_2_0": "SamlApplication", "SECURE_PASSWORD_STORE": "SecurePasswordStoreApplication", @@ -227,11 +230,7 @@ def get_discriminator_value(cls, obj: Dict[str, Any]) -> Optional[str]: """Returns the discriminator value (object type) of the data""" discriminator_value = obj[cls.__discriminator_property_name] if discriminator_value: - 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) - return "Application" + return cls.__discriminator_value_class_map.get(discriminator_value) else: return None @@ -253,7 +252,6 @@ def from_json(cls, json_str: str) -> Optional[ BrowserPluginApplication, Application, OpenIdConnectApplication, - Application, Saml11Application, SamlApplication, SecurePasswordStoreApplication, @@ -342,9 +340,6 @@ def to_dict(self) -> Dict[str, Any]: else: _dict["_links"] = self.links - # If we have an original sign-on mode (was unknown), return it instead of OTHER - if self._original_sign_on_mode: - _dict["signOnMode"] = self._original_sign_on_mode return _dict @classmethod @@ -356,7 +351,6 @@ def from_dict(cls, obj: Dict[str, Any]) -> Optional[ BrowserPluginApplication, Application, OpenIdConnectApplication, - Application, Saml11Application, SamlApplication, SecurePasswordStoreApplication, @@ -391,20 +385,6 @@ def from_dict(cls, obj: Dict[str, Any]) -> Optional[ if object_type == "Application": # Check if the discriminator maps to the same class to avoid infinite recursion if object_type == cls.__name__: - # Handle unknown sign-on modes - original_discriminator = obj.get(cls.__discriminator_property_name) - if ( - original_discriminator - and original_discriminator - not in cls.__discriminator_value_class_map - ): - # Store the original value and replace with OTHER - obj_copy = obj.copy() - # Note: This assumes an OTHER value exists in the discriminator enum - obj_copy[cls.__discriminator_property_name] = "OTHER" - instance = cls.model_validate(obj_copy) - instance._original_sign_on_mode = original_discriminator - return instance return cls.model_validate(obj) return models.Application.from_dict(obj) if object_type == "OpenIdConnectApplication": @@ -412,11 +392,6 @@ def from_dict(cls, obj: Dict[str, Any]) -> Optional[ if object_type == cls.__name__: return cls.model_validate(obj) return models.OpenIdConnectApplication.from_dict(obj) - if object_type == "Application": - # Check if the discriminator maps to the same class to avoid infinite recursion - if object_type == cls.__name__: - return cls.model_validate(obj) - return models.Application.from_dict(obj) if object_type == "Saml11Application": # Check if the discriminator maps to the same class to avoid infinite recursion if object_type == cls.__name__: diff --git a/okta/models/application_feature.py b/okta/models/application_feature.py index 573c46e6..456ca92f 100644 --- a/okta/models/application_feature.py +++ b/okta/models/application_feature.py @@ -78,11 +78,7 @@ def get_discriminator_value(cls, obj: Dict[str, Any]) -> Optional[str]: """Returns the discriminator value (object type) of the data""" discriminator_value = obj[cls.__discriminator_property_name] if discriminator_value: - 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) - return "ApplicationFeature" + return cls.__discriminator_value_class_map.get(discriminator_value) else: return None diff --git a/okta/models/application_sign_on_mode.py b/okta/models/application_sign_on_mode.py index a1b8376f..011925f7 100644 --- a/okta/models/application_sign_on_mode.py +++ b/okta/models/application_sign_on_mode.py @@ -53,7 +53,6 @@ class ApplicationSignOnMode(str, Enum): SECURE_PASSWORD_STORE = "SECURE_PASSWORD_STORE" WS_FEDERATION = "WS_FEDERATION" MFA_AS_SERVICE = "MFA_AS_SERVICE" - OTHER = "OTHER" @classmethod def from_json(cls, json_str: str) -> Self: diff --git a/okta/models/authenticator_base.py b/okta/models/authenticator_base.py index 4f9ec4f9..1519eecc 100644 --- a/okta/models/authenticator_base.py +++ b/okta/models/authenticator_base.py @@ -129,11 +129,7 @@ def get_discriminator_value(cls, obj: Dict[str, Any]) -> Optional[str]: """Returns the discriminator value (object type) of the data""" discriminator_value = obj[cls.__discriminator_property_name] if discriminator_value: - 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) - return "AuthenticatorBase" + return cls.__discriminator_value_class_map.get(discriminator_value) else: return None diff --git a/okta/models/authenticator_method_base.py b/okta/models/authenticator_method_base.py index de0626ce..1bdb93c1 100644 --- a/okta/models/authenticator_method_base.py +++ b/okta/models/authenticator_method_base.py @@ -93,11 +93,7 @@ def get_discriminator_value(cls, obj: Dict[str, Any]) -> Optional[str]: """Returns the discriminator value (object type) of the data""" discriminator_value = obj[cls.__discriminator_property_name] if discriminator_value: - 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) - return "AuthenticatorMethodBase" + return cls.__discriminator_value_class_map.get(discriminator_value) else: return None diff --git a/okta/models/authenticator_method_with_verifiable_properties.py b/okta/models/authenticator_method_with_verifiable_properties.py index 8c83016f..4bc2f639 100644 --- a/okta/models/authenticator_method_with_verifiable_properties.py +++ b/okta/models/authenticator_method_with_verifiable_properties.py @@ -97,11 +97,7 @@ def get_discriminator_value(cls, obj: Dict[str, Any]) -> Optional[str]: """Returns the discriminator value (object type) of the data""" discriminator_value = obj[cls.__discriminator_property_name] if discriminator_value: - 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) - return "AuthenticatorMethodWithVerifiableProperties" + return cls.__discriminator_value_class_map.get(discriminator_value) else: return None diff --git a/okta/models/authenticator_simple.py b/okta/models/authenticator_simple.py index 283259a8..84c98d9a 100644 --- a/okta/models/authenticator_simple.py +++ b/okta/models/authenticator_simple.py @@ -105,11 +105,7 @@ def get_discriminator_value(cls, obj: Dict[str, Any]) -> Optional[str]: """Returns the discriminator value (object type) of the data""" discriminator_value = obj[cls.__discriminator_property_name] if discriminator_value: - 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) - return "AuthenticatorSimple" + return cls.__discriminator_value_class_map.get(discriminator_value) else: return None diff --git a/okta/models/available_action_provider.py b/okta/models/available_action_provider.py index 78813125..13640be0 100644 --- a/okta/models/available_action_provider.py +++ b/okta/models/available_action_provider.py @@ -75,11 +75,7 @@ def get_discriminator_value(cls, obj: Dict[str, Any]) -> Optional[str]: """Returns the discriminator value (object type) of the data""" discriminator_value = obj[cls.__discriminator_property_name] if discriminator_value: - 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) - return "AvailableActionProvider" + return cls.__discriminator_value_class_map.get(discriminator_value) else: return None diff --git a/okta/models/behavior_rule.py b/okta/models/behavior_rule.py index 347268a0..644a887b 100644 --- a/okta/models/behavior_rule.py +++ b/okta/models/behavior_rule.py @@ -103,11 +103,7 @@ def get_discriminator_value(cls, obj: Dict[str, Any]) -> Optional[str]: """Returns the discriminator value (object type) of the data""" discriminator_value = obj[cls.__discriminator_property_name] if discriminator_value: - 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) - return "BehaviorRule" + return cls.__discriminator_value_class_map.get(discriminator_value) else: return None diff --git a/okta/models/device_assurance.py b/okta/models/device_assurance.py index 364c43fc..8521543e 100644 --- a/okta/models/device_assurance.py +++ b/okta/models/device_assurance.py @@ -126,11 +126,7 @@ def get_discriminator_value(cls, obj: Dict[str, Any]) -> Optional[str]: """Returns the discriminator value (object type) of the data""" discriminator_value = obj[cls.__discriminator_property_name] if discriminator_value: - 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) - return "DeviceAssurance" + return cls.__discriminator_value_class_map.get(discriminator_value) else: return None diff --git a/okta/models/enrollment_policy_authenticator_grace_period.py b/okta/models/enrollment_policy_authenticator_grace_period.py index 8d70481b..f15ff4d4 100644 --- a/okta/models/enrollment_policy_authenticator_grace_period.py +++ b/okta/models/enrollment_policy_authenticator_grace_period.py @@ -76,11 +76,7 @@ def get_discriminator_value(cls, obj: Dict[str, Any]) -> Optional[str]: """Returns the discriminator value (object type) of the data""" discriminator_value = obj[cls.__discriminator_property_name] if discriminator_value: - 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) - return "EnrollmentPolicyAuthenticatorGracePeriod" + return cls.__discriminator_value_class_map.get(discriminator_value) else: return None diff --git a/okta/models/inline_hook_channel.py b/okta/models/inline_hook_channel.py index 43de5420..974c6793 100644 --- a/okta/models/inline_hook_channel.py +++ b/okta/models/inline_hook_channel.py @@ -71,11 +71,7 @@ def get_discriminator_value(cls, obj: Dict[str, Any]) -> Optional[str]: """Returns the discriminator value (object type) of the data""" discriminator_value = obj[cls.__discriminator_property_name] if discriminator_value: - 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) - return "InlineHookChannel" + return cls.__discriminator_value_class_map.get(discriminator_value) else: return None diff --git a/okta/models/inline_hook_channel_create.py b/okta/models/inline_hook_channel_create.py index ce146c0c..584f1632 100644 --- a/okta/models/inline_hook_channel_create.py +++ b/okta/models/inline_hook_channel_create.py @@ -73,11 +73,7 @@ def get_discriminator_value(cls, obj: Dict[str, Any]) -> Optional[str]: """Returns the discriminator value (object type) of the data""" discriminator_value = obj[cls.__discriminator_property_name] if discriminator_value: - 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) - return "InlineHookChannelCreate" + return cls.__discriminator_value_class_map.get(discriminator_value) else: return None diff --git a/okta/models/log_stream.py b/okta/models/log_stream.py index 1a88392a..3c31a514 100644 --- a/okta/models/log_stream.py +++ b/okta/models/log_stream.py @@ -97,11 +97,7 @@ def get_discriminator_value(cls, obj: Dict[str, Any]) -> Optional[str]: """Returns the discriminator value (object type) of the data""" discriminator_value = obj[cls.__discriminator_property_name] if discriminator_value: - 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) - return "LogStream" + return cls.__discriminator_value_class_map.get(discriminator_value) else: return None diff --git a/okta/models/log_stream_put_schema.py b/okta/models/log_stream_put_schema.py index 9dab2cdd..7093deae 100644 --- a/okta/models/log_stream_put_schema.py +++ b/okta/models/log_stream_put_schema.py @@ -68,11 +68,7 @@ def get_discriminator_value(cls, obj: Dict[str, Any]) -> Optional[str]: """Returns the discriminator value (object type) of the data""" discriminator_value = obj[cls.__discriminator_property_name] if discriminator_value: - 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) - return "LogStreamPutSchema" + return cls.__discriminator_value_class_map.get(discriminator_value) else: return None diff --git a/okta/models/network_zone.py b/okta/models/network_zone.py index 821c14c0..d05a1810 100644 --- a/okta/models/network_zone.py +++ b/okta/models/network_zone.py @@ -108,11 +108,7 @@ def get_discriminator_value(cls, obj: Dict[str, Any]) -> Optional[str]: """Returns the discriminator value (object type) of the data""" discriminator_value = obj[cls.__discriminator_property_name] if discriminator_value: - 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) - return "NetworkZone" + return cls.__discriminator_value_class_map.get(discriminator_value) else: return None diff --git a/okta/models/o_auth2_client_json_signing_key_response.py b/okta/models/o_auth2_client_json_signing_key_response.py index 57040696..f5b939eb 100644 --- a/okta/models/o_auth2_client_json_signing_key_response.py +++ b/okta/models/o_auth2_client_json_signing_key_response.py @@ -127,11 +127,7 @@ def get_discriminator_value(cls, obj: Dict[str, Any]) -> Optional[str]: """Returns the discriminator value (object type) of the data""" discriminator_value = obj[cls.__discriminator_property_name] if discriminator_value: - 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) - return "OAuth2ClientJsonSigningKeyResponse" + return cls.__discriminator_value_class_map.get(discriminator_value) else: return None diff --git a/okta/models/policy.py b/okta/models/policy.py index 01004ed7..dc508702 100644 --- a/okta/models/policy.py +++ b/okta/models/policy.py @@ -125,11 +125,7 @@ def get_discriminator_value(cls, obj: Dict[str, Any]) -> Optional[str]: """Returns the discriminator value (object type) of the data""" discriminator_value = obj[cls.__discriminator_property_name] if discriminator_value: - 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) - return "Policy" + return cls.__discriminator_value_class_map.get(discriminator_value) else: return None diff --git a/okta/models/policy_rule.py b/okta/models/policy_rule.py index d44ec6a0..5a208a1e 100644 --- a/okta/models/policy_rule.py +++ b/okta/models/policy_rule.py @@ -118,11 +118,7 @@ def get_discriminator_value(cls, obj: Dict[str, Any]) -> Optional[str]: """Returns the discriminator value (object type) of the data""" discriminator_value = obj[cls.__discriminator_property_name] if discriminator_value: - 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) - return "PolicyRule" + return cls.__discriminator_value_class_map.get(discriminator_value) else: return None diff --git a/okta/models/privileged_resource.py b/okta/models/privileged_resource.py index a5e1ff15..65cca4e2 100644 --- a/okta/models/privileged_resource.py +++ b/okta/models/privileged_resource.py @@ -100,11 +100,7 @@ def get_discriminator_value(cls, obj: Dict[str, Any]) -> Optional[str]: """Returns the discriminator value (object type) of the data""" discriminator_value = obj[cls.__discriminator_property_name] if discriminator_value: - 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) - return "PrivilegedResource" + return cls.__discriminator_value_class_map.get(discriminator_value) else: return None diff --git a/okta/models/push_provider.py b/okta/models/push_provider.py index 181e78f1..01d53612 100644 --- a/okta/models/push_provider.py +++ b/okta/models/push_provider.py @@ -86,11 +86,7 @@ def get_discriminator_value(cls, obj: Dict[str, Any]) -> Optional[str]: """Returns the discriminator value (object type) of the data""" discriminator_value = obj[cls.__discriminator_property_name] if discriminator_value: - 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) - return "PushProvider" + return cls.__discriminator_value_class_map.get(discriminator_value) else: return None diff --git a/okta/models/registration_inline_hook_request.py b/okta/models/registration_inline_hook_request.py index 07d0dd15..365054b3 100644 --- a/okta/models/registration_inline_hook_request.py +++ b/okta/models/registration_inline_hook_request.py @@ -83,11 +83,7 @@ def get_discriminator_value(cls, obj: Dict[str, Any]) -> Optional[str]: """Returns the discriminator value (object type) of the data""" discriminator_value = obj[cls.__discriminator_property_name] if discriminator_value: - 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) - return "RegistrationInlineHookRequest" + return cls.__discriminator_value_class_map.get(discriminator_value) else: return None diff --git a/okta/models/service_account.py b/okta/models/service_account.py index f5aba212..1a5cd48d 100644 --- a/okta/models/service_account.py +++ b/okta/models/service_account.py @@ -144,11 +144,7 @@ def get_discriminator_value(cls, obj: Dict[str, Any]) -> Optional[str]: """Returns the discriminator value (object type) of the data""" discriminator_value = obj[cls.__discriminator_property_name] if discriminator_value: - 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) - return "ServiceAccount" + return cls.__discriminator_value_class_map.get(discriminator_value) else: return None diff --git a/okta/models/user_factor.py b/okta/models/user_factor.py index 351987ff..05911c07 100644 --- a/okta/models/user_factor.py +++ b/okta/models/user_factor.py @@ -128,11 +128,7 @@ def get_discriminator_value(cls, obj: Dict[str, Any]) -> Optional[str]: """Returns the discriminator value (object type) of the data""" discriminator_value = obj[cls.__discriminator_property_name] if discriminator_value: - 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) - return "UserFactor" + return cls.__discriminator_value_class_map.get(discriminator_value) else: return None diff --git a/okta/models/user_factor_push_transaction.py b/okta/models/user_factor_push_transaction.py index 5af838e2..c5172c7a 100644 --- a/okta/models/user_factor_push_transaction.py +++ b/okta/models/user_factor_push_transaction.py @@ -104,11 +104,7 @@ def get_discriminator_value(cls, obj: Dict[str, Any]) -> Optional[str]: """Returns the discriminator value (object type) of the data""" discriminator_value = obj[cls.__discriminator_property_name] if discriminator_value: - 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) - return "UserFactorPushTransaction" + return cls.__discriminator_value_class_map.get(discriminator_value) else: return None diff --git a/okta/models/user_risk_get_response.py b/okta/models/user_risk_get_response.py index 70b3cdc9..58c87f78 100644 --- a/okta/models/user_risk_get_response.py +++ b/okta/models/user_risk_get_response.py @@ -71,11 +71,7 @@ def get_discriminator_value(cls, obj: Dict[str, Any]) -> Optional[str]: """Returns the discriminator value (object type) of the data""" discriminator_value = obj[cls.__discriminator_property_name] if discriminator_value: - 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) - return "UserRiskGetResponse" + return cls.__discriminator_value_class_map.get(discriminator_value) else: return None diff --git a/okta/models/validation_detail_provider.py b/okta/models/validation_detail_provider.py index 4ac7627d..9ebbac15 100644 --- a/okta/models/validation_detail_provider.py +++ b/okta/models/validation_detail_provider.py @@ -71,11 +71,7 @@ def get_discriminator_value(cls, obj: Dict[str, Any]) -> Optional[str]: """Returns the discriminator value (object type) of the data""" discriminator_value = obj[cls.__discriminator_property_name] if discriminator_value: - 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) - return "ValidationDetailProvider" + return cls.__discriminator_value_class_map.get(discriminator_value) else: return None diff --git a/okta/models/verification_method.py b/okta/models/verification_method.py index ecec0e9c..d5e8edcb 100644 --- a/okta/models/verification_method.py +++ b/okta/models/verification_method.py @@ -73,11 +73,7 @@ def get_discriminator_value(cls, obj: Dict[str, Any]) -> Optional[str]: """Returns the discriminator value (object type) of the data""" discriminator_value = obj[cls.__discriminator_property_name] if discriminator_value: - 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) - return "VerificationMethod" + return cls.__discriminator_value_class_map.get(discriminator_value) else: return None diff --git a/openapi/api.yaml b/openapi/api.yaml index 4cb03def..2fe8bda9 100644 --- a/openapi/api.yaml +++ b/openapi/api.yaml @@ -59103,7 +59103,6 @@ components: maxLength: 1024 example: test team id Application: - x-handle-unknown-discriminator: true type: object properties: accessibility: @@ -59273,7 +59272,6 @@ components: SECURE_PASSWORD_STORE: '#/components/schemas/SecurePasswordStoreApplication' WS_FEDERATION: '#/components/schemas/WsFederationApplication' MFA_AS_SERVICE: '#/components/schemas/Application' - OTHER: '#/components/schemas/Application' ApplicationAccessibility: description: Specifies access settings for the app type: object @@ -59737,7 +59735,6 @@ components: | 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: type: string @@ -59752,7 +59749,6 @@ components: - SECURE_PASSWORD_STORE - WS_FEDERATION - MFA_AS_SERVICE - - OTHER ApplicationType: description: 'The type of client application. Default value: `web`.' type: string diff --git a/openapi/templates/model_generic.mustache b/openapi/templates/model_generic.mustache index 35198fe3..68fc3743 100644 --- a/openapi/templates/model_generic.mustache +++ b/openapi/templates/model_generic.mustache @@ -107,11 +107,7 @@ class {{classname}}({{#parent}}{{{.}}}{{/parent}}{{^parent}}BaseModel{{/parent}} """Returns the discriminator value (object type) of the data""" discriminator_value = obj[cls.__discriminator_property_name] if discriminator_value: - 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) - return '{{classname}}' + return cls.__discriminator_value_class_map.get(discriminator_value) else: return None From e7172d70f144669602f822961ffebcdc5b4512a0 Mon Sep 17 00:00:00 2001 From: BinoyOza-okta Date: Mon, 16 Mar 2026 01:47:55 +0530 Subject: [PATCH 3/4] feat: Handle unknown signOnMode values gracefully in Application models Implement custom ApplicationJsonConverter to handle unknown, null, and future signOnMode values without breaking SDK functionality. This enables forward compatibility when Okta introduces new application types. Key changes: - Add ApplicationJsonConverter for polymorphic Application deserialization - Route null signOnMode to ActiveDirectoryApplication - Preserve unknown signOnMode values for round-trip fidelity - Make sign_on_mode Optional to support null values - Store original unknown values in _original_sign_on_mode attribute - Use model_validate() to avoid recursion in from_dict() - Use model_dump(mode='json') for proper enum serialization The converter is only required in the base Application class as: 1. Unknown modes are architecturally impossible in subclasses 2. ApiClient.sanitize_for_serialization() handles enum conversion 3. Subclasses can only have known enum values (enforced by Pydantic) This implementation matches the .NET SDK approach where unknown modes are handled gracefully without spec changes. Fixes: Unknown signOnMode values causing ValidationError --- docs/ActiveDirectoryApplication.md | 31 +++ docs/ActiveDirectoryApplicationSettings.md | 36 +++ ...DirectoryApplicationSettingsApplication.md | 37 +++ docs/Application.md | 2 +- okta/__init__.py | 3 + okta/models/__init__.py | 4 + okta/models/action_provider.py | 2 +- okta/models/active_directory_application.py | 245 ++++++++++++++++++ .../active_directory_application_settings.py | 201 ++++++++++++++ ...ectory_application_settings_application.py | 183 +++++++++++++ okta/models/app_config.py | 2 +- okta/models/application.py | 150 +---------- okta/models/application_feature.py | 2 +- okta/models/application_json_converter.py | 205 +++++++++++++++ okta/models/authenticator_base.py | 2 +- okta/models/authenticator_method_base.py | 2 +- ...cator_method_with_verifiable_properties.py | 2 +- okta/models/authenticator_simple.py | 2 +- okta/models/available_action_provider.py | 2 +- okta/models/behavior_rule.py | 2 +- okta/models/device_assurance.py | 2 +- ...lment_policy_authenticator_grace_period.py | 2 +- okta/models/inline_hook_channel.py | 2 +- okta/models/inline_hook_channel_create.py | 2 +- okta/models/log_stream.py | 2 +- okta/models/log_stream_put_schema.py | 2 +- okta/models/network_zone.py | 2 +- ..._auth2_client_json_signing_key_response.py | 2 +- okta/models/policy.py | 2 +- okta/models/policy_rule.py | 2 +- okta/models/privileged_resource.py | 2 +- okta/models/push_provider.py | 2 +- .../registration_inline_hook_request.py | 2 +- okta/models/service_account.py | 2 +- okta/models/user_factor.py | 2 +- okta/models/user_factor_push_transaction.py | 2 +- okta/models/user_risk_get_response.py | 2 +- okta/models/validation_detail_provider.py | 2 +- okta/models/verification_method.py | 2 +- openapi/api.yaml | 56 +++- openapi/config.yaml | 4 + openapi/generate.sh | 8 - openapi/post_process_application.py | 189 -------------- .../application_json_converter.mustache | 221 ++++++++++++++++ openapi/templates/model_generic.mustache | 14 +- 45 files changed, 1280 insertions(+), 365 deletions(-) create mode 100644 docs/ActiveDirectoryApplication.md create mode 100644 docs/ActiveDirectoryApplicationSettings.md create mode 100644 docs/ActiveDirectoryApplicationSettingsApplication.md create mode 100644 okta/models/active_directory_application.py create mode 100644 okta/models/active_directory_application_settings.py create mode 100644 okta/models/active_directory_application_settings_application.py create mode 100644 okta/models/application_json_converter.py delete mode 100644 openapi/post_process_application.py create mode 100644 openapi/templates/application_json_converter.mustache diff --git a/docs/ActiveDirectoryApplication.md b/docs/ActiveDirectoryApplication.md new file mode 100644 index 00000000..ddbdc27e --- /dev/null +++ b/docs/ActiveDirectoryApplication.md @@ -0,0 +1,31 @@ +# ActiveDirectoryApplication + +Active Directory application for directory integrations. This application type has a null signOnMode. + +## Properties + +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**name** | **str** | Unique key for the Active Directory app definition. Always 'active_directory' for AD apps. | [optional] [readonly] +**settings** | [**ActiveDirectoryApplicationSettings**](ActiveDirectoryApplicationSettings.md) | | [optional] + +## Example + +```python +from okta.models.active_directory_application import ActiveDirectoryApplication + +# TODO update the JSON string below +json = "{}" +# create an instance of ActiveDirectoryApplication from a JSON string +active_directory_application_instance = ActiveDirectoryApplication.from_json(json) +# print the JSON string representation of the object +print(ActiveDirectoryApplication.to_json()) + +# convert the object into a dict +active_directory_application_dict = active_directory_application_instance.to_dict() +# create an instance of ActiveDirectoryApplication from a dict +active_directory_application_from_dict = ActiveDirectoryApplication.from_dict(active_directory_application_dict) +``` +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/docs/ActiveDirectoryApplicationSettings.md b/docs/ActiveDirectoryApplicationSettings.md new file mode 100644 index 00000000..21a1e758 --- /dev/null +++ b/docs/ActiveDirectoryApplicationSettings.md @@ -0,0 +1,36 @@ +# ActiveDirectoryApplicationSettings + +Settings for Active Directory applications + +## Properties + +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**em_opt_in_status** | **str** | The entitlement management opt-in status for the app | [optional] [readonly] +**identity_store_id** | **str** | Identifies an additional identity store app, if your app supports it. The `identityStoreId` value must be a valid identity store app ID. This identity store app must be created in the same org as your app. | [optional] +**implicit_assignment** | **bool** | Controls whether Okta automatically assigns users to the app based on the user's role or group membership. | [optional] +**inline_hook_id** | **str** | Identifier of an inline hook. Inline hooks are outbound calls from Okta to your own custom code, triggered at specific points in Okta process flows. They allow you to integrate custom functionality into those flows. See [Inline hooks](/openapi/okta-management/management/tag/InlineHook/). | [optional] +**notes** | [**ApplicationSettingsNotes**](ApplicationSettingsNotes.md) | | [optional] +**notifications** | [**ApplicationSettingsNotifications**](ApplicationSettingsNotifications.md) | | [optional] +**app** | [**ActiveDirectoryApplicationSettingsApplication**](ActiveDirectoryApplicationSettingsApplication.md) | | [optional] + +## Example + +```python +from okta.models.active_directory_application_settings import ActiveDirectoryApplicationSettings + +# TODO update the JSON string below +json = "{}" +# create an instance of ActiveDirectoryApplicationSettings from a JSON string +active_directory_application_settings_instance = ActiveDirectoryApplicationSettings.from_json(json) +# print the JSON string representation of the object +print(ActiveDirectoryApplicationSettings.to_json()) + +# convert the object into a dict +active_directory_application_settings_dict = active_directory_application_settings_instance.to_dict() +# create an instance of ActiveDirectoryApplicationSettings from a dict +active_directory_application_settings_from_dict = ActiveDirectoryApplicationSettings.from_dict(active_directory_application_settings_dict) +``` +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/docs/ActiveDirectoryApplicationSettingsApplication.md b/docs/ActiveDirectoryApplicationSettingsApplication.md new file mode 100644 index 00000000..bee90828 --- /dev/null +++ b/docs/ActiveDirectoryApplicationSettingsApplication.md @@ -0,0 +1,37 @@ +# ActiveDirectoryApplicationSettingsApplication + +App-specific settings for Active Directory applications + +## Properties + +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**activation_email** | **str** | Email address to send activation emails | [optional] +**filter_groups_by_ou** | **bool** | Whether to filter groups by organizational unit | [optional] +**jit_groups_across_domains** | **bool** | Whether to enable just-in-time provisioning of groups across domains | [optional] +**login** | **str** | Login username for AD connection | [optional] +**naming_context** | **str** | The AD domain naming context (e.g., 'corp.local') | [optional] +**password** | **str** | Password for AD connection | [optional] +**scan_rate** | **int** | Rate at which to scan the AD directory | [optional] +**search_org_unit** | **str** | Organizational unit to search within | [optional] + +## Example + +```python +from okta.models.active_directory_application_settings_application import ActiveDirectoryApplicationSettingsApplication + +# TODO update the JSON string below +json = "{}" +# create an instance of ActiveDirectoryApplicationSettingsApplication from a JSON string +active_directory_application_settings_application_instance = ActiveDirectoryApplicationSettingsApplication.from_json(json) +# print the JSON string representation of the object +print(ActiveDirectoryApplicationSettingsApplication.to_json()) + +# convert the object into a dict +active_directory_application_settings_application_dict = active_directory_application_settings_application_instance.to_dict() +# create an instance of ActiveDirectoryApplicationSettingsApplication from a dict +active_directory_application_settings_application_from_dict = ActiveDirectoryApplicationSettingsApplication.from_dict(active_directory_application_settings_application_dict) +``` +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/docs/Application.md b/docs/Application.md index 3d5710ed..00c1b018 100644 --- a/docs/Application.md +++ b/docs/Application.md @@ -15,7 +15,7 @@ Name | Type | Description | Notes **licensing** | [**ApplicationLicensing**](ApplicationLicensing.md) | | [optional] **orn** | **str** | The Okta resource name (ORN) for the current app instance | [optional] [readonly] **profile** | **Dict[str, object]** | Contains any valid JSON schema for specifying properties that can be referenced from a request (only available to OAuth 2.0 client apps). For example, add an app manager contact email address or define an allowlist of groups that you can then reference using the Okta Expression Language `getFilteredGroups` function. > **Notes:** > * `profile` isn't encrypted, so don't store sensitive data in it. > * `profile` doesn't limit the level of nesting in the JSON schema you created, but there is a practical size limit. Okta recommends a JSON schema size of 1 MB or less for best performance. | [optional] -**sign_on_mode** | [**ApplicationSignOnMode**](ApplicationSignOnMode.md) | | +**sign_on_mode** | [**ApplicationSignOnMode**](ApplicationSignOnMode.md) | | [optional] **status** | [**ApplicationLifecycleStatus**](ApplicationLifecycleStatus.md) | | [optional] **universal_logout** | [**ApplicationUniversalLogout**](ApplicationUniversalLogout.md) | | [optional] **visibility** | [**ApplicationVisibility**](ApplicationVisibility.md) | | [optional] diff --git a/okta/__init__.py b/okta/__init__.py index a301df4b..1edca2dc 100644 --- a/okta/__init__.py +++ b/okta/__init__.py @@ -191,6 +191,9 @@ "ActionProviderPayloadType": "okta.models.action_provider_payload_type", "ActionProviderType": "okta.models.action_provider_type", "Actions": "okta.models.actions", + "ActiveDirectoryApplication": "okta.models.active_directory_application", + "ActiveDirectoryApplicationSettings": "okta.models.active_directory_application_settings", + "ActiveDirectoryApplicationSettingsApplication": "okta.models.active_directory_application_settings_application", "ActiveDirectoryGroupScope": "okta.models.active_directory_group_scope", "ActiveDirectoryGroupType": "okta.models.active_directory_group_type", "AddGroupRequest": "okta.models.add_group_request", diff --git a/okta/models/__init__.py b/okta/models/__init__.py index 1a3ed8b6..b58eaaa2 100644 --- a/okta/models/__init__.py +++ b/okta/models/__init__.py @@ -36,6 +36,7 @@ 'AppConfigActiveDirectory', ], 'application': [ + 'ActiveDirectoryApplication', 'Application', 'AutoLoginApplication', 'BasicAuthApplication', @@ -391,6 +392,9 @@ "ActionProviderPayloadType": "okta.models.action_provider_payload_type", "ActionProviderType": "okta.models.action_provider_type", "Actions": "okta.models.actions", + "ActiveDirectoryApplication": "okta.models.active_directory_application", + "ActiveDirectoryApplicationSettings": "okta.models.active_directory_application_settings", + "ActiveDirectoryApplicationSettingsApplication": "okta.models.active_directory_application_settings_application", "ActiveDirectoryGroupScope": "okta.models.active_directory_group_scope", "ActiveDirectoryGroupType": "okta.models.active_directory_group_type", "AddGroupRequest": "okta.models.add_group_request", diff --git a/okta/models/action_provider.py b/okta/models/action_provider.py index 657b6fd5..415fdc1c 100644 --- a/okta/models/action_provider.py +++ b/okta/models/action_provider.py @@ -68,7 +68,7 @@ class ActionProvider(BaseModel): @classmethod def get_discriminator_value(cls, obj: Dict[str, Any]) -> Optional[str]: """Returns the discriminator value (object type) of the data""" - discriminator_value = obj[cls.__discriminator_property_name] + discriminator_value = obj.get(cls.__discriminator_property_name) if discriminator_value: return cls.__discriminator_value_class_map.get(discriminator_value) else: diff --git a/okta/models/active_directory_application.py b/okta/models/active_directory_application.py new file mode 100644 index 00000000..6e3de5b6 --- /dev/null +++ b/okta/models/active_directory_application.py @@ -0,0 +1,245 @@ +# The Okta software accompanied by this notice is provided pursuant to the following terms: +# Copyright © 2025-Present, Okta, Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the +# License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS +# IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and limitations under the License. +# coding: utf-8 + +""" +Okta Admin Management + +Allows customers to easily access the Okta Management APIs + +The version of the OpenAPI document: 5.1.0 +Contact: devex-public@okta.com +Generated by OpenAPI Generator (https://openapi-generator.tech) + +Do not edit the class manually. +""" # noqa: E501 + +from __future__ import annotations + +import json +import pprint +import re # noqa: F401 +from typing import Any, ClassVar, Dict, List +from typing import Optional, Set + +from pydantic import ConfigDict, Field, StrictStr +from typing_extensions import Self + +from okta.models.active_directory_application_settings import ( + ActiveDirectoryApplicationSettings, +) +from okta.models.application import Application +from okta.models.application_accessibility import ApplicationAccessibility +from okta.models.application_embedded import ApplicationEmbedded +from okta.models.application_express_configuration import ( + ApplicationExpressConfiguration, +) +from okta.models.application_licensing import ApplicationLicensing +from okta.models.application_links import ApplicationLinks +from okta.models.application_universal_logout import ApplicationUniversalLogout +from okta.models.application_visibility import ApplicationVisibility + + +class ActiveDirectoryApplication(Application): + """ + Active Directory application for directory integrations. This application type has a null signOnMode. + """ # noqa: E501 + + name: Optional[StrictStr] = Field( + default=None, + description="Unique key for the Active Directory app definition. Always 'active_directory' for AD apps.", + ) + settings: Optional[ActiveDirectoryApplicationSettings] = None + __properties: ClassVar[List[str]] = [ + "accessibility", + "created", + "expressConfiguration", + "features", + "id", + "label", + "lastUpdated", + "licensing", + "orn", + "profile", + "signOnMode", + "status", + "universalLogout", + "visibility", + "_embedded", + "_links", + "name", + "settings", + ] + + model_config = ConfigDict( + populate_by_name=True, + validate_assignment=True, + protected_namespaces=(), + ) + + def to_str(self) -> str: + """Returns the string representation of the model using alias""" + return pprint.pformat(self.model_dump(by_alias=True)) + + def to_json(self) -> str: + """Returns the JSON representation of the model using alias""" + # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead + return json.dumps(self.to_dict()) + + @classmethod + def from_json(cls, json_str: str) -> Optional[Self]: + """Create an instance of ActiveDirectoryApplication from a JSON string""" + return cls.from_dict(json.loads(json_str)) + + def to_dict(self) -> Dict[str, Any]: + """Return the dictionary representation of the model using alias. + + This has the following differences from calling pydantic's + `self.model_dump(by_alias=True)`: + + * `None` is only added to the output dict for nullable fields that + were set at model initialization. Other fields with value `None` + are ignored. + * OpenAPI `readOnly` fields are excluded. + """ + excluded_fields: Set[str] = set( + [ + "name", + ] + ) + + _dict = self.model_dump( + by_alias=True, + exclude=excluded_fields, + exclude_none=True, + ) + # override the default output from pydantic by calling `to_dict()` of accessibility + if self.accessibility: + if not isinstance(self.accessibility, dict): + _dict["accessibility"] = self.accessibility.to_dict() + else: + _dict["accessibility"] = self.accessibility + + # override the default output from pydantic by calling `to_dict()` of express_configuration + if self.express_configuration: + if not isinstance(self.express_configuration, dict): + _dict["expressConfiguration"] = self.express_configuration.to_dict() + else: + _dict["expressConfiguration"] = self.express_configuration + + # override the default output from pydantic by calling `to_dict()` of licensing + if self.licensing: + if not isinstance(self.licensing, dict): + _dict["licensing"] = self.licensing.to_dict() + else: + _dict["licensing"] = self.licensing + + # override the default output from pydantic by calling `to_dict()` of universal_logout + if self.universal_logout: + if not isinstance(self.universal_logout, dict): + _dict["universalLogout"] = self.universal_logout.to_dict() + else: + _dict["universalLogout"] = self.universal_logout + + # override the default output from pydantic by calling `to_dict()` of visibility + if self.visibility: + if not isinstance(self.visibility, dict): + _dict["visibility"] = self.visibility.to_dict() + else: + _dict["visibility"] = self.visibility + + # override the default output from pydantic by calling `to_dict()` of embedded + if self.embedded: + if not isinstance(self.embedded, dict): + _dict["_embedded"] = self.embedded.to_dict() + else: + _dict["_embedded"] = self.embedded + + # override the default output from pydantic by calling `to_dict()` of links + if self.links: + if not isinstance(self.links, dict): + _dict["_links"] = self.links.to_dict() + else: + _dict["_links"] = self.links + + # override the default output from pydantic by calling `to_dict()` of settings + if self.settings: + if not isinstance(self.settings, dict): + _dict["settings"] = self.settings.to_dict() + else: + _dict["settings"] = self.settings + + return _dict + + @classmethod + def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: + """Create an instance of ActiveDirectoryApplication from a dict""" + if obj is None: + return None + + if not isinstance(obj, dict): + return cls.model_validate(obj) + + _obj = cls.model_validate( + { + "accessibility": ( + ApplicationAccessibility.from_dict(obj["accessibility"]) + if obj.get("accessibility") is not None + else None + ), + "created": obj.get("created"), + "expressConfiguration": ( + ApplicationExpressConfiguration.from_dict( + obj["expressConfiguration"] + ) + if obj.get("expressConfiguration") is not None + else None + ), + "features": obj.get("features"), + "id": obj.get("id"), + "label": obj.get("label"), + "lastUpdated": obj.get("lastUpdated"), + "licensing": ( + ApplicationLicensing.from_dict(obj["licensing"]) + if obj.get("licensing") is not None + else None + ), + "orn": obj.get("orn"), + "profile": obj.get("profile"), + "signOnMode": obj.get("signOnMode"), + "status": obj.get("status"), + "universalLogout": ( + ApplicationUniversalLogout.from_dict(obj["universalLogout"]) + if obj.get("universalLogout") is not None + else None + ), + "visibility": ( + ApplicationVisibility.from_dict(obj["visibility"]) + if obj.get("visibility") is not None + else None + ), + "_embedded": ( + ApplicationEmbedded.from_dict(obj["_embedded"]) + if obj.get("_embedded") is not None + else None + ), + "_links": ( + ApplicationLinks.from_dict(obj["_links"]) + if obj.get("_links") is not None + else None + ), + "name": obj.get("name"), + "settings": ( + ActiveDirectoryApplicationSettings.from_dict(obj["settings"]) + if obj.get("settings") is not None + else None + ), + } + ) + return _obj diff --git a/okta/models/active_directory_application_settings.py b/okta/models/active_directory_application_settings.py new file mode 100644 index 00000000..a45ce7fd --- /dev/null +++ b/okta/models/active_directory_application_settings.py @@ -0,0 +1,201 @@ +# The Okta software accompanied by this notice is provided pursuant to the following terms: +# Copyright © 2025-Present, Okta, Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the +# License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS +# IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and limitations under the License. +# coding: utf-8 + +""" +Okta Admin Management + +Allows customers to easily access the Okta Management APIs + +The version of the OpenAPI document: 5.1.0 +Contact: devex-public@okta.com +Generated by OpenAPI Generator (https://openapi-generator.tech) + +Do not edit the class manually. +""" # noqa: E501 + +from __future__ import annotations + +import json +import pprint +import re # noqa: F401 +from typing import Any, ClassVar, Dict, List +from typing import Optional, Set + +from pydantic import ( + BaseModel, + ConfigDict, + Field, + StrictBool, + StrictStr, + field_validator, +) +from typing_extensions import Self + +from okta.models.active_directory_application_settings_application import ( + ActiveDirectoryApplicationSettingsApplication, +) +from okta.models.application_settings_notes import ApplicationSettingsNotes +from okta.models.application_settings_notifications import ( + ApplicationSettingsNotifications, +) + + +class ActiveDirectoryApplicationSettings(BaseModel): + """ + Settings for Active Directory applications + """ # noqa: E501 + + em_opt_in_status: Optional[StrictStr] = Field( + default=None, + description="The entitlement management opt-in status for the app", + alias="emOptInStatus", + ) + identity_store_id: Optional[StrictStr] = Field( + default=None, + description="Identifies an additional identity store app, if your app supports it. The `identityStoreId` value must " + "be a valid identity store app ID. This identity store app must be created in the same org as your app.", + alias="identityStoreId", + ) + implicit_assignment: Optional[StrictBool] = Field( + default=None, + description="Controls whether Okta automatically assigns users to the app based on the user's role or group " + "membership.", + alias="implicitAssignment", + ) + inline_hook_id: Optional[StrictStr] = Field( + default=None, + description="Identifier of an inline hook. Inline hooks are outbound calls from Okta to your own custom code, " + "triggered at specific points in Okta process flows. They allow you to integrate custom functionality " + "into those flows. See [Inline hooks](/openapi/okta-management/management/tag/InlineHook/).", + alias="inlineHookId", + ) + notes: Optional[ApplicationSettingsNotes] = None + notifications: Optional[ApplicationSettingsNotifications] = None + app: Optional[ActiveDirectoryApplicationSettingsApplication] = None + __properties: ClassVar[List[str]] = [ + "emOptInStatus", + "identityStoreId", + "implicitAssignment", + "inlineHookId", + "notes", + "notifications", + "app", + ] + + @field_validator("em_opt_in_status") + def em_opt_in_status_validate_enum(cls, value): + """Validates the enum""" + if value is None: + return value + + if value not in set(["DISABLED", "DISABLING", "ENABLED", "ENABLING", "NONE"]): + raise ValueError( + "must be one of enum values ('DISABLED', 'DISABLING', 'ENABLED', 'ENABLING', 'NONE')" + ) + return value + + model_config = ConfigDict( + populate_by_name=True, + validate_assignment=True, + protected_namespaces=(), + ) + + def to_str(self) -> str: + """Returns the string representation of the model using alias""" + return pprint.pformat(self.model_dump(by_alias=True)) + + def to_json(self) -> str: + """Returns the JSON representation of the model using alias""" + # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead + return json.dumps(self.to_dict()) + + @classmethod + def from_json(cls, json_str: str) -> Optional[Self]: + """Create an instance of ActiveDirectoryApplicationSettings from a JSON string""" + return cls.from_dict(json.loads(json_str)) + + def to_dict(self) -> Dict[str, Any]: + """Return the dictionary representation of the model using alias. + + This has the following differences from calling pydantic's + `self.model_dump(by_alias=True)`: + + * `None` is only added to the output dict for nullable fields that + were set at model initialization. Other fields with value `None` + are ignored. + * OpenAPI `readOnly` fields are excluded. + """ + excluded_fields: Set[str] = set( + [ + "em_opt_in_status", + ] + ) + + _dict = self.model_dump( + by_alias=True, + exclude=excluded_fields, + exclude_none=True, + ) + # override the default output from pydantic by calling `to_dict()` of notes + if self.notes: + if not isinstance(self.notes, dict): + _dict["notes"] = self.notes.to_dict() + else: + _dict["notes"] = self.notes + + # override the default output from pydantic by calling `to_dict()` of notifications + if self.notifications: + if not isinstance(self.notifications, dict): + _dict["notifications"] = self.notifications.to_dict() + else: + _dict["notifications"] = self.notifications + + # override the default output from pydantic by calling `to_dict()` of app + if self.app: + if not isinstance(self.app, dict): + _dict["app"] = self.app.to_dict() + else: + _dict["app"] = self.app + + return _dict + + @classmethod + def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: + """Create an instance of ActiveDirectoryApplicationSettings from a dict""" + if obj is None: + return None + + if not isinstance(obj, dict): + return cls.model_validate(obj) + + _obj = cls.model_validate( + { + "emOptInStatus": obj.get("emOptInStatus"), + "identityStoreId": obj.get("identityStoreId"), + "implicitAssignment": obj.get("implicitAssignment"), + "inlineHookId": obj.get("inlineHookId"), + "notes": ( + ApplicationSettingsNotes.from_dict(obj["notes"]) + if obj.get("notes") is not None + else None + ), + "notifications": ( + ApplicationSettingsNotifications.from_dict(obj["notifications"]) + if obj.get("notifications") is not None + else None + ), + "app": ( + ActiveDirectoryApplicationSettingsApplication.from_dict(obj["app"]) + if obj.get("app") is not None + else None + ), + } + ) + return _obj diff --git a/okta/models/active_directory_application_settings_application.py b/okta/models/active_directory_application_settings_application.py new file mode 100644 index 00000000..4c82c0cf --- /dev/null +++ b/okta/models/active_directory_application_settings_application.py @@ -0,0 +1,183 @@ +# The Okta software accompanied by this notice is provided pursuant to the following terms: +# Copyright © 2025-Present, Okta, Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the +# License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS +# IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and limitations under the License. +# coding: utf-8 + +""" +Okta Admin Management + +Allows customers to easily access the Okta Management APIs + +The version of the OpenAPI document: 5.1.0 +Contact: devex-public@okta.com +Generated by OpenAPI Generator (https://openapi-generator.tech) + +Do not edit the class manually. +""" # noqa: E501 + +from __future__ import annotations + +import json +import pprint +import re # noqa: F401 +from typing import Any, ClassVar, Dict, List +from typing import Optional, Set + +from pydantic import BaseModel, ConfigDict, Field, StrictBool, StrictInt, StrictStr +from typing_extensions import Self + + +class ActiveDirectoryApplicationSettingsApplication(BaseModel): + """ + App-specific settings for Active Directory applications + """ # noqa: E501 + + activation_email: Optional[StrictStr] = Field( + default=None, + description="Email address to send activation emails", + alias="activationEmail", + ) + filter_groups_by_ou: Optional[StrictBool] = Field( + default=None, + description="Whether to filter groups by organizational unit", + alias="filterGroupsByOU", + ) + jit_groups_across_domains: Optional[StrictBool] = Field( + default=None, + description="Whether to enable just-in-time provisioning of groups across domains", + alias="jitGroupsAcrossDomains", + ) + login: Optional[StrictStr] = Field( + default=None, description="Login username for AD connection" + ) + naming_context: Optional[StrictStr] = Field( + default=None, + description="The AD domain naming context (e.g., 'corp.local')", + alias="namingContext", + ) + password: Optional[StrictStr] = Field( + default=None, description="Password for AD connection" + ) + scan_rate: Optional[StrictInt] = Field( + default=None, + description="Rate at which to scan the AD directory", + alias="scanRate", + ) + search_org_unit: Optional[StrictStr] = Field( + default=None, + description="Organizational unit to search within", + alias="searchOrgUnit", + ) + additional_properties: Dict[str, Any] = {} + __properties: ClassVar[List[str]] = [ + "activationEmail", + "filterGroupsByOU", + "jitGroupsAcrossDomains", + "login", + "namingContext", + "password", + "scanRate", + "searchOrgUnit", + ] + + model_config = ConfigDict( + populate_by_name=True, + validate_assignment=True, + protected_namespaces=(), + ) + + def to_str(self) -> str: + """Returns the string representation of the model using alias""" + return pprint.pformat(self.model_dump(by_alias=True)) + + def to_json(self) -> str: + """Returns the JSON representation of the model using alias""" + # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead + return json.dumps(self.to_dict()) + + @classmethod + def from_json(cls, json_str: str) -> Optional[Self]: + """Create an instance of ActiveDirectoryApplicationSettingsApplication from a JSON string""" + return cls.from_dict(json.loads(json_str)) + + def to_dict(self) -> Dict[str, Any]: + """Return the dictionary representation of the model using alias. + + This has the following differences from calling pydantic's + `self.model_dump(by_alias=True)`: + + * `None` is only added to the output dict for nullable fields that + were set at model initialization. Other fields with value `None` + are ignored. + * Fields in `self.additional_properties` are added to the output dict. + """ + excluded_fields: Set[str] = set( + [ + "additional_properties", + ] + ) + + _dict = self.model_dump( + by_alias=True, + exclude=excluded_fields, + exclude_none=True, + ) + # puts key-value pairs in additional_properties in the top level + if self.additional_properties is not None: + for _key, _value in self.additional_properties.items(): + _dict[_key] = _value + + # set to None if login (nullable) is None + # and model_fields_set contains the field + if self.login is None and "login" in self.model_fields_set: + _dict["login"] = None + + # set to None if password (nullable) is None + # and model_fields_set contains the field + if self.password is None and "password" in self.model_fields_set: + _dict["password"] = None + + # set to None if scan_rate (nullable) is None + # and model_fields_set contains the field + if self.scan_rate is None and "scan_rate" in self.model_fields_set: + _dict["scanRate"] = None + + # set to None if search_org_unit (nullable) is None + # and model_fields_set contains the field + if self.search_org_unit is None and "search_org_unit" in self.model_fields_set: + _dict["searchOrgUnit"] = None + + return _dict + + @classmethod + def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: + """Create an instance of ActiveDirectoryApplicationSettingsApplication from a dict""" + if obj is None: + return None + + if not isinstance(obj, dict): + return cls.model_validate(obj) + + _obj = cls.model_validate( + { + "activationEmail": obj.get("activationEmail"), + "filterGroupsByOU": obj.get("filterGroupsByOU"), + "jitGroupsAcrossDomains": obj.get("jitGroupsAcrossDomains"), + "login": obj.get("login"), + "namingContext": obj.get("namingContext"), + "password": obj.get("password"), + "scanRate": obj.get("scanRate"), + "searchOrgUnit": obj.get("searchOrgUnit"), + } + ) + # store additional fields in additional_properties + for _key in obj.keys(): + if _key not in cls.__properties: + _obj.additional_properties[_key] = obj.get(_key) + + return _obj diff --git a/okta/models/app_config.py b/okta/models/app_config.py index d8d01076..35267377 100644 --- a/okta/models/app_config.py +++ b/okta/models/app_config.py @@ -61,7 +61,7 @@ class AppConfig(BaseModel): @classmethod def get_discriminator_value(cls, obj: Dict[str, Any]) -> Optional[str]: """Returns the discriminator value (object type) of the data""" - discriminator_value = obj[cls.__discriminator_property_name] + discriminator_value = obj.get(cls.__discriminator_property_name) if discriminator_value: return cls.__discriminator_value_class_map.get(discriminator_value) else: diff --git a/okta/models/application.py b/okta/models/application.py index dee2536a..99353699 100644 --- a/okta/models/application.py +++ b/okta/models/application.py @@ -26,15 +26,9 @@ import pprint import re # noqa: F401 from datetime import datetime -from importlib import import_module from typing import Any, ClassVar, Dict, List, Union -from typing import Optional, Set +from typing import Optional from typing import TYPE_CHECKING -from okta.application_converter import ( - handle_unknown_sign_on_mode, - get_discriminator_value_safe, - restore_original_sign_on_mode, -) from pydantic import BaseModel, ConfigDict, Field, StrictStr, field_validator @@ -63,6 +57,7 @@ SecurePasswordStoreApplication, ) from okta.models.ws_federation_application import WsFederationApplication + from okta.models.active_directory_application import ActiveDirectoryApplication class Application(BaseModel): # noqa: F811 @@ -105,7 +100,9 @@ class Application(BaseModel): # noqa: F811 "`profile` doesn't limit the level of nesting in the JSON schema you created, but there is a practical " "size limit. Okta recommends a JSON schema size of 1 MB or less for best performance.", ) - sign_on_mode: ApplicationSignOnMode = Field(alias="signOnMode") + sign_on_mode: Optional[ApplicationSignOnMode] = Field( + default=None, alias="signOnMode" + ) status: Optional[ApplicationLifecycleStatus] = None universal_logout: Optional[ApplicationUniversalLogout] = Field( default=None, alias="universalLogout" @@ -113,8 +110,6 @@ class Application(BaseModel): # noqa: F811 visibility: Optional[ApplicationVisibility] = None 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 __properties: ClassVar[List[str]] = [ "accessibility", "created", @@ -223,12 +218,13 @@ def features_validate_enum(cls, value): "SAML_2_0": "SamlApplication", "SECURE_PASSWORD_STORE": "SecurePasswordStoreApplication", "WS_FEDERATION": "WsFederationApplication", + "ActiveDirectoryApplication": "ActiveDirectoryApplication", } @classmethod def get_discriminator_value(cls, obj: Dict[str, Any]) -> Optional[str]: """Returns the discriminator value (object type) of the data""" - discriminator_value = obj[cls.__discriminator_property_name] + discriminator_value = obj.get(cls.__discriminator_property_name) if discriminator_value: return cls.__discriminator_value_class_map.get(discriminator_value) else: @@ -256,6 +252,7 @@ def from_json(cls, json_str: str) -> Optional[ SamlApplication, SecurePasswordStoreApplication, WsFederationApplication, + ActiveDirectoryApplication, ] ]: """Create an instance of Application from a JSON string""" @@ -276,71 +273,9 @@ def to_dict(self) -> Dict[str, Any]: * OpenAPI `readOnly` fields are excluded. * OpenAPI `readOnly` fields are excluded. """ - excluded_fields: Set[str] = set( - [ - "created", - "features", - "id", - "last_updated", - "orn", - ] - ) - - _dict = self.model_dump( - by_alias=True, - exclude=excluded_fields, - exclude_none=True, - ) - # override the default output from pydantic by calling `to_dict()` of accessibility - if self.accessibility: - if not isinstance(self.accessibility, dict): - _dict["accessibility"] = self.accessibility.to_dict() - else: - _dict["accessibility"] = self.accessibility - - # override the default output from pydantic by calling `to_dict()` of express_configuration - if self.express_configuration: - if not isinstance(self.express_configuration, dict): - _dict["expressConfiguration"] = self.express_configuration.to_dict() - else: - _dict["expressConfiguration"] = self.express_configuration - - # override the default output from pydantic by calling `to_dict()` of licensing - if self.licensing: - if not isinstance(self.licensing, dict): - _dict["licensing"] = self.licensing.to_dict() - else: - _dict["licensing"] = self.licensing - - # override the default output from pydantic by calling `to_dict()` of universal_logout - if self.universal_logout: - if not isinstance(self.universal_logout, dict): - _dict["universalLogout"] = self.universal_logout.to_dict() - else: - _dict["universalLogout"] = self.universal_logout - - # override the default output from pydantic by calling `to_dict()` of visibility - if self.visibility: - if not isinstance(self.visibility, dict): - _dict["visibility"] = self.visibility.to_dict() - else: - _dict["visibility"] = self.visibility - - # override the default output from pydantic by calling `to_dict()` of embedded - if self.embedded: - if not isinstance(self.embedded, dict): - _dict["_embedded"] = self.embedded.to_dict() - else: - _dict["_embedded"] = self.embedded - - # override the default output from pydantic by calling `to_dict()` of links - if self.links: - if not isinstance(self.links, dict): - _dict["_links"] = self.links.to_dict() - else: - _dict["_links"] = self.links + from okta.models.application_json_converter import ApplicationJsonConverter - return _dict + return ApplicationJsonConverter.to_dict(self) @classmethod def from_dict(cls, obj: Dict[str, Any]) -> Optional[ @@ -355,69 +290,10 @@ def from_dict(cls, obj: Dict[str, Any]) -> Optional[ SamlApplication, SecurePasswordStoreApplication, WsFederationApplication, + ActiveDirectoryApplication, ] ]: """Create an instance of Application from a dict""" - # look up the object type based on discriminator mapping - object_type = cls.get_discriminator_value(obj) - # Import from okta.models to ensure class identity consistency with lazy imports - models = import_module("okta.models") - if object_type == "AutoLoginApplication": - # Check if the discriminator maps to the same class to avoid infinite recursion - if object_type == cls.__name__: - return cls.model_validate(obj) - return models.AutoLoginApplication.from_dict(obj) - if object_type == "BasicAuthApplication": - # Check if the discriminator maps to the same class to avoid infinite recursion - if object_type == cls.__name__: - return cls.model_validate(obj) - return models.BasicAuthApplication.from_dict(obj) - if object_type == "BookmarkApplication": - # Check if the discriminator maps to the same class to avoid infinite recursion - if object_type == cls.__name__: - return cls.model_validate(obj) - return models.BookmarkApplication.from_dict(obj) - if object_type == "BrowserPluginApplication": - # Check if the discriminator maps to the same class to avoid infinite recursion - if object_type == cls.__name__: - return cls.model_validate(obj) - return models.BrowserPluginApplication.from_dict(obj) - if object_type == "Application": - # Check if the discriminator maps to the same class to avoid infinite recursion - if object_type == cls.__name__: - return cls.model_validate(obj) - return models.Application.from_dict(obj) - if object_type == "OpenIdConnectApplication": - # Check if the discriminator maps to the same class to avoid infinite recursion - if object_type == cls.__name__: - return cls.model_validate(obj) - return models.OpenIdConnectApplication.from_dict(obj) - if object_type == "Saml11Application": - # Check if the discriminator maps to the same class to avoid infinite recursion - if object_type == cls.__name__: - return cls.model_validate(obj) - return models.Saml11Application.from_dict(obj) - if object_type == "SamlApplication": - # Check if the discriminator maps to the same class to avoid infinite recursion - if object_type == cls.__name__: - return cls.model_validate(obj) - return models.SamlApplication.from_dict(obj) - if object_type == "SecurePasswordStoreApplication": - # Check if the discriminator maps to the same class to avoid infinite recursion - if object_type == cls.__name__: - return cls.model_validate(obj) - return models.SecurePasswordStoreApplication.from_dict(obj) - if object_type == "WsFederationApplication": - # Check if the discriminator maps to the same class to avoid infinite recursion - if object_type == cls.__name__: - return cls.model_validate(obj) - return models.WsFederationApplication.from_dict(obj) + from okta.models.application_json_converter import ApplicationJsonConverter - raise ValueError( - "Application failed to lookup discriminator value from " - + json.dumps(obj) - + ". Discriminator property name: " - + cls.__discriminator_property_name - + ", mapping: " - + json.dumps(cls.__discriminator_value_class_map) - ) + return ApplicationJsonConverter.from_dict(obj) diff --git a/okta/models/application_feature.py b/okta/models/application_feature.py index 456ca92f..a21b61d2 100644 --- a/okta/models/application_feature.py +++ b/okta/models/application_feature.py @@ -76,7 +76,7 @@ class ApplicationFeature(BaseModel): @classmethod def get_discriminator_value(cls, obj: Dict[str, Any]) -> Optional[str]: """Returns the discriminator value (object type) of the data""" - discriminator_value = obj[cls.__discriminator_property_name] + discriminator_value = obj.get(cls.__discriminator_property_name) if discriminator_value: return cls.__discriminator_value_class_map.get(discriminator_value) else: diff --git a/okta/models/application_json_converter.py b/okta/models/application_json_converter.py new file mode 100644 index 00000000..55bdae85 --- /dev/null +++ b/okta/models/application_json_converter.py @@ -0,0 +1,205 @@ +""" +Okta Admin Management + +Allows customers to easily access the Okta Management APIs + +The version of the OpenAPI document: 5.1.0 +Contact: devex-public@okta.com +Generated by OpenAPI Generator (https://openapi-generator.tech) + +Do not edit the class manually. +""" # noqa: E501 +import logging +logger = logging.getLogger(__name__) + +from typing import Dict, Any, Optional + +# Import the base and subclass models +from okta.models.application import Application +from okta.models.active_directory_application import ActiveDirectoryApplication +from okta.models.auto_login_application import AutoLoginApplication +from okta.models.basic_auth_application import BasicAuthApplication +from okta.models.bookmark_application import BookmarkApplication +from okta.models.browser_plugin_application import BrowserPluginApplication +from okta.models.open_id_connect_application import OpenIdConnectApplication +from okta.models.saml11_application import Saml11Application +from okta.models.saml_application import SamlApplication +from okta.models.secure_password_store_application import SecurePasswordStoreApplication +from okta.models.ws_federation_application import WsFederationApplication + + +class ApplicationJsonConverter: + """ + Custom JSON converter/factory for Application that handles null, empty, and unknown signOnMode values. + + Routing logic: + - null (None) → ActiveDirectoryApplication (AD integrations have null signOnMode) + - "" (empty string) → Application base class with original value preserved + - Known modes (AUTO_LOGIN, SAML_2_0, etc.) → Specific subclasses (case-insensitive) + - Unknown modes (e.g., FUTURE_MODE) → Application base class with original value preserved + + Round-trip behavior: + - Unknown values stored in _original_sign_on_mode attribute + - Restored in to_dict() output for API fidelity + - None (for missing field) routes to ActiveDirectoryApplication + - Empty string is preserved as-is for round-trip integrity + + Case handling: + - Known modes are normalized to uppercase (e.g., "auto_login" → "AUTO_LOGIN") + - Unknown modes preserve original casing (e.g., "Future_Mode" stays "Future_Mode") + """ + + # All known signOnMode values from ApplicationSignOnMode enum + KNOWN_SIGN_ON_MODES = { + "AUTO_LOGIN", + "BASIC_AUTH", + "BOOKMARK", + "BROWSER_PLUGIN", + "OPENID_CONNECT", + "SAML_1_1", + "SAML_2_0", + "SECURE_PASSWORD_STORE", + "WS_FEDERATION", + "MFA_AS_SERVICE", # Maps to base Application class + } + + SIGN_ON_MODE_MAPPING = { + "AUTO_LOGIN": AutoLoginApplication, + "BASIC_AUTH": BasicAuthApplication, + "BOOKMARK": BookmarkApplication, + "BROWSER_PLUGIN": BrowserPluginApplication, + "OPENID_CONNECT": OpenIdConnectApplication, + "SAML_1_1": Saml11Application, + "SAML_2_0": SamlApplication, + "SECURE_PASSWORD_STORE": SecurePasswordStoreApplication, + "WS_FEDERATION": WsFederationApplication, + "MFA_AS_SERVICE": Application, # Explicitly map to base Application + } + + @classmethod + def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Application]: + """ + Reads the dictionary representation of the Application object, using signOnMode + to determine the correct type. Handles the special case where signOnMode is null + (Active Directory applications) or unknown (future sign-on modes). + """ + if obj is None: + return None + + # Get the signOnMode value to determine the concrete type + sign_on_mode = obj.get("signOnMode") + original_sign_on_mode = None + + # Make a copy to avoid modifying the input + obj_copy = dict(obj) + + # Determine the target type based on signOnMode + if sign_on_mode is None: + # null signOnMode indicates an Active Directory application + target_type = ActiveDirectoryApplication + elif sign_on_mode == "": + # Empty string signOnMode is treated as unknown (not ActiveDirectory) + original_sign_on_mode = sign_on_mode + obj_copy["signOnMode"] = None + target_type = Application + elif str(sign_on_mode).upper() in cls.KNOWN_SIGN_ON_MODES: + # Known sign-on mode - map to specific class (or base Application for MFA_AS_SERVICE) + target_type = cls.SIGN_ON_MODE_MAPPING[str(sign_on_mode).upper()] + obj_copy["signOnMode"] = str( + sign_on_mode + ).upper() # Normalize for validation + else: + # Unknown sign-on mode - preserve original value and set to None for validation + logger.debug( + f"Unknown signOnMode '{sign_on_mode}' encountered, " + f"routing to base Application class" + ) + original_sign_on_mode = sign_on_mode + obj_copy["signOnMode"] = None + target_type = Application + + # Use Pydantic's model_validate to avoid recursion + # (calling from_dict would trigger the converter again in subclasses) + instance = target_type.model_validate(obj_copy) + + # Validate consistency (in dev/test mode) + if isinstance(instance, Application) and not isinstance( + instance, type(instance).__bases__[0] + ): + # Log warning if subclass instantiated but fields seem inconsistent + logger.debug(f"Routed {sign_on_mode} to {type(instance).__name__}") + + # Store the original unknown sign-on mode if present + # For known modes, set to None to indicate no preservation needed + if original_sign_on_mode is not None: + # Use object.__setattr__ to bypass Pydantic's validation + object.__setattr__( + instance, "_original_sign_on_mode", original_sign_on_mode + ) + else: + object.__setattr__(instance, "_original_sign_on_mode", None) + + return instance + + @classmethod + def to_dict(cls, value: Application) -> Optional[Dict[str, Any]]: + """ + Writes the dictionary representation of the Application object. + Uses Pydantic's model_dump directly to avoid recursion. + Restores original unknown signOnMode values for round-trip fidelity. + """ + if value is None: + return None + + # Use Pydantic's model_dump to serialize the instance + # This avoids calling to_dict() which would recurse back to this converter + # Derive from model configuration + excluded_fields = { + field_name + for field_name, field_info in Application.model_fields.items() + if field_info.exclude + or ( + field_info.json_schema_extra + and field_info.json_schema_extra.get("readOnly") + ) + } + + _dict = value.model_dump( + by_alias=True, + exclude=excluded_fields, + exclude_none=True, + mode="json", # Serialize enums to their values + ) + + # Restore original unknown signOnMode if it was preserved + if ( + hasattr(value, "_original_sign_on_mode") + and value._original_sign_on_mode is not None + ): + _dict["signOnMode"] = value._original_sign_on_mode + # Ensure signOnMode is serialized as string (in case mode='json' didn't handle it) + elif "signOnMode" in _dict and hasattr(_dict["signOnMode"], "value"): + _dict["signOnMode"] = _dict["signOnMode"].value + + # NOTE: Pydantic's model_dump(mode='json') handles most nested BaseModel serialization. + # However, subclass-specific fields (settings, credentials, name) need manual handling + # because they're not in the base Application schema and model_dump doesn't call + # their custom to_dict() methods which may have additional logic. + + # Handle subclass-specific fields that may have custom serialization logic + if hasattr(value, "settings") and value.settings: + if not isinstance(value.settings, dict): + _dict["settings"] = value.settings.to_dict() + else: + _dict["settings"] = value.settings + + if hasattr(value, "credentials") and value.credentials: + if not isinstance(value.credentials, dict): + _dict["credentials"] = value.credentials.to_dict() + else: + _dict["credentials"] = value.credentials + + if hasattr(value, "name") and value.name: + _dict["name"] = value.name + + return _dict diff --git a/okta/models/authenticator_base.py b/okta/models/authenticator_base.py index 1519eecc..b7a99dd5 100644 --- a/okta/models/authenticator_base.py +++ b/okta/models/authenticator_base.py @@ -127,7 +127,7 @@ class AuthenticatorBase(BaseModel): @classmethod def get_discriminator_value(cls, obj: Dict[str, Any]) -> Optional[str]: """Returns the discriminator value (object type) of the data""" - discriminator_value = obj[cls.__discriminator_property_name] + discriminator_value = obj.get(cls.__discriminator_property_name) if discriminator_value: return cls.__discriminator_value_class_map.get(discriminator_value) else: diff --git a/okta/models/authenticator_method_base.py b/okta/models/authenticator_method_base.py index 1bdb93c1..6df59a40 100644 --- a/okta/models/authenticator_method_base.py +++ b/okta/models/authenticator_method_base.py @@ -91,7 +91,7 @@ class AuthenticatorMethodBase(BaseModel): @classmethod def get_discriminator_value(cls, obj: Dict[str, Any]) -> Optional[str]: """Returns the discriminator value (object type) of the data""" - discriminator_value = obj[cls.__discriminator_property_name] + discriminator_value = obj.get(cls.__discriminator_property_name) if discriminator_value: return cls.__discriminator_value_class_map.get(discriminator_value) else: diff --git a/okta/models/authenticator_method_with_verifiable_properties.py b/okta/models/authenticator_method_with_verifiable_properties.py index 4bc2f639..f157b6a4 100644 --- a/okta/models/authenticator_method_with_verifiable_properties.py +++ b/okta/models/authenticator_method_with_verifiable_properties.py @@ -95,7 +95,7 @@ class AuthenticatorMethodWithVerifiableProperties(AuthenticatorMethodBase): # n @classmethod def get_discriminator_value(cls, obj: Dict[str, Any]) -> Optional[str]: """Returns the discriminator value (object type) of the data""" - discriminator_value = obj[cls.__discriminator_property_name] + discriminator_value = obj.get(cls.__discriminator_property_name) if discriminator_value: return cls.__discriminator_value_class_map.get(discriminator_value) else: diff --git a/okta/models/authenticator_simple.py b/okta/models/authenticator_simple.py index 84c98d9a..aa461df4 100644 --- a/okta/models/authenticator_simple.py +++ b/okta/models/authenticator_simple.py @@ -103,7 +103,7 @@ class AuthenticatorSimple(AuthenticatorBase): @classmethod def get_discriminator_value(cls, obj: Dict[str, Any]) -> Optional[str]: """Returns the discriminator value (object type) of the data""" - discriminator_value = obj[cls.__discriminator_property_name] + discriminator_value = obj.get(cls.__discriminator_property_name) if discriminator_value: return cls.__discriminator_value_class_map.get(discriminator_value) else: diff --git a/okta/models/available_action_provider.py b/okta/models/available_action_provider.py index 13640be0..4affa78e 100644 --- a/okta/models/available_action_provider.py +++ b/okta/models/available_action_provider.py @@ -73,7 +73,7 @@ class AvailableActionProvider(BaseModel): @classmethod def get_discriminator_value(cls, obj: Dict[str, Any]) -> Optional[str]: """Returns the discriminator value (object type) of the data""" - discriminator_value = obj[cls.__discriminator_property_name] + discriminator_value = obj.get(cls.__discriminator_property_name) if discriminator_value: return cls.__discriminator_value_class_map.get(discriminator_value) else: diff --git a/okta/models/behavior_rule.py b/okta/models/behavior_rule.py index 644a887b..31a93a71 100644 --- a/okta/models/behavior_rule.py +++ b/okta/models/behavior_rule.py @@ -101,7 +101,7 @@ class BehaviorRule(BaseModel): @classmethod def get_discriminator_value(cls, obj: Dict[str, Any]) -> Optional[str]: """Returns the discriminator value (object type) of the data""" - discriminator_value = obj[cls.__discriminator_property_name] + discriminator_value = obj.get(cls.__discriminator_property_name) if discriminator_value: return cls.__discriminator_value_class_map.get(discriminator_value) else: diff --git a/okta/models/device_assurance.py b/okta/models/device_assurance.py index 8521543e..1103f849 100644 --- a/okta/models/device_assurance.py +++ b/okta/models/device_assurance.py @@ -124,7 +124,7 @@ def display_remediation_mode_validate_enum(cls, value): @classmethod def get_discriminator_value(cls, obj: Dict[str, Any]) -> Optional[str]: """Returns the discriminator value (object type) of the data""" - discriminator_value = obj[cls.__discriminator_property_name] + discriminator_value = obj.get(cls.__discriminator_property_name) if discriminator_value: return cls.__discriminator_value_class_map.get(discriminator_value) else: diff --git a/okta/models/enrollment_policy_authenticator_grace_period.py b/okta/models/enrollment_policy_authenticator_grace_period.py index f15ff4d4..7a2ab2b0 100644 --- a/okta/models/enrollment_policy_authenticator_grace_period.py +++ b/okta/models/enrollment_policy_authenticator_grace_period.py @@ -74,7 +74,7 @@ def type_validate_enum(cls, value): @classmethod def get_discriminator_value(cls, obj: Dict[str, Any]) -> Optional[str]: """Returns the discriminator value (object type) of the data""" - discriminator_value = obj[cls.__discriminator_property_name] + discriminator_value = obj.get(cls.__discriminator_property_name) if discriminator_value: return cls.__discriminator_value_class_map.get(discriminator_value) else: diff --git a/okta/models/inline_hook_channel.py b/okta/models/inline_hook_channel.py index 974c6793..49ddc1b9 100644 --- a/okta/models/inline_hook_channel.py +++ b/okta/models/inline_hook_channel.py @@ -69,7 +69,7 @@ class InlineHookChannel(BaseModel): @classmethod def get_discriminator_value(cls, obj: Dict[str, Any]) -> Optional[str]: """Returns the discriminator value (object type) of the data""" - discriminator_value = obj[cls.__discriminator_property_name] + discriminator_value = obj.get(cls.__discriminator_property_name) if discriminator_value: return cls.__discriminator_value_class_map.get(discriminator_value) else: diff --git a/okta/models/inline_hook_channel_create.py b/okta/models/inline_hook_channel_create.py index 584f1632..7ce093b3 100644 --- a/okta/models/inline_hook_channel_create.py +++ b/okta/models/inline_hook_channel_create.py @@ -71,7 +71,7 @@ class InlineHookChannelCreate(BaseModel): @classmethod def get_discriminator_value(cls, obj: Dict[str, Any]) -> Optional[str]: """Returns the discriminator value (object type) of the data""" - discriminator_value = obj[cls.__discriminator_property_name] + discriminator_value = obj.get(cls.__discriminator_property_name) if discriminator_value: return cls.__discriminator_value_class_map.get(discriminator_value) else: diff --git a/okta/models/log_stream.py b/okta/models/log_stream.py index 3c31a514..3ac575d5 100644 --- a/okta/models/log_stream.py +++ b/okta/models/log_stream.py @@ -95,7 +95,7 @@ def status_validate_enum(cls, value): @classmethod def get_discriminator_value(cls, obj: Dict[str, Any]) -> Optional[str]: """Returns the discriminator value (object type) of the data""" - discriminator_value = obj[cls.__discriminator_property_name] + discriminator_value = obj.get(cls.__discriminator_property_name) if discriminator_value: return cls.__discriminator_value_class_map.get(discriminator_value) else: diff --git a/okta/models/log_stream_put_schema.py b/okta/models/log_stream_put_schema.py index 7093deae..6d159736 100644 --- a/okta/models/log_stream_put_schema.py +++ b/okta/models/log_stream_put_schema.py @@ -66,7 +66,7 @@ class LogStreamPutSchema(BaseModel): @classmethod def get_discriminator_value(cls, obj: Dict[str, Any]) -> Optional[str]: """Returns the discriminator value (object type) of the data""" - discriminator_value = obj[cls.__discriminator_property_name] + discriminator_value = obj.get(cls.__discriminator_property_name) if discriminator_value: return cls.__discriminator_value_class_map.get(discriminator_value) else: diff --git a/okta/models/network_zone.py b/okta/models/network_zone.py index d05a1810..f28bdf64 100644 --- a/okta/models/network_zone.py +++ b/okta/models/network_zone.py @@ -106,7 +106,7 @@ class NetworkZone(BaseModel): @classmethod def get_discriminator_value(cls, obj: Dict[str, Any]) -> Optional[str]: """Returns the discriminator value (object type) of the data""" - discriminator_value = obj[cls.__discriminator_property_name] + discriminator_value = obj.get(cls.__discriminator_property_name) if discriminator_value: return cls.__discriminator_value_class_map.get(discriminator_value) else: diff --git a/okta/models/o_auth2_client_json_signing_key_response.py b/okta/models/o_auth2_client_json_signing_key_response.py index f5b939eb..c36cb201 100644 --- a/okta/models/o_auth2_client_json_signing_key_response.py +++ b/okta/models/o_auth2_client_json_signing_key_response.py @@ -125,7 +125,7 @@ def use_validate_enum(cls, value): @classmethod def get_discriminator_value(cls, obj: Dict[str, Any]) -> Optional[str]: """Returns the discriminator value (object type) of the data""" - discriminator_value = obj[cls.__discriminator_property_name] + discriminator_value = obj.get(cls.__discriminator_property_name) if discriminator_value: return cls.__discriminator_value_class_map.get(discriminator_value) else: diff --git a/okta/models/policy.py b/okta/models/policy.py index dc508702..40ce2db0 100644 --- a/okta/models/policy.py +++ b/okta/models/policy.py @@ -123,7 +123,7 @@ class Policy(BaseModel): @classmethod def get_discriminator_value(cls, obj: Dict[str, Any]) -> Optional[str]: """Returns the discriminator value (object type) of the data""" - discriminator_value = obj[cls.__discriminator_property_name] + discriminator_value = obj.get(cls.__discriminator_property_name) if discriminator_value: return cls.__discriminator_value_class_map.get(discriminator_value) else: diff --git a/okta/models/policy_rule.py b/okta/models/policy_rule.py index 5a208a1e..9b45192d 100644 --- a/okta/models/policy_rule.py +++ b/okta/models/policy_rule.py @@ -116,7 +116,7 @@ class PolicyRule(BaseModel): @classmethod def get_discriminator_value(cls, obj: Dict[str, Any]) -> Optional[str]: """Returns the discriminator value (object type) of the data""" - discriminator_value = obj[cls.__discriminator_property_name] + discriminator_value = obj.get(cls.__discriminator_property_name) if discriminator_value: return cls.__discriminator_value_class_map.get(discriminator_value) else: diff --git a/okta/models/privileged_resource.py b/okta/models/privileged_resource.py index 65cca4e2..3ac01531 100644 --- a/okta/models/privileged_resource.py +++ b/okta/models/privileged_resource.py @@ -98,7 +98,7 @@ class PrivilegedResource(BaseModel): @classmethod def get_discriminator_value(cls, obj: Dict[str, Any]) -> Optional[str]: """Returns the discriminator value (object type) of the data""" - discriminator_value = obj[cls.__discriminator_property_name] + discriminator_value = obj.get(cls.__discriminator_property_name) if discriminator_value: return cls.__discriminator_value_class_map.get(discriminator_value) else: diff --git a/okta/models/push_provider.py b/okta/models/push_provider.py index 01d53612..08dbf9f4 100644 --- a/okta/models/push_provider.py +++ b/okta/models/push_provider.py @@ -84,7 +84,7 @@ class PushProvider(BaseModel): @classmethod def get_discriminator_value(cls, obj: Dict[str, Any]) -> Optional[str]: """Returns the discriminator value (object type) of the data""" - discriminator_value = obj[cls.__discriminator_property_name] + discriminator_value = obj.get(cls.__discriminator_property_name) if discriminator_value: return cls.__discriminator_value_class_map.get(discriminator_value) else: diff --git a/okta/models/registration_inline_hook_request.py b/okta/models/registration_inline_hook_request.py index 365054b3..128d3f49 100644 --- a/okta/models/registration_inline_hook_request.py +++ b/okta/models/registration_inline_hook_request.py @@ -81,7 +81,7 @@ class RegistrationInlineHookRequest(BaseModel): @classmethod def get_discriminator_value(cls, obj: Dict[str, Any]) -> Optional[str]: """Returns the discriminator value (object type) of the data""" - discriminator_value = obj[cls.__discriminator_property_name] + discriminator_value = obj.get(cls.__discriminator_property_name) if discriminator_value: return cls.__discriminator_value_class_map.get(discriminator_value) else: diff --git a/okta/models/service_account.py b/okta/models/service_account.py index 1a5cd48d..62429a4c 100644 --- a/okta/models/service_account.py +++ b/okta/models/service_account.py @@ -142,7 +142,7 @@ def name_validate_regular_expression(cls, value): @classmethod def get_discriminator_value(cls, obj: Dict[str, Any]) -> Optional[str]: """Returns the discriminator value (object type) of the data""" - discriminator_value = obj[cls.__discriminator_property_name] + discriminator_value = obj.get(cls.__discriminator_property_name) if discriminator_value: return cls.__discriminator_value_class_map.get(discriminator_value) else: diff --git a/okta/models/user_factor.py b/okta/models/user_factor.py index 05911c07..03ba4997 100644 --- a/okta/models/user_factor.py +++ b/okta/models/user_factor.py @@ -126,7 +126,7 @@ class UserFactor(BaseModel): @classmethod def get_discriminator_value(cls, obj: Dict[str, Any]) -> Optional[str]: """Returns the discriminator value (object type) of the data""" - discriminator_value = obj[cls.__discriminator_property_name] + discriminator_value = obj.get(cls.__discriminator_property_name) if discriminator_value: return cls.__discriminator_value_class_map.get(discriminator_value) else: diff --git a/okta/models/user_factor_push_transaction.py b/okta/models/user_factor_push_transaction.py index c5172c7a..7d5a7530 100644 --- a/okta/models/user_factor_push_transaction.py +++ b/okta/models/user_factor_push_transaction.py @@ -102,7 +102,7 @@ def factor_result_validate_enum(cls, value): @classmethod def get_discriminator_value(cls, obj: Dict[str, Any]) -> Optional[str]: """Returns the discriminator value (object type) of the data""" - discriminator_value = obj[cls.__discriminator_property_name] + discriminator_value = obj.get(cls.__discriminator_property_name) if discriminator_value: return cls.__discriminator_value_class_map.get(discriminator_value) else: diff --git a/okta/models/user_risk_get_response.py b/okta/models/user_risk_get_response.py index 58c87f78..eb137f06 100644 --- a/okta/models/user_risk_get_response.py +++ b/okta/models/user_risk_get_response.py @@ -69,7 +69,7 @@ class UserRiskGetResponse(BaseModel): @classmethod def get_discriminator_value(cls, obj: Dict[str, Any]) -> Optional[str]: """Returns the discriminator value (object type) of the data""" - discriminator_value = obj[cls.__discriminator_property_name] + discriminator_value = obj.get(cls.__discriminator_property_name) if discriminator_value: return cls.__discriminator_value_class_map.get(discriminator_value) else: diff --git a/okta/models/validation_detail_provider.py b/okta/models/validation_detail_provider.py index 9ebbac15..da833cb6 100644 --- a/okta/models/validation_detail_provider.py +++ b/okta/models/validation_detail_provider.py @@ -69,7 +69,7 @@ class ValidationDetailProvider(BaseModel): @classmethod def get_discriminator_value(cls, obj: Dict[str, Any]) -> Optional[str]: """Returns the discriminator value (object type) of the data""" - discriminator_value = obj[cls.__discriminator_property_name] + discriminator_value = obj.get(cls.__discriminator_property_name) if discriminator_value: return cls.__discriminator_value_class_map.get(discriminator_value) else: diff --git a/okta/models/verification_method.py b/okta/models/verification_method.py index d5e8edcb..8ea7ab46 100644 --- a/okta/models/verification_method.py +++ b/okta/models/verification_method.py @@ -71,7 +71,7 @@ class VerificationMethod(BaseModel): @classmethod def get_discriminator_value(cls, obj: Dict[str, Any]) -> Optional[str]: """Returns the discriminator value (object type) of the data""" - discriminator_value = obj[cls.__discriminator_property_name] + discriminator_value = obj.get(cls.__discriminator_property_name) if discriminator_value: return cls.__discriminator_value_class_map.get(discriminator_value) else: diff --git a/openapi/api.yaml b/openapi/api.yaml index 2fe8bda9..357b9ac0 100644 --- a/openapi/api.yaml +++ b/openapi/api.yaml @@ -57967,6 +57967,59 @@ components: enum: - DISTRIBUTION - SECURITY + ActiveDirectoryApplication: + description: Active Directory application for directory integrations. This application type has a null signOnMode. + allOf: + - $ref: '#/components/schemas/Application' + - type: object + properties: + name: + type: string + description: Unique key for the Active Directory app definition. Always 'active_directory' for AD apps. + readOnly: true + settings: + $ref: '#/components/schemas/ActiveDirectoryApplicationSettings' + ActiveDirectoryApplicationSettings: + description: Settings for Active Directory applications + allOf: + - $ref: '#/components/schemas/ApplicationSettings' + - type: object + properties: + app: + $ref: '#/components/schemas/ActiveDirectoryApplicationSettingsApplication' + ActiveDirectoryApplicationSettingsApplication: + description: App-specific settings for Active Directory applications + type: object + properties: + activationEmail: + type: string + description: Email address to send activation emails + filterGroupsByOU: + type: boolean + description: Whether to filter groups by organizational unit + jitGroupsAcrossDomains: + type: boolean + description: Whether to enable just-in-time provisioning of groups across domains + login: + type: string + description: Login username for AD connection + nullable: true + namingContext: + type: string + description: The AD domain naming context (e.g., 'corp.local') + password: + type: string + description: Password for AD connection + nullable: true + scanRate: + type: integer + description: Rate at which to scan the AD directory + nullable: true + searchOrgUnit: + type: string + description: Organizational unit to search within + nullable: true + additionalProperties: true AdminConsoleSettings: title: Okta Admin Console Settings description: Settings specific to the Okta Admin Console @@ -59103,6 +59156,7 @@ components: maxLength: 1024 example: test team id Application: + x-json-converter: ApplicationJsonConverter type: object properties: accessibility: @@ -59237,6 +59291,7 @@ components: additionalProperties: true signOnMode: $ref: '#/components/schemas/ApplicationSignOnMode' + nullable: true status: $ref: '#/components/schemas/ApplicationLifecycleStatus' universalLogout: @@ -59257,7 +59312,6 @@ components: _links: $ref: '#/components/schemas/ApplicationLinks' required: - - signOnMode - label discriminator: propertyName: signOnMode diff --git a/openapi/config.yaml b/openapi/config.yaml index 2fda5b65..d708f3bd 100644 --- a/openapi/config.yaml +++ b/openapi/config.yaml @@ -41,6 +41,10 @@ files: okta/DOC_GUIDE.mustache: destinationFilename: okta/DOC_GUIDE.md templateType: SupportingFiles + application_json_converter.mustache: + destinationFilename: application_json_converter.py + templateType: SupportingFiles + folder: okta/models modelPackage: models apiPackage: api additionalProperties: diff --git a/openapi/generate.sh b/openapi/generate.sh index c63c32c6..989e85d7 100755 --- a/openapi/generate.sh +++ b/openapi/generate.sh @@ -6,11 +6,3 @@ export JAVA_TOOL_OPTIONS="-Dswagger.v3.parser.max_depth=5000 -Dsnakeyaml.maxAliasesForCollections=10000" java -Xmx12g -DmaxYamlCodePoints=10000000 -Dsnakeyaml.codepoint.limit=10000000 -jar openapi-generator-cli-7.7.0.jar generate -g python -c config.yaml -i api.yaml --skip-validate-spec #openapi-generator-cli generate -g python -c config.yaml -i api.yaml --skip-validate-spec - -# Post-process Application model to add unknown sign-on mode handling -echo "" -echo "=========================================" -echo "Post-processing Application model..." -echo "=========================================" -python3 post_process_application.py - diff --git a/openapi/post_process_application.py b/openapi/post_process_application.py deleted file mode 100644 index c45faf08..00000000 --- a/openapi/post_process_application.py +++ /dev/null @@ -1,189 +0,0 @@ -#!/usr/bin/env python3 -""" -Post-processing script to add unknown discriminator handling ONLY to Application model. -This runs after SDK generation to add the specific logic without affecting other models. - -Usage: python3 post_process_application.py -""" - -import re -import sys -from pathlib import Path - -# Paths -APPLICATION_FILE = Path(__file__).parent.parent / 'okta' / 'models' / 'application.py' - -def check_if_already_processed(): - """Check if the file has already been processed""" - content = APPLICATION_FILE.read_text() - return '_original_sign_on_mode: Optional[str] = None' in content - -def add_original_field(): - """Add the _original_sign_on_mode field to the Application class""" - content = APPLICATION_FILE.read_text() - - # Pattern: after links field, before __properties - pattern = r'( links: Optional\[ApplicationLinks\] = Field\(default=None, alias="_links"\)\n)( __properties: ClassVar)' - - if not re.search(pattern, content): - print("⚠️ Warning: Could not find insertion point for _original_sign_on_mode field") - return False - - replacement = r"\1 # Store the original sign-on mode value when it's not in the enum\n _original_sign_on_mode: Optional[str] = None\n\2" - content = re.sub(pattern, replacement, content) - - APPLICATION_FILE.write_text(content) - print("✓ Added _original_sign_on_mode field") - return True - -def add_to_dict_logic(): - """Add logic to return original sign-on mode in to_dict method""" - content = APPLICATION_FILE.read_text() - - # Pattern: before "return _dict" in to_dict method - pattern = r"( # override the default output from pydantic by calling `to_dict\(\)` of links\n if self\.links:\n if not isinstance\(self\.links, dict\):\n _dict\['_links'\] = self\.links\.to_dict\(\)\n else:\n _dict\['_links'\] = self\.links\n\n)( return _dict)" - - if not re.search(pattern, content): - print("⚠️ Warning: Could not find insertion point for to_dict logic") - return False - - replacement = r"\1 # If we have an original sign-on mode (was unknown), return it instead of OTHER\n if self._original_sign_on_mode:\n _dict['signOnMode'] = self._original_sign_on_mode\n\2" - content = re.sub(pattern, replacement, content) - - APPLICATION_FILE.write_text(content) - print("✓ Added to_dict logic for original sign-on mode") - return True - -def add_from_dict_logic(): - """Add logic to handle unknown sign-on modes in from_dict method""" - content = APPLICATION_FILE.read_text() - - # Pattern: Find where Application class processes itself (first occurrence in the mappedModels loop) - # We need to find: if object_type == 'Application': ... if object_type == cls.__name__: return cls.model_validate(obj) - # Use a more specific pattern that only matches the first occurrence - pattern = r"( if object_type == 'Application':\n # Check if the discriminator maps to the same class to avoid infinite recursion\n if object_type == cls\.__name__:\n)( return cls\.model_validate\(obj\)\n)( return models\.Application\.from_dict\(obj\))" - - # Check if already processed - if "# Handle unknown sign-on modes" in content: - print(" → Already processed (skipping duplicate addition)") - return True - - if not re.search(pattern, content): - print("⚠️ Warning: Could not find insertion point for from_dict logic") - return False - - replacement = r'''\1 # Handle unknown sign-on modes - original_discriminator = obj.get(cls.__discriminator_property_name) - if original_discriminator and original_discriminator not in cls.__discriminator_value_class_map: - # Store the original value and replace with OTHER - obj_copy = obj.copy() - # Note: This assumes an OTHER value exists in the discriminator enum - obj_copy[cls.__discriminator_property_name] = "OTHER" - instance = cls.model_validate(obj_copy) - instance._original_sign_on_mode = original_discriminator - return instance -\2\3''' - - # Only replace the FIRST occurrence - content = re.sub(pattern, replacement, content, count=1) - - APPLICATION_FILE.write_text(content) - print("✓ Added from_dict logic for unknown sign-on mode handling") - return True - -def remove_discriminator_logic_from_other_models(): - """Remove the _original_ field from other discriminator-based models""" - models_dir = APPLICATION_FILE.parent - - # Find all Python files with discriminators (except Application) - count_fixed = 0 - for py_file in models_dir.glob('*.py'): - if py_file.name == 'application.py' or py_file.name == '__init__.py': - continue - - content = py_file.read_text() - - # Check if this file has the _original_ field pattern - if re.search(r'_original_\w+: Optional\[str\] = None', content): - # Remove the field - content = re.sub( - r' # Store the original discriminator value.*?\n _original_\w+: Optional\[str\] = None\n', - '', - content - ) - - # Remove the to_dict logic - content = re.sub( - r' # If we have an original discriminator value.*?\n.*?_dict\[.*?\] = self\._original_.*?\n', - '', - content - ) - - # Remove the from_dict logic (more complex pattern) - content = re.sub( - r' # Handle unknown discriminator values\n.*?instance\._original_.*?\n.*?return instance\n', - '', - content, - flags=re.DOTALL - ) - - py_file.write_text(content) - count_fixed += 1 - print(f" ✓ Cleaned up {py_file.name}") - - if count_fixed > 0: - print(f"✓ Removed discriminator logic from {count_fixed} other model(s)") - - return count_fixed - -def main(): - """Main function to apply Application-specific modifications""" - print("="*70) - print("Post-Processing: Adding Unknown Discriminator Handling to Application") - print("="*70) - - if not APPLICATION_FILE.exists(): - print(f"❌ Error: Application file not found at {APPLICATION_FILE}") - sys.exit(1) - - # Check if already processed - if check_if_already_processed(): - print("\n⚠️ Application.py appears to already be processed.") - print(" Skipping modifications to avoid duplication.") - print(" If you need to reprocess, regenerate the SDK first.") - return - - try: - success = True - success &= add_original_field() - success &= add_to_dict_logic() - success &= add_from_dict_logic() - - # Clean up other models - print("\nCleaning up other models...") - remove_discriminator_logic_from_other_models() - - if success: - print("\n" + "="*70) - print("✓ Post-processing completed successfully!") - print(" Application.py now handles unknown sign-on modes") - print(" Other models remain unaffected") - print("="*70) - else: - print("\n⚠️ Post-processing completed with warnings") - sys.exit(1) - - except Exception as e: - print(f"\n❌ Post-processing failed: {e}") - import traceback - traceback.print_exc() - sys.exit(1) - -if __name__ == "__main__": - main() - - - - - - diff --git a/openapi/templates/application_json_converter.mustache b/openapi/templates/application_json_converter.mustache new file mode 100644 index 00000000..58946aee --- /dev/null +++ b/openapi/templates/application_json_converter.mustache @@ -0,0 +1,221 @@ +{{>partial_header}} + +import json +from typing import Dict, Any, Optional + +# Import the base and subclass models +from {{packageName}}.models.application import Application +from {{packageName}}.models.active_directory_application import ActiveDirectoryApplication +from {{packageName}}.models.auto_login_application import AutoLoginApplication +from {{packageName}}.models.basic_auth_application import BasicAuthApplication +from {{packageName}}.models.bookmark_application import BookmarkApplication +from {{packageName}}.models.browser_plugin_application import BrowserPluginApplication +from {{packageName}}.models.open_id_connect_application import OpenIdConnectApplication +from {{packageName}}.models.saml11_application import Saml11Application +from {{packageName}}.models.saml_application import SamlApplication +from {{packageName}}.models.secure_password_store_application import SecurePasswordStoreApplication +from {{packageName}}.models.ws_federation_application import WsFederationApplication + +class ApplicationJsonConverter: + """ + Custom JSON converter/factory for Application that handles null, empty, and unknown signOnMode values. + + Routing logic: + - null (None) → ActiveDirectoryApplication (AD integrations have null signOnMode) + - "" (empty string) → Application base class with original value preserved + - Known modes (AUTO_LOGIN, SAML_2_0, etc.) → Specific subclasses (case-insensitive) + - Unknown modes (e.g., FUTURE_MODE) → Application base class with original value preserved + + Round-trip behavior: + - Unknown values stored in _original_sign_on_mode attribute + - Restored in to_dict() output for API fidelity + - None (for missing field) routes to ActiveDirectoryApplication + - Empty string is preserved as-is for round-trip integrity + + Case handling: + - Known modes are normalized to uppercase (e.g., "auto_login" → "AUTO_LOGIN") + - Unknown modes preserve original casing (e.g., "Future_Mode" stays "Future_Mode") + """ + + # All known signOnMode values from ApplicationSignOnMode enum + KNOWN_SIGN_ON_MODES = { + "AUTO_LOGIN", + "BASIC_AUTH", + "BOOKMARK", + "BROWSER_PLUGIN", + "OPENID_CONNECT", + "SAML_1_1", + "SAML_2_0", + "SECURE_PASSWORD_STORE", + "WS_FEDERATION", + "MFA_AS_SERVICE", # Maps to base Application class + } + + SIGN_ON_MODE_MAPPING = { + "AUTO_LOGIN": AutoLoginApplication, + "BASIC_AUTH": BasicAuthApplication, + "BOOKMARK": BookmarkApplication, + "BROWSER_PLUGIN": BrowserPluginApplication, + "OPENID_CONNECT": OpenIdConnectApplication, + "SAML_1_1": Saml11Application, + "SAML_2_0": SamlApplication, + "SECURE_PASSWORD_STORE": SecurePasswordStoreApplication, + "WS_FEDERATION": WsFederationApplication, + "MFA_AS_SERVICE": Application, # Explicitly map to base Application + } + + @classmethod + def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Application]: + """ + Reads the dictionary representation of the Application object, using signOnMode + to determine the correct type. Handles the special case where signOnMode is null + (Active Directory applications) or unknown (future sign-on modes). + """ + if obj is None: + return None + + # Get the signOnMode value to determine the concrete type + sign_on_mode = obj.get("signOnMode") + original_sign_on_mode = None + + # Make a copy to avoid modifying the input + obj_copy = dict(obj) + + # Determine the target type based on signOnMode + if sign_on_mode is None: + # null signOnMode indicates an Active Directory application + target_type = ActiveDirectoryApplication + elif sign_on_mode == "": + # Empty string signOnMode is treated as unknown (not ActiveDirectory) + original_sign_on_mode = sign_on_mode + obj_copy["signOnMode"] = None + target_type = Application + elif str(sign_on_mode).upper() in cls.KNOWN_SIGN_ON_MODES: + # Known sign-on mode - map to specific class (or base Application for MFA_AS_SERVICE) + target_type = cls.SIGN_ON_MODE_MAPPING[str(sign_on_mode).upper()] + obj_copy["signOnMode"] = str(sign_on_mode).upper() # Normalize for validation + else: + # Unknown sign-on mode - preserve original value and set to None for validation + logger.info( + f"Unknown signOnMode '{sign_on_mode}' encountered, " + f"routing to base Application class" + ) + original_sign_on_mode = sign_on_mode + obj_copy["signOnMode"] = None + target_type = Application + + # Use Pydantic's model_validate to avoid recursion + # (calling from_dict would trigger the converter again in subclasses) + instance = target_type.model_validate(obj_copy) + + # Validate consistency (in dev/test mode) + if isinstance(instance, Application) and not isinstance(instance, type(instance).__bases__[0]): + # Log warning if subclass instantiated but fields seem inconsistent + logger.debug(f"Routed {sign_on_mode} to {type(instance).__name__}") + + # Store the original unknown sign-on mode if present + # For known modes, set to None to indicate no preservation needed + if original_sign_on_mode is not None: + # Use object.__setattr__ to bypass Pydantic's validation + object.__setattr__(instance, '_original_sign_on_mode', original_sign_on_mode) + else: + object.__setattr__(instance, '_original_sign_on_mode', None) + + return instance + + @classmethod + def to_dict(cls, value: Application) -> Optional[Dict[str, Any]]: + """ + Writes the dictionary representation of the Application object. + Uses Pydantic's model_dump directly to avoid recursion. + Restores original unknown signOnMode values for round-trip fidelity. + """ + if value is None: + return None + + # Use Pydantic's model_dump to serialize the instance + # This avoids calling to_dict() which would recurse back to this converter + # Derive from model configuration + excluded_fields = { + field_name for field_name, field_info in Application.model_fields.items() + if field_info.exclude or (field_info.json_schema_extra and field_info.json_schema_extra.get('readOnly')) + } + + _dict = value.model_dump( + by_alias=True, + exclude=excluded_fields, + exclude_none=True, + mode='json', # Serialize enums to their values + ) + + # Restore original unknown signOnMode if it was preserved + if hasattr(value, '_original_sign_on_mode') and value._original_sign_on_mode is not None: + _dict["signOnMode"] = value._original_sign_on_mode + # Ensure signOnMode is serialized as string (in case mode='json' didn't handle it) + elif "signOnMode" in _dict and hasattr(_dict["signOnMode"], 'value'): + _dict["signOnMode"] = _dict["signOnMode"].value + + # Handle nested model serialization manually + # (Pydantic will handle most of this, but we need to ensure proper to_dict() calls for nested models) + if value.accessibility: + if not isinstance(value.accessibility, dict): + _dict["accessibility"] = value.accessibility.to_dict() + else: + _dict["accessibility"] = value.accessibility + + if value.express_configuration: + if not isinstance(value.express_configuration, dict): + _dict["expressConfiguration"] = value.express_configuration.to_dict() + else: + _dict["expressConfiguration"] = value.express_configuration + + if value.licensing: + if not isinstance(value.licensing, dict): + _dict["licensing"] = value.licensing.to_dict() + else: + _dict["licensing"] = value.licensing + + if value.universal_logout: + if not isinstance(value.universal_logout, dict): + _dict["universalLogout"] = value.universal_logout.to_dict() + else: + _dict["universalLogout"] = value.universal_logout + + if value.visibility: + if not isinstance(value.visibility, dict): + _dict["visibility"] = value.visibility.to_dict() + else: + _dict["visibility"] = value.visibility + + if value.embedded: + if not isinstance(value.embedded, dict): + _dict["_embedded"] = value.embedded.to_dict() + else: + _dict["_embedded"] = value.embedded + + if value.links: + if not isinstance(value.links, dict): + _dict["_links"] = value.links.to_dict() + else: + _dict["_links"] = value.links + + # Handle subclass-specific fields + # Check if instance has settings (for subclasses like ActiveDirectoryApplication) + if hasattr(value, 'settings') and value.settings: + if not isinstance(value.settings, dict): + _dict["settings"] = value.settings.to_dict() + else: + _dict["settings"] = value.settings + + # Check if instance has credentials (for subclasses like AutoLoginApplication) + if hasattr(value, 'credentials') and value.credentials: + if not isinstance(value.credentials, dict): + _dict["credentials"] = value.credentials.to_dict() + else: + _dict["credentials"] = value.credentials + + # Check if instance has name (for various subclasses) + if hasattr(value, 'name') and value.name: + _dict["name"] = value.name + + return _dict diff --git a/openapi/templates/model_generic.mustache b/openapi/templates/model_generic.mustache index 68fc3743..7a5d79e8 100644 --- a/openapi/templates/model_generic.mustache +++ b/openapi/templates/model_generic.mustache @@ -105,7 +105,7 @@ class {{classname}}({{#parent}}{{{.}}}{{/parent}}{{^parent}}BaseModel{{/parent}} @classmethod def get_discriminator_value(cls, obj: Dict[str, Any]) -> Optional[str]: """Returns the discriminator value (object type) of the data""" - discriminator_value = obj[cls.__discriminator_property_name] + discriminator_value = obj.get(cls.__discriminator_property_name) if discriminator_value: return cls.__discriminator_value_class_map.get(discriminator_value) else: @@ -143,6 +143,11 @@ class {{classname}}({{#parent}}{{{.}}}{{/parent}}{{^parent}}BaseModel{{/parent}} * Fields in `self.additional_properties` are added to the output dict. {{/isAdditionalPropertiesTrue}} """ + {{#vendorExtensions.x-json-converter}} + from {{packageName}}.models.application_json_converter import {{{.}}} + return {{{.}}}.to_dict(self) + {{/vendorExtensions.x-json-converter}} + {{^vendorExtensions.x-json-converter}} excluded_fields: Set[str] = set([ {{#vendorExtensions.x-py-readonly}} "{{{.}}}", @@ -247,11 +252,17 @@ class {{classname}}({{#parent}}{{{.}}}{{/parent}}{{^parent}}BaseModel{{/parent}} {{/isNullable}} {{/allVars}} return _dict + {{/vendorExtensions.x-json-converter}} {{#hasChildren}} @classmethod def from_dict(cls, obj: Dict[str, Any]) -> Optional[{{#discriminator}}Union[{{#mappedModels}}{{{modelName}}}{{^-last}}, {{/-last}}{{/mappedModels}}]{{/discriminator}}{{^discriminator}}Self{{/discriminator}}]: """Create an instance of {{{classname}}} from a dict""" + {{#vendorExtensions.x-json-converter}} + from {{packageName}}.models.application_json_converter import {{{.}}} + return {{{.}}}.from_dict(obj) + {{/vendorExtensions.x-json-converter}} + {{^vendorExtensions.x-json-converter}} {{#discriminator}} # look up the object type based on discriminator mapping object_type = cls.get_discriminator_value(obj) @@ -269,6 +280,7 @@ class {{classname}}({{#parent}}{{{.}}}{{/parent}}{{^parent}}BaseModel{{/parent}} json.dumps(obj) + ". Discriminator property name: " + cls.__discriminator_property_name + ", mapping: " + json.dumps(cls.__discriminator_value_class_map)) {{/discriminator}} + {{/vendorExtensions.x-json-converter}} {{/hasChildren}} {{^hasChildren}} @classmethod From add115e4183755aa5c47509213a0710653ff4e07 Mon Sep 17 00:00:00 2001 From: BinoyOza-okta Date: Mon, 16 Mar 2026 03:54:18 +0530 Subject: [PATCH 4/4] - Removed MFA_AS_SERVICE sign on mode as we have added the support for handling unknown signOnModes. - Updated application_json_converter.mustache for flake8 issues. --- docs/ApplicationSignOnMode.md | 2 +- okta/models/application.py | 6 +- okta/models/application_json_converter.py | 17 ++- okta/models/application_sign_on_mode.py | 4 +- openapi/api.yaml | 3 - .../application_json_converter.mustache | 111 ++++++++---------- 6 files changed, 62 insertions(+), 81 deletions(-) diff --git a/docs/ApplicationSignOnMode.md b/docs/ApplicationSignOnMode.md index 505d1bd2..a8bb096b 100644 --- a/docs/ApplicationSignOnMode.md +++ b/docs/ApplicationSignOnMode.md @@ -1,6 +1,6 @@ # ApplicationSignOnMode -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: +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 | Select the `signOnMode` for your custom app: ## Properties diff --git a/okta/models/application.py b/okta/models/application.py index 99353699..b9f331b7 100644 --- a/okta/models/application.py +++ b/okta/models/application.py @@ -49,7 +49,6 @@ from okta.models.basic_auth_application import BasicAuthApplication from okta.models.bookmark_application import BookmarkApplication 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.saml11_application import Saml11Application from okta.models.saml_application import SamlApplication @@ -60,7 +59,7 @@ from okta.models.active_directory_application import ActiveDirectoryApplication -class Application(BaseModel): # noqa: F811 +class Application(BaseModel): """ Application """ # noqa: E501 @@ -212,7 +211,6 @@ def features_validate_enum(cls, value): "BASIC_AUTH": "BasicAuthApplication", "BOOKMARK": "BookmarkApplication", "BROWSER_PLUGIN": "BrowserPluginApplication", - "MFA_AS_SERVICE": "Application", "OPENID_CONNECT": "OpenIdConnectApplication", "SAML_1_1": "Saml11Application", "SAML_2_0": "SamlApplication", @@ -246,7 +244,6 @@ def from_json(cls, json_str: str) -> Optional[ BasicAuthApplication, BookmarkApplication, BrowserPluginApplication, - Application, OpenIdConnectApplication, Saml11Application, SamlApplication, @@ -284,7 +281,6 @@ def from_dict(cls, obj: Dict[str, Any]) -> Optional[ BasicAuthApplication, BookmarkApplication, BrowserPluginApplication, - Application, OpenIdConnectApplication, Saml11Application, SamlApplication, diff --git a/okta/models/application_json_converter.py b/okta/models/application_json_converter.py index 55bdae85..c5b57454 100644 --- a/okta/models/application_json_converter.py +++ b/okta/models/application_json_converter.py @@ -1,3 +1,13 @@ +# The Okta software accompanied by this notice is provided pursuant to the following terms: +# Copyright © 2025-Present, Okta, Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the +# License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS +# IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and limitations under the License. +# coding: utf-8 + """ Okta Admin Management @@ -10,7 +20,6 @@ Do not edit the class manually. """ # noqa: E501 import logging -logger = logging.getLogger(__name__) from typing import Dict, Any, Optional @@ -27,6 +36,8 @@ from okta.models.secure_password_store_application import SecurePasswordStoreApplication from okta.models.ws_federation_application import WsFederationApplication +logger = logging.getLogger(__name__) + class ApplicationJsonConverter: """ @@ -60,7 +71,6 @@ class ApplicationJsonConverter: "SAML_2_0", "SECURE_PASSWORD_STORE", "WS_FEDERATION", - "MFA_AS_SERVICE", # Maps to base Application class } SIGN_ON_MODE_MAPPING = { @@ -73,7 +83,6 @@ class ApplicationJsonConverter: "SAML_2_0": SamlApplication, "SECURE_PASSWORD_STORE": SecurePasswordStoreApplication, "WS_FEDERATION": WsFederationApplication, - "MFA_AS_SERVICE": Application, # Explicitly map to base Application } @classmethod @@ -151,8 +160,6 @@ def to_dict(cls, value: Application) -> Optional[Dict[str, Any]]: if value is None: return None - # Use Pydantic's model_dump to serialize the instance - # This avoids calling to_dict() which would recurse back to this converter # Derive from model configuration excluded_fields = { field_name diff --git a/okta/models/application_sign_on_mode.py b/okta/models/application_sign_on_mode.py index 011925f7..65c73fc1 100644 --- a/okta/models/application_sign_on_mode.py +++ b/okta/models/application_sign_on_mode.py @@ -36,8 +36,7 @@ class ApplicationSignOnMode(str, Enum): 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: + WS-Federation Passive Requestor Profile | Select the `signOnMode` for your custom app: """ """ @@ -52,7 +51,6 @@ class ApplicationSignOnMode(str, Enum): SAML_2_0 = "SAML_2_0" SECURE_PASSWORD_STORE = "SECURE_PASSWORD_STORE" WS_FEDERATION = "WS_FEDERATION" - MFA_AS_SERVICE = "MFA_AS_SERVICE" @classmethod def from_json(cls, json_str: str) -> Self: diff --git a/openapi/api.yaml b/openapi/api.yaml index 357b9ac0..7082c82f 100644 --- a/openapi/api.yaml +++ b/openapi/api.yaml @@ -59325,7 +59325,6 @@ components: SAML_2_0: '#/components/schemas/SamlApplication' SECURE_PASSWORD_STORE: '#/components/schemas/SecurePasswordStoreApplication' WS_FEDERATION: '#/components/schemas/WsFederationApplication' - MFA_AS_SERVICE: '#/components/schemas/Application' ApplicationAccessibility: description: Specifies access settings for the app type: object @@ -59788,7 +59787,6 @@ components: | 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: type: string @@ -59802,7 +59800,6 @@ components: - SAML_2_0 - SECURE_PASSWORD_STORE - WS_FEDERATION - - MFA_AS_SERVICE ApplicationType: description: 'The type of client application. Default value: `web`.' type: string diff --git a/openapi/templates/application_json_converter.mustache b/openapi/templates/application_json_converter.mustache index 58946aee..9b832067 100644 --- a/openapi/templates/application_json_converter.mustache +++ b/openapi/templates/application_json_converter.mustache @@ -1,5 +1,17 @@ +# The Okta software accompanied by this notice is provided pursuant to the following terms: +# Copyright © 2025-Present, Okta, Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the +# License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS +# IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and limitations under the License. +# coding: utf-8 + {{>partial_header}} +import logging + import json from typing import Dict, Any, Optional @@ -16,6 +28,9 @@ from {{packageName}}.models.saml_application import SamlApplication from {{packageName}}.models.secure_password_store_application import SecurePasswordStoreApplication from {{packageName}}.models.ws_federation_application import WsFederationApplication +logger = logging.getLogger(__name__) + + class ApplicationJsonConverter: """ Custom JSON converter/factory for Application that handles null, empty, and unknown signOnMode values. @@ -48,7 +63,6 @@ class ApplicationJsonConverter: "SAML_2_0", "SECURE_PASSWORD_STORE", "WS_FEDERATION", - "MFA_AS_SERVICE", # Maps to base Application class } SIGN_ON_MODE_MAPPING = { @@ -61,7 +75,6 @@ class ApplicationJsonConverter: "SAML_2_0": SamlApplication, "SECURE_PASSWORD_STORE": SecurePasswordStoreApplication, "WS_FEDERATION": WsFederationApplication, - "MFA_AS_SERVICE": Application, # Explicitly map to base Application } @classmethod @@ -93,10 +106,12 @@ class ApplicationJsonConverter: elif str(sign_on_mode).upper() in cls.KNOWN_SIGN_ON_MODES: # Known sign-on mode - map to specific class (or base Application for MFA_AS_SERVICE) target_type = cls.SIGN_ON_MODE_MAPPING[str(sign_on_mode).upper()] - obj_copy["signOnMode"] = str(sign_on_mode).upper() # Normalize for validation + obj_copy["signOnMode"] = str( + sign_on_mode + ).upper() # Normalize for validation else: # Unknown sign-on mode - preserve original value and set to None for validation - logger.info( + logger.debug( f"Unknown signOnMode '{sign_on_mode}' encountered, " f"routing to base Application class" ) @@ -109,7 +124,9 @@ class ApplicationJsonConverter: instance = target_type.model_validate(obj_copy) # Validate consistency (in dev/test mode) - if isinstance(instance, Application) and not isinstance(instance, type(instance).__bases__[0]): + if isinstance(instance, Application) and not isinstance( + instance, type(instance).__bases__[0] + ): # Log warning if subclass instantiated but fields seem inconsistent logger.debug(f"Routed {sign_on_mode} to {type(instance).__name__}") @@ -117,9 +134,11 @@ class ApplicationJsonConverter: # For known modes, set to None to indicate no preservation needed if original_sign_on_mode is not None: # Use object.__setattr__ to bypass Pydantic's validation - object.__setattr__(instance, '_original_sign_on_mode', original_sign_on_mode) + object.__setattr__( + instance, "_original_sign_on_mode", original_sign_on_mode + ) else: - object.__setattr__(instance, '_original_sign_on_mode', None) + object.__setattr__(instance, "_original_sign_on_mode", None) return instance @@ -133,89 +152,53 @@ class ApplicationJsonConverter: if value is None: return None - # Use Pydantic's model_dump to serialize the instance - # This avoids calling to_dict() which would recurse back to this converter # Derive from model configuration excluded_fields = { - field_name for field_name, field_info in Application.model_fields.items() - if field_info.exclude or (field_info.json_schema_extra and field_info.json_schema_extra.get('readOnly')) + field_name + for field_name, field_info in Application.model_fields.items() + if field_info.exclude + or ( + field_info.json_schema_extra + and field_info.json_schema_extra.get("readOnly") + ) } _dict = value.model_dump( by_alias=True, exclude=excluded_fields, exclude_none=True, - mode='json', # Serialize enums to their values + mode="json", # Serialize enums to their values ) # Restore original unknown signOnMode if it was preserved - if hasattr(value, '_original_sign_on_mode') and value._original_sign_on_mode is not None: + if ( + hasattr(value, "_original_sign_on_mode") + and value._original_sign_on_mode is not None + ): _dict["signOnMode"] = value._original_sign_on_mode # Ensure signOnMode is serialized as string (in case mode='json' didn't handle it) - elif "signOnMode" in _dict and hasattr(_dict["signOnMode"], 'value'): + elif "signOnMode" in _dict and hasattr(_dict["signOnMode"], "value"): _dict["signOnMode"] = _dict["signOnMode"].value - # Handle nested model serialization manually - # (Pydantic will handle most of this, but we need to ensure proper to_dict() calls for nested models) - if value.accessibility: - if not isinstance(value.accessibility, dict): - _dict["accessibility"] = value.accessibility.to_dict() - else: - _dict["accessibility"] = value.accessibility - - if value.express_configuration: - if not isinstance(value.express_configuration, dict): - _dict["expressConfiguration"] = value.express_configuration.to_dict() - else: - _dict["expressConfiguration"] = value.express_configuration - - if value.licensing: - if not isinstance(value.licensing, dict): - _dict["licensing"] = value.licensing.to_dict() - else: - _dict["licensing"] = value.licensing - - if value.universal_logout: - if not isinstance(value.universal_logout, dict): - _dict["universalLogout"] = value.universal_logout.to_dict() - else: - _dict["universalLogout"] = value.universal_logout - - if value.visibility: - if not isinstance(value.visibility, dict): - _dict["visibility"] = value.visibility.to_dict() - else: - _dict["visibility"] = value.visibility - - if value.embedded: - if not isinstance(value.embedded, dict): - _dict["_embedded"] = value.embedded.to_dict() - else: - _dict["_embedded"] = value.embedded - - if value.links: - if not isinstance(value.links, dict): - _dict["_links"] = value.links.to_dict() - else: - _dict["_links"] = value.links + # NOTE: Pydantic's model_dump(mode='json') handles most nested BaseModel serialization. + # However, subclass-specific fields (settings, credentials, name) need manual handling + # because they're not in the base Application schema and model_dump doesn't call + # their custom to_dict() methods which may have additional logic. - # Handle subclass-specific fields - # Check if instance has settings (for subclasses like ActiveDirectoryApplication) - if hasattr(value, 'settings') and value.settings: + # Handle subclass-specific fields that may have custom serialization logic + if hasattr(value, "settings") and value.settings: if not isinstance(value.settings, dict): _dict["settings"] = value.settings.to_dict() else: _dict["settings"] = value.settings - # Check if instance has credentials (for subclasses like AutoLoginApplication) - if hasattr(value, 'credentials') and value.credentials: + if hasattr(value, "credentials") and value.credentials: if not isinstance(value.credentials, dict): _dict["credentials"] = value.credentials.to_dict() else: _dict["credentials"] = value.credentials - # Check if instance has name (for various subclasses) - if hasattr(value, 'name') and value.name: + if hasattr(value, "name") and value.name: _dict["name"] = value.name return _dict