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
Related
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
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..
I'm trying to write a Plugin for Trac.
I've succeeded to export variables contents from my request (process_request)
to my template ...but I still having problems doing it in the other way : how to catch the information taped by the user ?
<form name="input" action="" method="POST">
Configuration : <input type="text" name="configuration" value ="$my_var" /> <br /><br />
<label for="file">URL:</label>
<input type="text" name="file" id="WillPosted" value="This text will be changed by the user"/>
<input type="submit" name="SubmiT" value="Browse" /><br /><br />
So how can I catch the content of the input form with the id="WillPosted" ?
Notice : actual problem is in IRequestHandler methods from Trac !
Any Idea ?
Thanks
You're asking about the common web-UI interaction
my_var --> template with input field 'file' and default value 'This will ..'
user input to field
submission by POST request
How to get (changed) new value of input, right? If so, you explained it rather complicated.
And I'm wondering, if you don't know about trac-hacks.org, probably the most authoritative source of Trac plugin code on the planet - much more working examples to look at than needed here.
Anyway, the key is reading the return value(s) from reg.args dict like so:
from pkg_resources import resource_filename
from trac.web.chrome import ITemplateProvider, add_stylesheet
implements(IRequestHandler, ITemplateProvider)
# IRequestHandler methods
def match_request(self, req):
# Any condition evaluating to True will fire the second method.
return req.path_info == '/<your_path>'
def process_request(self, req):
"Demo how to interact with your template."
# Check, if you'r really processing the right request by target matching,
# and only act on input of authorized users - as an added suggestion/option.
if req.path_info.startswith('/<your_path>') and req.authname != 'anonymous':
# Check for request type.
if req.method == 'POST':
# Ok, go pock for the new value now.
value = req.args.get('file', 'default_value')))
# Now go, process it, store it, even redirect away, if you're done.
# Fallback for Trac 0.11 compatibility included.
referer = req.args.get('referer') or req.get_header('Referer')
# Steer clear of requests going nowhere or loop to self
if referer is None or \
referer.startswith(str(req.abs_href()) + '/<your_path>'):
referer = req.abs_href()
req.redirect(referer)
# Still there, so that's the initial call or anonymous user, hey?
# So let's prepare information for displaying the template ...
data = dict(my_var='any_string_or_number')
# Take the env obj from self, if needed.
env = self.env
mod = MyOwnSecondaryModule(env)
if mod.can_do_something:
data['my_var'] = mod.do('better')
# Why not apply an own style?
add_stylesheet(req, 'your_plugin/most_famous_style.css')
return 'your_template.html', data
# ITemplateProvider methods
def get_htdocs_dirs(self):
"""Return the absolute path of a directory containing additional
static resources (such as images, style sheets, etc).
"""
return [('your_plugin', resource_filename('your_plugin_pkg_base_dir', 'htdocs'))]
def get_templates_dirs(self):
"""Return the absolute path of the directory containing the provided
Genshi templates.
"""
return [resource_filename('your_plugin_pkg_base_dir', 'templates')]
Questions on using various Trac extension point interfaces? See the authoritative wiki documentation on the subject as well!
We're on CF 8 and we have a CF application with a 1 hour session timeout specified.
<cfapplication sessionmanagement="yes" sessiontimeout="#CreateTimeSpan(0, 0, 60, 0)#" name="myAppName" clientmanagement="yes">
Within the CF administrator, we have a default session timeout of 1 hour, and a max session timeout of 1 day.
We're having some odd (intermittent) form timeout issues on submission of a regular form (not a flash form)...
Let's say the user hits the form page at 10:30am. (I know this because I'm appending the time to the "Action" property of the form tag). At 11:10am, the user submits the form, but none of the form struct is available to the action page, so the user gets an error.
I know that it's coming from the correct page since the CGI.referrer is defined properly.
In my custom error handler, I iterate over any form, session, or client structs -- and there is NO form struct defined at this point. All of the other Session and Client variables ARE still available.
Excerpts from Form Page:
<cfform name="chairsComment" id="chairsComment" action="library/save_chairsComment.cfm?Start=0224_153027" method="post" >
<input name="chairsCommentNumber" id="chairsCommentNumber" type="hidden" value="13" />
<textarea name="comment_13" rows="50" wrap="virtual" cols="100" id="comment_13" ></textarea>
<input name="save_answer" id="save_answer" type="submit" value="Save Response" />
</cfform>
And for the Action page, it throws an error on line 1:
<cfset whichCommentNumber = form.chairsCommentNumber>
It works during testing, but when some of our users use it, it throws the error that "Element CHAIRSCOMMENTNUMBER is undefined in FORM." We've just started looking at the specific browser that this individual is using (Safari 4.0.3) and asked him to upgrade to 5.x, but with SUCH a vanilla form, it seems an unlikely culprit.
Any Ideas?
In the midst of a discussion on Ray Camden's blog about file uploading, someone mentions a problem with Safari 4.0.5 throwing an error because the form field did not contain a file ... it's not the same problem, necessarily, but it could be related. Unfortunately, the commenter never returned with more information.
There's a post on another blog here where a commenter mentions an issue with Safari and a cfform inside a cfdiv tag. If your cfform is similarly nested, you might need to reverse the order (nest the cfdiv inside the form) to make this work.
I might have a freudian lips but i cant figure out how to send an email address to a specific url without the url encodes the AT symbol in %40...
basically i have a basic form
<form class="firstLoginform" id="firstLoginform" name="firstLoginform" method="get" action="https://myurl" >
<label for="email" >Email Address</label>
<input type="text" name="userEmail" id="userEmail" size="20" maxlength="150">
<center>
<input type="submit" value="submit" />
</center>
</form>
but i submit the form the url is gonna be like
https://myurl?userEmail=myemail%40mydomain.com
but I NEED, for soe reason of the previous settings a ul like this
https://myurl?userEmail=myemail#mydomain.com
i dont have access to the page that is receiving the variables...so i wonder if i can send the email address AS IS..
thanks!!!
No, you can't according to RFC1738, the URL Spec. The # symbol is reserved because it has a special meaning.
As Alan mentioned the URL specification (RFC1738) forbids the use of the # symbol in URLs because it's reserved for use within any type of URL. An example of this would be an FTP URL which provides the option to specify a username#host.domain syntax.
Section 3 of the RFC shows a number of cases that use the # symbol in a URL.
For this reason # along with a number of other characters can't be used as part of an HTTP URL.
No, you can't. Variables in query strings must be encoded, otherwise it isn't a valid URL.