The Java portlet standard defines a simple security scheme using portlet roles and their mapping to portal roles. On top of that, Liferay provides a fine-grained permissions system you can use to implement access security in your custom portlets. Here we’ll give an overview of the standard Java security system, Liferay’s permission system, and how to use them in your own portlets.
JSR Portlet Security
The JSR specification defines a means to specify roles used by portlets in their
portlet.xml
definitions. The role names themselves, however, are not
standardized, so when these portlets run in Liferay, you’ll recognize familiar
role names. For example, the Liferay Blogs portlet definition references the
guest, power-user, and user roles:
<portlet>
<portlet-name>33</portlet-name>
<display-name>Blogs</display-name>
<portlet-class>com.liferay.portlet.StrutsPortlet</portlet-class>
<init-param>
<name>view-action</name>
<value>/blogs/view</value>
</init-param>
<init-param>
<name>config-jsp</name>
<value>/html/portlet/blogs/configuration.jsp</value>
</init-param>
<expiration-cache>0</expiration-cache>
<supports>
<mime-type>text/html</mime-type>
</supports>
<resource-bundle>com.liferay.portlet.StrutsResourceBundle</resource-bundle>
<security-role-ref>
<role-name>guest</role-name>
</security-role-ref>
<security-role-ref>
<role-name>power-user</role-name>
</security-role-ref>
<security-role-ref>
<role-name>user</role-name>
</security-role-ref>
<supported-public-render-parameter>categoryId</supported-public-render-parameter>
<supported-public-render-parameter>tag</supported-public-render-parameter>
</portlet>
Your portlet.xml
roles need to be mapped to specific roles in the portal.
That way the portal can resolve conflicts between roles with the same name that
are from different portlets (e.g. portlets from different developers).
To map the roles to the portal, you’ll have to use a Liferay-specific
configuration file called liferay-portlet.xml
. For an example, see the mapping
defined inside liferay-portlet.xml
found in portal-web/docroot/WEB-INF
:
<role-mapper>
<role-name>administrator</role-name>
<role-link>Administrator</role-link>
</role-mapper>
<role-mapper>
<role-name>guest</role-name>
<role-link>Guest</role-link>
</role-mapper>
<role-mapper>
<role-name>power-user</role-name>
<role-link>Power User</role-link>
</role-mapper>
<role-mapper>
<role-name>user</role-name>
<role-link>User</role-link>
</role-mapper>
If a portlet definition references the role power-user
, that portlet is
mapped to the Liferay role called Power User that’s already in its database.
Once roles are mapped to the portal, you can use methods as defined in portlet specification:
getRemoteUser()
isUserInRole()
getUserPrincipal()
For example, you can use the following code to check if the current user has
the power-user
role:
if (renderRequest.isUserInRole("power-user")) {
// ...
}
By default, Liferay doesn’t use the isUserInRole()
method in any built-in
portlets. Liferay uses its own permission system directly to achieve more
fine-grained security. Next we’ll describe Liferay’s permission system and
how to use it in your portlets. We recommend using Liferay’s permission system,
because it offers a much more robust way of tailoring your application’s
permissions.
Liferay’s Permission System
You can add permissions to your custom portlets using four easy steps (also known as DRAC):
-
Define all resources and their permissions.
-
Register all defined resources in the permissions system. This is also known as adding resources.
-
Associate the necessary permissions with resources.
-
Check permission before returning resources.
Before you start adding permissions to a portlet, make sure you understand these two critical terms used throughout this section:
- Resource: A generic term for any object represented in the portal. Examples of resources include portlets (e.g. Message Boards, Calendar, etc.), Java classes (e.g. Message Board Topics, Calendar Events, etc.), and files (e.g. documents, images, etc.).
- Permission: An action on a resource. For example, the view action with respect to viewing the calendar portlet is defined as a permission in Liferay.
It’s important to know that permissions for portlet resources are implemented a little differently than for other resources like Java classes and files. Below, we’ll describe permission implementation for the portlet resource first, followed by the model resource.
The first step is to define your resources and the actions that can be defined
on them. Let’s use the Blogs portlet to demonstrate. Open the blogs.xml
file in
portal-impl/src/resource-actions
and you’ll see the following mapping of
resources to actions:
<?xml version="1.0"?>
<!DOCTYPE resource-action-mapping PUBLIC "-//Liferay//DTD Resource Action Mapping 6.1.0//EN" "http://www.liferay.com/dtd/liferay-resource-action-mapping_6_1_0.dtd">
<resource-action-mapping>
<portlet-resource>
<portlet-name>33</portlet-name>
<permissions>
<supports>
<action-key>ADD_TO_PAGE</action-key>
<action-key>CONFIGURATION</action-key>
<action-key>VIEW</action-key>
</supports>
<site-member-defaults>
<action-key>VIEW</action-key>
</site-member-defaults>
<guest-defaults>
<action-key>VIEW</action-key>
</guest-defaults>
<guest-unsupported>
<action-key>CONFIGURATION</action-key>
</guest-unsupported>
</permissions>
</portlet-resource>
<portlet-resource>
<portlet-name>161</portlet-name>
<permissions>
<supports>
<action-key>ACCESS_IN_CONTROL_PANEL</action-key>
<action-key>CONFIGURATION</action-key>
<action-key>VIEW</action-key>
</supports>
<site-member-defaults>
<action-key>VIEW</action-key>
</site-member-defaults>
<guest-defaults>
<action-key>VIEW</action-key>
</guest-defaults>
<guest-unsupported>
<action-key>ACCESS_IN_CONTROL_PANEL</action-key>
<action-key>CONFIGURATION</action-key>
</guest-unsupported>
</permissions>
</portlet-resource>
<model-resource>
<model-name>com.liferay.portlet.blogs</model-name>
<portlet-ref>
<portlet-name>33</portlet-name>
</portlet-ref>
<permissions>
<supports>
<action-key>ADD_ENTRY</action-key>
<action-key>PERMISSIONS</action-key>
<action-key>SUBSCRIBE</action-key>
</supports>
<site-member-defaults />
<guest-defaults />
<guest-unsupported>
<action-key>ADD_ENTRY</action-key>
<action-key>PERMISSIONS</action-key>
<action-key>SUBSCRIBE</action-key>
</guest-unsupported>
</permissions>
</model-resource>
<model-resource>
<model-name>com.liferay.portlet.blogs.model.BlogsEntry</model-name>
<portlet-ref>
<portlet-name>33</portlet-name>
</portlet-ref>
<permissions>
<supports>
<action-key>ADD_DISCUSSION</action-key>
<action-key>DELETE</action-key>
<action-key>DELETE_DISCUSSION</action-key>
<action-key>PERMISSIONS</action-key>
<action-key>UPDATE</action-key>
<action-key>UPDATE_DISCUSSION</action-key>
<action-key>VIEW</action-key>
</supports>
<site-member-defaults>
<action-key>ADD_DISCUSSION</action-key>
<action-key>VIEW</action-key>
</site-member-defaults>
<guest-defaults>
<action-key>ADD_DISCUSSION</action-key>
<action-key>VIEW</action-key>
</guest-defaults>
<guest-unsupported>
<action-key>DELETE</action-key>
<action-key>DELETE_DISCUSSION</action-key>
<action-key>PERMISSIONS</action-key>
<action-key>UPDATE</action-key>
<action-key>UPDATE_DISCUSSION</action-key>
</guest-unsupported>
</permissions>
...
</model-resource>
</resource-action-mapping>
Permissions in the blogs portlet are defined at two different levels: the
portlet level and the model level. The portlet level defines permissions on the
portlet as a whole. The model level defines permissions on the model layer of
the application, as defined by the entities in the application. Each level
coincides with a section of the resource-actions
XML file (in this case,
blogs.xml
).
At the <portlet-resource>
level, actions and default permissions
are defined on the portlet itself. Changes to portlet level permissions are
performed on a per-site basis, and define whether users can add the portlet to a
page, edit the portlet’s configuration, or view the portlet. All these actions
are defined in the <supports>
tag for the portlet resource’s permissions. The
default portlet-level permissions for members of the site are defined in the
<site-member-defaults>
tag. In the case of the Blogs portlet, site members can
view any blog in the site. Similarly, default guest permissions are defined in
the <guest-defaults>
tag. The <guest-unsupported>
tag contains permissions
forbidden to guests. Here, guests can’t be given permission to configure the
portlet.
The <model-resource>
section contains the next level of permissions, based on
the scope of an individual instance of the portlet. A scope in Liferay
refers to how widely the data from an instance of a portlet is shared. For
example, if you place a Blogs portlet on a page in the guest site and place
another Blogs portlet on another page in the same site, the two blogs share the
same set of posts. That happens because portlets are given a site level scope by
default. If you reconfigure one of the two Blogs and change its scope to be the
current page, that Blogs portlet instance no longer shares content with the
other instance (or any other Blogs instance in that site). A portlet instance’s
scope-based permissions can thus span an entire site or be restricted to a
single page. If you set the scope to the page, it’s possible to have multiple
distinct Blog instances within a site, each with different permissions for site
users. For example, a food site could have one blog open to posts from any site
member, but also have a separate informational blog about the site itself
restricted to posts from administrators.
After defining the portlet and portlet instance as resources, we need to define
permissions on the models in the portlet. The model resource is surrounded by
the <model-resource>
tag. Inside the tag, we first define the model name; the
<model-name>
isn’t the name of a Java class, but the fully qualified name of
the portlet’s package (e.g. the blog portlet’s package
com.liferay.portlet.blogs
). This is the recommended convention for permissions
that refer to an instance of the portlet as a whole. Permissions like the
ability to add or subscribe to a blog entry are defined here, and affect the
portlet at the instance level. The <portlet-ref>
element comes next and
contains a <portlet-name>
. The value of <portlet-name>
references the name
of the portlet to which the model resource belongs. Theoretically, a model
resource can belong to multiple portlets referenced with multiple
<portlet-name>
elements, but this is uncommon. Like the portlet resource, the
model resource lets you define a list of supported actions that require
permission to perform. You must list all the performable actions that require a
permission check. For a blog entry, users must belong to appropriate roles for
permission to do the following:
- Add comments to an entry
- Delete an entry
- Change the permission setting of an entry
- Update an entry
- View an entry
As with a portlet resource, the <site-member-defaults>
tag, <guest-defaults>
tag, and <guest-unsupported>
tag define default permissions for site members
and guests, respectively, for model resources.
After defining resource permissions for your custom portlet, you need to refer
Liferay to the resource-actions
XML file that contains your definitions (e.g.
blogs.xml
for the Blogs portlet). For Liferay core, the resource-actions
XML files are in the portal/portal-impl/src/resource-actions
directory
and the file named default.xml
file refers to each of these files. This
excerpt from default.xml
references the resource permission definition files
for all built-in Liferay portlets (including the blogs portlet):
<?xml version="1.0"?>
<!DOCTYPE resource-action-mapping PUBLIC "-//Liferay//DTD Resource Action Mapping 6.1.0//EN" "http://www.liferay.com/dtd/liferay-resource-action-mapping_6_1_0.dtd">
<resource-action-mapping>
<resource file="resource-actions/portal.xml" />
<resource file="resource-actions/announcements.xml" />
<resource file="resource-actions/asset.xml" />
<resource file="resource-actions/blogs.xml" />
...
</resource-action-mapping>
You should put your plugin’s resource-actions
XML files (e.g. default.xml
and blogs.xml
) in a directory in your project’s classpath. Then create a
properties file (typically named portlet.properties
) for your portlet that
references the the file that specifies your <resource-action-mapping>
element
(e.g. default.xml
). In this portlet properties file, create a property
named resource.actions.configs
with the relative path to your portlet’s
resource-action mapping file (e.g. default.xml
) as its value. Here’s what
this property specification might look like:
resource.actions.configs=resource-actions/default.xml
Check out a copy of the Liferay source code from the Liferay Github repository
to see an example of a portlet that defines its resources and permissions as we
just described; start by looking at the definition files found in the
portal-impl/src/resource-actions
directory. For an example of defining
permissions in the context of a portlet plugin, check out plugins/trunk
and
look at the portlet sample-permissions-portlet
.
Next we’ll show you how to add resources.
Adding a Resource
After defining resources and actions, it’s time to add resources into the
permissions system. Resources are added at the same time entities are added to
the database. Managing resources follows the same pattern you’ve seen throughout
Liferay: there’s a service to use. Adding resources is as easy as calling the
addResources(...)
method of the ResourceLocalServiceUtil
class. Here’s the
signature of that method:
public void addResources(
long companyId, long groupId, long userId, String name,
String primKey, boolean portletActions,
boolean addGroupPermissions, boolean addGuestPermissions)
Each Entity that requires access permission must be added as a resource
every time it is stored. For example, every time a user adds a new
entry to her blog, the addResources(...)
method must be called to add the
new entry object to the resource system. Here’s an example of the call from the
BlogsEntryLocalServiceImpl
class:
resourceLocalService.addResources(
entry.getCompanyId(), entry.getGroupId(), entry.getUserId(),
BlogsEntry.class.getName(), entry.getEntryId(), false,
addGroupPermissions, addGuestPermissions);
In the addResources(...)
method, the parameters companyId
, groupId
, and
userId
correspond to the portal instance, the site in which the entity is
being saved, and the user who is saving it. Let’s look more closely at the
remaining parameters:
- The
name
parameter is the fully qualified Java class name for the entity being added. - The
primKey
parameter is the primary key of the entity. - The
portletActions
parameter should be set totrue
if you’re adding portlet action permissions. In our example, it’sfalse
because we’re adding a model resource, which should be associated with permissions related to the model action defined inblogs.xml
. - The
addGroupPermissions
and theaddGuestPermissions
parameters are inputs from the user. If set totrue
,ResourceLocalService
adds the default permissions to the current group and the guest group for this resource, respectively.
You can let your users choose whether to add the default site permission and/or
the guest permission for your custom portlet resources: Liferay has a custom
JSP tag <liferay-ui:input-permissions />
that you can use to quickly add that
functionality. You just insert the tag into the appropriate JSP and the
checkboxes appear on that page. Make sure that the tag is inside the
appropriate <form>
tags, and that’s all there is to it.
When you remove entities from the database, it’s good practice to remove
permissions mapped directly to the entity. To prevent dead resources from
taking up space in the Resource_
database table, remember to remove them from
the Resource_
table when the resource no longer applies. Do this by calling
the deleteResource(...)
method of ResourceLocalServiceService
. Here’s an
example of a blogs entry being removed:
resourceLocalService.deleteResource(
entry.getCompanyId(), BlogsEntry.class.getName(),
ResourceConstants.SCOPE_INDIVIDUAL, entry.getEntryId());
Great! Now that you know how to work with resource permissions, we’ll show you how to provide a user interface for managing resource permissions.
Adding Permission
On the portlet level, no code needs to be written in order to have the
permission system work for your custom portlet. If you’ve defined any custom
permissions (supported actions) in your configuration file’s portlet-resource
tag, they’re automatically added to a list of permissions in Liferay’s
permissions UI. What good, however, are permissions that are available but can’t
be set by users?
To let a user set permissions on model resources, you must expose the permission interface to the user. Just add these two Liferay UI tags to your JSP:
-
<liferay-security:permissionsURL>
: Returns a URL to the permission settings configuration page. -
<liferay-ui:icon>
: Shows an icon to the user. These are defined in the theme, and one of them (see below) is used for permissions.
This example demonstrates the use of both tags; it comes from the
view_entry_content.jspf
file.
<liferay-security:permissionsURL
modelResource="<%= BlogsEntry.class.getName() %>"
modelResourceDescription="<%= entry.getTitle() %>"
resourcePrimKey="<%= entry.getPrimaryKey().toString() %>"
var="entryURL"
/>
<liferay-ui:icon image="permissions" url="<%= entryURL %>" />
For the first tag, specify the following attributes:
modelResource
: The fully qualified Java object class name. This class name gets translated into its more readable name as specified inLanguage.properties
.modelResourceDescription
: You can pass in anything that best describes this model instance. In this example, the blogs title was passed in.resourcePrimKey
: The primary key of your model instance.var
: Specifies the name of the variable to be assigned the resulting URL String. The variable is then passed to the<liferay-ui:icon>
tag so the permission icon has the proper URL link.
There’s an optional attribute called redirect
that’s available if you want to
override the default behavior of the upper right arrow link. That’s it; now your
users can configure the permission settings for model resources.
Next we’ll show you how to implement permissions checking.
Checking Permissions
The last major step toward implementing permissions for your custom portlet is to ensure the configured permissions are enforced. You’ll do this by adding permission checks to your application. For example, your business layer can check for permission before deleting a resource, or your user interface can hide a button that adds an entity (e.g. a calendar event) if the user doesn’t have permission.
You need to implement checking of any custom permissions you defined in your
resource-actions
XML file. In the Blogs portlet, one supported custom action
is ADD_ENTRY
. There are two places in the source code to check for this
permission: in your JSP files and in the business logic. For the JSP files, you
want to wrap certain elements in permission checks, so they only appear for
users with the permission to perform those functions. For example, the presence
of the Add Entry button is contingent on whether the user has permission to add
a blog entry. Here’s the ADD_ENTRY
action in a JSP file:
<%
if (permissionChecker.hasPermission(
scopeGroupId, "com.liferay.portlet.blogs.model",
scopeGroupId, "ADD_ENTRY") {
// Show add entry button
}
%>
The second place to check for the add entry permission is in the business
logic. If the check fails, a PrincipalException
is thrown and the add entry request is aborted:
if (!permissionChecker.hasPermission(
scopeGroupId, "com.liferay.portlet.blogs.model",
scopeGroupId, "ADD_ENTRY")) {
throw new PrincipalException();
}
blogsEntryLocalService.addEntry(...);
The PermissionChecker
class has a method hasPermission(...)
that checks
whether a user making a resource request has the necessary access permission.
If the user isn’t signed in (guest user), it checks for guest permissions.
Otherwise, it checks for user permissions. Let’s quickly review the parameters
of this method:
-
groupId
: Represents the scope where the permission check is performed. In Liferay, many scopes are available, including a specific site, organization, personal site of a user, or a page in a site. This is important because a user may be allowed to add blog entries in one site, but not in another. For resources that don’t belong to a scope (extremely rare and unlikely), set the value of this parameter to0
. There are several ways you can obtain thegroupId
of the current scope:-
JSP that uses the
<theme:defineObjects/>
tag: there’s an implicit variable calledscopeGroupId
. -
Business logic class: If you’re using the ServiceContext pattern, you can obtain the
groupId
by usingserviceContext.getScopeGroupId()
. If you’re not using theServiceContext
pattern, your can obtaingroupId
from the theme display request object:ThemeDisplay themeDisplay = (ThemeDisplay)request.getAttribute( WebKeys.THEME_DISPLAY); long scopeGroupId = themeDisplay.getScopeGroupId();
-
-
name
: The name of the resource as specified in the XML file of the previous sections. -
primKey
: The primary key of the resource. In this example the resource doesn’t exist as an entry in the database, so we use thegroupId
again. If we were checking for a permission on a given blog entry, we’d use the primary key of that blog entry instead. -
actionId
: The name of the action as it appears in the XML file. To simplify searching for usages, consider creating a helper class that has constants for all the actions defined.
In the examples above, we’re assuming there’s a variable called
permissionChecker
already available. Liferay automatically creates a
PermissionChecker
instance that has the necessary information from the user
for every request. Liferay also caches the security checks to ensure good
performance. There are several ways to obtain a permission checker:
-
In a JSP that uses the
<theme:defineObjects/>
tag, there’s an implicit variable calledpermissionChecker
. -
With Service Builder, every service implementation class can access the
PermissionChecker
instance by using the methodgetPermissionChecker()
. -
If you’re not using Service Builder,
PermissionChecker
can be obtained from the theme display request object:ThemeDisplay themeDisplay = (ThemeDisplay) request.getAttribute(WebKeys.THEME_DISPLAY); PermissionChecker permissionChecker = themeDisplay.getPermissionChecker();
Next you’ll optimize permission checking by creating helper classes to do most of the heavy lifting.
Creating Helper Classes for Permission Checking
Helper classes streamline your code. They encapsulate the use of
permissionChecker
and the names of the resources for a specific portlet. This
is especially useful when there are complex parent-child relationships, or if
your permission logic calls for checking multiple action types.
BlogsPermission
is an example of a permission helper class. Here’s how
BlogsPermission
is used in a JSP:
<%
if (BlogsPermission.contains(permissionChecker, scopeGroupId,
ActionKeys.ADD_ENTRY)) {
// show add entry button
}
%>
Now let’s see how a ServiceImpl
class BlogsEntryServiceImpl
uses the
BlogsPermission
helper class. In the method
BlogsEntryServiceImpl.addEntry(...)
, a call is made to check whether the
incoming request has permission to add an entry. The check is done using the
helper class BlogsPermission
. If the check fails, it throws a
PrincipalException
and the add entry request aborts.
public BlogsEntry addEntry(
String title, String description, String content,
int displayDateMonth, int displayDateDay, int displayDateYear,
int displayDateHour, int displayDateMinute, boolean allowPingbacks,
boolean allowTrackbacks, String[] trackbacks, boolean smallImage,
String smallImageURL, String smallImageFileName,
InputStream smallImageInputStream, ServiceContext serviceContext)
throws PortalException, SystemException {
BlogsPermission.check(
getPermissionChecker(), serviceContext.getScopeGroupId(),
ActionKeys.ADD_ENTRY);
return blogsEntryLocalService.addEntry(
getUserId(), title, description, content, displayDateMonth,
displayDateDay, displayDateYear, displayDateHour, displayDateMinute,
allowPingbacks, allowTrackbacks, trackbacks, smallImage,
smallImageURL, smallImageFileName, smallImageInputStream,
serviceContext);
}
Check out the parameters passed into the check(...)
method. Again, the
getPermissionChecker()
method is readily available in all ServiceImpl
classes. The blogs entry ID is available in the serviceContext
, indicating
that the permission check is against the Blogs portlet. ActionKeys.ADD_ENTRY
is a static string used to indicate the action requiring the permission check.
Likewise, you’re encouraged to use custom portlet action keys.
Let’s review what we’ve just covered. Implementing permission into your custom portlet consists of four main steps:
-
Define any custom resources and actions.
-
Implement code to register (or add) any newly created resources, such as a
BlogsEntry
object. -
Provide an interface for the user to configure permission.
-
Implement code to check permission before returning resources or showing custom features.
The two major resources are portlets and entities. Liferay does most of the work for you by providing a configuration file and a system of resources that let you check permissions wherever you need to in your application.
You’re now equipped to implement security in your custom Liferay portlets!
Next, let’s learn how to use the Asset Framework.