X

Register now for unlimited access to Sitecore resources.


Already have an account? Log in now

*{0} must be filled in.
*{0} must be filled in.
*{0} must be filled in.
*{0} must be filled in.
*{0} must be filled in.
*{0} must be filled in.
*{0} must be filled in.
X

Request a demo

It’s easy to get started. Sign up for a personalized demo.

*{0} must be filled in.
*{0} must be filled in.
*{0} must be filled in.
*{0} must be filled in.
*{0} must be filled in.
*{0} must be filled in.
*{0} must be filled in.
Sitecore Blog: Getting to Know Sitecore

Custom Access Rights

By Adam Conn, March 01, 2011 | Rating:  | Comments (3)

custom access rights

Sitecore's standard authorization model provides the ability to control permissions at a variety of levels of granularity: site, item, field, language and workflow.  This functionality is often sufficient for handling authorization-related requirements. 

But it doesn't handle all of them.  Authorization rights can be used to "allow" or "deny" comments.  What if you want to allow comments, but no more than 5 comments on any page?  Or if you want to prevent comments from being added to pages that are more than 30 days old? 

In this post I'm going to describe how to create a custom authorization that controls the ability to add comments to a page.  I also explain how properties can be defined on a specific right that provide additional levels of control.

I want to offer a quick disclaimer before I get started.  There are certainly other - arguably easier - ways to implement this functionality.  But I thought it would be helpful to explain how to authorization rights can be used.  Now then, on with the post.

STEP 1 - Create a custom access right

An access right is basically a label that is applied to a Sitecore item.  The label tells Sitecore if a user or role is allowed or denied the ability to do something.  Access rights don't really do much except store information such as what kind of item the access right applies to (items, fields, workflow, etc.).

The access right I need stores a little more information.  I need to be able to specify the maximum number of comments that should be allowed, as well as a value that determines the number of days after a page is updated that comments can be left.

The following code presents the access right I need:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
using System.Collections.Specialized;
using Sitecore.Security.AccessControl;
  
namespace Sitecore.Marketing.AccessRights
{
    public class CommentingAccessRight : AccessRight
    {
        public CommentingAccessRight(string name) : base(name)
        {
        }
  
        public string MaxCommentsFieldName { get; private set; }
        public int DaysToAllowComments { get; private set; }
  
        public override void Initialize(NameValueCollection config)
        {
            base.Initialize(config);
            //
            //Read the maxCommentsFieldName value from the config file.
            this.MaxCommentsFieldName = config["maxCommentsFieldName"];
            //
            //Read the daysToAllowComments value from the config file.
            //The value should be an int, but if it isn't (or if the 
            //value isn't set), -1 should be used. -1 indicates that
            //comments are allowed indefinitely.
            var count = 0;
            if (!int.TryParse(config["daysToAllowComments"], out count))
            {
                count = -1;
            }
            this.DaysToAllowComments = count;
        }
    }
}

 

STEP 2 - Create ItemAuthorizationHelper

An ItemAuthorizationHelper is used to determine if an access right applies to an item.  The following code implements the helper:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
using System;
using Sitecore.Configuration;
using Sitecore.Data.Items;
using Sitecore.Security.AccessControl;
using Sitecore.Security.Accounts;
  
namespace Sitecore.Marketing.AccessRights
{
    public class MyItemAuthorizationHelper : ItemAuthorizationHelper
    {
        protected override AccessResult GetItemAccess(Item item, Account account, AccessRight accessRight, PropagationType propagationType)
        {
            //
            //This method applies the specified AccessRight.  Since the custom AccessRight 
            //has been extended to support additional properties (max comments and 
            //time range), these properties must be considered.
            var result = base.GetItemAccess(item, account, accessRight, propagationType);
            //
            //
            if (result == null || result.Permission != AccessPermission.Allow)
            {
                return result;
            }
            //
            //
            if (accessRight.Name != CommentingRights.AddComments)
            {
                return result;
            }
            //
            //
            var right = accessRight as CommentingAccessRight;
            if (right != null)
            {
                result = GetItemAccess(item, account, right);
            }
            return result;
        }
  
        protected virtual AccessResult GetItemAccess(Item item, Account account, CommentingAccessRight right)
        {
            //
            //Determine if comments should be allowed based on the max comments count.
            var result = HandleMaxComments(item, account, right);
            if (result.Permission == AccessPermission.Deny)
            {
                return result;
            }
            //
            //Determine if comments should be allowed based on the time range.
            result = HandleDaysToAllowComments(item, account, right);
            if (result.Permission == AccessPermission.Deny)
            {
                return result;
            }
            //
            //Allow comments.
            var ex = new AccessExplanation("Comments are allowed on this item.");
            return new AccessResult(AccessPermission.Allow, ex);
        }
  
        protected virtual AccessResult HandleMaxComments(Item item, Account account, CommentingAccessRight right)
        {
            //
            //Allow unlimited comments if no field name is specified for MaxCommentsFieldName.
            if (string.IsNullOrEmpty(right.MaxCommentsFieldName))
            {
                var ex = new AccessExplanation("Unlimited comments are allowed.");
                return new AccessResult(AccessPermission.Allow, ex);
            }
            //
            //Allow unlimited comments if the specified field does not exist on the item.
            var field = item.Fields[right.MaxCommentsFieldName];
            if (field == null)
            {
                var ex = new AccessExplanation("The item {0} does not have a field named \"{1}\", so unlimited comments are allowed.", item.ID.ToString(), right.MaxCommentsFieldName);
                return new AccessResult(AccessPermission.Allow, ex);
            }
            //
            //Deny commenting if the max comments value is not an integer.
            var maxCommentCount = 0;
            if (! string.IsNullOrEmpty(field.Value) && !int.TryParse(field.Value, out maxCommentCount))
            {
                var ex = new AccessExplanation("The value specified for the field named \"{0}\" is not a valid integer: {1}", right.MaxCommentsFieldName, field.Value);
                return new AccessResult(AccessPermission.Deny, ex);
            }
            //
            //Deny commenting if the max comments limit has already been met.
            if (maxCommentCount > -1)
            {
                var currentCount = GetCurrentCommentCount(item);
                if (currentCount >= maxCommentCount)
                {
                    var ex = new AccessExplanation("{0} comments already exist, and the maximum number allowed is {1}.", currentCount, maxCommentCount);
                    return new AccessResult(AccessPermission.Deny, ex);
                }
            }
            //
            //No other rules need to be implemented, so allow comments.
            var ex1 = new AccessExplanation("Additional comments are allowed.");
            return new AccessResult(AccessPermission.Allow, ex1);
        }
  
        protected virtual AccessResult HandleDaysToAllowComments(Item item, Account account, CommentingAccessRight right)
        {
            //
            //Allow commenting if the value is -1 since that value means comments
            //may be added indefinitely.
            if (right.DaysToAllowComments == -1)
            {
                var ex = new AccessExplanation("Comments can be added indefinitely.");
                return new AccessResult(AccessPermission.Allow, ex);
            }
            //
            //Deny commenting if the item has not been updated within the allowed 
            //time range.
            var d1 = item.Statistics.Updated;
            var d2 = d1.AddDays(right.DaysToAllowComments);
            if (DateTime.Compare(d1, d2) != -1)
            {
                var ex = new AccessExplanation("Comments cannot be added after {0} {1}.", d2.ToLongDateString(), d2.ToLongTimeString());
                return new AccessResult(AccessPermission.Deny, ex);
            }
            //
            //No other rules need to be implemented, so allow comments.
            var ex1 = new AccessExplanation("Comments can be added until {0} {1}.", d2.ToLongDateString(), d2.ToLongTimeString());
            return new AccessResult(AccessPermission.Allow, ex1);
        }
  
        protected virtual int GetCurrentCommentCount(Item item)
        {
            //
            //Get the number of children that are based on the template 
            //specified in the config file.
            var templateId = Settings.GetSetting("CommentTemplate");
            var path = string.Format("./*[@@templateid='{0}']", templateId);
            var items = item.Axes.SelectItems(path);
            if (items == null)
            {
                return 0;
            }
            return (items.Length);
        }
    }
}

 

STEP 3 - Create AuthorizationProvider

Next I need to create an authorization provider.  One thing an AuthorizationProvider does is use the ItemAuthorizationHelper to determine if an access right applies to an item. 

Another thing an AuthorizationProvider does is handle access right caching.  For my requirements, determining if an access right applies or not depends on the value of a field on the item (max comments field name).  The value may change at any time.  Since access rights don't ordinarily depend on specific items, changes to items do not cause access rights to cleared from the cache.  For this reason, I do not want my custom access right to be cached.  I need to create an AuthorizationProvider that works this way.

The following code implements the authorization provider that is needed:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
using Sitecore.Security.AccessControl;
using Sitecore.Security.Accounts;
  
namespace Sitecore.Marketing.AccessRights
{
    public class MyAuthorizationProvider : SqlServerAuthorizationProvider
    {
        public MyAuthorizationProvider()
        {
            _itemHelper = new MyItemAuthorizationHelper();
        }
  
        private ItemAuthorizationHelper _itemHelper;
        protected override ItemAuthorizationHelper ItemHelper
        {
            get { return _itemHelper; }
            set { _itemHelper = value; }
        }
          
        protected override void AddAccessResultToCache(ISecurable entity, Account account, AccessRight accessRight, AccessResult accessResult, PropagationType propagationType)
        {
            //
            //Do not cache the access result because the result depends 
            //on the value that is currently set on the item.
            if (accessRight.Name == CommentingRights.AddComments)
            {
                return;
            }
            base.AddAccessResultToCache(entity, account, accessRight, accessResult, propagationType);
        }
    }
}

 

STEP 4 - Create config file

Access rights are controlled using the web.config file.  Rather than modify the web.config file directly, an include file should be used. 

The following include file is needed.  Please note that the specific IDs included below may not match the IDs on your Sitecore server:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<?xml version="1.0"?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <settings>
      <!-- 
        Any item based on the template /sitecore/templates/Common/Folder should 
        be considered to be a comment for the purpose of determining if the max 
        comment count has been met.
      -->
      <setting name="CommentTemplate" value="{A87A00B1-E6DB-45AB-8B54-636FEC3B5523}"/> 
    </settings>
    <authorization>
      <!-- Set the custom AuthorizationProvider as the default provider -->
      <patch:attribute name="defaultProvider">custom</patch:attribute>
      <providers>
        <clear />
        <add name="custom" type="Sitecore.Marketing.AccessRights.MyAuthorizationProvider, Sitecore.Marketing.AccessRights" connectionStringName="core" embedAclInItems="true" />
      </providers>
    </authorization>
    <accessRights>
      <rights>
        <!-- Add the custom access right -->
        <add patch:before="*[1]" name="commenting:addcomments" comment="Add comments." title="Add comments" type="Sitecore.Marketing.AccessRights.CommentingAccessRight, Sitecore.Marketing.AccessRights" maxCommentsFieldName="Max Comments" daysToAllowComments="30" />
      </rights>
      <rules>
        <!-- 
          Any access right whose name begins with "commenting" should apply to 
          any child items under /sitecore/content/.
        -->
        <add patch:before="*[1]" prefix="commenting" ancestor="{0DE95AE4-41AB-4D01-9EB0-67441B7C2450}" comment="/sitecore/content" />
      </rules>
    </accessRights>
  </sitecore>
</configuration>

 

STEP 5 - Define a constant to store the access right name

In the config file, access rights are named.  In this example, the custom access right is named "commenting:addcomments".  In a later step I will create a class that adds comments, but only if the access right allows it.  In this code I will need to refer to this name. 

Rather than hard-coding a name, I will create a class that defines the name as a constant.  The following code serves this purpose:

1
2
3
4
5
6
7
namespace Sitecore.Marketing.AccessRights
{
    public class CommentingRights
    {
        public const string AddComments = "commenting:addcomments";
    }
}

 

STEP 6 - Define CommentManager

The CommentManager class is used to add comments.  This class includes the logic to determine if access rights allow the user to add comments.

The following code implements this logic:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
using Sitecore.Security.AccessControl;
using Sitecore.Data.Items;
  
namespace Sitecore.Marketing.AccessRights
{
    public class CommentManager
    {
        public CommentManager(Item item)
        {
            this.Item = item;
        }
  
        public Item Item { get; private set; }
  
        public virtual bool IsAddCommentAllowed
        {
            get
            {
                var right = AccessRight.FromName(CommentingRights.AddComments);
                if (right == null)
                {
                    return false;
                }
                var allowed = AuthorizationManager.IsAllowed(this.Item, right, Sitecore.Context.User);
                return allowed;
            }
        }
  
        public virtual Item AddComment()
        {
            if (!this.IsAddCommentAllowed)
            {
                return null;
            }
            //
            //TODO: Add logic to actually add a comment.
            return null;
        }
    }
}

 

STEP 7 - Create a sublayout for adding comments

I am not going to detail the steps for creating a sublayout.  The sublayout does not actually allow comments to be added.  It simply displays a message that indicates if comments should be allowed or not.

The sublayout uses the following code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
using System;
using System.Web.UI;
using Sitecore.Marketing.AccessRights;
  
namespace Sitecore.Marketing.AccessRights.Controls
{
    public partial class CommentingControl : System.Web.UI.UserControl
    {
        protected override void Render(HtmlTextWriter writer)
        {
            base.Render(writer);
            var commentManager = new CommentManager(Sitecore.Context.Item);
            if (commentManager.IsAddCommentAllowed)
            {
                writer.Write("Comments allowed");
            }
            else
            {
                writer.Write("Comments NOT allowed");
            }
        }
    }
}

 

STEP 8 - Compile and deploy

Now I need to compile the code and deploy the assembly and config file.

STEP 9 - Testing

I am using a clean installation of Sitecore for my testing.  The first thing I want to test is if the custom access right is available in the Security Settings dialog. 

  1. In the Content Editor, navigate to /Sitecore/Content/Home. 
  2. Select the Security tab.


  3. Click Security > Assign


  4. You should see "Add comments" as an available access right.  Change the security settings so the "Add comments" permission is allowed for the extranet\Anonymous user.


  5. In the Content Editor, navigate to /sitecore/templates/Sample/Sample Item.
  6. Add a new field named Max Comments (the type should be Integer).
  7. In the Content Editor, navigate to /Sitecore/Content/Home. 
  8. Set the value of the Max Comments field to -1.
  9. Add the commenting sublayout to the presentation details.
  10. Publish the changes.
  11. View the published site.  Comments should be allowed.


  12. Change the Max Comments value to 0.
  13. Publish the changes.
  14. View the published site.  Comments should not be allowed.


  15. Change the Max Comments value back to -1.
  16. Publish the changes.
  17. View the published site. Comments should be allowed.


  18. In the config file, change the daysToAllowComments to 0.
  19. View the published site. Comments should not be allowed.


  20. In the config file, change the daysToAllowComments back to 30.
  21. View the published site. Comments should be allowed.


Next Steps

Hopefully this sheds a little bit of light on the subject of access rights and how to interact with them.  For more information, you should read the following documents available on SDN:

I'm curious to hear about how you have used custom access rights, or how you think you might use them in the future.  Let me know in the comments!

Tags: API

Comments

  • Adam - This is absolutely awesome and helped me so much in understanding how to implement custom access rights. I am running into a strange issue, however, and I'm hoping you can shed some light on this. I did a custom access right to grant access to an additional workflow command. I created the access right and then set up a rule to filter the right to only apply to certain items that are the descendents of a specified item. I set the rule so that it would patch the
    <access rights><rules><<add prefix="myPrefix" ancestor="{0DE95AE4-41AB-4D01-9EB0-67441B7C2450}" comment="/sitecore/content/Home/TopItemToApplyRightTo" /> </rules> </access rights>

    in my config where ancestor was the ID for the item i specified in the comment In my Sitecore 6.5 POC, this was perfect. Everything worked beautiful.. excited cheering. Now.. when I install this on the client's system, everything works exactly as it should, except for the access right is applied to ALL items rather than my ancestor branch only. Now.. the client I did this for is running 6.2 or maybe 6.3. Either way, I can't find that it would matter because I don't see where there was something changed that would impact setting the access rule. I've double checked everything and I'm struggling to find what is different in the older versions of Sitecore that might ignore my access rule declaration.

    - Lewanna Rathbun
    October 01, 2012 at 5:59 PM

  • Hi Lewanna, A couple of things come to mind: 1. Are you sure that the ancestor value (the guid) matches the ID in the client's system? 2. If you followed my instructions, your setting is in a separate config file (as opposed to being added directly to web.config). I don't think 6.2 and 6.3 would restart the app if an included config file was changed. Try restarting IIS (iisreset /restart) on the client's system to ensure the changes you make to the config file get picked up.

    - Adam Conn
    October 03, 2012 at 9:49 AM

  • Hi Adam - I was recommended to view your blog by one of your associates to potentially help craft a solution. I'd like to apply regionalization to all Media Library items based on a Region multilist that a content author populates for each Media Item. For instance, a PDF item is allowed to be viewed in NA and Europe but not other regions. Since this logic would be applied to all Media Items, would I be able to skip creating and assigning security for each individual item? And instead use the AuthorizationHelper to look at the Regions for that item and deny or grant access at that point? I like your solution because it would allow me to implement this logic at a much higher level than modifying every sublayout that Media Items appear on.

    - Andy Osika
    January 03, 2013 at 12:23 PM

*{0} must be filled in.
*{0} must be filled in.
*{0} must be filled in.
Cookies help us improve your website experience. By using our website, you agree to our use of cookies. OK Learn more