The RightPermission works in the background.  Out of sight, out of mind and easily forgotten.  But it performs the "hard" work of securing the code.  The RightPermission clips in at just over 300 lines of code.  If you have not already read Part one - IPrincipal, Part two - IHttpModule and Part three - Attributes go back and take a look; We'll wait for you.

First, I am going to give you all the code for the RightPermission and I will cover each method separately.

 

  using System;
  using System.Collections.Generic;
  using System.Linq;
  using System.Text;
  using System.Security;
  using System.Security.Principal;
  using System.Threading;
  using System.Security.Permissions;  

  namespace ObjectHelpDesk.Security
  {
      public class RightPermission : IPermission, ICloneable, IEquatable<RightPermission>
      {
          private String _userName;
          private String _rightName;
          private Boolean _authenticated;

          public Boolean Authenticated
          {
              get { return _authenticated; }
              set { _authenticated = value; }
          }

          public String RightName
          {
              get { return _rightName; }
              set { _rightName = value; }
          }  

          public String UserName
          {
              get { return _userName; }
              set { _userName = value; }
          }  

          private RightPermission()
              : this(false, String.Empty, String.Empty)
          {
          }  

          public RightPermission(Boolean authenticated, String userName, String rightName)
          {
              _authenticated = authenticated;
              _userName = userName;
              _rightName = rightName;
          }

          #region IEquatable<RightPermission> Members

          public bool Equals(RightPermission other)
          {
              if (this.Authenticated != other.Authenticated) return false;
              if (this.UserName != other.UserName) return false;
              if (this.RightName != other.RightName) return false;
              return true;
          }  

          #endregion  

          private Boolean IsUnrestircted()
          {
              if (_authenticated) return false;
              if (!String.IsNullOrEmpty(_userName)) return false;
              if (!String.IsNullOrEmpty(_rightName)) return false;
              return true;
          }  

          private void DenyAccess()
          {
              DenyAccess("You lack the rights to do this");
          }  

          private void DenyAccess(String message)
          {
              throw new SecurityException(message);
          }

          #region IPermission Members  

          public IPermission Copy()
          {
              return this.Clone();
          }  

          public void Demand()
          {
              if (IsUnrestircted()) return;
              IPrincipal user = Thread.CurrentPrincipal;
              if ((_authenticated) && (!user.Identity.IsAuthenticated)) DenyAccess("You must be logged in to do this.");
              if ((!String.IsNullOrEmpty(_userName)) && (_userName != user.Identity.Name)) DenyAccess("Your account lacks the rights to do this.");
              if (!String.IsNullOrEmpty(_rightName))              {                  if (!(user is RightPrincipal)) DenyAccess();
                  if (!((RightPrincipal)user).HasRight(_rightName)) DenyAccess();
              }
          }

          public IPermission Intersect(IPermission target)
          {
              RightPermission result = null;
              Boolean hasIntersection = true;
              if (target != null)
              {
                  if (!(target is RightPermission)) throw new ArgumentException("Target was not a RightPermission");
                    RightPermission p = (RightPermission)target;
                    if (this.Equals(p))                      result = Clone();
                  else
                  {
                      result = new RightPermission();

                      result.Authenticated = (_authenticated || p.Authenticated);
                      if (_userName == p.UserName) result.UserName = _userName;
                      else if (String.IsNullOrEmpty(_userName)) result.UserName = p.UserName;
                      else if (String.IsNullOrEmpty(p.UserName)) result.UserName = _userName;
                      else hasIntersection = false;

                      if (_rightName == p.RightName) result.RightName = _rightName;
                      else if (String.IsNullOrEmpty(_rightName)) result.RightName = p.RightName;
                      else if (String.IsNullOrEmpty(p.RightName)) result.RightName = _rightName;
                      else hasIntersection = false;
                  }
              }
              if (!hasIntersection) result = null;
              return result;
          }

          private Boolean IsSubsetOf(RightPermission target)
          {
              Boolean result = false;
              if (Equals(target))
                  result = true;
              else if ((!_authenticated) && (target.Authenticated))
                  result = false;
              else if ((String.IsNullOrEmpty(target.UserName)) || (target.UserName == _userName))
                  result = ((String.IsNullOrEmpty(target.RightName)) || (target.RightName == _rightName));
              else
                  result = false;
              return result;
          }

          public Boolean IsSubsetOf(PrincipalPermission target)
          {
              Boolean result = false;

              if (target == null) return result;
              if (target.IsUnrestricted())
              {
                  result = true;
              }
              else
              {
                  SecurityElement element = target.ToXml();
                  Boolean authencation = CheckSubsetAuthencation(element);
                  Boolean userId = CheckSubsetUserId(element);
                  Boolean roleId = CheckSubsetRole(element);

                  foreach (SecurityElement item in element.Children)
                  {
                      if (authencation) authencation = CheckSubsetAuthencation(item);
                      if (userId) userId = CheckSubsetUserId(item);
                      if (roleId) roleId = CheckSubsetRole(item);
                  }
                  result = authencation && userId && roleId;
               }  
               return result;
          }

          private bool CheckSubsetRole(SecurityElement item)
          {
              Boolean result = true;
              String value = String.Empty;
              value = item.Attribute("Role");
              if (!String.IsNullOrEmpty(value))
              {
                  result = RightManager.GetRightsByRoleName(value).Count(r => r.RightName == value) > 0;
              }
              else
              {
                  result = false;
              }
              return result;
          }  

          private bool CheckSubsetUserId(SecurityElement item)
          {
              Boolean result = true;
              String value = String.Empty;
              value = item.Attribute("ID");
              if (value !=null)
              {
                  result = (String.IsNullOrEmpty(value) || value == _userName);
              }
              else
              {
                  result = false;
              }
              return result;
          }  

          private bool CheckSubsetAuthencation(SecurityElement item)
          {
              Boolean result = true;
              String value = String.Empty;
              value = item.Attribute("Authenticated");
              if (!String.IsNullOrEmpty(value))
              {
                  result = String.Compare(value, "true", StringComparison.OrdinalIgnoreCase) == 0;
              }
              else
              {
                  result = false;
              }
              return result;
          }

          public bool IsSubsetOf(IPermission target)
          {
              Boolean result = false;
              if (target == null) return result;

              if (target is RightPermission)
              {
                  result = IsSubsetOf((RightPermission)target);
              }
              else if (target is PrincipalPermission)
              {
                  result = IsSubsetOf((PrincipalPermission)target);
              }

              return result;
          }

          public IPermission Union(IPermission target)
          {
              IPermission result = null;
              if ((target == null) || (target is RightPermission && Equals((RightPermission)target)))
                  result = Clone();
              else
                  throw new NotSupportedException();
                return result;
          }  

          #endregion

          #region ISecurityEncodable Members

          public override string ToString()
          {
              return ToXml().ToString();
          }  
  
          public void FromXml(SecurityElement e)
          {
              switch ((String)e.Attributes["Version"])
              {
                  case "1":
                  default:
                      this.RightName = (String)e.Attributes["Role"];
                      this.UserName = (String)e.Attributes["User"];
                      this.Authenticated = ("Y" == (String)e.Attributes["Authenticated"]);
                      break;
              }
          }

          public SecurityElement ToXml()
          {
              SecurityElement element = new SecurityElement("IPermission");
              String typename = "ObjectHelpDesk.Security.RightPermission";
              element.AddAttribute("class", typename + ", " + this.GetType().Module.Assembly.FullName.Replace('"', '\''));
              element.AddAttribute("Version", "1");
              element.AddAttribute("Role", _rightName);
              element.AddAttribute("User", _userName);
              element.AddAttribute("Authenticated", _authenticated ? "Y" : "N");
                return element;
          }

          #endregion  

          #region ICloneable Members  

          public RightPermission Clone()
          {
              return (RightPermission)((ICloneable)this).Clone();
          }

          object ICloneable.Clone()
          {
              return this.MemberwiseClone();
          }

          #endregion
      }
  }

 

If you are are wondering about the class declaration public class RightPermission : IPermission, ICloneable, IEquatable<RightPermission>  and what IPermission, ICloneable, IEquatable<RightPermission> means a friend of mine has a nice post about Interfaces here.  In case you do not want to go read that right now; here is a quick blurb on Interfaces - IPermission, ICloneable, IEquatable are all Interfaces.  Normally Interfaces are prefix with a capital "i".  An Interface can define method and/or property signatures but not implementations.  Ok, that is a gross over simplification of an Interface but it will do for our purposes.

I am working under the assumption that you understand generics (IEquatable<RightPermission> is a generic). If not, do not worry; You do not not really need to know about generics right now. 

So lets start with the ICloneable code, if you've read Ira's article on ICloneable this may look familiar...

        #region ICloneable Members
          public RightPermission Clone()
          {
              return (RightPermission)((ICloneable)this).Clone();
          }

          object ICloneable.Clone()
          {
              return this.MemberwiseClone();
          }

          #endregion

 

This code simply returns a new copy of the RightPermission. 

If you looked at the code you may have noticed that ISecurtyEncodable snuck in someplace even though we did not include it in our class declaration.  Well we did, ISecurtyEncodable comes along with IPermission.  It includes to methods FromXml and ToXml.  I overrode the default ToString() method and placed it into the ISecurtyEncodable region.  Take note, if you change the namespace or class name you will want to edit the ToXml() method (psst, I know, yes.  But if it was just about the code I could have posted a zip file and moved on).

        #region ISecurityEncodable Members
          public override string ToString()
          {
              return ToXml().ToString();
          }  

          public void FromXml(SecurityElement e)
          {
              switch ((String)e.Attributes["Version"])
              {
                  case "1":
                  default:
                      this.RightName = (String)e.Attributes["Role"];
                      this.UserName = (String)e.Attributes["User"];
                      this.Authenticated = ("Y" == (String)e.Attributes["Authenticated"]);
                      break;
              }
          }

          public SecurityElement ToXml()
          {
              SecurityElement element = new SecurityElement("IPermission");
              String typename = "ObjectHelpDesk.Security.RightPermission";
              element.AddAttribute("class", typename + ", " + this.GetType().Module.Assembly.FullName.Replace('"', '\''));
              element.AddAttribute("Version", "1");              element.AddAttribute("Role", _rightName);
              element.AddAttribute("User", _userName);
              element.AddAttribute("Authenticated", _authenticated ? "Y" : "N");
                return element;
          }
            #endregion

 

The next region I'm going to talk about is IEquatable<RightPermission>.  All this does is checks to see if the passed in RightPermission has the same values as the current instance.  We make use of this method in a few spots but again there is nothing fancy here outside of the IEquatable generic interface.

        #region IEquatable<RightPermission> Members

          public bool Equals(RightPermission other)
          {
              if (this.Authenticated != other.Authenticated) return false;
              if (this.UserName != other.UserName) return false;
              if (this.RightName != other.RightName) return false;
              return true;
          }

          #endregion

 

There are some properties and constructors specific to the RightPermission.  I, despite the new public Property {get; set;} features of .net still use fields most of the time (Let's leave it at a bad day with INotifyPropertyChanged and move on).   First we have the three public properties, a private constructor and a public constructor.  There are two places where the constructors are used, the RightAttribute and within the RightPermission itself.  The method Unrestricted() checks to see if any of the properties are set that would cause a request to fail.  You can have a permission that does not restrict access.  Who knew?  Finally we have DenyAccess() and an overload that takes a string.  This method raises a security exception and stops the code execution.

        private String _userName;
        private String _rightName;

        private Boolean _authenticated;

          public Boolean Authenticated
          {
              get { return _authenticated; }
              set { _authenticated = value; }
          }

          public String RightName
          {
              get { return _rightName; }
              set { _rightName = value; }
          }  

          public String UserName
          {
              get { return _userName; }
              set { _userName = value; }
          }

          private RightPermission()
              : this(false, String.Empty, String.Empty)
          {
          }  

          public RightPermission(Boolean authenticated, String userName, String rightName)
          {
              _authenticated = authenticated;
              _userName = userName;
              _rightName = rightName;
          }  

          private Boolean IsUnrestircted()
          {
              if (_authenticated) return false;
              if (!String.IsNullOrEmpty(_userName)) return false;
              if (!String.IsNullOrEmpty(_rightName)) return false;
              return true;
          }

          private void DenyAccess()
          {
              DenyAccess("You lack the rights to do this");
          }
          private void DenyAccess(String message)
          {
              throw new SecurityException(message);
          }

 

The rest of the code is part of IPermission or an overload of an IPermission method.

First is the Demand() method..

          public void Demand()
          {
              if (IsUnrestircted()) return;
              IPrincipal user = Thread.CurrentPrincipal;
              if ((_authenticated) && (!user.Identity.IsAuthenticated)) DenyAccess("You must be logged in to do this.");
              if ((!String.IsNullOrEmpty(_userName)) && (_userName != user.Identity.Name)) DenyAccess("Your account lacks the rights to do this.");
              if (!String.IsNullOrEmpty(_rightName))
              {
                  if (!(user is RightPrincipal)) DenyAccess();
                  if (!((RightPrincipal)user).HasRight(_rightName)) DenyAccess();
              }
          }

 

Demand is what checks the current user for the needed right to access the code.  Right away we check the permission to see if it restricts access.  There is no point in checking it if every request would pass.  Next we get the current user from the Current thread.  If you recall in Part two - IHttpModule I said it was important to set the Principal of the thread and this is why.  If you are working on a large project and you dive into an assembly you may not have the HttpConext to pull the Principal from.  In addition, if you spawn a "worker" thread it inherits the parent threads principal.  That means the RightAttribute can be applied in all of the code, not just the web site code and it can be used in a win forms application.

The Copy() method is easy enough, it simply calls clone.

          public IPermission Copy()
          {
              return this.Clone();
          }

 

The Intersect method returns a new permission that is the "intersection" of this permission and the passed in permission.  By default if the passed in permission is not null and is not the same type of permission Intersect throws an ArgumentException.  If there is no intersection we return null.  What we do is build the new permission from the most restrictive values from the two permissions.

        public IPermission Intersect(IPermission target)
          {
              RightPermission result = null;
              Boolean hasIntersection = true;
              if (target != null)
              {
                  if (!(target is RightPermission)) throw new ArgumentException("Target was not a RightPermission");
                    RightPermission p = (RightPermission)target;
                    if (this.Equals(p))                      result = Clone();
                  else
                  {
                      result = new RightPermission();

                      result.Authenticated = (_authenticated || p.Authenticated);
                      if (_userName == p.UserName) result.UserName = _userName;
                      else if (String.IsNullOrEmpty(_userName)) result.UserName = p.UserName;
                      else if (String.IsNullOrEmpty(p.UserName)) result.UserName = _userName;
                      else hasIntersection = false;

                      if (_rightName == p.RightName) result.RightName = _rightName;
                      else if (String.IsNullOrEmpty(_rightName)) result.RightName = p.RightName;
                      else if (String.IsNullOrEmpty(p.RightName)) result.RightName = _rightName;
                      else hasIntersection = false;
                  }
              }
              if (!hasIntersection) result = null;
              return result;
            }

 

The Union(...) method also requires that the parameter target is the same type of permission as the current permission (so it has to be a RightPermission).  If the target is null or the same as the current permission we return a clone of the current permission.  Strictly speaking, if you have two permissions x and y calling x.Union(y) should return the same thing as y.Union(x).  I am throwing a NotSupportedException if it is not a simple clone.

        public IPermission Union(IPermission target)
        {
              IPermission result = null;
              if ((target == null) || (target is RightPermission && Equals((RightPermission)target)))
                  result = Clone();
              else
                  throw new NotSupportedException();
                return result;
          }

 

Last but not least is the IsSubSetOf(...) method.  This looks to see if the permission is contained within the target permission.  I used a few overloads with this to keep each subset check small and self contained.  If the target is null there is no way the current permission is a subset so return false and bail.

          public bool IsSubsetOf(IPermission target)
          {
              Boolean result = false;
              if (target == null) return result;

              if (target is RightPermission)
              {
                  result = IsSubsetOf((RightPermission)target);
              }
              else if (target is PrincipalPermission)
              {
                  result = IsSubsetOf((PrincipalPermission)target);
              }

              return result;
          }

 

Normally you can throw an ArgumentException if the target permission type is not the same type as the current permission; however, since a right can be assigned to a role it makes sense, to me, that a RightPermission could very well a subset of a default PrincipalPermission.  So the IsSubsetOf(RightPermission) compares the current permission against the target to see if it is a subset.

          private Boolean IsSubsetOf(RightPermission target)
          {
              Boolean result = false;
              if (Equals(target))
                  result = true;
              else if ((!_authenticated) && (target.Authenticated))
                  result = false;
              else if ((String.IsNullOrEmpty(target.UserName)) || (target.UserName == _userName))
                  result = ((String.IsNullOrEmpty(target.RightName)) || (target.RightName == _rightName));
              else
                  result = false;
              return result;
          }

 

IsSubsetOf(PrincipalPermission)  does the same thing except it looks into the the RightPermission and checks if the Role has the right assigned to it.

        public Boolean IsSubsetOf(PrincipalPermission target)
          {
              Boolean result = false;