Can we have read-only user federation without setting up XA datasource?

I have a custom provider that uses a read only datasource to fetch the users. We don’t want to keep the users in Keycloak so we have a read-only datasource to avoid the complexity of XA transactions.

Now the users are being displayed correctly in the Keycloak admin console, however when you proceed to Role Mapping tab and try to assign a role, an exception is thrown.

[0m[32m07:34:05,489 DEBUG [org.keycloak.services.resources.admin.AdminRoot] (default task-2) authenticated admin access for: admin
[0m[32m07:34:05,489 DEBUG [org.keycloak.services.resources.admin.AdminRoot] (default task-6) authenticated admin access for: admin
[0m[0m07:34:05,490 INFO  [stdout] (default task-2) >>>> Asking for user by id: f:cb213c8f-35f5-4e80-9456-7a20a22bfe81:someuser
[0m[0m07:34:05,490 INFO  [stdout] (default task-6) >>>> Asking for user by id: f:cb213c8f-35f5-4e80-9456-7a20a22bfe81:someuser
[0m[32m07:34:05,491 DEBUG [org.keycloak.transaction.JtaTransactionWrapper] (default task-1) new JtaTransactionWrapper
[0m[32m07:34:05,491 DEBUG [org.keycloak.transaction.JtaTransactionWrapper] (default task-1) was existing? false
[0m[32m07:34:05,496 DEBUG [org.keycloak.services.resources.admin.AdminRoot] (default task-1) authenticated admin access for: admin
[0m[0m07:34:05,498 INFO  [stdout] (default task-1) >>>> Asking for user by id: f:cb213c8f-35f5-4e80-9456-7a20a22bfe81:someuser
[0m[32m07:34:05,564 DEBUG [org.keycloak.transaction.JtaTransactionWrapper] (default task-1) JtaTransactionWrapper  commit
[0m[0m07:34:05,562 INFO  [org.jboss.jca.core.connectionmanager.listener.TxConnectionListener] (default task-6) IJ000311: Throwable from unregister connection: java.lang.IllegalStateException: IJ000152: Trying to return an unknown connection: org.jboss.jca.adapters.jdbc.jdk8.WrappedConnectionJDK8@5320975f
    at org.jboss.ironjacamar.impl@1.4.27.Final//org.jboss.jca.core.connectionmanager.ccm.CachedConnectionManagerImpl.unregisterConnection(CachedConnectionManagerImpl.java:408)
    at org.jboss.ironjacamar.impl@1.4.27.Final//org.jboss.jca.core.connectionmanager.listener.TxConnectionListener.connectionClosed(TxConnectionListener.java:645)
    at org.jboss.ironjacamar.jdbcadapters@1.4.27.Final//org.jboss.jca.adapters.jdbc.BaseWrapperManagedConnection.returnHandle(BaseWrapperManagedConnection.java:600)
    at org.jboss.ironjacamar.jdbcadapters@1.4.27.Final//org.jboss.jca.adapters.jdbc.BaseWrapperManagedConnection.closeHandle(BaseWrapperManagedConnection.java:545)
    at org.jboss.ironjacamar.jdbcadapters@1.4.27.Final//org.jboss.jca.adapters.jdbc.WrappedConnection.returnConnection(WrappedConnection.java:310)
    at org.jboss.ironjacamar.jdbcadapters@1.4.27.Final//org.jboss.jca.adapters.jdbc.WrappedConnection.close(WrappedConnection.java:268)
    at org.hibernate@5.3.20.Final//org.hibernate.engine.jdbc.connections.internal.DatasourceConnectionProviderImpl.closeConnection(DatasourceConnectionProviderImpl.java:127)
    at org.hibernate@5.3.20.Final//org.hibernate.internal.NonContextualJdbcConnectionAccess.releaseConnection(NonContextualJdbcConnectionAccess.java:46)
    at org.hibernate@5.3.20.Final//org.hibernate.resource.jdbc.internal.LogicalConnectionManagedImpl.releaseConnection(LogicalConnectionManagedImpl.java:196)
    at org.hibernate@5.3.20.Final//org.hibernate.resource.jdbc.internal.LogicalConnectionManagedImpl.afterStatement(LogicalConnectionManagedImpl.java:149)
    at org.hibernate@5.3.20.Final//org.hibernate.engine.jdbc.internal.JdbcCoordinatorImpl.afterStatementExecution(JdbcCoordinatorImpl.java:265)
    at org.hibernate@5.3.20.Final//org.hibernate.internal.AbstractScrollableResults.close(AbstractScrollableResults.java:105)
    at java.base/java.util.stream.AbstractPipeline.close(AbstractPipeline.java:323)
    at org.keycloak.keycloak-server-spi-private@14.0.0//org.keycloak.utils.ClosingStream.close(ClosingStream.java:280)
    at org.keycloak.keycloak-server-spi-private@14.0.0//org.keycloak.utils.ClosingStream.collect(ClosingStream.java:183)
    at org.keycloak.keycloak-server-spi@14.0.0//org.keycloak.storage.federated.UserRoleMappingsFederatedStorage$Streams.getRoleMappings(UserRoleMappingsFederatedStorage.java:65)
    at org.keycloak.keycloak-server-spi@14.0.0//org.keycloak.storage.adapter.AbstractUserAdapterFederatedStorage.getFederatedRoleMappings(AbstractUserAdapterFederatedStorage.java:224)
    at org.keycloak.keycloak-server-spi@14.0.0//org.keycloak.storage.adapter.AbstractUserAdapterFederatedStorage.getRoleMappings(AbstractUserAdapterFederatedStorage.java:217)
    at org.keycloak.keycloak-server-spi@14.0.0//org.keycloak.storage.adapter.AbstractUserAdapterFederatedStorage.hasRole(AbstractUserAdapterFederatedStorage.java:183)
    at java.base/java.util.function.Predicate.lambda$negate$1(Predicate.java:80)
    at java.base/java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:176)
    at java.base/java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:177)
    at java.base/java.util.HashMap$KeySpliterator.forEachRemaining(HashMap.java:1603)
    at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:484)
    at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474)
    at java.base/java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:150)
    at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:173)
    at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)

Looking at the log you can see that Keycloak is calling our method (check the log >>>> Asking for user by id ) and the user’s detail has been returned but it seems KC tries to do something afterwards which ends with error. This is no different than the example provided by Keycloak to read the users from a file. We are only reading it from a different database.

Our datasource is defined with jta=false because no transaction is needed and we are not persisting anything on our side.