I have implemented a custom user storage provider for federating users from our database.
I want to manage OTP for those users via keycloak, when I set the OTP to required in the flow and Configure OTP as required action the otp form is shown after federated user login, but when I try to setup the OTP I receive the error user is read only for this update.
How can I allow read only federated users to allow OTP configuration via keycloak?
2022-01-31 17:00:12,704 ERROR [org.keycloak.services.error.KeycloakErrorHandler] (default task-669) Uncaught server error: org.keycloak.storage.ReadOnlyException: user is read only for this update
at org.keycloak.keycloak-server-spi#15.1.1//org.keycloak.storage.adapter.AbstractUserAdapter.removeRequiredAction(AbstractUserAdapter.java:77)
at org.keycloak.keycloak-services#15.1.1//org.keycloak.services.resources.LoginActionsService.processRequireAction(LoginActionsService.java:1044)
at org.keycloak.keycloak-services#15.1.1//org.keycloak.services.resources.LoginActionsService.requiredActionPOST(LoginActionsService.java:967)
the user adapter is
public class UserAdminAdapter extends AbstractUserAdapter {
private final CustomUser user;
public UserAdminAdapter(
KeycloakSession session,
RealmModel realm,
ComponentModel storageProviderModel,
CustomUser user) {
super(session, realm, storageProviderModel);
this.user = user;
}
#Override
public String getUsername() {
return user.getUsername();
}
#Override
public Stream<String> getAttributeStream(String name) {
Map<String, List<String>> attributes = getAttributes();
return (attributes.containsKey(name)) ? attributes.get(name).stream() : Stream.empty();
}
#Override
protected Set<GroupModel> getGroupsInternal() {
if (user.getGroups() != null) {
return user.getGroups().stream().map(UserGroupModel::new).collect(Collectors.toSet());
}
return new HashSet<>();
}
#Override
protected Set<RoleModel> getRoleMappingsInternal() {
if (user.getRoles() != null) {
return user.getRoles().stream().map(roleName -> new UserRoleModel(roleName, realm)).collect(Collectors.toSet());
}
return new HashSet<>();
}
#Override
public boolean isEnabled() {
return user.isEnabled();
}
#Override
public String getId() {
return StorageId.keycloakId(storageProviderModel, user.getUserId() + "");
}
#Override
public String getFirstAttribute(String name) {
List<String> list = getAttributes().getOrDefault(name, Collections.emptyList());
return list.isEmpty() ? null : list.get(0);
}
#Override
public Map<String, List<String>> getAttributes() {
MultivaluedHashMap<String, String> attributes = new MultivaluedHashMap<>();
attributes.add(UserModel.USERNAME, getUsername());
attributes.add(UserModel.EMAIL, getEmail());
attributes.add(UserModel.FIRST_NAME, getFirstName());
attributes.add(UserModel.LAST_NAME, getLastName());
attributes.addAll(user.getAttributes());
return attributes;
}
#Override
public String getFirstName() {
return user.getFirstName();
}
#Override
public String getLastName() {
return user.getLastName();
}
#Override
public String getEmail() {
return user.getEmail();
}
}
The reason is that in your UserAdminAdapter class, you have not implemented the removeRequiredAction and addRequiredAction methods. The message you're receiving is from the default implementation provided by the base class. You should either implement these methods yourself and store the required actions in your underlying storage, OR consider extending your class from AbstractUserAdapterFederatedStorage instead which delegates all such functionalities to the internal Keycloak implementation.
FULL OTP support in my external DB
Well, finally after more than a week I got this working with Keycloak 18.0. What do you need to do?, simply, you have to implement each and every step in the authentication workflow:
Create your user storage SPI
Implement Credential Update SPI
Implement a custom Credential Provider SPI
Implement a custom Required Action SPI
Implement your authenticator SPI
Implement your forms (I kinda used the internal OTP forms in KC)
Enable your Required action
Create a copy of the browser workflow and plaster there your authenticator
And what do we get with this?
We get a fully customizable OTP authenticator (realm's policy pending...)
You can use that code for verification in your app (it's in your db)
You can setup users for OTP authentication in your app (no KC admin page involved, so, you can leave the admin page outside the firewall)
In my opinion, this is kinda annoying, since there are a lot of loops we have to make to be able to store our data locally and how to deal with the integrated OTP forms (for a "natural look"), but it gives me full control over my OTP integration, also, I can backup my database and their OTP authentication is still there, so, if I have a failure in a KC upgrade or it gets corrupted, I still have all that data.
Lastly, heres what it should look like when your manager has the custom OTP authentication
Related
I'm trying to set a user's attribute after they register in my custom Keycloak extension. My event listener implementation looks as follows:
#AutoService(EventListenerProviderFactory.class)
public class EventListener implements EventListenerProvider {
private final KeycloakSession session;
public EventListener(KeycloakSession session) {
this.session = session;
}
#Override
public void onEvent(Event event) {
if (event.getType() != EventType.REGISTER)
return;
RealmModel realm = session.realms().getRealm(event.getRealmId());
UserModel user = session.users().getUserById(realm, event.getUserId());
user.setSingleAttribute("hello", "world");
}
#Override
public void onEvent(AdminEvent event, boolean includeRepresentation) {
}
#Override
public void close() {
}
}
My extension is recognized by Keycloak and successfully triggers onEvent() when an event occurs (hence why I didn't include the factory class).
However, the attribute isn't added to the user. How do I actually persist the changes to the user?
While searching for a solution to the above, I came across this discussion of a very similar issue. Extending RegistrationUserCreation instead of EventListenerProvider and using the solution given by #dvlpphb did actually manage to solve my problem; however, the solution only worked when overriding the RegistrationUserCreation's validate() method, which is called every time the user attempts to register.
If anyone knows a way to set a user attribute without EventListenerProvider through RegistrationUserCreation's success() callback, that would also solve my issue.
Thank you!
I am looking for any way to get the firstname or the lastname of a new self-registered user on keyclock using a User Storage Provider ?
In my case, i'm working with Keycloak 12 and an old legacy MySQL database where I have to store all my users.
Implementing into my SPI the UserRegistrationProvider.addUser(RealmModel realm, String username) and the CredentialInputUpdater.updateCredential(RealmModel realm, UserModel user, CredentialInput input) allows me to catch the username and the password ... but I can't get any other basic information such as firstname/lastname ?
Here are my two implementation of CredentialInputUpdater and UserRegistrationProvider :
#Override
public UserModel addUser(final RealmModel realmModel, final String username) {
Integer id = userRepository.createUser(username, "password");
User user = null;
if (id != null) {
user = new User(id.toString(), username, "firstName", "userName", "password");
}
return user != null ? new UserAdapter(session, realmModel, model, user) : null;
}
#Override
public boolean updateCredential(RealmModel realm, UserModel user, CredentialInput input) {
if (!supportsCredentialType(input.getType()) || !(input instanceof UserCredentialModel)) {
return false;
}
UserCredentialModel cred = (UserCredentialModel) input;
return userRepository.updateCredentials(user.getUsername(), cred.getChallengeResponse());
}
Any idea on how to get these two data ?
A bit late for your question, but I ran into a similar problem. My use case was the following : I had to call an external API from the addUser method while providing both username and password.
After debugging Keycloak I found out you can retrieve every parameters filled in the form inside the Keycloak session (or the context).
Here is an example for the password :
String password = this.session.getContext()
.getContextObject(HttpRequest.class)
.getDecodedFormParameters()
.getFirst("password");
Hopefully, this will help other people.
After some days of research, I finally found the solution.
I'm not sure if it works on keycloak 12, but it does on keycloak 18.
This comment give me the point.
So in addUser you don't have to persit the new user, you only have to create your UserAdapter and just before return UserModel I added:
javax.enterprise.inject.spi.CDI.current().getBeanManager().fireEvent(userAdapter);
and in the same class I added the event listener:
public void observeUserAddedEvent(#Observes(during = TransactionPhase.BEFORE_COMPLETION) UserModel addedUser) {
// persist the "post populated" user
}
In Keycloak I implemented a custom UserLookupProvider and a CredentialInputValidator. The legacy system which I want to get connected to keycloak is only able to check if a user exist if I supply the system with the user ID and the password.
Unfortunately I dont have access to the password in the getUserByUsername function. Therefore I cant check if the user exists in the legacy system.
I found this similar question without an answer: https://lists.jboss.org/pipermail/keycloak-user/2019-May/018271.html
I found an answer:
I've found the idea here How to implement Recaptcha on keycloak login page (https://github.com/raptor-group/keycloak-login-recaptcha/blob/master/src/main/java/org/keycloak/marjaa/providers/login/recaptcha/authenticator/RecaptchaUsernamePasswordForm.java)
I created a UserNamePasswordForm
public class RecaptchaUsernamePasswordForm extends UsernamePasswordForm implements Authenticator{
#Override
protected Response createLoginForm( LoginFormsProvider form ) {
return super.createLoginForm( form );
}
#Override
public void authenticate(AuthenticationFlowContext context) {
super.authenticate(context);
}
#Override
public void action(AuthenticationFlowContext context) {
context.getSession().setAttribute("password", context.getHttpRequest().getDecodedFormParameters().get("password").get(0));
super.action(context);
}
}
I can access the password now everywhere:
#Override
public UserModel getUserByUsername(String username, RealmModel realm) {
session.getAttribute("password")
if (isAuthenticatedInUls(username,session.getAttribute("password"))) {
return migrate(username, username, realm);
//
}
return null;
}
I want to keep some information in Keycloak as custom user attributes.
Some of them should be managed by the user itself. Other attributes should be managed only by a Keycloak administrator. Attributes managed by the administrator should be read-only visible in the "Edit account" web page for the user.
I went through the guide to add custom user attributes in this page and customized the "Edit account" web page.
My question is:
Is it ensured that the user cannot change the attribute that is meant as read-only for the user? E.g. by submitting a form where he/she sends correct data that will be automatically mapped on the server side to the user attribute.
For what you've said, it seems that you have three choices.
One would be to keep the keycloak "Edit Account" page and use an update profile listener to check what attributes are stored or which ones are updated by who, something like this:
public class UpdateProfile implements RequiredActionProvider, RequiredActionFactory, DisplayTypeRequiredActionFactory {
#Override
public InitiatedActionSupport initiatedActionSupport() {
return InitiatedActionSupport.SUPPORTED;
}
#Override
public void evaluateTriggers(RequiredActionContext context) {
}
#Override
public void requiredActionChallenge(RequiredActionContext context) {
Response challenge = context.form()
.createResponse(UserModel.RequiredAction.UPDATE_PROFILE);
context.challenge(challenge);
}
// Check the custom attribute 1 not being modified by the user
#Override
public void processAction(RequiredActionContext context) {
EventBuilder event = context.getEvent();
event.event(EventType.UPDATE_PROFILE);
MultivaluedMap<String, String> formData = context.getHttpRequest().getDecodedFormParameters();
UserModel user = context.getUser();
KeycloakSession session = context.getSession();
RealmModel realm = context.getRealm();
String newYourCustomAttribute1 = formData.getFirst("yourCustomAttribute1");
String oldYourCustomAttribute1 = user.getFirstAttribute("yourCustomAttribute1")
if (!newYourCustomAttribute1.equals(oldYourCustomAttribute1)) {
Response challenge = context.form()
.setError("User cannot change the attribute")
.setFormData(formData)
.createResponse(UserModel.RequiredAction.UPDATE_PROFILE);
context.challenge(challenge);
return;
}
context.success();
}
#Override
public void close() {
}
#Override
public RequiredActionProvider create(KeycloakSession session) {
return this;
}
#Override
public RequiredActionProvider createDisplay(KeycloakSession session, String displayType) {
if (displayType == null) return this;
if (!OAuth2Constants.DISPLAY_CONSOLE.equalsIgnoreCase(displayType)) return null;
return ConsoleUpdateProfile.SINGLETON;
}
#Override
public void init(Config.Scope config) {
}
#Override
public void postInit(KeycloakSessionFactory factory) {
}
#Override
public String getDisplayText() {
return "Update Profile";
}
#Override
public String getId() {
return UserModel.RequiredAction.UPDATE_PROFILE.name();
}
}
What I don't know is if this listener will be called when you update the profile from your client application too. If it gets called, you'll need to check which is the logged in client, if it's the public client do not let update the attributes, if it's your service client, let it.
The second one would be to only let your service client update the user profiles and make a custom view in your application which sends a form POST to your client, instead of to keycloak directly. This way you can validate it in the service before sending it to keycloak.
The third one is to implement a FormAction interface, which would allow you to validate the incoming form at server side:
The core interface you have to implement is the FormAction interface. A FormAction is responsible for rendering and processing a portion of the page. Rendering is done in the buildPage() method, validation is done in the validate() method, post validation operations are done in success().
#Override
public void validate(ValidationContext context) {
MultivaluedMap<String, String> formData = context.getHttpRequest().getDecodedFormParameters();
UserModel user = context.getUser();
KeycloakSession session = context.getSession();
RealmModel realm = context.getRealm();
String newYourCustomAttribute1 = formData.getFirst("yourCustomAttribute1");
String oldYourCustomAttribute1 = user.getFirstAttribute("yourCustomAttribute1")
if (!newYourCustomAttribute1.equals(oldYourCustomAttribute1)) {
Response challenge = context.form()
.setError("User cannot change the attribute")
.setFormData(formData)
.createResponse(UserModel.RequiredAction.UPDATE_PROFILE);
context.challenge(challenge);
return;
}
context.success();
}
perform an update to version 12.0.4.
There were some issues < 12.0.4 with dropping all attributes if user updates his profile.
Additionally with 12.0.4 you can create user- and admin-read only attributes.
Check documentation: https://www.keycloak.org/docs/latest/server_admin/#_read_only_user_attributes
Cheers
We have implemented a custom authenticator for supporting a workflow to reset password via a SMS OTP. The authenticator uses the phone number stored in a user attribute.
We wish to store the credentials for the SMS provider in the Realm Settings, so we're looking for a way to add some additional configuration attributes to Realm Settings,in a separate tag like Login,Theme etc. It would be ideal if the Authentication Provider can "declare" these configuration attributes. If not, is there any other way to extend the Realm Settings ?
Here is an example of how you'd add the configurable properties to the authenticator. Once the authenticator is added to the flow you'll be able to set configurations for that specific instance of the authenticator. if you add the authenticator to another flow it'll have another set of configs specific to that instance in that other flow.
public class MyFactory implements AuthenticatorFactory {
#Override
public boolean isConfigurable() {
return true;
}
private static final List<ProviderConfigProperty> configProperties = new ArrayList<>();
static {
ProviderConfigProperty someCheck = new ProviderConfigProperty(
"some.check.property.name",
"Some Check",
"This does some check. You'll see this in the UI.",
ProviderConfigProperty.BOOLEAN_TYPE,
true);
configProperties.add(someCheck);
}
#Override
public List<ProviderConfigProperty> getConfigProperties() {
return configProperties;
}
}