Add custom validation in Update User Profile in Keycloak - jboss

I have added a custom attribute in login-update-profile.ftl named organization, it is able to save the input from user into Keycloak.
<div class="${properties.kcFormGroupClass!}">
<div class="${properties.kcLabelWrapperClass!}">
<label for="user.attributes.organization" class="${properties.kcLabelClass!}">${msg("organization")}</label>
</div>
<div class="${properties.kcInputWrapperClass!}">
<div class="${properties.kcInputWrapperClass!}">
<input type="text" id="user.attributes.organization" name="user.attributes.organization" value="${(user.attributes.organization!'')}" class="${properties.kcInputClass!}" aria-invalid="<#if messagesPerField.existsError('organization')>true</#if>"
/>
</div>
<#if messagesPerField.existsError('organization')>
<span id="input-error-organization" class="${properties.kcInputErrorMessageClass!}" aria-live="polite">
${kcSanitize(messagesPerField.get('organization'))?no_esc}
</span>
</#if>
</div>
</div>
How to add validation for this field? I need to make it an obligatory field and meet certain condition (for example the length of the string). If the input is invalid, the error message is expected to be shown (like what we see in email or username field)

I found out the solution by creating a custom implementation of service provider Review Profile in First Broker Login flow. Here is the steps:
Open keycloak's github repo and find the relevant Java codes for Review Profile. At the time of writing this answer (keycloak v15.0.2), we need 3 files: AbstractIdpAuthenticator.java, IdpReviewProfileAuthenticatorFactory.java, and IdpReviewProfileAuthenticator.java
Make sure you are able to build a .jar using those files
Open IdpReviewProfileAuthenticator.java and add this function:
public List<FormMessage> getCustomAttributeError(String organization) {
List<FormMessage> errors = new ArrayList<>();
// You can add more conditions & parameters to be validated
if(Validation.isBlank(organization)){
errors.add(new FormMessage("organization", "missingOrganizationMessage"));
}
return errors;
}
Go to actionImpl function and add this lines between profile.update((attributeName, userModel) and catch (ValidationException pve):
if(getCustomAttributeError(profile.getAttributes().getFirstValue("organization")).size() > 0){
throw new ValidationException();
}
Add this lines in catch (ValidationException pve) after List<FormMessage> errors:
List<FormMessage> extraErrors = getCustomAttributeErrors(profile.getAttributes().getFirstValue("organization"));
for(FormMessage error : extraErrors) {
errors.add(error);
}
Open IdpReviewProfileAuthenticatorFactory.java go to getDisplayType() function and change the return value into "Custom Review Profile"
Build the .jar, deploy it into keycloak, create a copy of First Broker Login flow we name Custom First Broker Login, and in Custom First Broker Login we replace Review Profile with Custom Review Profile
Configure Custom Review Profile by clicking it's action button, give it an alias, and turn Update Profile on First Login into on
Bind the desired Identity Providers with the new Custom First Broker Login

Related

How to access OIDC client information in Keycloak E-Mail templates?

I know how to access some variable information in Keycloak E-Mail templates. E.g.:
user.getUsername()
user.getEmail()
user.getFirstName()
user.getLastName()
But I need to access client specific variables. The Keycloak Java Code shows there is all information I need in the ClientModel Java Class: https://github.com/keycloak/keycloak/blob/main/server-spi/src/main/java/org/keycloak/models/ClientModel.java
client.getClientId()
client.getName()
client.getDescription()
client.getRootUrl()
client.getBaseUrl()
client.getAttribute(name)
And the client_id=account Query Parameter is also set on the page, where the password reset action is triggered:
https://example.com/auth/realms/my-realm/login-actions/reset-credentials?client_id=account&tab_id=bQiVx012SZg
The information is set on the client:
But the client varaible seems to be unset while the email template gets rendered.
# password-reset.ftl
# This does NOT work
${client.name}
# This does NOT work
${kcSanitize(msg("clientinfohtml",client.getName()))?no_esc}
How to access client variables in Keycloak E-Mail templates?
Yep, client segment seems not accessible and give https://null !
But if you check availabilty of segment it is not empty!
<#if client?? && client['baseUrl']?? && client['baseUrl']?has_content>
<img height="100" src='${client["rootUrl"]}/${kcSanitize(msg("emailSignatureLogoUrl"))?no_esc}' style="max-width: 250px;">
<#else>
<img height="100" src='${kcSanitize(msg("emailSignatureLogoUrl"))?no_esc}' style="max-width: 250px;">
</#if>
This is the same result with client['rootUrl']
Searching an anwser, I have found this topics about Model provisioning for EmailTemplate:
https://keycloak.discourse.group/t/access-to-group-attributes-in-custom-email-template/10874/2

Keycloak registration - custom user attribute value not picked up correctly

I am evaluating KeyCloak, and am trying to configure it as a SAML IDP to a SalesForce client. The SAML flow works fine, however I am running into an issue with new user registration. KeyCloak is backed by a User Federation LDAP (AD LDS).
I have a custom field mapped to the cn field in the LDAP, and have updated the register.ftl in my custom theme. I have also added a mapper, which maps this custom field to the LDAP attribute.
I get an error when I try to register the user, and it looks like the value of the custom attribute is not being picked up correctly.
I cannot find anything useful when I search online, which makes me think that there is probably a silly mistake somewhere that I haven't spotted.
Mapping:
Name: MRN
Mapper Type: user-attribute-ldap-mapper
User Model Attribute: mrn
LDAP Attribute: cn
Read Only: OFF
Is Mandatory In LDAP: ON
Is Binary Attribute : OFF
Federation provider settings:
Username LDAP attribute :userPrincipalName
RDN LDAP attribute :cn
UUID LDAP attribute :mail
Template:
<div class="${properties.kcFormGroupClass!} ${messagesPerField.printIfExists('mrn',properties.kcFormGroupErrorClass!)}">
<div class="${properties.kcLabelWrapperClass!}">
<label for="mrn" class="${properties.kcLabelClass!}">MRN</label>
</div>
<div class="${properties.kcInputWrapperClass!}">
<input type="text" id="user.attributes.mrn" class="${properties.kcInputClass!}" name="user.attributes.mrn" value="${(register.formData['user.attributes.mrn']!'')}" />
</div>
</div>
This is the error I get when I try to register a new user (I entered 123456 into the MRN field) :
14:08:43,514 WARN [org.keycloak.services] (default task-1) KC-SERVICES0013: Failed authentication: org.keycloak.models.ModelException: Could not retrieve identifier for entry [cn=\ ,OU=Emp,DC=MyPortal,DC=TEST,DC=COM].
at org.keycloak.storage.ldap.idm.store.ldap.LDAPIdentityStore.getEntryIdentifier(LDAPIdentityStore.java:546)
at org.keycloak.storage.ldap.idm.store.ldap.LDAPIdentityStore.add(LDAPIdentityStore.java:99)
at org.keycloak.storage.ldap.LDAPUtils.addUserToLDAP(LDAPUtils.java:78)
I am not sure how this was fixed, but I deleted my mapper and re-created it, and it is working now. The only difference I can see is that the new mapper has its name in lower case while the old one had it in all uppercase. I do not see why that should matter though..

The right way to fill and test a web form in ScalaTest and FluentLenium

I'm trying to fill, submit, and test a web form in Play Framework, using ScalaTest and FluentLenium. It seems like it should be very straightforward, but I'm having all kinds of problems.
First, part of the web form in question:
<form class="signin" id="loginform" method="POST" action="/login">
<div class="form-group">
<label for="name">Email Address:</label>
<input type="email" class="form-control" placeholder="Enter Email Address" id="email" name="email" required />
...
This works fine from a real web browser. Now the problem comes when I try to fill in and submit the form:
#RunWith(classOf[JUnitRunner])
#SharedDriver(deleteCookies = false)
#SharedDriver(`type` = SharedDriver.SharedType.PER_CLASS)
class TestWebsiteAuthentication extends Specification {
"Application" should {
"login as an administrative user on the web site" in new WithBrowser with GPAuthenticationTestUtility {
browser.goTo(loginURL)
browser.fill("#email").`with`(prerequisiteAccounts.head.userIdentity) must equalTo(OK)
...
At that last line, I get an exception:
[info] x login as an administrative user on the web site
[error] 'org.fluentlenium.core.action.FillConstructor#1c25c183' is not equal to '200' (TestWebsiteAuthentication.scala:93)
[error] Expected: 200
[error] Actual: org.fluentlenium.core.action.FillConstructor#1c25c183
Any ideas what I'm doing wrong here?
I've tried taking out the "must equalTo(OK)" but this just causes the form to fail on submit -- unfortunately, I haven't been able to find ANY documentation on how to do this, so I'm basically piecing it together bit by bit. Pointers to relevant documentation would be appreciated -- there doesn't seem to be anything complete at Tyrpesafe... just "teasers" that get you started, but no depth. :-(
When you write browser.fill("#email").``with``("x#y.com"), all you're really doing is telling Fluentlenium to edit the template to add a value attribute inside the input tag.
On the other hand, OK is an HTTP status code, so comparing them will naturally yield false.
When you say you tried to submit the form and it failed, i am assuming you did something such as:
browser.fill("#email").`with`("x#y.com")
browser.fill("#password").`with`("myPass")
browser.click("#button") // this should submit the form and load the page after login
and then tried to make an assertion such as:
browser.title() must equalTo("next page") // fails because "next page" != "login page"
one suggestion is to try something like this, Before browser.click:
browser.pageSource() must contain("xyz") // this will fail
When the above assertion fails, it will print the content of browser.pageSource() to your terminal, and you'll be able to see the modifications the Fill function did to the HTML.
In my case, I observed that my pageSource() now contained the following:
<input type="text" id="email" name="email" value="x#y.com"/>
<input type="password" id="password" name="password"/>
Notice how the first input has a value="x#y.com", but the second input is still empty. It turns out the second one is empty because it is an input of type password, however I eventually made the form login work.
Here is a list of things you can look into:
have a database enabled in that Spec
have it populated with Users (in case your form validation connects to a DB, that is)
from what I have experienced, using browser.goTo more than once in a test will not work well with form submission (anyone can confirm?)
Hope this helps

form with multiple submit buttons that execute different actions

I'm missing something fundamental when it comes to mapping a view to a controller's action and hoping someone can point me in the right direction. I'm working on an existing project and still familiarizing myself with the language and the way it was configured. I have a form that will resolve a qaCase (question answer case) through the resolveForm action and qaCase/resolve view. below is a simplified version of what I have (please let me know if I need to include more information).
QaCaseController
#RequestMapping(value="/resolve/{id}/**", method=RequestMethod.GET)
public String resolveForm(#PathVariable("id") Integer id, Model model) {
QaCase qaCase = qaCaseDAO.findById(id);
// Load the backing objects into the session
model.addAttribute("qaCase", qaCase);
model.addAttribute("users", userDAO.findAll());
model.addAttribute("exams", examDAO.findAll());
return "qacases/resolve";
}
qaCase/resolve.jsp
the resolve view has a form that will accept text input and a resolve button.
<sf:form method="POST" modelAttribute="qaCase" onsubmit="return isValid()">
// some input fields
<input type="submit" name="submitted" value="resolve" />
</sf:form>
when submit button is clicked, the following query string is created
http://localhost:8080/qacases/resolve/<id>/<location>/<name>/<created by>
What I'd like to do is add an additional input field and button to the existing form so I can optionally add comments instead of resolving a case.
<sf:form method="POST" modelAttribute="qaCase" onsubmit="return isValid()">
// some input fields
<input type="submit" name="submitted" value="resolve" />
</sf:form>
<sf:form method="POST" modelAttribute="qaCase" action="addComment">
// optionally Add comment
<input type="submit" name="submitted" value="addComment" />
</sf:form>
If addComment is clicked then I want the query string to be created.
http://localhost:8080/qacases/addComment
Instead, I get the following query string with a 400 status code.
http://localhost:8080/qacases/resolve/<id>/<location>/<name>/<created by>/addComment
I've been going through configuration files to find how the mapping is being set but haven't had any luck. Not sure if this is an answer that can be answered without someone going through the project and determining how it's configured. Appreciate any advice and/or answers.
when you are using action = "addComment" without "/" before "addComment" in <form> that means you are posting your form to current_url_that_invokes_view/addComment
if add "/" to action = "/addComment" you will go to localhost:8080/addComment
so if you need http://localhost:8080/qacases/addComment
type action = "/qacases/addComment" and pay attantion to "/" before qacases to direct root url

Adding new columns to trac AccountManager plugin

I want to add new columns to the trac AccountManager plugin, I already edited the admin_user.html, but in python what do I have to edit, which files. I want to add two more data field. ex: group and telephone, How can I do that?
UPDATED
This is the two field I added in the admin_user.html
<div class="field">
<label>Telephone:<br />
<input type="text" name="tel" class="textwidget"
value="${account.tel}" /></label>
</div>
<div class="field">
<label>Group:<br />
<input type="text" name="group" class="textwidget"
value="${account.group}" /></label>
</div>
and I also added the two column to the table:
<td>${acct.group}</td>
<td>${acct.last_visit}</td>
But I'm not familiar with python, so I don't know how can I add the functionality in the python code
Did you check UserManagerPlugin for it's ability to add arbitrary new columns?
But maybe you want it some additions to the generic view in users admin page, right? These must be optional, if you care for universal use, of course.
In 'admin.py' you'll see a Class 'AccountManagerAdminPages' with a module '_do_users'. This module is primary handler for all requests related to the admin page in question. While the beginning is about processing 'POST' (user input) everything after the line 'if listing_enabled:' will prepare the page displayed next. You'll see various sources, but main source is query to Trac db table 'session_attribute'.
Again I'd like you to take a look at UserManagerPlugin - all what you may want and more.
Disclosure: I'm the current maintainer of AccountManagerPlugin.