Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion api/src/main/resources/custom_templates/ApiClient.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -238,9 +238,19 @@ protected List<ServerConfiguration> servers = new ArrayList<ServerConfiguration>
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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,19 @@
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;
import org.testng.annotations.Test;
Expand Down Expand Up @@ -135,4 +144,156 @@ 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<Application> apps = objectMapper.readValue(json, new TypeReference<List<Application>>() { });

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<SamlAttributeStatement> 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<OrgContactTypeObj> contacts = objectMapper.readValue(json, new TypeReference<List<OrgContactTypeObj>>() { });

assertEquals(contacts.size(), 2);
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<AgentJsonSigningKeyRequest> keys = objectMapper.readValue(json,
new TypeReference<List<AgentJsonSigningKeyRequest>>() { });

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<AgentJsonSigningKeyResponse> keys = objectMapper.readValue(json,
new TypeReference<List<AgentJsonSigningKeyResponse>>() { });

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<ManagedConnection> connections = objectMapper.readValue(json,
new TypeReference<List<ManagedConnection>>() { });

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<ManagedConnectionCreatable> connections = objectMapper.readValue(json,
new TypeReference<List<ManagedConnectionCreatable>>() { });

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<PotentialConnection> connections = objectMapper.readValue(json,
new TypeReference<List<PotentialConnection>>() { });

assertEquals(connections.size(), 2);
assertEquals(connections.get(1).getConnectionType(), PotentialConnection.ConnectionTypeEnum.STS_SERVICE_ACCOUNT);
}
}