Liferay control panel portlet with specific role permissions

Goal

Startup hook to configure a Liferay control panel custom portlet permissions

Description

This recipe is about a way to configure automatically, on deploy, with the help of a Liferay startup hook a portlet that one wants to be accessible in the control panel to only a specific role

How to

The recipe consists on the following steps (we assume the existence of a specific portlet that we will deploy on a Liferay 6.1+):

  1. Define the portlet to be accessible on control panel only
  2. Define a specific system role to which the portlet will be associated with
  3. Implement the startup hook that will configure the access permissions to the portlet
  4. Register the startup hook on the Liferay configurations properties file

1 – The first step is to register the portlet (we will show the full registration steps – portlet.xml, liferay-portlet.xml and liferay-display.xml). So, the first listing is the portlet.xml file, one of the first steps to register a portlet in any portal solution (notice the additional definition of the custom system role we will use for the specification of permissions for the portlet):

<?xml version="1.0"?>

<portlet-app xmlns="http://java.sun.com/xml/ns/portlet/portlet-app_2_0.xsd"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/portlet/portlet-app_2_0.xsd 
    http://java.sun.com/xml/ns/portlet/portlet-app_2_0.xsd"
    version="2.0">

  <portlet>
    <portlet-name>ImportDataPortlet</portlet-name>
    <display-name>O portlet de importação de dados no sistema</display-name>
    <portlet-class>javax.portlet.faces.GenericFacesPortlet</portlet-class>
    <init-param>
      <name>javax.portlet.faces.defaultViewId.view</name>
      <value>/views/upload/upload_modules.xhtml</value>
    </init-param>
    <expiration-cache>0</expiration-cache>
    <supports>
      <mime-type>text/html</mime-type>
    </supports>
    <resource-bundle>upload.Language</resource-bundle>
    <portlet-info>
      <title>Import system data</title>
      <short-title>ImportData</short-title>
      <keywords>ImportData</keywords>
    </portlet-info>
    <security-role-ref>
      <role-name>CUSTOM_ROLE</role-name>
    </security-role-ref>
  </portlet>
  ...
  <role-mapper>
    <role-name>CUSTOM_ROLE</role-name>
    <role-link>Custom Role</role-link>
  </role-mapper>
</portlet-app>

Followed by the liferay-portlet.xml file, which is a specific Liferay portlet descriptor:

<?xml version="1.0"?>
<!DOCTYPE liferay-portlet-app PUBLIC "-//Liferay//DTD Portlet Application 6.1.0//EN" 
"http://www.liferay.com/dtd/liferay-portlet-app_6_1_0.dtd">

<liferay-portlet-app>
  <portlet>
    <portlet-name>ImportDataPortlet</portlet-name>
    <icon>/upload.png</icon>
    <control-panel-entry-category>portal</control-panel-entry-category>
    <control-panel-entry-weight>50.0</control-panel-entry-weight>
    <action-url-redirect>true</action-url-redirect>
    <instanceable>false</instanceable>
    <ajaxable>false</ajaxable>
    <header-portlet-css>/css/main.css</header-portlet-css>
  </portlet>
  ...
</liferay-portlet-app>

And finally, the liferay-display.xml file which defines the category in which we will show the custom portlet. By using the key category.hidden, we will enforce the visualization of the previously defined portlet on control panel only (through hiding its visualization in the portlet addition panel in Liferay):

<?xml version="1.0"?>
<!DOCTYPE display PUBLIC "-//Liferay//DTD Display 6.1.0//EN" 
"http://www.liferay.com/dtd/liferay-display_6_1_0.dtd">

<display>
  <category name="category.hidden">
    <portlet id="ImportDataPortlet" />
  </category>
  ...
</display>

2 – To automate the solution, we will define the system role as a system property in portal-ext.properties file (this step is optional because we may create the system role through the Liferay GUI, if we prefer doing it that way):

...
system.roles=Custom Role
system.role.Custon.Role.description=A custom role to which I will associate my custom portlets

3 – Next, we present the implementation of the startup hook class that will configure the portlet’s permissions:

package com.linkare.myproject;

import java.util.List;

import com.liferay.portal.kernel.dao.orm.QueryUtil;
import com.liferay.portal.kernel.events.ActionException;
import com.liferay.portal.kernel.events.SimpleAction;
import com.liferay.portal.kernel.exception.PortalException;
import com.liferay.portal.kernel.exception.SystemException;
import com.liferay.portal.model.ResourceAction;
import com.liferay.portal.model.ResourceConstants;
import com.liferay.portal.model.ResourcePermission;
import com.liferay.portal.model.Role;
import com.liferay.portal.service.ResourceActionLocalServiceUtil;
import com.liferay.portal.service.ResourcePermissionLocalServiceUtil;
import com.liferay.portal.service.RoleLocalServiceUtil;

/**
 * 
 * @author Paulo Zenida - Linkare TI
 * 
 */
public class StartupHook extends SimpleAction {

  // The name of the permission that the portlet resource should have
  private static final String ACCESS_IN_CONTROL_PANEL_ACTION_ID = "ACCESS_IN_CONTROL_PANEL";

  // This is the name with which we registered the portlet. Notice, however, that the full name
  // will contain the extension _WAR followed by the specific war file name, such as _mywarfile
  private static final String IMPORT_DATA_PORTLET_ID = "ImportDataPortlet";

  @Override
  public void run(String[] ids) throws ActionException {
    final int[] scopes = new int[] { ResourceConstants.SCOPE_COMPANY, ResourceConstants.SCOPE_GROUP };
    for (final String id : ids) {
      try {
        final Long companyId = Long.valueOf(id);
        preparePortletPermissions(scopes, companyId);
      } catch (final Exception e) {
        e.printStackTrace();
      }
    }
  }

  private void preparePortletPermissions(final int[] scopes, final Long companyId) 
    throws SystemException, PortalException {
    Role role = RoleLocalServiceUtil.fetchRole(companyId, "Custom Role");
    if (role != null) {
      // go through the resource permissions associated to the role Custom Role
      final List<ResourcePermission> resourcePermissions = ResourcePermissionLocalServiceUtil.
            getRoleResourcePermissions(role.getRoleId(), scopes, QueryUtil.ALL_POS, QueryUtil.ALL_POS);

      boolean hasCustomPermission = false;
      for (ResourcePermission resourcePermission : resourcePermissions) {
        // the name of the portlet contains the extension _WAR and the name of the war. Therefore, use
        // startsWith instead of equals - there is a resource permission for the import data portlet
        // and Custom Role
        if (resourcePermission.getName().startsWith(IMPORT_DATA_PORTLET_ID)) {
          hasCustomPermission = true;
          // check if it is necessary to add the permission for the pair Custom Role and Import Data portlet
          createResourcePermissionIfNecessary(companyId, role, resourcePermission);
        }
      }
      // if we did not find any resource permission for the portlet import data, we need to create it
      if (!hasCustomPermission) {
        createResourcePermissionIfNecessary(companyId, role, IMPORT_DATA_PORTLET_ID, String.valueOf(companyId));
      }
    }
  }

  private void createResourcePermissionIfNecessary(
    final Long companyId, Role role, ResourcePermission resourcePermission) 
    throws SystemException, PortalException {
  
    final List<ResourceAction> resourceActions = ResourceActionLocalServiceUtil.getResourceActions(
                                                 resourcePermission.getName());

    for (ResourceAction resourceAction : resourceActions) {
      if (ACCESS_IN_CONTROL_PANEL_ACTION_ID.equals(resourceAction.getActionId())) {
        if (!ResourcePermissionLocalServiceUtil.hasActionId(resourcePermission, resourceAction)) {
          ResourcePermissionLocalServiceUtil.addResourcePermission(
                  companyId, resourcePermission.getName(), ResourceConstants.SCOPE_COMPANY,
                  resourcePermission.getPrimKey(), role.getRoleId(), resourceAction.getActionId());
        }
      }
    }
  }

  private void createResourcePermissionIfNecessary(
    final Long companyId, Role role, String permissionName, final String permissionPrimKey)
    throws SystemException, PortalException {
    final List<ResourceAction> resourceActions = ResourceActionLocalServiceUtil.getResourceActions(permissionName);

    for (ResourceAction resourceAction : resourceActions) {
      if (ACCESS_IN_CONTROL_PANEL_ACTION_ID.equals(resourceAction.getActionId())) {
        ResourcePermissionLocalServiceUtil.addResourcePermission(
                companyId, permissionName, ResourceConstants.SCOPE_COMPANY, permissionPrimKey,
                role.getRoleId(), resourceAction.getActionId());
      }
    }
  }
}

4 – And to terminate this recipe, all we still have to do is to register the startup hook. So, first, we define in the liferay-hook.xml file the name of the specific portal properties for the WAR file containing the custom portlet we developed:

<?xml version="1.0"?>
<!DOCTYPE hook PUBLIC "-//Liferay//DTD Hook 6.1.0//EN" "http://www.liferay.com/dtd/liferay-hook_6_1_0.dtd">

<hook>
  <portal-properties>myproject-portal.properties</portal-properties>
  ...
</hook>

And at last, we may define our startup hook, in our file myproject-portal.properties:

application.startup.events=com.linkare.myproject.StartupHook
...

Explanations

With this recipe, we are now allowed to register automatically the permissions that a control panel portlet should have.

Important: Notice that since we are using a custom system role, before deploying our WAR file, we should simply define the system role in our portal-ext.properties file (or create it manually in the Liferay GUI, if you prefer to do it that way) and check that, simply starting up Liferay will create that system role. After checking that the role exists, we may deploy our WAR file containing the startup hook and the result should be the creation of a resource permission allowing Custom Role role to access the Import data portlet in the control panel. So, if you create a specific non administrator user having the Custom Role role, she will be able to access the functionality provided by the portlet without any further manual configuration.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s