Hello,
I have a custom UserStorageProvider as a replacement for the deprecated
UserFederationProvider mechanism which connects users from a legacy user
store with Keycloak.
Since the legacy user store will remain the leading system for a while.
I want to prohibit users from changing passwords and OTP secrets via the
account page.
For password changes this can be easily done by throwing an
UnsupportedOperationException
in the implementation of the org.keycloak.credential.CredentialInputUpdater.
updateCredential(RealmModel, UserModel, CredentialInput) method for the
UserCredentialModel.PASSWORD.
If a user tries to change a password he/she sees an error message that this
is not supported for their accounts.
However prohibiting users from configuring a TOTP token cannot be done this
way, since thowing an exception results in a stacktrace presented to the
user. (stacktrace below)
I think the reason for this is that the AccountService.processTotpUpdate
method:
org.keycloak.services.resources.AccountService.processTotpUpdate(
MultivaluedMap<String, String>)
is missing some error handling:
See:
https://github.com/keycloak/keycloak
/blob/ccaac408630d83b6d66b041a2ef4c23102f0ed60/services/src/main/java/org/
keycloak/services/resources/AccountService.java#L563
In AccountService.processPasswordUpdate the error handling seems to be fine.
org.keycloak.services.resources.AccountService.processPasswordUpdate(
MultivaluedMap<String, String>)
see:
https://github.com/keycloak/keycloak
/blob/ccaac408630d83b6d66b041a2ef4c23102f0ed60/services/src/main/java/org/
keycloak/services/resources/AccountService.java#L641
Could the error handling (try / catch) of AccountService.
processPasswordUpdate also be applied to AccountService.processTotpUpdate?
This would ease the control of what credentials a "federated" user can
change.
Bottom line it should be possible for a UserStorageProvider to only
validate credentials (Password, OTP, etc.) ( org.keycloak.credential.
CredentialInputUpdater) but prohibit updating them.
Currently the login in org.keycloak.credential.UserCredentialStoreManager.
updateCredential(RealmModel, UserModel, CredentialInput)
falls back to updating the credentials anyway if the CredentialInputUpdater.
updateCredential(..) Method implemented by the custom UserStorageProvider does
NOT handle the credential update by returning false in
CredentialInputUpdater.updateCredential(..)
Cheers,
Thomas
Error processing request
Context Path:/auth
Servlet Path:
Path Info:/realms/user-storage-provider-dev/account/totp
Query String:null
Stack Trace
org.jboss.resteasy.spi.UnhandledException: java.lang.IllegalStateException:
updateCredential(realm,user,credentialInput) not supported
org.jboss.resteasy.core.ExceptionHandler.handleApplicationException(
ExceptionHandler.java:76)
org.jboss.resteasy.core.ExceptionHandler.handleException(ExceptionHandler
.java:212)
org.jboss.resteasy.core.SynchronousDispatcher.writeException(
SynchronousDispatcher.java:168)
org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher
.java:411)
org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher
.java:202)
org.jboss.resteasy.plugins.server.servlet.ServletContainerDispatcher
.service(ServletContainerDispatcher.java:221)
org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher.service(
HttpServletDispatcher.java:56)
org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher.service(
HttpServletDispatcher.java:51)
javax.servlet.http.HttpServlet.service(HttpServlet.java:847)
io.undertow.servlet.handlers.ServletHandler.handleRequest(ServletHandler
.java:85)
io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(
FilterHandler.java:129)
org.keycloak.services.filters.KeycloakSessionServletFilter.doFilter(
KeycloakSessionServletFilter.java:90)
io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:60)
io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(
FilterHandler.java:131)
io.undertow.servlet.handlers.FilterHandler.handleRequest(FilterHandler
.java:84)
io.undertow.servlet.handlers.security.ServletSecurityRoleHandler.
handleRequest(ServletSecurityRoleHandler.java:62)
io.undertow.servlet.handlers.ServletDispatchingHandler.handleRequest(
ServletDispatchingHandler.java:36)
io.undertow.servlet.handlers.security.SSLInformationAssociationHandler.
handleRequest(SSLInformationAssociationHandler.java:131)
io.undertow.servlet.handlers.security.ServletAuthenticationCallHandler.
handleRequest(ServletAuthenticationCallHandler.java:57)
io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler
.java:43)
io.undertow.security.handlers.AbstractConfidentialityHandler.handleRequest(
AbstractConfidentialityHandler.java:46)
io.undertow.servlet.handlers.security.
ServletConfidentialityConstraintHandler.handleRequest(
ServletConfidentialityConstraintHandler.java:64)
io.undertow.security.handlers.AuthenticationMechanismsHandler.handleRequest(
AuthenticationMechanismsHandler.java:60)
io.undertow.servlet.handlers.security.CachedAuthenticatedSessionHandler.
handleRequest(CachedAuthenticatedSessionHandler.java:77)
io.undertow.security.handlers.AbstractSecurityContextAssociationHandler.
handleRequest(AbstractSecurityContextAssociationHandler.java:43)
io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler
.java:43)
io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler
.java:43)
io.undertow.servlet.handlers.ServletInitialHandler.handleFirstRequest(
ServletInitialHandler.java:284)
io.undertow.servlet.handlers.ServletInitialHandler.dispatchRequest(
ServletInitialHandler.java:263)
io.undertow.servlet.handlers.ServletInitialHandler.access$000(
ServletInitialHandler.java:81)
io.undertow.servlet.handlers.ServletInitialHandler$1.handleRequest(
ServletInitialHandler.java:174)
io.undertow.server.Connectors.executeRootHandler(Connectors.java:202)
io.undertow.server.HttpServerExchange$1.run(HttpServerExchange.java:793)
java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor
.java:1142)
java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor
.java:617)
java.lang.Thread.run(Thread.java:745)