Monday 23 May 2011

How the permissions system works in Liferay


Permissioning

Table of Contents [-+]

Introduction #

This article explains the inner details of how the permissions system works in Liferay. And it does so by examining each of the tables (and therefore the entities) and the relationships that are involved in the security logic. The following ERD diagram shows the relationships among all the entities that are going to be explained:

Basic entities #

Let's start by examining a few tables called "entity" tables. In other words, they define entities in the system that can have permissions/roles attached to them.

User_ #

The most obvious entity is the User. In the simplest terms, permissions is all about asking the question, "Does this user have permission to do this action on this thing?"

Organization_ #

Users can belong to only one Organization and one Location. The confusing thing about the data model is that we use the same table to represent both. Basically, if the parentOrganizationId column has a -1, then it's an Organization, and if it's any other number, it's a Location. The "only one Organization and one Location" rule is maintained through logic in our code...not through data integrity. The logic also enforces that the Organization that a User belongs to must be the parent of the Location that the user belongs to. Users inherit permissions/roles from the Organization and Location that they belong to.

UserGroups #

Users -> UserGroups (Collection of Users)
Users can inherit permissions/roles from UserGroups. In Liferay 4.4, parentUserGroupId column is not yet being used.

Group_ #

Group is the old name for what are now called Communities. Users can belong to any number of Communities and inherit permissions/roles from them. Notice that in the Group_ table, there is a className and classPK column. If className and classPK are blank, then the record is a Community. If className is com.liferay.portal.model.User, then the record represents a private user community (only applies to Power Users). If className is com.liferay.portal.model.Organization, then the record represents an Organization or Location. If className is com.liferay.portal.model.UserGroup, then the record represents a UserGroup.
The reason for having Group records representing Organizations/Locations and UserGroups is to have one entry in this table for each entity in the system that represents a set of users. This simplifies relationships of other entities (such as permissions or roles) with these sets of users.

Permissions and Roles #

A permission is defined as an ACTION acting on a RESOURCE. Permissions can be assigned directly to a User or inherited through different means. A collection of permissions is known as a Role.

Resource_ #

A resource is a representation of an object in the portal...anything that you'd want to slap a permission on. It could be a portlet, a community, a user, a message board topic, a blog entry...whatever. Let's take a quick look at each of the columns in the Resource_ table:
  • resourceId = The unique id of the resource
  • companyId = The company that this resource belongs to
  • name = The "name" of the object that this resource represents. If the object is a portlet, it's the portletId. If it's a class, it's the fully qualified package name of the class
  • typeId = For the time being, we're only concerned with permissioning classes, so this value will always be "class". However, in the future, we might start permissioning files or folders, so typeId might take on the values of "file" or "folder".
  • scope = The possible values are "company" (we refer to it as "Enterprise Scope"), "group" (we refer to it as "Community Scope"), or "individual" (we refer to it as "Individual Scope"). Why the different naming conventions? Because things change... The numeric values for scope (as found in the database) can be found in the class com.liferay.portal.model.impl.ResourceImpl

Permission_ #

As stated earlier, a permission is an action acting on a resource. Therefore, the Permission_ table has an actionId and resourceId column. As expected, the resourceId column references the resourceId column from the Resource_ table. However, the actionId column doesn't have a corresponding Action_ table. All actionIds are defined in the ActionKeys class. Wanna know how to define new actions for resources? Read Implementing Permissions

Role_ #

This is the table where a role is defined. Really, the only important column is the name column because roles must have unique names.

Roles_Permissions #

This is the relational table that links a permission to a role. Without these links, roles would be useless...they'd just be empty containers.

Assignment of users to communities and organizations #

Users_Groups #

This is the relational table that links a User to a Community. You're probably wondering why we don't have a className and classPK column in this table so we could handle all entities (e.g., Communites, Organization/Locations, UserGroups) in one table. Again, too hard to explain, but trust us...it's better this way.

Users_Orgs #

This is the relational table that links a User to an Organization/Location.

Users_UserGroups #

This is the relational table that links a User to a UserGroup.

Assignment of roles and permissions #

Users_Permissions #

This is the relational table that directly links a permission to a user.

Users_Roles #

This is the relational table that links a role to a user. The user then inherits all the permissions linked to that role (via Roles_Permissions).

Groups_Permissions #

This is the relational table that links a permission to a Group. Remember earlier in our discussion how we said a Group_ record could either represent a Community, Organization/Location, or UserGroup? Well, this is the table that directly links those permissions to each of these entities. And then of course, users that belong to these entities would inherit the permissions. Notice there is no Orgs_Permissions or UserGroups_Permissions tables. Groups_Permissions is enough to handle all cases. Maybe now it makes more sense why this is simpler.

Groups_Roles #

This is the relational table that links a role to a Group. Just like for Groups_Permissions, a Group could either refer to a Community, Organization/Location, or UserGroup. Users that belong to these "Groups" would then inherit the permissions from the corresponding roles.

Groups_Orgs #

This is the relational table that links Organizations/Locations to Communities. In other words, an admin can assign every User that is part of a particular Organization or Location to a Community. As a result, any permissions or roles assigned to that Community would now be inherited by every User in the selected Organization/Location

Groups_UserGroups #

Exactly the same reasoning as Groups_Orgs, except for UserGroups

OrgGroupPermission #

Here's the oddball of the bunch. This table is used for "Exclusive Permissions". Basically, a user has to belong to a particular Location (or Organization since Liferay v4.4) AND a particular Community to receive this permission. By the way, though the OrgGroupRole table exists, it is never used in our code.
Let's see why this option is useful with an example. Within a community there is a message boards, and the administrator wants to set up a category of the message boards so that only the users that belong to a given location can post to it. So he clicks the 'Permissions' icon for that category and selects the appropriate location. Now ALL users of the location will be able to post, regardless of whether they are members of the community or not.
In some circumstances, the administrator may want to restrict this further, saying that the user must also belong to the organization to post to that category. This is done by setting "Exclusive Permissions."

Using Liferay's Permission System from a portlet

Table of Contents [-+]

Liferay's permission system is a very flexible mechanism to define the actions a given user can perform within the general context of the portal, or within a given portlet and its data. Portal and portlet developers break down the operations which can be performed withing the portlet, and/or on the data they create, into distinct "actions". The act of granting the ability to perform a given action to an individual user, role, user group, etc. is of course the act of granting that permission. In this way, portal/portlet developers are not tied to named roles, user group, or any other authorization model in particular, all they need to care about is clearly defining the different types of operations required to suite the business logic of their applications.
Once the actions have been identified and configured, the system administrator is free to grant permissions to perform those actions to users, groups, communities, etc, either directly or through roles, using the portal's administration tools or specifically within portlet's configurations UIs.
The purpose of this article is to explain how a portlet developer can use this service during development to give the portal administrator the same level of control over permissions that she has over the bundled Liferay portlets.

Important concepts #

The main concepts behind Liferay's permission system are those of actions and resources. Examples of actions would be: VIEW, UPDATE, MOVE, etc. Examples of resources could be: RSS portlet hF5f, the message board post with id 5, an event of the community X, a wiki page of the portal. Note that these four examples each have a different degree of specificity. That's one of the reasons for the flexibility of the system and we'll get back to it in detail later.
Permissions in Liferay are defined as actions on a given resource. For example view the message board post with id 5, edit events of community X, etc. Permissions can be assigned directly to users, although it's recommended to do it through roles (which can be seen as a group of permissions), user groups, organizations or communities.
In order to use the permission system, a portlet developer must follow these steps:
  1. Define the types of resources of the portlet and the actions that can be performed on each. This is done in an XML file.
  2. If the portlet defines a domain model, create the resource (or resources) which can be controlled through the permission system. This is done through a API call. An example of this are Message Board Threads.
  3. Add permission checks whenever a user is performing a defined action. This is also done through a API call.
One important point related specifically to the portlet resource itself, is that, by default, and regardless of the actions specified by the developers, the portlet will implicitly have two actions defined on it. These are CONFIGURATION and VIEW.
:;CONFIGURATION : involves, at the least, performing maintenance operations that are provided through portal functionality such as adjusting the look-and-feel of the portlet, changing it's title, and assign permissions on the portlet. :;VIEW : involves two facets, the first is whether to allow a user, who has the ability to see and use the Add Content/Application/Portlet menu, to see a listing of the portlet, and secondly, if the portlet is already on a page/layout, whether that user can see the portlet or either a message indicating they don't have permission, or nothing at all.
In some cases it might be desirable to do some further steps to add more functionality. But we'll leave that for a later section.

Learning with a real world example #

Introducing the example: student scores management #

The example that we are going to use is that of a portlet that allows a teacher to record the scores of his students. We'll assume that the teacher and the students each have individual portal accounts already. The system will have two types of persistent entities: Test and Score (of a student in a test).

Step 1) Defining the types of resources #

The definition of the portlet resources is done in an XML file that should be placed in the classpath. Liferay's convention is to place it in the portal-impl/classes/resource-actions/ directory and give the file the 'semantic' name of the portlet (such as messageboards.xml). (In the ext environment it change portal-impl for ext-impl in the previous path).
We'll name the file studentscores.xml and create it with the following content:
  <resource-action-mapping>
    <portlet-resource>
        <portlet-name>studentscores</portlet-name>
        <supports>
            <action-key>ADD_TEST</action-key>
            <action-key>CONFIGURATION</action-key>
            <action-key>VIEW</action-key>
        </supports>
        <community-defaults>
            <action-key>VIEW</action-key>
        </community-defaults>
        <guest-defaults>
            <action-key>VIEW</action-key>
        </guest-defaults>
    </portlet-resource>
    <model-resource>
        <model-name>com.liferay.portlet.studentscores.model.SCTest</model-name>
        <portlet-ref>
            <portlet-name>studentscores</portlet-name>
        </portlet-ref>
        <supports>
            <action-key>ADD_SCORE</action-key>
            <action-key>DELETE</action-key>
            <action-key>UPDATE</action-key>
            <action-key>VIEW</action-key>
        </supports>
        <community-defaults />
        <guest-defaults />
    </model-resource>
    <model-resource>
        <model-name>com.liferay.portlet.studentscores.model.SCScore</model-name>
        <portlet-ref>
            <portlet-name>studentscores</portlet-name>
        </portlet-ref>
        <supports>
            <action-key>DELETE</action-key>
            <action-key>UPDATE</action-key>
            <action-key>VIEW</action-key>
        </supports>
        <community-defaults />
        <guest-defaults />
    </model-resource>
  </resource-action-mapping>
Let's review the file from beginning to end. The first thing to note is that it defines 3 resources.
The first one is a resource for the portlet itself. By defining it, the administrator will be able to configure which users can perform each operation on the portlet when it's placed in a page. This resource supports 3 actions: :;ADD_TEST: the user with the permission to perform this action is allowed to add a new test through the portlet. :;CONFIGURATION: the user with this permission can alter the look-and-feel, change the title, and assign permissions on the portlet. :;VIEW: the user with the permission to perform this action is allowed to view the contents of the portlet, and, if they are a layout manager (see definition bellow), see it in the Add Content/Application/Portlet menu.
The second resource represents a test created through the portlet. It supports the following actions: :;ADD_SCORE: Add the score of a student for this test :;DELETE: Delete the test :;UPDATE: Update the test :;VIEW: View the test
The score also has an associated type of resource with the following actions: :;DELETE: Delete the score :;UPDATE: Update the score :;VIEW: View the score
Once this file is finished we are ready to start the development part of our assignment.

Step 2) Create the resources that will be controlled by the permission system #

Whenever a new test or a new score is added through our portlet we need to inform the permission system of it so that it can create a set of resources associated with them. This is achieved through a call to the ResourceLocalService. This service can be invoked through the ResourceLocalServiceUtil class or by receiving an instance through dependency injection. In this example we'll assume the latter. So after our custom code has created a new test we invoke:
 resourceLocalService.addResources(
     test.getCompanyId(), test.getGroupId(), test.getUserId(),
     SCTest.class.getName(), test.getTestId(),
     false, addCommunityPermissions, addGuestPermissions);
There are several things to note here:
  1. The test class contains a companyId field. This is necessary to support Liferay's multi-instance functionality
  2. The test class contains a groupId field. This means that our tests will be scoped by group (that is by community or organization). If we had decided to have a unique set of tests for the whole portal instance we should have specified 0.
  3. The third parameter, userId, is the userId of the user that created the resource. The permission system will automatically give all permissions to this user.
  4. The fourth parameter, name identifies the type of resource and by convention it is recommended to use the class name that represents this type of entries.
  5. The fifth parameter, primKey, must be a unique id of the current entry
  6. The sixth parameter, portletActions, must be false because this is not an entity that represents a portlet. You will hardly ever need to set this parameter to true.
  7. The seventh and eight parameter determine if a set of permissions should be given to the owners of the group to which the test belongs and to anonymous users or not. That set of permissions is configured through the XML file. We left both sets empty so it doesn't make any difference to us. You can just give them a value of false.
It's interesting to understand what happens when this method is invoked. In short it will create several resources associated with the test we just created. Let's see why more than one resource and the purpose of each of them:
1) A resource with scope individual which represents one and only one SCTest (the one we just created). This resource will allow the administrator to set up permissions to operate on this specific SCTest. 2) A resource with scope group. This is done only once for the first SCTest created within this group. All others will share the same resource. This resource will allow the administrator to set a permission that applies to all SCTest within a given community or organization. Usually this is done through regular roles. 3) A resource with scope group_template. It's very similar to the previous but it's used slightly different by administrator. This resource allows a community or organization role to include a permission to do an action on all SCTest within a given community or organization. Which community or organization will be determined when the role is assigned to a user. 4) A resource with scope company. This is only done for the first template created within this company. This resource allows an administrator to set a permission that applies to all SCTest in the whole portal instance (called Enterprise in the permissions administration UI).
Whenever permissions are checked for a SCTest entity it checks first if it has permission in the associated individual scoped resource, then in the group scoped resource and then in the company scoped resource.
We are ready to test it. But first lets sure the system is always in good shape and does not contain resources of SCTest that do not exist any more. To achieve that when a SCTest is deleted we should also delete its associated resources. Actually we only have to delete the resource with scope individual since all other are shared with other tests. So we invoke:
 resourceLocalService.deleteResource(
     message.getCompanyId(), SCTest.class.getName(),
     ResourceImpl.SCOPE_INDIVIDUAL, test.getTestId());
For each score that we add or delete we should do the same thing that we've just shown for tests.

Step 3) Add permission checks whenever a user is performing a restricted operation #

Our third step will be to check whether a user has permission to do a certain action when he tries to do it. First we'll check whether the user has permission to add a new test.
The API to check permission is centralized in a class called PermissionChecker (for the curious, actually that's an interface, and the actual class provided by default to implement the interface is PermissionCheckerImpl). Liferay creates an instance of that class for every request and reuses it for all checks for permissions to ensure maximum eficiency. To obtain the current permission checker the easiest way is to use Liferay's taglib theme:defineObjects:
 <theme:defineObjects/>
After invoking this tag a JSP variable called permissionChecker will be available. If you don't want to use the tag you can access the permission checker using the following code (add the imports necessary to make it run):
 <%
  ThemeDisplay themeDisplay = (ThemeDisplay)request.getAttribute(WebKeys.THEME_DISPLAY);
  PermissionChecker permissionChecker = themeDisplay.getPermissionChecker();
%>
Once we have the PermissionChecker instance we can invoke its method contains to find out if the user has the desired permission:
  permissionChecker.hasPermission(
        layout.getGroupId(), "studentscores", testId, "ADD_SCORE");
As a result of this invocation our code should throw an Exception if the user is not allowed to do the requested action and the UI should catch that exception and show a message to the user. Liferay's portlets use PrincipalException for that task, so feel free to reuse it.
Important Note: by passing the groupId to permissionChecker we are implicitly telling the permission system that this action is performed within the context of the current group. Because of that an administrator of the current group will always have permission to perform that action even if it wasn't assigned explicitly. If you don't want that use 0 as the groupId. Note that in that case permissions to perform this action within an specific community will have no effect.
The same check should also be applied in the UI to show or hide the actions that are presented to a user based on his privileges. For example:
 <c:if test="<%= permissionChecker.hasPermission(layout.getGroupId(), "studentscores", testId, "ADD_SCORE");
%>">
    <a href="<%= addScoreURL %>" >Add score<a/>
</c:if>

Test it #

Once we've added the appropriate checks all around our portlets we are ready to test. The easiest way to do it is by creating custom roles that give different permissions within our portlets.
To do that go to the Enterprise Admin portlet and create one or more regular roles. For each role chosse the "Define permissions" action. Then click the "Add portlet action" and look for your portlet. When you click on it you'll see a form with all the actions that can be performed on each of the resources defined in the XML file above. (Note: for versions of Liferay Portal prior to 4.4 this screen is divided, you'll have to navigate using the links to configure each resource)
Note that for each action you can just leave the combo next to it blank (which means don't assign the permission), set it to enterprise (which means allow it for all resources within the portal) or set it to community (which means allow it for specific communities). For the latter option you'll have to choose one or more communities using the provided selection pop-up.
Once the role or roles have been defined assign it to a user and go to your portlet to test out the different permissions.

Getting more out of the permission system #

Utility classes to check permission #

The PermissionChecker API is pretty low level. Usually portlet developers develop classes around it to ease checking permissions. Liferay Portal comes with several classes available that can be used and others that can be used as an example to build your own. Here are some significant ones:
:;PortletPermissionUtil: useful for checking "portlet" level actions. In our case that would be VIEW, CONFIGURATION and ADD_TEST. Note that this class grants a user with UPDATE permission in the page where the portlet is, with the permission to do any action, by default. In order to prevent this in prior versions just don't use this class and use permissionChecker directly. Beginning with 4.4, this can be disabled by using the 'strict' parameter set to true; although, this does not prevent higher level layout managers (Community Administrators/Owners, like Power Users) from performing any action on the portlet. In order to do that you must explicitly indicate what actions these users can perform by adding a layout-manager element to the portlet resource configuration: e.g. This limits the layout manager to only VIEW action by default.
    <layout-manager>
        <action-key>VIEW</action-key>
    </layout-manager>
:: Of course further permissions can be granted to these users through explicit assignment. But remember, that by leaving this out, the layout manager will be able to perform ALL actions available on the portlet. :;PortalPermissionUtil: useful for checking "portal" level actions. This is useful when we have some actions that do not really belong to a given portlet nor want to be assigned for a specific community. An example would be a permission to 'Add Users' to the portal. :;BookmarksFolderPermission: this is a class created explicitly to check permissions to operate with folders of the Bookmarks portlet. You probably won't be using it (unless you are interacting with that portlet). But you may want to take a look at its code to create a similar one for your own data. In our example we could create a SCTestPermission class and a SCScorePermission class.

Default permissions #

Often we want our data entries to have certain default permissions when they are created. That can be achieved by specifying those actions in the XML file. Two sets can be specified, one for the default permissions for community members and another for guest users:
        <community-defaults>
            <action-key>ADD_FILE</action-key>
            <action-key>ADD_MESSAGE</action-key>
            <action-key>REPLY_TO_MESSAGE</action-key>
            <action-key>SUBSCRIBE</action-key>
            <action-key>VIEW</action-key>
        </community-defaults>
        <guest-defaults>
            <action-key>VIEW</action-key>
        </guest-defaults>
Note that for this defaults to be applied you have to give a value of true to the addCommunityPermissions and addGuestPermissions parameters when creating the associated resources:
 resourceLocalService.addResources(
     test.getCompanyId(), test.getGroupId(), test.getUserId(),
     SCTest.class.getName(), test.getTestId(),
     false, addCommunityPermissions, addGuestPermissions);

Disabling certain permissions for guest #

Some permissions that are considered dangerous can be disabled for guests. By doing this not even the administrator will be able to assign the permissions to users that are not logged in:
        <guest-unsupported>
            <action-key>ADD_FILE</action-key>
            <action-key>ADD_SUBCATEGORY</action-key>
            <action-key>SUBSCRIBE</action-key>
            <action-key>UPDATE</action-key>
            <action-key>UPDATE_THREAD_PRIORITY</action-key>
        </guest-unsupported>

Learn more #

http://www.liferay.com/web/guest/community/wiki/-/wiki/Main/Permissioning+Explained
Definitions : :;layout manager: Any user who has editorial rights to the current page/layout. These include Community Administrators, Community Owners, Power Users (only when in their private Community), or any user who has been granted or has inherited UPDATE permission on the current page/layout.

Overwrite Liferay Portlet Permissions

Table of Contents [-+]

There are times when you want to change the default and available permissions of Liferay portlets. The default permissions are found in the configuration screen -> permissions tab for each Liferay portlet and their respective modules. For example you can set the permissions for the Document Library Portlet at large, and also at the Folder and File level. This document will help you overwrite Liferay portlet permissions files without touching the source code.
Disclaimer: This document will only help you change what you see in the configuration screen and permissions tabs. This document will NOT implement permissions for you. If you want to implement custom permissions, you can use this document in conjunction with http://content.liferay.com/4.0.0/docs/developers/ch07.html
For a more up to date explanation of permissions read http://www.liferay.com/web/jshum/blog/-/blogs/permissionchecker-explained

Key Concept#

The permissions for all portlets are held inside /resource-actions/$portletname.xml files, so the goal would be to overwrite the $portletname.xml resource-actions file. Since there is a lot of mapping inside Liferay code, we need to include the files that map up to $portletname.xml.

Overwrite#

In the Source Code, there are 3 files and some related code lines to pay attention to:
  1. portal-ejb/classes/portal.properties
    • resource.actions.configs=resource-actions/default.xml
  2. portal-ejb/classes/resource-actions/default.xml
    • <resource-action-mapping>
    • <resource file="resource-actions/$portletname.xml" />
    • </resource-action-mapping>
  3. portal-ejb/classes/resource-actions/$portletname.xml
Notice how the files map to each other! In order to overwrite $portletname.xml we need to duplicate this structure and mapping in the EXT environment.
Create or modify these 3 files:
  1. ext-ejb/classes/portal-ext.properties
    • resource.actions.configs=resource-actions/default.xml,resource-actions/default-ext.xml
  2. ext-ejb/classes/resource-actions/default-ext.xml
    • <resource-action-mapping>
    • <resource file="resource-actions/$portletname.xml" />
    • </resource-action-mapping>
  3. ext-ejb/classes/resource-actions/$portletname.xml
Note: If you are not using an extension environment but modifying an existing installation of Liferay just create those files and put them in the application classpath.

19 comments:

  1. Thanks for the explanation.

    ReplyDelete
    Replies
    1. Liferay Tech Support: How The Permissions System Works In Liferay >>>>> Download Now

      >>>>> Download Full

      Liferay Tech Support: How The Permissions System Works In Liferay >>>>> Download LINK

      >>>>> Download Now

      Liferay Tech Support: How The Permissions System Works In Liferay >>>>> Download Full

      >>>>> Download LINK yk

      Delete
  2. very useful, thank you

    ReplyDelete
  3. Hi,

    Thanks for detailed explanation !!!

    Regards
    Nilang

    ReplyDelete
  4. HY IF YOU HAVE PROBLEM REGARDING DELL LAPTOP , DESKTOP YOU CAN CALL OUR TOLL FREE NUMBER : +18007234210

    ReplyDelete
  5. c@LL M..E FOR dELL TECHNICAL SUPPORT USA AND CANDA +1800-723-4210

    ReplyDelete
  6. Hi Satish,
    I am Rashmi here. I have created new site under one organization, assigned the users to it. Is there a way to restrict the same site should not be available to admin of other organization ? I tried to create private/restricted site under org but its still appearing for the org2. Is there a way to restrict the site1 should not be available to Org2.

    Second thing , is there a way to restrict the users existing under org1 should not be available to org2.

    ReplyDelete
  7. This comment has been removed by the author.

    ReplyDelete
  8. Thank you for this detailed explanation!
    I would like to ask you if everything still aplies to Liferay 7, given that the post date is from 2011.
    Thank you.

    ReplyDelete
  9. • Nice and good article. It is very useful for me to learn and understand easily. Thanks for sharing your valuable information and time. Please keep updatingAzure Online course

    ReplyDelete
  10. You briefly describe here about system work liferay and those who never know about it really well so after reading your blog they can get knowledge related to it.
    assignment help UK

    ReplyDelete
  11. This comment has been removed by the author.

    ReplyDelete
  12. Great post!I am actually getting ready to across this information,i am very happy to this commands.Also great blog here with all of the valuable information you have.Well done,its a great knowledge

    Azure Training in Chennai

    Azure Training in Bangalore

    Azure Training in Hyderabad

    Azure Training in Pune

    Azure Training | microsoft azure certification | Azure Online Training Course

    Azure Online Training


    ReplyDelete
  13. Liferay Tech Support: How The Permissions System Works In Liferay >>>>> Download Now

    >>>>> Download Full

    Liferay Tech Support: How The Permissions System Works In Liferay >>>>> Download LINK

    >>>>> Download Now

    Liferay Tech Support: How The Permissions System Works In Liferay >>>>> Download Full

    >>>>> Download LINK 8z

    ReplyDelete
  14. This software will make your dream come true while editing video files on your system. This software is the best alternative to other local software available .https://crackdj.com/edius-pro-crack/

    ReplyDelete
  15. Merry Christmas To My Sweetheart. You brighten my days, you always make me laugh, and you add so much joy to my life. You truly are ..
    Christmas Card for Husband/a>

    ReplyDelete