+1-888-365-2779
Try Now
More in this section
Categories
Bloggers
Blogs RSS feed

Products module: Implementing permissions

by Ivan Osmak
[This post is part of the developer's manual preview published on this blog. You can find temporary TOC here.]

 

In case you are building module where different users will have different level of permissions for working with the module, you can take advantage of the Generic Content security API. Generally speaking, there are four steps you will need to perform in order to enable permissions on your module.
  • Make your module class inherit from SecuredModule, as opposed to WebModule base class
  • Implement security members on your manager class
  • Override GetPermission methods in PermissionsView to use the methods you have implemented on your manager (step 2)
  • Override GetPermissions methods on any View that may need to check or demand permissions so that methods from your manager class are used (step 2) as opposed to methods defined in Generic Content modules
The basic difference in the functionality of the WebModule and SecuredModule can be understood from the following diagram.

 

Every module can inherit from WebModule or SecuredModule class. If module inherits from WebModule class, all users will be able to perform all operations. If module, however, inherits from SecuredModule class - we will have ability to access following two methods of the security API:
  • CheckDemand
    We get the permissions for a specified secured object (e.g. module) and call the CheckDemand method for a given right (e.g. delete). If the currently logged in user has the permissions to delete in the given module, method will return true, otherwise it will return false. CheckDemand is generally used for setting up the user interface elements. In our given example, we could check the delete permission and hide the delete button if the method returns false.
  • Demand
    Demand method works very similarly to the CheckDemand method, with one important difference. If user does not have permission that we have demanded from our code - SecurityException will be thrown. Generally speaking, CheckDemand and Demand are used in conjunction. For example, let us suppose that we can delete an item by clicking on a delete button and by appending a special query string to our url. We will use CheckDemand to hide the button, but some users still may try to hack our query strings and try to delete the item. To prevent such scenarios, before actually deleting an item, we will demand permissions and prevent unauthorized users from performing tasks for which they have no permissions.

Inheriting SecuredModule abstract class


The first step for securing our module is to inherit from SecuredModule abstract class on our module class. You can see an example of this in the ProductsModule class declared in our sample products module. The two members that we will have to implement are following:
/// <summary> 
/// Gets the security root for the default provider 
/// </summary> 
public override ISecured SecurityRoot 
            get 
            { 
                return this.SecurityRoots[Configuration.ConfigHelper.Handler.DefaultProvider]; 
            } 
 
/// <summary> 
/// Gets key/value information for provider/global permissions. Preserves information 
/// for all providers. 
/// </summary> 
public override IDictionary<string, ISecured> SecurityRoots 
            get 
            { 
                return ProductsManager.SecurityRoots; 
            } 
 
SecurityRoots are the objects that we wish to secure. For products module we have decided to provide security for each provider of the Products module, meaning that we can have different permissions for different providers. Therefore, providers of the products module are security roots. You can see from the code above that we are using static property of the ProductManager class, our Manager class. We will discuss the manager class in the next paragraph.

 

Implement security members on your manager class


Now that we have made our module secured, we have to implement the business logic for security which will rely heavily on Sitefinity security API. This implementation is placed traditionally in the module’s manager class.

 

You can examine the full code we are about to describe in ProductsManager class in our sample products module.

 

Our first goal is to make every provider of the products module a security root which can have its own permissions. Following excerpt of the code will allow us to do that:
/// <summary> 
/// Provides a static constructor of ProductsManager class. 
/// </summary> 
static ProductsManager() 
            foreach (GenericContentElement element in ConfigHelper.Handler.GenericContentProviders) 
                ProductsManager.contentSettings.Add(element.ProviderName, element); 
 
/// <summary> 
/// Gets key/value information for provider/global permissions. Preserves information 
/// for all providers. 
/// </summary> 
public static Dictionary<String, ISecured> SecurityRoots 
            get 
            { 
                if (securityRoots == null
                { 
                    securityRoots = new Dictionary<String, ISecured>(ContentManager.Providers.Count); 
                    foreach (string name in contentSettings.Keys) 
                        securityRoots.Add(name, new GlobalPermissions(name)); 
                } 
                return securityRoots; 
            } 
 
private static Dictionary<String, ISecured> securityRoots; 
private static readonly IDictionary<string, GenericContentElement> contentSettings = new Dictionary<string, GenericContentElement>(); 
 
The code itself is rather self-explanatory. In the static constructor of the class we populate the contentSettings dictionary with all the providers defined for our module. Then in the static property SecurityRoots we populate the dictionary where each provider name is the key and each value is the new GlobalPermissions object for that provider.

 

By doing so, we have set up the security for our module. From now on, we can use the CheckDemand and Demand methods explained at the beginning of this article.

 

To allow for centralized and simple access to permissions from all of our Views it is advisable that we implement the GetPermission methods in the manager class. Here is how this implementation looks like for the products module:
/// <summary> 
/// Gets the permissions for the current provider. 
/// </summary> 
public GlobalPermissions Permissions 
            get 
            { 
                return (GlobalPermissions)ProductsManager.SecurityRoots[this.settingsElement.ProviderName]; 
            } 
 
/// <summary> 
/// Gets GlobalPermission object for the secured object. 
/// </summary> 
/// <returns>GlobalPermission object</returns> 
public GlobalPermission GetPermission() 
            return new GlobalPermission(this.Permissions); 
 
/// <summary> 
/// Gets the current provider permissions for the specified rights. 
/// </summary> 
/// <param name="requestRights">the requested rights to check permissions for</param> 
/// <returns>GlobalPermission object</returns> 
public GlobalPermission GetPermission(int requestRights) 
            return new GlobalPermission(this.Permissions, requestRights); 
 
/// <summary> 
/// Gets the current provider permissions for the specified content owner identifier. 
/// </summary> 
/// <param name="contentOwnerId">the identifier of the content's owner</param> 
/// <returns>GlobalPermission object</returns> 
public GlobalPermission GetPermission(Guid contentOwnerId) 
            IContent cnt = this.Content.GetCurrentState(contentOwnerId); 
            return this.GetPermission(cnt); 
 
/// <summary> 
/// Gets information for the current provider permissions for the specified 
/// content owner ID and rights. 
/// </summary> 
/// <param name="contentOwnerId">the identifier of the content's owner</param> 
/// <param name="requestRights">the requested rights to check permissions for</param> 
/// <returns>GlobalPermission object</returns> 
public GlobalPermission GetPermission(Guid contentOwnerId, int requestRights) 
            IContent cnt = this.Content.GetCurrentState(contentOwnerId); 
            return this.GetPermission(cnt, requestRights); 
 
/// <summary> 
/// Gets the current provider permissions for the specified owner of the content. 
/// </summary> 
/// <param name="contentOwner">the owner of the content</param> 
/// <returns>GlobalPermission object</returns> 
public GlobalPermission GetPermission(IContent contentOwner) 
            return new GlobalPermission(this.Permissions, contentOwner); 
 
/// <summary> 
/// Gets the current provider permissions for the specified 
/// content owner and rights. 
/// </summary> 
/// <param name="contentOwner">the owner of the content</param> 
/// <param name="requestRights">the requested rights to check permissions for</param> 
/// <returns>GlobalPermission object</returns> 
public GlobalPermission GetPermission(IContent contentOwner, int requestRights) 
            return new GlobalPermission(this.Permissions, requestRights, contentOwner); 
 

PermissionsView - the permissions management console


Once the fundamentals have been set up, we need to allow our users to set up the permissions. This is easily done by simply implementing a view for permissions and inheriting PermissionsView from Generic Content module. In our sample products module this View is called ProductsPermissionsView. The PermissionSet control will take care of the hard work, all we need to do is to override GetPermissions and GetPermission methods, so that they call the methods in our ProductManager (and not methods in the base ContentManager class). The implementation of these methods looks like following:
/// <summary> 
/// Gets the permissions. 
/// </summary> 
/// <returns></returns> 
protected override GlobalPermissions GetPermissions() 
            return this.Host.ProductsManager.Permissions; 
 
/// <summary> 
/// Gets the permission. 
/// </summary> 
/// <returns></returns> 
protected override GlobalPermission GetPermission() 
            return this.Host.ProductsManager.GetPermission(); 
 
/// <summary> 
/// Gets the permission. 
/// </summary> 
/// <param name="requestRights">The request rights.</param> 
/// <returns></returns> 
protected override GlobalPermission GetPermission(int requestRights) 
            return this.Host.ProductsManager.GetPermission(requestRights); 
 
/// <summary> 
/// Gets the permission. 
/// </summary> 
/// <param name="contentOwnerId">The content owner id.</param> 
/// <returns></returns> 
protected override GlobalPermission GetPermission(Guid contentOwnerId) 
            return this.Host.ProductsManager.GetPermission(contentOwnerId); 
 
/// <summary> 
/// Gets the permission. 
/// </summary> 
/// <param name="contentOwnerId">The content owner id.</param> 
/// <param name="requestRights">The request rights.</param> 
/// <returns></returns> 
protected override GlobalPermission GetPermission(Guid contentOwnerId, int requestRights) 
            return this.Host.ProductsManager.GetPermission(contentOwnerId, requestRights); 
 
/// <summary> 
/// Gets the permission. 
/// </summary> 
/// <param name="contentOwner">The content owner.</param> 
/// <returns></returns> 
protected override GlobalPermission GetPermission(IContent contentOwner) 
            return this.Host.ProductsManager.GetPermission(contentOwner); 
 
/// <summary> 
/// Gets the permission. 
/// </summary> 
/// <param name="contentOwner">The content owner.</param> 
/// <param name="requestRights">The request rights.</param> 
/// <returns></returns> 
protected override GlobalPermission GetPermission(IContent contentOwner, int requestRights) 
            return this.Host.ProductsManager.GetPermission(contentOwner, requestRights); 
 
 
Note that we can call ProductsManager property defined in the ProductsControlPanel class, since we have defined ProductsControlPanel as a generic host of PermissionsView View in its declaration:
public class ProductsPermissionsView : PermissionsView<ProductsControlPanel> 
Now, that we have set up the entire infrastructure, all we are left with is to take advantage of these permissions in our code.

 

Check and demand permissions from your code


We may use permissions anywhere we see fit, however since all of our Views in the sample products module are inherited, it is mandatory that we rewire their permission check and demand mechanism to use our security roots defined in our manager class (ProductsManager).

 

If you are unsure which Views are using permissions, you can simply try to override CheckPermission and DemandPermission in all Views - we have strictly followed this naming convention. For example, on the ProductsItemListView we have buttons for Creating new product, Editing existing product and Deleting a product. Since our products module is a secured module, we should check if currently logged in user has permissions to create new product, edit existing product and delete product and set up the user interface accordingly.

Generally, if we are building a custom module, we would need to perform all the logic for permissions and user interface alterations manually. Luckily, when you are building a Generic Content based module all this is done for you - all you need to do is point Sitefinity to the right GetPermission method.

 

In our example of the ProductsItemListView the only thing we need to do is to override CheckPermission method (this View does not demand any permissions) as follows:

/// <summary> 
/// Checks the permission. 
/// </summary> 
/// <param name="right">The right.</param> 
/// <returns></returns> 
protected override bool CheckPermission(int right) 
            return this.Host.ProductsManager.GetPermission(right).CheckDemand(); 
 
As you can see, we are simply calling one of the GetPermission methods we have declared in our ProductsManager and then calling CheckDemand method on the returned GlobalPermission object.

 

You can examine all the Views in products module and look for the regions named “Security overrides” to see different implementations of this approach, based on the security needs of every individual View.


Leave a comment