 | Also consider blockvalue scripting
The embedded element that this solution provides is an interesting alternative to conditionally display content based on the user's role. RIFE however provides a similar feature that only requires template changes and allows finer-grained checks. It does however add complexity to your templates in that it includes boolean expression scriptlets. Occurding to your requirements, you might need the solution explained on this page, or blockvalue scripting. |
See also: User identification facility
Displaying alternate content based on user privileges
It is frequently convenient to display different content on a page depending on the user's authentication status and privilege level. For example, if a user is logged in, a "Log In" link might be replaced by a "Log Out" link, and a user with administrative privileges might see additional controls to perform site maintenance.
It is possible, using the User identification facility, to implement that logic in Java code, and that is indeed the right solution when the logic is complex or there are large numbers of permutations of content. However, in many cases the content to display may be determined simply by the user's roles and logged-in status. For those cases, this embedded element will reduce the amount of logic required in element implementation classes.
First, make sure the page element is either in an authenticated subsite or an identified one. Otherwise the element code won't have any authentication information about the user. Please see User identification facility for more information on that.
With that out of the way, declare the element in a site XML file.
<element id="PC" implementation="com.foo.PrivilegedContent" />
Let's take a simple case first: displaying a "log in" or "log out" link in a navbar as appropriate. Define both links as blocks in the navbar HTML template. The blocks' names should be the same as the differentiator of the PrivilegedContent, one followed by a suffix:
<div class="navbar">
<a href="${v EXIT:QUERY:Home/}">My Site Dot Com</a>
...
<r:v name="ELEMENT:+PC:loginLink"/>
<r:b name="loginLink">
<a href="${v EXIT:QUERY:Login/}">Please Log In</a>
</r:b>
<r:b name="loginLink:AUTH">
<a href="${v EXIT:QUERY:Logout/">Log Out</a>
</r:b>
...
Note the "+" in the <r:v> element; that is required so that RIFE will properly evaluate any values that are embedded in the blocks.
The PrivilegedContent element checks to see if the user is logged in. If not, it looks for its differentiator as a block name in the embedding template. If the user is logged in, the string :AUTH is added to the differentiator, and that block is used instead.
If there is no block with the correct name, PrivilegedContent returns silently. This may be used for content that only appears for authorized users but has no alternate version for unauthorized ones. For example, to add a "My Account" link for logged-in users:
<r:v name="ELEMENT:+PC:myAccountLink"/>
<r:b name="myAccountLink:AUTH">
<a href="${v EXIT:QUERY:MyAccount/">My Account</a>
</r:b>
In addition to differentiating based on logged-in status, PrivilegedContent can also look at a user's roles. List the possible roles in the embedded value, like so:
<r:v name="ELEMENT:+PC:userLevel">roles=user,admin,developer</r:v>
<r:b name="userLevel"/>You are an anonymous user.</r:b>
<r:b name="userLevel:AUTH">You are logged in, but have no privileges.</r:b>
<r:b name="userLevel:user">You are a normal user.</r:b>
<r:b name="userLevel:admin">You are an administrator.</r:b>
<r:b name="userLevel:developer">You are a developer.</r:b>
If the user doesn't have any of the listed roles, the "AUTH" block will be used, if present.
That's all there is to it! Here is the source code to the element.
import java.util.Properties;
import com.uwyn.rife.authentication.credentialsmanagers.RoleUserIdentity;
import com.uwyn.rife.authentication.elements.Identified;
import com.uwyn.rife.engine.Element;
import com.uwyn.rife.template.Template;
/**
* Embedded element that displays different content depending on whether the
* user is logged in or not. The discriminator is used as the name of a block
* to output. If the user is logged in, ":AUTH" is added to the discriminator
* and a block by that name is printed instead.
* <p>
* <code><r:v name="ELEMENT:+PrivilegedContent:foobar"/><br/>
* <r:b name="foobar"><br/>
* (version of content for unauthenticated users)<br/>
* </r:b><br/>
* <r:b name="foobar:AUTH"><br/>
* (version of content for authenticated users)<br/>
* </r:b></code>
* <p>
* Note the "+" in front of the element name. That is critical -- otherwise
* template tags in your content may not be evaluated.
* <p>
* You may also specify a list of roles that vary the content. The first one
* that the user has determines which block is used. For example:
* <p>
* <code><r:v name="ELEMENT:+PrivilegedContent:foobar"><br/>
* roles=admin,user
* </r:v>
* <r:b name="foobar"><br/>
* (version of content for unauthenticated users)<br/>
* </r:b><br/>
* <r:b name="foobar:admin"><br/>
* (version of content for users with "admin" role)<br/>
* </r:b><br/>
* <r:b name="foobar:user"><br/>
* (version of content for users with "user" role)<br/>
* </r:b><br/>
* <r:b name="foobar:AUTH"><br/>
* (version of content for authenticated users who don't
* have the "admin" or "user" roles)<br/>
* </r:b></code>
* <p>
* If there isn't a block with the expected name, the element does nothing;
* there is no need to define an empty block if you don't want output in a
* particular case.
*/
public class PrivilegedContent extends Element {
public void processElement() {
String elementName = getEmbedDifferentiator();
Template tpl = getEmbeddingTemplate();
String blockName = elementName;
Properties props = getEmbedProperties();
if (null == elementName)
return;
if (isLoggedIn()) {
String roles = null;
if (null != props)
roles = props.getProperty("roles");
if (null == roles)
roles = "AUTH";
blockName = elementName + ":AUTH";
for (String role : roles.split(",")) {
if (isInRole(role)) {
blockName = elementName + ':' + role;
break;
}
}
}
if (tpl.hasBlock(blockName))
print(tpl.getBlock(blockName));
}
private boolean _haveIdentified = false;
private RoleUserIdentity _identity = null;
/**
* Loads authentication information about the current user.
*/
private void identify() {
if (! _haveIdentified) {
_identity = (RoleUserIdentity)getRequestAttribute(Identified.IDENTITY_ATTRIBUTE_NAME);
_haveIdentified = true;
}
}
/**
* Returns true if the user is logged in.
*/
protected boolean isLoggedIn() {
identify();
return _identity != null;
}
/**
* Returns true if the user is in a role.
*/
protected boolean isInRole(String roleName) {
identify();
return _identity.getAttributes().isInRole(roleName);
}
/**
* Returns the user's login name.
*/
protected String getLogin() {
identify();
if (_identity != null)
return _identity.getLogin();
return null;
}
}
Possible future enhancements
Adding hooks for more sophisticated logic to determine which variant to output, either by calling back into the embedding element class or some other class specified in a parameter.
Subclassing the appropriate RIFE authentication elements explicitly, so that we can get identity information even if the embedding element isn't in an authenticated or identified subsite.
Using this mechanism for localization support.
If you're using this approach to display some rows. Than you need to call [processEmbeddedElement|http://rifers.org/docs/api/com/uwyn/rife/engine/ElementSupport.html#processEmbeddedElement(com.uwyn.rife.template.Template, java.lang.String)|processEmbeddedElement] before appending block to value.
If you have more then one PC element on the page than you should call [http://rifers.org/docs/api/com/uwyn/rife/engine/ElementSupport.html#processEmbeddedElement(com.uwyn.rife.template.Template, java.lang.String, java.lang.String)]