Skip to content

Organization member registration fails with NPE when stateless feature is enabled (userCache null in registerOrgGroupsMembershipInvalidation) #51170

Description

@apivom-adogan

Before reporting an issue

  • I have read and understood the above terms for submitting issues, and I understand that my issue may be closed without action if I do not follow them.

Area

organizations

Describe the bug

When the experimental stateless feature is enabled, InfinispanUserCacheProviderFactory.isSupported() returns false, so no user cache provider is registered and UserCacheSession is null inside InfinispanOrganizationProvider.

The org-groups membership invalidation path introduced in 26.7 (InfinispanOrganizationProvider.registerOrgGroupsMembershipInvalidation) dereferences this.userCache without a null guard, similar to the class of NPEs fixed by #34603 for the disabled-user-cache case.

As a result, any organization member add that fires GroupModel.GroupMemberJoinEvent throws an NPE. In our case this broke invitation-driven self-registration: the registration form submit returns HTTP 400 and — because the NPE happens mid-flow after the user entity and membership were persisted — leaves partially committed state behind (a disabled user + org membership + the invitation still PENDING), which requires manual cleanup before the user can retry.

KC-SERVICES0013: Failed authentication: java.lang.NullPointerException: Cannot invoke "org.keycloak.models.cache.infinispan.UserCacheSession.registerInvalidation(String)" because "this.userCache" is null
        at org.keycloak.models.cache.infinispan.organization.InfinispanOrganizationProvider.registerOrgGroupsMembershipInvalidation(InfinispanOrganizationProvider.java:523)
        at org.keycloak.models.cache.infinispan.organization.InfinispanOrganizationProviderFactory.registerOrgGroupMembershipInvalidation(InfinispanOrganizationProviderFactory.java:114)
        at org.keycloak.models.cache.infinispan.organization.InfinispanOrganizationProviderFactory.lambda$postInit$2(InfinispanOrganizationProviderFactory.java:69)
        at org.keycloak.services.DefaultKeycloakSessionFactory.publish(DefaultKeycloakSessionFactory.java:87)
        at org.keycloak.models.GroupModel$GroupMemberJoinEvent.fire(GroupModel.java:114)
        at org.keycloak.models.jpa.UserAdapter.joinGroupImpl(UserAdapter.java:490)
        at org.keycloak.models.jpa.UserAdapter.joinGroup(UserAdapter.java:472)
        at org.keycloak.organization.jpa.JpaOrganizationProvider.addMember(JpaOrganizationProvider.java:221)
        at org.keycloak.organization.jpa.JpaOrganizationProvider.addManagedMember(JpaOrganizationProvider.java:190)
        at org.keycloak.models.cache.infinispan.organization.InfinispanOrganizationProvider.addManagedMember(InfinispanOrganizationProvider.java:187)
        at org.keycloak.authentication.forms.RegistrationUserCreation.addOrganizationMember(RegistrationUserCreation.java:363)
        at org.keycloak.authentication.forms.RegistrationUserCreation.success(RegistrationUserCreation.java:158)

Version

26.7.0

Regression

  • The issue is a regression (works with stateless disabled; the failing invalidation path is new in 26.7)

Expected behavior

Organization membership operations (including invitation-driven registration) work when stateless is enabled, or the org-groups invalidation code null-guards the user cache the same way other cache-aware code paths do when the user cache provider is absent.

Actual behavior

NPE as above; the registering user sees a generic error page (HTTP 400), and the realm is left with a disabled user, an org membership row, and the invitation still PENDING.

How to reproduce

  1. Start Keycloak 26.7.0 with --features=stateless (plus defaults; organizations enabled on the realm).
  2. Create an organization in a realm with self-registration enabled.
  3. Invite a new (not-yet-existing) user via POST /admin/realms/{realm}/organizations/{id}/members/invite-user.
  4. Follow the invitation link and submit the registration form.
  5. Registration fails with the NPE above; the user is left disabled with a persisted org membership and a PENDING invitation.

Anything else?

Workaround: remove stateless from the enabled features — registration works immediately afterwards (after manually deleting the partially-created user).

Root cause chain as we traced it: InfinispanUserCacheProviderFactory.isSupported() returns false under Feature.STATELESS → user cache provider absent → InfinispanOrganizationProvider.userCache is null → unguarded dereference at line 523.

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