From 22fffbee909f1f55bacd519e80764b80647f45dd Mon Sep 17 00:00:00 2001 From: Prachi Pandey Date: Tue, 21 Jul 2026 13:46:15 +0530 Subject: [PATCH 1/2] fix: disable broken polymorphic resolution on 6 more oneOf/discriminator schemas Deep-dive into the still-open GitHub issues (#1654, #1664, #1693) led to auditing every oneOf/anyOf + discriminator pair in the spec for the same defect class as OKTA-1227472 (subtypes that don't actually extend the declared base type via allOf, so Jackson's @JsonSubTypes throws InvalidTypeIdException). Found and fixed 6 more instances: - AgentJsonSigningKeyRequest / AgentJsonSigningKeyResponse (unreferenced in the spec today, but fixed for correctness/consistency) - ManagedConnection / ManagedConnectionCreatable - PotentialConnection - OrgContactTypeObj - this one is live and reachable from the real listOrgContactTypes endpoint Also adds a regression test confirming GH-1654's likely root cause: a mixed OIDC/SAML Application list, where the SAML app's settings.signOn.attributeStatements uses the SamlAttributeStatement type fixed in OKTA-1227472/#1699, now deserializes without throwing. Before that fix, an org with SAML apps using attribute statements could see listApplications fail on those specific apps. Co-Authored-By: Claude Code --- .../custom_templates/ApiClient.mustache | 12 +++- .../client/ApiClientJacksonMixinTest.java | 55 +++++++++++++++++++ 2 files changed, 66 insertions(+), 1 deletion(-) diff --git a/api/src/main/resources/custom_templates/ApiClient.mustache b/api/src/main/resources/custom_templates/ApiClient.mustache index 9d25f21af89..fe3e6b5279e 100644 --- a/api/src/main/resources/custom_templates/ApiClient.mustache +++ b/api/src/main/resources/custom_templates/ApiClient.mustache @@ -238,9 +238,19 @@ protected List servers = new ArrayList objectMapper.enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY); // OKTA-1227472: disable broken polymorphic type resolution on models whose spec-declared - // discriminator subtypes don't actually extend them. + // discriminator subtypes don't actually extend them. Found via an audit of every + // oneOf/anyOf + discriminator pair in the spec for this same defect class - these six + // have the identical problem (ListJwk200ResponseInner/SamlAttributeStatement above were + // the first two found; OrgContactTypeObj in particular breaks the real, commonly-used + // listOrgContactTypes endpoint). objectMapper.addMixIn(com.okta.sdk.resource.model.ListJwk200ResponseInner.class, NoPolymorphicTypeInfoMixin.class); objectMapper.addMixIn(com.okta.sdk.resource.model.SamlAttributeStatement.class, NoPolymorphicTypeInfoMixin.class); + objectMapper.addMixIn(com.okta.sdk.resource.model.AgentJsonSigningKeyRequest.class, NoPolymorphicTypeInfoMixin.class); + objectMapper.addMixIn(com.okta.sdk.resource.model.AgentJsonSigningKeyResponse.class, NoPolymorphicTypeInfoMixin.class); + objectMapper.addMixIn(com.okta.sdk.resource.model.ManagedConnection.class, NoPolymorphicTypeInfoMixin.class); + objectMapper.addMixIn(com.okta.sdk.resource.model.ManagedConnectionCreatable.class, NoPolymorphicTypeInfoMixin.class); + objectMapper.addMixIn(com.okta.sdk.resource.model.OrgContactTypeObj.class, NoPolymorphicTypeInfoMixin.class); + objectMapper.addMixIn(com.okta.sdk.resource.model.PotentialConnection.class, NoPolymorphicTypeInfoMixin.class); // OKTA-1218351: restore NON_NULL serialization on Application and its subtypes, overriding the // per-property JsonInclude(ALWAYS) generated for their required properties. diff --git a/api/src/test/java/com/okta/sdk/resource/client/ApiClientJacksonMixinTest.java b/api/src/test/java/com/okta/sdk/resource/client/ApiClientJacksonMixinTest.java index a3cfcc8ef70..baac63be070 100644 --- a/api/src/test/java/com/okta/sdk/resource/client/ApiClientJacksonMixinTest.java +++ b/api/src/test/java/com/okta/sdk/resource/client/ApiClientJacksonMixinTest.java @@ -19,10 +19,14 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.okta.sdk.cache.Cache; import com.okta.sdk.cache.CacheManager; +import com.okta.sdk.resource.model.Application; import com.okta.sdk.resource.model.ApplicationVisibility; import com.okta.sdk.resource.model.ApplicationVisibilityHide; import com.okta.sdk.resource.model.ListJwk200ResponseInner; import com.okta.sdk.resource.model.OpenIdConnectApplication; +import com.okta.sdk.resource.model.OrgContactType; +import com.okta.sdk.resource.model.OrgContactTypeObj; +import com.okta.sdk.resource.model.SamlApplication; import com.okta.sdk.resource.model.SamlAttributeStatement; import org.apache.hc.client5.http.impl.classic.HttpClients; import org.testng.annotations.Test; @@ -135,4 +139,55 @@ public void serializeFullOpenIdConnectApplication_stillIncludesRequiredFields() assertNotNull(json); assertTrue(json.contains("\"name\""), "name should be present when set, got: " + json); } + + /** + * GH-1654: reported that listApplications only returned OIDC apps, no SAML apps. A SAML app whose + * settings.signOn.attributeStatements uses the (now-fixed) broken SamlAttributeStatement type would + * throw InvalidTypeIdException while parsing the response array - depending on how a caller's + * pagination/error handling reacted to that, it could plausibly look like SAML apps were being + * silently dropped. Confirms a mixed OIDC/SAML list - with attribute statements populated - now + * deserializes cleanly end-to-end via Application's own (structurally correct) polymorphism. + */ + @Test + public void deserializeApplicationList_withMixedOidcAndSamlAttributeStatements_doesNotThrow() throws Exception { + String json = "[" + + "{\"signOnMode\":\"OPENID_CONNECT\",\"label\":\"oidc-app\",\"id\":\"0oa1\"}," + + "{\"signOnMode\":\"SAML_2_0\",\"label\":\"saml-app\",\"id\":\"0oa2\",\"settings\":{\"signOn\":{" + + "\"attributeStatements\":[" + + "{\"type\":\"EXPRESSION\",\"name\":\"email\",\"values\":[\"user.email\"]}," + + "{\"type\":\"GROUP\",\"filterType\":\"STARTS_WITH\",\"filterValue\":\"Team\"}" + + "]}}}" + + "]"; + + List apps = objectMapper.readValue(json, new TypeReference>() { }); + + assertEquals(apps.size(), 2); + assertTrue(apps.get(0) instanceof OpenIdConnectApplication, "expected OpenIdConnectApplication, got " + apps.get(0).getClass()); + assertTrue(apps.get(1) instanceof SamlApplication, "expected SamlApplication, got " + apps.get(1).getClass()); + + SamlApplication samlApp = (SamlApplication) apps.get(1); + List statements = samlApp.getSettings().getSignOn().getAttributeStatements(); + assertEquals(statements.size(), 2); + assertEquals(statements.get(0).getType(), SamlAttributeStatement.TypeEnum.EXPRESSION); + assertEquals(statements.get(1).getType(), SamlAttributeStatement.TypeEnum.GROUP); + } + + /** + * Found via an audit of every oneOf/anyOf + discriminator pair in the spec for the same defect class + * as OKTA-1227472: OrgContactTypeObj declares BILLING/TECHNICAL subtypes that don't extend it, breaking + * the real listOrgContactTypes endpoint. + */ + @Test + public void deserializeOrgContactTypeObj_withBillingAndTechnicalEntries_doesNotThrow() throws Exception { + String json = "[" + + "{\"contactType\":\"BILLING\"}," + + "{\"contactType\":\"TECHNICAL\"}" + + "]"; + + List contacts = objectMapper.readValue(json, new TypeReference>() { }); + + assertEquals(contacts.size(), 2); + assertEquals(contacts.get(0).getContactType(), OrgContactType.BILLING); + assertEquals(contacts.get(1).getContactType(), OrgContactType.TECHNICAL); + } } From 3cf281ee4065c3a4cf1bcd700f9cf618fea9c71f Mon Sep 17 00:00:00 2001 From: Prachi Pandey Date: Tue, 21 Jul 2026 13:59:01 +0530 Subject: [PATCH 2/2] test: add missing coverage for the 5 Agent/Connection discriminator fixes AgentJsonSigningKeyRequest, AgentJsonSigningKeyResponse, ManagedConnection, ManagedConnectionCreatable, and PotentialConnection got the same @JsonTypeInfo(Id.NONE) mixin fix as OrgContactTypeObj in the previous commit, but only OrgContactTypeObj had a regression test. Add the missing five. Note: ManagedConnection/ManagedConnectionCreatable/PotentialConnection have a separate, pre-existing quirk where their flattened ConnectionTypeEnum only retains one oneOf branch's single-value enum (STS_SERVICE_ACCOUNT) - every other connectionType value falls back to UNKNOWN_DEFAULT_OPEN_API. That's harmless (no exception) but means these tests only assert the polymorphism fix, not full enum fidelity; fully fixing that would require reworking how the spec merges oneOf branches, which is out of scope here. Co-Authored-By: Claude Code --- .../client/ApiClientJacksonMixinTest.java | 106 ++++++++++++++++++ 1 file changed, 106 insertions(+) diff --git a/api/src/test/java/com/okta/sdk/resource/client/ApiClientJacksonMixinTest.java b/api/src/test/java/com/okta/sdk/resource/client/ApiClientJacksonMixinTest.java index baac63be070..9fb7ba34a46 100644 --- a/api/src/test/java/com/okta/sdk/resource/client/ApiClientJacksonMixinTest.java +++ b/api/src/test/java/com/okta/sdk/resource/client/ApiClientJacksonMixinTest.java @@ -19,13 +19,18 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.okta.sdk.cache.Cache; import com.okta.sdk.cache.CacheManager; +import com.okta.sdk.resource.model.AgentJsonSigningKeyRequest; +import com.okta.sdk.resource.model.AgentJsonSigningKeyResponse; import com.okta.sdk.resource.model.Application; import com.okta.sdk.resource.model.ApplicationVisibility; import com.okta.sdk.resource.model.ApplicationVisibilityHide; import com.okta.sdk.resource.model.ListJwk200ResponseInner; +import com.okta.sdk.resource.model.ManagedConnection; +import com.okta.sdk.resource.model.ManagedConnectionCreatable; import com.okta.sdk.resource.model.OpenIdConnectApplication; import com.okta.sdk.resource.model.OrgContactType; import com.okta.sdk.resource.model.OrgContactTypeObj; +import com.okta.sdk.resource.model.PotentialConnection; import com.okta.sdk.resource.model.SamlApplication; import com.okta.sdk.resource.model.SamlAttributeStatement; import org.apache.hc.client5.http.impl.classic.HttpClients; @@ -190,4 +195,105 @@ public void deserializeOrgContactTypeObj_withBillingAndTechnicalEntries_doesNotT assertEquals(contacts.get(0).getContactType(), OrgContactType.BILLING); assertEquals(contacts.get(1).getContactType(), OrgContactType.TECHNICAL); } + + /** + * Same audit finding as OrgContactTypeObj: AgentJsonSigningKeyRequest declares RSA/EC subtypes that + * don't extend it. Currently unreferenced by any operation in the spec, fixed for consistency. + */ + @Test + public void deserializeAgentJsonSigningKeyRequest_withRsaAndEcEntries_doesNotThrow() throws Exception { + String json = "[" + + "{\"kty\":\"RSA\",\"e\":\"AQAB\",\"n\":\"mkC6\",\"use\":\"sig\",\"alg\":\"RS256\"}," + + "{\"kty\":\"EC\",\"crv\":\"P-256\",\"x\":\"abc\",\"y\":\"def\",\"use\":\"sig\",\"alg\":\"ES256\"}" + + "]"; + + List keys = objectMapper.readValue(json, + new TypeReference>() { }); + + assertEquals(keys.size(), 2); + assertEquals(keys.get(0).getE(), "AQAB"); + assertEquals(keys.get(1).getCrv().getValue(), "P-256"); + } + + /** + * Same audit finding, response-side counterpart of AgentJsonSigningKeyRequest. + */ + @Test + public void deserializeAgentJsonSigningKeyResponse_withRsaAndEcEntries_doesNotThrow() throws Exception { + String json = "[" + + "{\"kty\":\"RSA\",\"e\":\"AQAB\",\"n\":\"mkC6\",\"use\":\"sig\",\"alg\":\"RS256\",\"id\":\"key1\"}," + + "{\"kty\":\"EC\",\"crv\":\"P-256\",\"x\":\"abc\",\"y\":\"def\",\"use\":\"sig\",\"alg\":\"ES256\",\"id\":\"key2\"}" + + "]"; + + List keys = objectMapper.readValue(json, + new TypeReference>() { }); + + assertEquals(keys.size(), 2); + assertEquals(keys.get(0).getId(), "key1"); + assertEquals(keys.get(1).getCrv().getValue(), "P-256"); + } + + /** + * Same audit finding: ManagedConnection declares 4 connectionType subtypes that don't extend it. + * Reachable from the managed-connection-list endpoint. + * + * Separate, pre-existing quirk unrelated to this fix: each oneOf branch declares its own + * single-value connectionType enum (e.g. just "IDENTITY_ASSERTION_APP_INSTANCE"), and the flat + * merged class ends up keeping only the last-merged branch's enum ("STS_SERVICE_ACCOUNT") - every + * other value falls back to UNKNOWN_DEFAULT_OPEN_API (harmless, since + * READ_UNKNOWN_ENUM_VALUES_AS_NULL-style fallback is already relied on elsewhere; it doesn't throw). + * This test only asserts the polymorphism fix - that deserialization doesn't throw - not full type + * fidelity, which is a separate, wider issue with how the generator merges oneOf enum properties. + */ + @Test + public void deserializeManagedConnection_withDifferentConnectionTypes_doesNotThrow() throws Exception { + String json = "[" + + "{\"connectionType\":\"IDENTITY_ASSERTION_APP_INSTANCE\",\"id\":\"conn1\"}," + + "{\"connectionType\":\"STS_SERVICE_ACCOUNT\",\"id\":\"conn2\"}" + + "]"; + + List connections = objectMapper.readValue(json, + new TypeReference>() { }); + + assertEquals(connections.size(), 2); + assertEquals(connections.get(0).getId(), "conn1"); + assertEquals(connections.get(1).getConnectionType(), ManagedConnection.ConnectionTypeEnum.STS_SERVICE_ACCOUNT); + } + + /** + * Same audit finding, "creatable" (request-body) counterpart of ManagedConnection. See the enum + * fidelity caveat on {@link #deserializeManagedConnection_withDifferentConnectionTypes_doesNotThrow}. + */ + @Test + public void deserializeManagedConnectionCreatable_withDifferentConnectionTypes_doesNotThrow() throws Exception { + String json = "[" + + "{\"connectionType\":\"IDENTITY_ASSERTION_CUSTOM_AS\"}," + + "{\"connectionType\":\"STS_SERVICE_ACCOUNT\"}" + + "]"; + + List connections = objectMapper.readValue(json, + new TypeReference>() { }); + + assertEquals(connections.size(), 2); + assertEquals(connections.get(1).getConnectionType(), ManagedConnectionCreatable.ConnectionTypeEnum.STS_SERVICE_ACCOUNT); + } + + /** + * Same audit finding: PotentialConnection is a near-duplicate of ManagedConnection with the identical + * defect (same 4 subtypes, same discriminator). See the enum fidelity caveat on + * {@link #deserializeManagedConnection_withDifferentConnectionTypes_doesNotThrow}. + */ + @Test + public void deserializePotentialConnection_withDifferentConnectionTypes_doesNotThrow() throws Exception { + String json = "[" + + "{\"connectionType\":\"IDENTITY_ASSERTION_APP_INSTANCE\"}," + + "{\"connectionType\":\"STS_SERVICE_ACCOUNT\"}" + + "]"; + + List connections = objectMapper.readValue(json, + new TypeReference>() { }); + + assertEquals(connections.size(), 2); + assertEquals(connections.get(1).getConnectionType(), PotentialConnection.ConnectionTypeEnum.STS_SERVICE_ACCOUNT); + } }