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/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/__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 5c3641b7..b9f331b7 100644 --- a/okta/models/application.py +++ b/okta/models/application.py @@ -26,9 +26,8 @@ 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 pydantic import BaseModel, ConfigDict, Field, StrictStr, field_validator @@ -50,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 @@ -58,9 +56,10 @@ SecurePasswordStoreApplication, ) from okta.models.ws_federation_application import WsFederationApplication + from okta.models.active_directory_application import ActiveDirectoryApplication -class Application(BaseModel): # noqa: F811 +class Application(BaseModel): """ Application """ # noqa: E501 @@ -100,7 +99,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" @@ -210,18 +211,18 @@ 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", "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: @@ -243,12 +244,12 @@ def from_json(cls, json_str: str) -> Optional[ BasicAuthApplication, BookmarkApplication, BrowserPluginApplication, - Application, OpenIdConnectApplication, Saml11Application, SamlApplication, SecurePasswordStoreApplication, WsFederationApplication, + ActiveDirectoryApplication, ] ]: """Create an instance of Application from a JSON string""" @@ -269,71 +270,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[ @@ -342,75 +281,15 @@ def from_dict(cls, obj: Dict[str, Any]) -> Optional[ BasicAuthApplication, BookmarkApplication, BrowserPluginApplication, - Application, OpenIdConnectApplication, Saml11Application, 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..c5b57454 --- /dev/null +++ b/okta/models/application_json_converter.py @@ -0,0 +1,212 @@ +# 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 +import logging + +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 + +logger = logging.getLogger(__name__) + + +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", + } + + 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, + } + + @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 + + # 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/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/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..7082c82f 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 @@ -59271,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 @@ -59734,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 @@ -59748,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/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/templates/application_json_converter.mustache b/openapi/templates/application_json_converter.mustache new file mode 100644 index 00000000..9b832067 --- /dev/null +++ b/openapi/templates/application_json_converter.mustache @@ -0,0 +1,204 @@ +# 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 + +# 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 + +logger = logging.getLogger(__name__) + + +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", + } + + 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, + } + + @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 + + # 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/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