How to use SpEL with hasRole to set html attribute? - spring

In Spring/Thymeleaf: I want to set attribute based on whether user has ROLE_A or not.
I tried HTML below:
<section class="footer" ... th:someattr="#{hasRole('ROLE_A')} ? 'true' : 'false'">
It always rendered as <section class="footer" ... someattr="true"> regardless of whether user had ROLE_A or not:
I tried also ${hasRole('ROLE_A')} ? 'true' : 'false' but that failed to work ():
SpelEvaluationException: EL1004E:(pos 0): Method call: Method
hasRole(java.lang.String) cannot be found on
org.thymeleaf.spring4.expression.SPELContextMapWrapper type
How to set an attribute to true|false depending on assigned authentication roles.
Thanks.

Sometimes, this is cause, because of the configuration of your project. So, let's do some changes and use the following code.
<section class="footer" ... th:someattr="${#authorization.expression('hasRole(''ROLE_A'')' ? 'true' : 'false'}">
To use #authorization, you will need to add the following dependency though, thymeleaf-extras-springsecurity4.
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity4</artifactId>
</dependency>
If you are using <artifactId>spring-boot-starter-parent</artifactId>, you won't need to add any version to your Thymeleaf Extras, since Spring Boot manages that for you. If not, try adding this version <version>3.0.4.RELEASE</version>.
Note: If it doesn't work change it for thymeleaf-extras-springsecurity5, depending on your Spring version one will work and the other won't.

I think you are looking for something like:
<section class="footer" ... th:someattr="${#request.isUserInRole('A') ? 'true' : 'false'}">
(No "extras" needed.)

Related

How to escape '-' in SpEL in thymeleaf

In my Spring Controller I set following to my model attribute:
model.addAttribute("abc-def", "Hello World");
In my thymeleaf html I want to read the value of abc-def.
<th:block th:text="${abc-def}"></th:block>
But I get the error:
The operator 'SUBTRACT' is not supported between objects of type 'null' and 'null'
Its clear because - is an arithmetic operator. Is there a way to escape - for reading out the model value?
My advice would be: don't use variables names with dashes in them. (Would you try to define a variable int abc-def = 5; in java?)
In any case, this seems to work if you have to use it:
<th:block th:text="${#request.getAttribute('abc-def')}" />
Thymeleaf 2
Per the Expression Basic Objects section of the documentation (with more details in Appendix A), the context variables are in a #vars object. So, you can access variables with something like this:
<th:block th:text="${#vars.get('abc-def')}" />
Thymeleaf 3
As Metroids commented this all changes in Thymeleaf 3. It combines the #ctx and #vars objects, so you need to use the Context's getVariable method:
<th:block th:text="${#ctx.getVariable('abc-def')}" />
But this isn't the best plan
While certainly these will "work", having variables with punctuation in them is a bit unusual, and may confuse the next programmer to see your code. I wouldn't do it unless I had a really good reason to use that name.

Use Spring Bean in Camunda condition

I have a Camunda process which looks like this:
I use an Exclusive Gateway to make a branch in my workflow: When an email is already confirmed I just go on, if not I want to confirm it.
To implement this, I added the following condition to my Sequence Flows:
<!-- GATEWAY -->
<bpmn2:exclusiveGateway id="ExclusiveGateway_1" default="GO_TO_CONFIRM_EMAIL">
<bpmn2:incoming>SequenceFlow_4</bpmn2:incoming>
<bpmn2:outgoing>GO_TO_CONFIRM_EMAIL</bpmn2:outgoing>
<bpmn2:outgoing>GO_TO_IDENTIFY_ORDER</bpmn2:outgoing>
</bpmn2:exclusiveGateway>
<!-- DEFAULT FLOW -->
<bpmn2:sequenceFlow id="GO_TO_CONFIRM_EMAIL" name="!confirmed" sourceRef="ExclusiveGateway_1" targetRef="CONFIRM_EMAIL"/>
<!-- FLOW WITH CONDITION -->
<bpmn2:sequenceFlow id="GO_TO_IDENTIFY_ORDER" name="confirmed" sourceRef="ExclusiveGateway_1" targetRef="IDENTIFY_ORDER">
<bpmn2:conditionExpression xsi:type="bpmn2:tFormalExpression">${confirmEmailTaskAdapter.isConfirmed(CONFIRMED_ORDER_JSON)}</bpmn2:conditionExpression>
</bpmn2:sequenceFlow>
However, when I run this I get the following exception:
org.camunda.bpm.engine.ProcessEngineException: Cannot construct activity-execution mapping: there are more scope executions missing than explained by the flow scope hierarchy.
at org.camunda.bpm.engine.impl.pvm.runtime.LegacyBehavior.createActivityExecutionMapping(LegacyBehavior.java:294)
at org.camunda.bpm.engine.impl.pvm.runtime.PvmExecutionImpl.createActivityExecutionMapping(PvmExecutionImpl.java:1211)
at org.camunda.bpm.engine.impl.pvm.runtime.PvmExecutionImpl.createActivityExecutionMapping(PvmExecutionImpl.java:1144)
at org.camunda.bpm.engine.impl.bpmn.behavior.AbstractBpmnActivityBehavior$ErrorDeclarationFinder.collect(AbstractBpmnActivityBehavior.java:248)
at org.camunda.bpm.engine.impl.bpmn.behavior.AbstractBpmnActivityBehavior$ErrorDeclarationFinder.collect(AbstractBpmnActivityBehavior.java:223)
at org.camunda.bpm.engine.impl.tree.TreeWalker.walkUntil(TreeWalker.java:72)
at org.camunda.bpm.engine.impl.bpmn.behavior.AbstractBpmnActivityBehavior.propagateError(AbstractBpmnActivityBehavior.java:124)
at org.camunda.bpm.engine.impl.bpmn.behavior.AbstractBpmnActivityBehavior.propagateExceptionAsError(AbstractBpmnActivityBehavior.java:94)
at org.camunda.bpm.engine.impl.bpmn.behavior.ServiceTaskExpressionActivityBehavior.execute(ServiceTaskExpressionActivityBehavior.java:64)
at org.camunda.bpm.engine.impl.pvm.runtime.operation.PvmAtomicOperationActivityExecute.execute(PvmAtomicOperationActivityExecute.java:42)
at org.camunda.bpm.engine.impl.pvm.runtime.operation.PvmAtomicOperationActivityExecute.execute(PvmAtomicOperationActivityExecute.java:27)
at org.camunda.bpm.engine.impl.interceptor.CommandContext.performOperation(CommandContext.java:134)
at org.camunda.bpm.engine.impl.persistence.entity.ExecutionEntity.performOperationSync(ExecutionEntity.java:494)
at org.camunda.bpm.engine.impl.persistence.entity.ExecutionEntity.performOperation(ExecutionEntity.java:484)
at org.camunda.bpm.engine.impl.persistence.entity.ExecutionEntity.performOperation(ExecutionEntity.java:464)
... and some more ...
However, if I change the condition to something very simple, the process works:
<!-- THIS WORKS FINE -->
<bpmn2:conditionExpression xsi:type="bpmn2:tFormalExpression">${true}</bpmn2:conditionExpression>
My Spring Bean looks just normal:
#Component
public class ConfirmEmailTaskAdapter {
public boolean isConfirmed(String json) {
// bla bla bla
return true;
}
}
Why doesn't using my Spring Bean in the Camunda condition doesn't work?
Since it helped Thomas, I will post my above comment as an answer, though it is not a technical solution for the issue.
Not a solution but a best-practice that might help as a workaround: we do not use dynamic service calls for gateway decisions, only pre-evaluated process variables. So in your case, why not have a Listener or even a serviceTask determine the confirmed value, write it to the process variables and than just use ${isConfirmed} on the sequence flow ...

Implicit expression language object "component" not working in jsf 2.2.6

I'm migrating a jsf 2.0 application to jsf 2.2.6. There is a extensive use of implicit EL object component as styleClass="#{component.valid?'':'err'}".
In jsf 2.2.6 (jsf-impl-2.2.6-jbossorg-4.jar) valid is not recognized, throwing "ServletException: The class 'javax.faces.component.html.xxx' does not have the property 'valid".
Is this functionality deprecated in jsf 2.x.x?
Can be related to JBoss EL?
It seems that you trying the component.valid on element that does not support it at all, for example the h:panelGroup does not have the isValid getter , while h:inputText does.
A workaround could be to abuse the validation status of another element in your page in order to apply the styleClass of another, see example:
<h:panelGroup styleClass="#{myComponent.valid ? '' : 'error'}">
<h:inputText id="input" value="#{myBean.myValue}" binding="#{myComponent}">
</h:inputText>
</h:panelGroup >
Finally found the reason of the exception. The problem was that I had a comment in the code containing "component.valid". Removing the comment resolves the problem.
<!-- styleClass="#{component.valid ? '': 'err' }" -->
It's tricky. The exception was not clear about the line of code.

Spring, property file, empty values

I have configured spring security with a ldap server (but continue reading, it's not a problem if you have no knowledge about it, this is really a spring problem). All runs like a charm. Here is the line I use for that:
<ldap-server ldif="" root="" manager-dn="" manager-password="" url="" id="ldapServer" />
If I fill ldif and root attributes, it will run an embeded server:
<ldap-server ldif="classpath://ldap.ldif" root="dc=springframework,dc=org" manager-dn="" manager-password="" url="" id="ldapServer" />
If I fill other fields, it will run a distant server:
<ldap-server ldif="" root="" manager-dn="dc=admin,dc=springframeworg,dc=org" manager-password="password" url="ldap://myldapserver.com/dc=springframeworg,dc=org" id="ldapServer" />
All this stuff run correctly. Now I want to use Spring mechanism to load such parameters from a property file:
So I replace attribute values like this:
<ldap-server ldif="${ldap.ldif.path}" root="${ldap.ldif.root}" manager-dn="${ldap.server.manager.dn}" manager-password="${ldap.server.manager.password}" url="${ldap.server.url}" id="ldapServer" />
and create a property file with:
ldap.server.url=
ldap.server.manager.dn=
ldap.server.manager.password=
ldap.ldif.path=
ldap.ldif.root=
Now, the funny part of the problem. If I fill the following properties in the file:
ldap.server.url=ldap://myldapserver.com/dc=springframeworg,dc=org
ldap.server.manager.dn=dc=admin,dc=springframeworg,dc=org
ldap.server.manager.password=password
ldap.ldif.path=
ldap.ldif.root=
It runs a distant server as expected.
If I fill the property file like this:
ldap.server.url=
ldap.server.manager.dn=
ldap.server.manager.password=
ldap.ldif.path= classpath:ldap.ldif
ldap.ldif.root= dc=springframeworg,dc=org
It does not run, complaining that the ldap url is missing. But the problem is that if I change the spring configuration from:
<ldap-server ldif="${ldap.ldif.path}" root="${ldap.ldif.root}" manager-dn="${ldap.server.manager.dn}" manager-password="${ldap.server.manager.password}" url="${ldap.server.url}" id="ldapServer" />
to (by just removing the reference to the variable ${ldap.server.url})
<ldap-server ldif="${ldap.ldif.path}" root="${ldap.ldif.root}" manager-dn="${ldap.server.manager.dn}" manager-password="${ldap.server.manager.password}" url="" id="ldapServer" />
It runs !
My thoughs are that spring does not replace the attribute value with the property config one if this one is empty. But I find it strange.
Can you give me some clue to understand that ? And what's the best to do to configure my ldap server via a property file ?
EDIT: this is due to a poor design choice (look at accepted answer), an issue has been opened on jira :
https://jira.springsource.org/browse/SEC-1966
Ok, I think this is a spring security bug.
If I debug and look at the class LdapServerBeanDefinition, there is a method called "parse". Here is an extract:
public BeanDefinition parse(Element elt, ParserContext parserContext) {
String url = elt.getAttribute(ATT_URL);
RootBeanDefinition contextSource;
if (!StringUtils.hasText(url)) {
contextSource = createEmbeddedServer(elt, parserContext);
} else {
contextSource = new RootBeanDefinition();
contextSource.setBeanClassName(CONTEXT_SOURCE_CLASS);
contextSource.getConstructorArgumentValues().addIndexedArgumentValue(0, url);
}
contextSource.setSource(parserContext.extractSource(elt));
String managerDn = elt.getAttribute(ATT_PRINCIPAL);
String managerPassword = elt.getAttribute(ATT_PASSWORD);
if (StringUtils.hasText(managerDn)) {
if(!StringUtils.hasText(managerPassword)) {
parserContext.getReaderContext().error("You must specify the " + ATT_PASSWORD +
" if you supply a " + managerDn, elt);
}
contextSource.getPropertyValues().addPropertyValue("userDn", managerDn);
contextSource.getPropertyValues().addPropertyValue("password", managerPassword);
}
...
}
If I debug here, all variables (url, managerDn, managerPassword...) are not replaced by the value specified in the property file. And so, url has the value ${ldap.server.url}, managerDn has the value ${ldap.server.manager.dn} and so on.
The method parse creates a bean, a context source that will be used further. And when this bean will be used, place holders will be replaced.
Here, we got the bug. The parse method check if url is empty or not. The problem is that url is not empty here because it has the value ${ldap.server.url}. So, the parse method creates a context source as a distant server.
When the created source will be used, it will replace the ${ldap.server.url} by empty value (like specified in the property file). And....... Bug !
I don't know really how to solve this for the moment, but I now understand why it bugs ;)
I cannot explain it, but I think you can fix your problem using defaulting syntax, available since Spring 3.0.0.RC1 (see).
In the chageg log you can read: PropertyPlaceholderConfigurer supports "${myKey:myDefaultValue}" defaulting syntax
Anyway, I think that the problem is because "" is valid value, but no value in the property file don't.
I think that url="" works because url attribute is of type xs:token in spring-security XSD and empty string is converted to null (xs:token is removing any leading or trailing spaces, so "" can be recognized as no value). Maybe the value of ${ldap.server.url} is resolved as empty string and that is why you've got an error.
You can try use Spring profiles to define different configurations of ldap server (see Spring Team Blog for details about profiles)
I believe there is an issue here while using place holders. The following will most probably solve the problem:
Create a class which extends PropertyPlaceHolderConfigurer and override its method convertPropertyValue()
in the method you can return the property as empty string if you find anything other than a string which is of type LDAP url i.e. ldap://myldapserver.com/dc=springframeworg,dc=org
Also you need to configure your new specialization of class PropertyPlaceHolderConfigurer in the context file.
Hope this helps.
You can define empty String in the application.properties file as following:
com.core.estimation.stopwords=\ \

EL in <sec:authorize> access attribute

Is it not possible to use EL in access attribute? I dislike to hardcode the role names in the tag, instead would like to use a constant. But it is throwing an exception saying:
org.apache.jasper.JasperException: abc.jsp(19,4) According to TLD or
attribute direc tive in tag file, attribute access does not accept
any expressions
Here is what I have in jsp(using unstandard taglib for constants):
<%#taglib uri="http://jakarta.apache.org/taglibs/unstandard-1.0" prefix="un"%>
<un:useConstants className="com.xxx.PrivilegeConstants" var="privilege" />
.....
<sec:authorize access="hasRole('${privilege.USER_ROLE}')"> // throwing ex here
security content here....
</sec:authorize>
Is there any other alternative? Thanks in advance...
Looks like the support was added later on. See the JIRA here. Using the version which adds this support should work, I guess.

Resources