Skip to content

GroupProfile / OktaUserGroupProfile regressions (2.9.x β†’ 3.x) as a result of model regeneration via OpenAPI with pydantic v2Β #555

Description

@apizz

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 v3.11.9
okta 3.4.3

Affected Class/Method(s)

okta.models.group_profile
okta.UserClient.list_user_groups

Customer Information

Organization Name: Hudson River Trading
Paid Customer: yes

Code Snippet

import okta.client
import okta.exceptions

okta_client = okta.client.Client(
            {
                "orgUrl": org_url,
                "authorizationMode": "PrivateKey",
                "clientId": client_id,
                "privateKey": private_key,
                "scopes": ["okta.users.read", "okta.groups.read"],
                "kid": "<kid>",
            }
        )

try:
        # Get user status and ID
        okta_user, _, err = await okta_client.get_user(user)
        if err or not okta_user:
            error_message = (
                f"User '{user}' not found in Okta or error fetching user: {err}"
            )
            print(f"WARNING: {error_message}")
            return None, error_message

        user_data["status"] = okta_user.status
        user_data["id"] = okta_user.id

        # Get user groups
        all_groups = []
        okta_user_groups, resp, err = await okta_client.list_user_groups(okta_user.id)
        if okta_user_groups:
            all_groups.extend(okta_user_groups)

        while resp and resp.has_next():
            next_groups, next_err = await resp.next()
            if next_err:
                error_message = f"Error for paginated groups for '{user}': {next_err}"
                print(f"WARNING: {error_message}")
                break
            if next_groups:
                all_groups.extend(next_groups)
        if not error_message:
            user_data["groups"] = all_groups
        else:
            user_data["groups"] = []

    except okta.exceptions.OktaAPIException as okta_api_error:
        error_message = (
            f"Error occurred while looking up user '{user}' in Okta."  # type: ignore[attr-defined]
            f"\nError Code: {okta_api_error.code}"
            f" Error Message: {okta_api_error.message}"
        )
        print(f"WARNING: {error_message}")
        return None, error_message
    except Exception as error:
        error_message = (
            f"An unexpected error occurred while looking up user '{user}' in Okta."
            f"\nError Message: {error}"
        )
        print(f"WARNING: {error_message}", style="warning")
        return None, error_message

Debug Output / Traceback

'ApiResponse' object has no attribute 'has_next'

Expected Behavior

User groups returned

Actual Behavior

Errors

Summary

Between okta==2.9.x and okta==3.4.3 the SDK models were regenerated from the
OpenAPI 5.1.0 spec with pydantic v2. GroupProfile changed from a single
permissive object into a strict anyOf union of OktaUserGroupProfile and
OktaActiveDirectoryGroupProfile. This introduced two regressions that break
Client.list_user_groups() (and any Group-returning endpoint) for group data
that 2.9.x handled without error.

okta 2.9.x β€” okta/models/group_profile.py

GroupProfile(OktaObject) is a single flat object. Fields live directly on it
(group.profile.name, group.profile.description), custom/AD attributes are set
as plain attributes, and unknown fields are silently retained. It never raises.

okta 3.4.3 β€” okta/models/group_profile.py

GroupProfile(BaseModel) is an anyOf of two strict pydantic models:

Model Allowed objectClass Fields
OktaUserGroupProfile okta:user_group description, name, objectClass (all StrictStr)
OktaActiveDirectoryGroupProfile okta:windows_security_principal AD-specific fields (all StrictStr)

The profile fields are no longer on GroupProfile itself β€” they live on
group.profile.actual_instance.

Issue 1 β€” attribute access breaks (silent API break)

group.profile.name
# AttributeError: 'GroupProfile' object has no attribute 'name'

Callers must now use group.profile.actual_instance.name.

Issue 2 β€” deserialization crashes (hard break)

GroupProfile.from_json tries each strict member and, if neither matches, raises:

ValueError: No match found when deserializing the JSON string into GroupProfile
with anyOf schemas: OktaActiveDirectoryGroupProfile, OktaUserGroupProfile.

Reproduced for real-world group profiles that 2.9.x accepted:

Profile 2.9.x 3.4.3
objectClass: okta:user_group OK OK
objectClass: okta:windows_security_principal OK OK
objectClass: okta:app_group (or any other/future value) OK CRASH
custom attribute with non-string value (e.g. int) OK CRASH (StrictStr)

Because this happens inside SDK deserialization, the caller can't avoid it; if a
user belongs to even one such group, list_user_groups() fails entirely.

Issue 3 β€” pagination crash (okta/api_response.py)

OktaAPIResponse.next() builds models positionally for
User/Group/UserSchema/GroupSchema:

self._type(item)   # item is a dict

pydantic v2 BaseModel.__init__ rejects a positional dict, so the 2nd and
later
pages of any paginated call raise:

TypeError: BaseModel.__init__() takes 1 positional argument but 2 were given

Steps to reproduce

  1. Install okta==2.9.X package
  2. Run test code to successfully get user groups
  3. Install okta==3.4.3 (ie latest)
  4. Run test code and note failures

References

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions