RequiredAction not deleted from admin UI - jboss

I removed VERIFY_EMAIL required action from user. It's has been deleted from database and user's field. BUT it is still on ADMIN UI. I can not understand why
#Override
public void sendVerifyEmail(String link, long expirationInMinutes) throws EmailException {
Map<String, Object> attributes = new HashMap<String, Object>(this.attributes);
addUserAttributes(user, attributes);
addLinkInfoIntoAttributes(link, expirationInMinutes, attributes);
attributes.put("realmName", getRealmName());
// here I delete required action to not require a user to verify email before he can login
user.removeRequiredAction(UserModel.RequiredAction.VERIFY_EMAIL);
user.removeRequiredAction(UserModel.RequiredAction.VERIFY_EMAIL.name());
send("emailVerificationSubject", EnvVariablesUtils.getVerifyEmailMailgunTemplate(), attributes);
}

Related

Keycloak OTP for read only federated users

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

How to get FirstName/LastName using Keycloak UserRegistrationProvider?

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
}

Keycloak - read-only user attributes

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

Onsubmit of form, page get refreshed and PageParameters gets empty in wicket

In LoginPage.java, I have added some PageParameters and redirect the user to another page.
PageParameters parameters = new PageParameters();
parameters.add("firstName", firstNameValue);
parameters.add("lastName", lastNameValue);
parameters.add("emailAddress", emailAddressValue);
throw new RedirectToPageException(UserInformationPage.class, parameters);
In AdditionalInformationPage.java
public class UserInformationPage extends BaseWebPage {
private static final long serialVersionUID = 3614392599102678526L;
public UserInformationPage(final PageParameters parameters) {
super(parameters);
setOutputMarkupId(true);
String firstName, lastName, emailAddress;
firstName = parameters.get("firstName").toOptionalString();
lastName = parameters.get("lastName").toOptionalString();
emailAddress = parameters.get("emailAddress").toOptionalString();
WebMarkupContainer userInformationPageWrapper = new WebMarkupContainer("userInformationPageWrapper");
userInformationPageWrapper.add(new UserInformationPanel("userInformationPageContent", firstName, lastName, emailAddress));
add(userInformationPageWrapper.setMarkupId("userInformationPageWrapper"));
}
}
UserInformationPanel.java
public class UserInformationPanel extends Panel {
private static final long serialVersionUID = -1016518626600751985L;
public UserInformationPanel(String id, String idpUuid, firstName, lastName, emailAddress) {
super(id);
setOutputMarkupId(true);
Form<Void> userInformationForm = new CSRFSafeForm<Void>("userInformationForm") {
private static final long serialVersionUID = 2633350725131958527L;
#Override
protected void onConfigure() {
super.onConfigure();
setVisible(true);
}
};
FeedbackPanel errorFeedbackPanel = new TrackedFeedbackPanel("errorFeedback", new ErrorLevelFeedbackMessageFilter(FeedbackMessage.ERROR));
errorFeedbackPanel.setMaxMessages(MAX_ERROR_MESSAGES);
userInformationForm.add(errorFeedbackPanel.setOutputMarkupId(true));
TextField<String> firstName = new TextField<>("firstName", firstName);
firstName.add(StringValidator.maximumLength(DatabaseConstants.User.FIRST_NAME_MAX_LENGTH));
userInformationForm.add(firstName.setRequired(true).setEnabled(true));
TextField<String> lastName = new TextField<>("lastName", lastName));
lastName.add(StringValidator.maximumLength(DatabaseConstants.User.LAST_NAME_MAX_LENGTH));
userInformationForm.add(lastName.setRequired(true).setEnabled(true));
EmailAddressValidator emailAddressValidator = EmailAddressValidator.getInstance();
TextField<String> emailAddress = new EmailTextField("emailAddress", emailAddress), emailAddressValidator);
emailAddress.setRequired(false)
.add(UniqueEmailValidator.getInstance(UniqueEmailValidator.ErrorMsgType.REGISTER))
.add(StringValidator.maximumLength(DatabaseConstants.EMAIL_ADDRESS_MAX_LENGTH));
emailAddress.setEnabled(false);
userInformationForm.add(emailAddress);
userInformationForm.add(new AjaxButton("submitButton") {
private static final long serialVersionUID = -1723378347103997463L;
#Override
public void onSubmit(AjaxRequestTarget target, Form<?> form) {
super.onSubmit(target, form);
Map<String, Object> userAttributes = new LinkedHashMap<>();
userAttributes.put("email_Address", emailAddress);
userAttributes.put("first_Name", firstName);
userAttributes.put("last_Name", lastName);
// logic to save userAttributes in DB.
throw new RedirectToUrlException("/home");
}
#Override
protected void onError(AjaxRequestTarget target, Form<?> form) {
super.onError(target, form);
target.add(errorFeedbackPanel);
}
});
userInformationForm.setOutputMarkupId(true);
add(userInformationForm);
}
}
Button in html file like this:
<button wicket:id="submitButton" type="submit" class="adb-button__primary nextStep">
<span><wicket:message key="Submit"/></span>
</button>
First time page gets rendered successfully. Fields get pre-populated. When I click on submit buttton first time, the page gets refreshed and page parameters gets empty. Form is rendered again but with empty values. On second click it works properly.
So, How can I stop page refresh so that pageParams does not gets empty and on click of submit button in first time, it validates the form and show error if any on page?
I couldn't add a comment so I am suggesting this answer.
Please check this line : throw new RedirectToUrlException("/home"); inside the onSubmit() method. Why are you using it? This means that you are redirecting to /home whenever the user submits the form, and probably at home your are redirecting back to the LoginPage. Please check what is happening at the /home route, or post more information so I can help you better.

Adding more attributes to Realm Settings

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;
}
}