Skip to content

UserTypeCondition.exclude/.include reject null, but the API returns null — one policy rule aborts the whole list_policy_rules page #572

Description

@jackvaughanjr

Community Note

  • Please vote on this issue by adding a 👍 reaction to the original issue to help the community and maintainers prioritize this request.
  • Please do not leave +1 or me too comments, they generate extra noise for issue followers and do not help prioritize the request.
  • If you are interested in working on this issue or have submitted a pull request, please leave a comment.

Before submitting a bug report, we ask that you first search existing issues and pull requests to see if someone else may have experienced the same issue or may have already submitted a fix for it.

Python Version & Okta SDK Version(s)

Python 3.13.9
okta     3.4.4
pydantic 2.11.5

(Also reproduces on the versions any consumer currently pinned to okta==3.4.4 would be running; pydantic version is included here because it materially affects the exact error shape.)

Affected Class/Method(s)

  • okta.models.user_type_condition.UserTypeCondition
  • okta.models.access_policy_rule_conditions.AccessPolicyRuleConditions (embeds UserTypeCondition as user_type/userType)
  • okta.api.policy_api.PolicyApi.list_policy_rules (deserializes the 200 response to List[PolicyRule], and PolicyRule/AccessPolicyRule embed AccessPolicyRuleConditions)
  • okta.api_client.ApiClient.__deserialize (bulk-deserializes each list element with no per-item fallback, so one bad rule aborts the whole page)

Customer Information

Organization Name: withheld

This report reproduces entirely from synthetic payloads. No tenant data is required to confirm it.

Code Snippet

from okta.models.user_type_condition import UserTypeCondition
from okta.models.access_policy_rule import AccessPolicyRule

# 1. Minimal isolated repro
UserTypeCondition.from_dict({"exclude": None, "include": None})

# 2. Full synthetic policy-rule payload shaped like a real list_policy_rules() response
synthetic_rule = {
    "id": "rul000000000000000000",
    "name": "Example synthetic rule",
    "status": "ACTIVE",
    "priority": 1,
    "system": False,
    "created": "2026-01-01T00:00:00.000Z",
    "lastUpdated": "2026-01-01T00:00:00.000Z",
    "type": "ACCESS_POLICY",
    "conditions": {
        "network": {"connection": "ANYWHERE"},
        "people": {
            "users": {"exclude": [], "include": []},
            "groups": {"exclude": [], "include": []},
        },
        "userType": {
            "exclude": None,
            "include": None,
        },
    },
    "actions": {
        "appSignOn": {
            "access": "ALLOW",
            "verificationMethod": {
                "type": "ASSURANCE",
                "factorMode": "1FA",
                "reauthenticateIn": "PT2H",
            },
        }
    },
}
AccessPolicyRule.from_dict(synthetic_rule)

In production this payload doesn't need to be constructed by hand. It is the shape returned by GET /api/v1/policies/{policyId}/rules for a rule whose conditions.userType is present but carries no explicit include/exclude list. That is the common case: a rule that doesn't filter by user type at all.

Debug Output / Traceback

Inlined rather than linked to a Gist so it stays reproducible from the issue body alone. All output below is copied verbatim from a real run against okta==3.4.4.

Case 1, minimal isolated repro:

2 validation errors for UserTypeCondition
exclude
  Input should be a valid list [type=list_type, input_value=None, input_type=NoneType]
    For further information visit https://errors.pydantic.dev/2.11/v/list_type
include
  Input should be a valid list [type=list_type, input_value=None, input_type=NoneType]
    For further information visit https://errors.pydantic.dev/2.11/v/list_type

Case 2, the full synthetic AccessPolicyRule.from_dict(...) payload. The same two errors bubble up unchanged. The outer AccessPolicyRuleConditions.user_type field is itself Optional, so the failure happens when the nested UserTypeCondition is constructed:

2 validation errors for UserTypeCondition
exclude
  Input should be a valid list [type=list_type, input_value=None, input_type=NoneType]
    For further information visit https://errors.pydantic.dev/2.11/v/list_type
include
  Input should be a valid list [type=list_type, input_value=None, input_type=NoneType]
    For further information visit https://errors.pydantic.dev/2.11/v/list_type

Control case: the identical rule with "userType": {"exclude": [], "include": []} (explicit empty lists instead of null) parses without error. The model itself is fine. Only the null case is broken:

SUCCESS. Parsed rule id: rul000000000000000000
userType condition: exclude=[] include=[]

Expected Behavior

list_policy_rules(policy_id) should return every rule on the policy, including rules whose conditions.userType block has null exclude/include arrays (i.e., rules that don't restrict on user type). A null array for an optional filter is ordinary, well-formed API output.

Actual Behavior

okta/models/user_type_condition.py (lines 40-41) declares both fields as required, non-nullable lists:

exclude: List[StrictStr] = Field(description="The user types to exclude")
include: List[StrictStr] = Field(description="The user types to include")

When the API returns "userType": {"exclude": null, "include": null} inside a rule's conditions, both fields fail pydantic validation (see traceback above). Because PolicyApi.list_policy_rules deserializes the whole page as List[PolicyRule] via ApiClient.__deserialize (okta/api_client.py, ~lines 450-466) with no per-item try/except, a single rule with a null userType block aborts deserialization of the entire page. Every other rule on that policy becomes unreachable through the SDK, not only the offending one.

This model is the outlier among its own siblings. Every other condition model in the same SDK that has the same exclude/include shape already marks both fields Optional with default=None:

Model File Fields
GroupCondition okta/models/group_condition.py:40-45 exclude/include: Optional[List[StrictStr]] = Field(default=None, ...)
UserCondition okta/models/user_condition.py:40-45 same
PolicyNetworkCondition okta/models/policy_network_condition.py:45-52 same
PlatformPolicyRuleCondition okta/models/platform_policy_rule_condition.py:44-45 Optional[List[PlatformConditionEvaluatorPlatform]] = None
UserTypeCondition (this bug) okta/models/user_type_condition.py:40-41 List[StrictStr], required, no default

There's no behavioral reason UserTypeCondition should be stricter than GroupCondition or UserCondition. They are structurally identical include/exclude pairs used in the same AccessPolicyRuleConditions object, which points to an inconsistency in whichever OpenAPI Generator pass or hand-authored spec fragment produced this one model.

Two related models share the same root cause. Neither is the primary subject of this issue, and neither was confirmed against a live tenant, but both are structurally identical and worth fixing in the same pass if the spec is being touched:

  • okta/models/risk_detection_types_policy_rule_condition.py:43-48: exclude/include are required List[DetectedRiskEvents] with no default, the same pattern as this bug.
  • okta/models/user_identifier_policy_rule_condition.py:49: patterns: List[UserIdentifierConditionEvaluatorPattern] is a bare required annotation with no default at all, not even wrapped in Field(...).

Steps to reproduce

  1. Run the code snippet above with okta==3.4.4, pydantic==2.11.5, Python 3.13.
  2. Observe the two ValidationErrors for exclude/include on UserTypeCondition.
  3. Change "userType": {"exclude": None, "include": None} to "userType": {"exclude": [], "include": []} in the same payload and re-run. It now succeeds, which isolates null as the cause rather than the model or the rest of the payload.
  4. Against a live tenant: call GET /api/v1/policies/{accessPolicyId}/rules on any ACCESS_POLICY that has at least one rule not scoped by user type, then call the SDK's list_policy_rules(policy_id) for the same policy. The SDK call raises where the raw API call succeeds.

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions