Security and Permissions

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):

  1. Define all resources and their permissions.

  2. Register all defined resources in the permissions system. This is also known as adding resources.

  3. Associate the necessary permissions with resources.

  4. 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 to true if you’re adding portlet action permissions. In our example, it’s false because we’re adding a model resource, which should be associated with permissions related to the model action defined in blogs.xml.
  • The addGroupPermissions and the addGuestPermissions parameters are inputs from the user. If set to true, 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:

  1. <liferay-security:permissionsURL>: Returns a URL to the permission settings configuration page.

  2. <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.jspffile.

<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 in Language.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 to 0. There are several ways you can obtain the groupId of the current scope:

    • JSP that uses the <theme:defineObjects/> tag: there’s an implicit variable called scopeGroupId.

    • Business logic class: If you’re using the ServiceContext pattern, you can obtain the groupId by using serviceContext.getScopeGroupId(). If you’re not using the ServiceContext pattern, your can obtain groupId 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 the groupId 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 called permissionChecker.

  • With Service Builder, every service implementation class can access the PermissionChecker instance by using the method getPermissionChecker().

  • 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:

  1. Define any custom resources and actions.

  2. Implement code to register (or add) any newly created resources, such as a BlogsEntry object.

  3. Provide an interface for the user to configure permission.

  4. 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.

« Introduction to Liferay FrameworksAsset Framework »
Este artigo foi útil?
Utilizadores que acharam útil: 0 de 0