wso2 / wso2/product-is

JIT provisioning fails unconditionally for any application shared into an organization

Open
#28,362 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Team/B2B Type/Bug
Dominant language
Java
Stars
877
Forks
1k
Avg merge
18h 48m
Merged PRs (30d)
37

Description

Description

Summary

Any federated end-user who signs in through an organization's own IdP, into an application that is shared into that organization (as opposed to natively created inside it), never gets a local WSO2 account. JIT provisioning is correctly configured and genuinely runs, but fails every time with IdentityProvisioningException: Invalid service provider name. Because every application a customer's users sign into under the Organizations feature is necessarily a shared application, this means there is no way to get a persistent local SCIM2 account for a federated organization user in this version.

Root cause (traced against carbon-identity-framework source)

  1. DefaultStepBasedSequenceHandler.setThreadLocalProvisioningServiceProvider (handler/sequence/impl/DefaultStepBasedSequenceHandler.java:819-828) stashes the plain application name (ApplicationConfig.getApplicationName()) into a thread-local, with no ProvisioningServiceProviderType.OAUTH set — so the one branch that would resolve the app by client ID instead of name never runs.

  2. DefaultInboundUserProvisioningListener.provisionEntity (line 594-627) calls OutboundProvisioningManager.getInstance().provision(entity, "<app-name>", ..., tenantDomainName, ...), where tenantDomainName comes from the listener's own resolveTenantDomain() (line 639-654). This part is genuinely organization-aware — inside an organization it correctly asks OrganizationManager.resolveTenantDomain(appResidentOrganizationId) for that organization's own tenant-domain representation, not the physical tenant's.

  3. OutboundProvisioningManager.provision (line 365-372) then calls ApplicationManagementService.getServiceProvider(name, tenantDomain)ApplicationManagementServiceImpl.getServiceProvider (line 1374-1407) → ApplicationDAOImpl.getApplication (line 1864) → getApplicationIdByName (line 2926-2936):

    int tenantId = IdentityTenantUtil.getTenantId(tenantDomain);
    return getApplicationIDByName(applicationName, tenantId, connection);
    

    which runs ApplicationMgtDBQueries.LOAD_APP_ID_BY_APP_NAME — a plain WHERE APP_NAME = ? AND TENANT_ID = ? query. There is no organization-id column, join, or parameter anywhere in this path. It is a pre-Organizations, tenant-only lookup with no concept of a shared/fragment application.

  4. Applications genuinely do have real per-organization identity once shared — ApplicationManagementServiceImpl.getSharedAppId(mainAppId, ownerOrgId, sharedOrgId) / getMainAppId(sharedAppId) (lines 3045-3061) map a shared/fragment application to/from its owning application, keyed by organization id, not by name+tenant-domain. This is presumably what the org-scoped Applications REST API uses (it resolves the same application correctly). OutboundProvisioningManager's SP-name resolution was never wired to this mechanism.

In one sentence: JIT provisioning correctly resolves the current organization and asks for the application in that organization's own tenant-domain terms, but the underlying application lookup is a legacy, pre-Organizations, tenant-only query that has no idea a shared/fragment application exists.

Workarounds considered

  • Per-org native (non-shared) copy of the application instead of sharing it — ruled out: application names are unique per tenant, not per organization (409 APP-60007 "already exists in tenantDomain" when attempting this), and even with a different name each standalone app gets its own OAuth client ID/secret, which breaks any client designed around one shared app + orgHandle routing.
  • Disabling outbound provisioning connectors — already true in our setup (both the SP's provisioningConfigurations.outboundProvisioningIdps and the IdP's provisioning.outboundConnectors.connectors were empty) and made no difference — the SP-name lookup fails unconditionally as part of resolving any provisioning entity, not only when an outbound connector is actually configured.
  • Disabling DefaultInboundUserProvisioningListener entirely, via a [[event_listener]] block in deployment.toml (AbstractIdentityUserOperationEventListener.isEnable() gates every hook on this). This works — the create-user event that triggers the broken lookup never fires, and login now produces a real SCIM2 account. But it is a global, server-wide switch: IdentityUtil's event-listener config is a single static map loaded once from one identity.xml for the whole server, with no per-tenant or per-organization scope anywhere in that path. It disables all outbound provisioning for every tenant on the instance, not just the organization-scoped case that's actually broken — not viable for any deployment where a different tenant on the same instance genuinely needs outbound provisioning.

Suggested fix direction

The application-mgt component already has what's needed to resolve a shared application correctly — it just isn't wired into the provisioning path. On a null result from the legacy name+tenant-domain lookup, OutboundProvisioningManager.provision() (or DefaultInboundUserProvisioningListener.provisionEntity, where the organization context is already available via PrivilegedCarbonContext.getApplicationResidentOrganizationId()) could check whether the operation is happening inside an organization and, if so, resolve via the shared-application mapping instead: find the application by name at the owning organization's tenant domain, use getSharedAppId to find its fragment id in the current organization, then load it by internal id via getServiceProvider(int appId) (ApplicationManagementServiceImpl.java:1438), sidestepping the name+tenant-domain path entirely.

Related issues

  • #26544 — "SCIM outbound provisioning for sub-organizational applications" confirms the same underlying gap at the epic level ("SCIM outbound provisioning is currently limited to organization-level configuration in the root organization only"), targeted for 7.3.0-alpha, but this specific crash (JIT-triggered, DefaultInboundUserProvisioningListenerOutboundProvisioningManagerApplicationDAOImpl.getApplicationIdByName) is still present in the 7.3.0 GA build tested here.
  • #14335 — "Organization Authenticator fails to find the fragment application" is the same shape of bug (tenant-only resolution of a fragment app) in a different code path (the Organization Authenticator's own lookup), fixed back in 6.0.0-alpha3. That fix does not cover the outbound-provisioning listener's separate lookup reported here.
Steps to Reproduce
  1. Create organization org-child (child of org-parent), federating to an external OIDC IdP, with JIT provisioning enabled on that IdP:
    "provisioning": {
      "jit": {
        "isEnabled": true, "scheme": "PROVISION_SILENTLY", "userstore": "PRIMARY",
        "associateLocalUser": false, "attributeSyncMethod": "OVERRIDE_ALL",
        "idpGroupSyncMethod": "MERGE_WITH_EXISTING"
      }
    }
    
  2. Register an application once at the tenant root and share it into org-child (standard B2B pattern — "configure federation once per customer, share the app into every organization"). Confirmed via GET /o/api/server/v1/applications/{id} returning "advancedConfigurations": {"fragment": true} and an isFragmentApp: "true" SP property.
  3. A real end-user authenticates through the full redirect chain (/oauth2/authorize?orgHandle=org-child → external IdP login → /commonauth → back to WSO2) and receives a valid authorization code/token. Login succeeds.
  4. GET /o/scim2/Users on org-child immediately after still lists only the org's own fixture accounts — the federated user was never provisioned.
  5. The container log for that exact request shows JIT provisioning ran and failed:
ERROR {DefaultStepBasedSequenceHandler} - User provisioning failed!
org.wso2.carbon.identity.application.authentication.framework.exception.FrameworkException:
    Error while provisioning user : <federated-subject>
	at DefaultProvisioningHandler.handleProvisioningException(DefaultProvisioningHandler.java:1072)
	at DefaultProvisioningHandler.handleWithV2Roles(DefaultProvisioningHandler.java:215)
	at DefaultStepBasedSequenceHandler.handleJitProvisioningWithV2Roles(DefaultStepBasedSequenceHandler.java:694)
	at DefaultStepBasedSequenceHandler.callJitProvisioningWithV2Roles(DefaultStepBasedSequenceHandler.java:635)
	at JITProvisioningPostAuthenticationHandler.callDefaultProvisioningHandler(JITProvisioningPostAuthenticationHandler.java:1133)
	at JITProvisioningPostAuthenticationHandler.handleRequestFlow(JITProvisioningPostAuthenticationHandler.java:474)
	at JITProvisioningPostAuthenticationHandler.handle(JITProvisioningPostAuthenticationHandler.java:178)
Caused by: org.wso2.carbon.user.core.UserStoreException: Error occurred while checking for user provisioning
	at DefaultProvisioningHandler.handleUserProvisioning(DefaultProvisioningHandler.java:347)
Caused by: org.wso2.carbon.identity.provisioning.IdentityProvisioningException:
    Error occurred while checking for user provisioning
	at OutboundProvisioningManager.provision(OutboundProvisioningManager.java:698)
	at DefaultInboundUserProvisioningListener.provisionEntity(DefaultInboundUserProvisioningListener.java:614)
	at DefaultInboundUserProvisioningListener.doPreAddUser(DefaultInboundUserProvisioningListener.java:135)
Caused by: org.wso2.carbon.identity.provisioning.IdentityProvisioningException:
    Invalid service provider name : <the shared application's name>
	at OutboundProvisioningManager.provision(OutboundProvisioningManager.java:371)

The application named in the exception is real, currently shared into the organization, and returned correctly by the org-scoped Applications API. The failure is entirely internal to how JIT provisioning resolves it.

Please select the area issue is related to

B2B

Version

wso2/wso2is:7.3.0

Environment Details (with versions)
  • wso2/wso2is:7.3.0 (carbon-identity-framework component build 7.10.156)
  • Postgres 16
  • Reproduced in a devstack with WSO2 Organizations (B2B) enabled, one child organization, one federated (OIDC) IdP on the child org, and one application shared from the root tenant into that organization
Developer Checklist
  • [Behavioural Change] Does this change introduce a behavioral change to the product?
  •  ↳ Approved by team lead
  •  ↳ Label impact/behavioral-change added
  • [Migration Impact] Does this change have a migration impact?
  •  ↳ Migration label added (e.g., 7.2.0-migration)
  •  ↳ Migration issues created and linked
  • [New Configuration] Does this change introduce a new configuration?
  •  ↳ Label config added
  •  ↳ Configuration is properly documented
  • [IAM CTL] Does this change affect the behavior of the IAM CTL tool?
  •  ↳ Necessary changes made in the IAM CTL tool

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with OutboundProvisioningManager.provision and DefaultInboundUserProvisioningListener.provisionEntity, then trace the ApplicationManagementService and ApplicationDAOImpl lookup paths named in the report. Read getSharedAppId, getMainAppId, and getServiceProvider(int appId) to understand shared-application resolution. Done means JIT provisioning creates a local SCIM2 account for a federated user signing into a shared application without the invalid service provider error.

Written by the indexing model from the issue text.

Assessment

Tech stack
java
Domain
authentication, authorization, backend
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.