Extending the ASP.Net Security model to use rights

Part one - IPrincipal

Every now and again I find myself disappointed in the asp.net security model.  The ability to assign roles is useful but if a role changes and I have implemented code security by role I now have to alter my PrincipalAttributes.  That isn't a huge issue, but I am not of a fan of recompiling my code because the of a minor change when I could secure the code by a right and assign a right to a role in a data store.

The first thing we need is a right

using System;

namespace ObjectHelpDesk.Security
{
public class Right
{
private Guid _id = Guid.Empty;
public Guid Id
{
get { return _id; }
set { _id = value; }
}

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

Nothing to exciting there, its a simple little object.    It is however the IPrincipal the is the workhorse of the asp.net security model.  Now that we have a right to extend the security system with we need an IPrincipal for asp.net to pass around.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Principal;

namespace ObjectHelpDesk.Security
{
public class RightPrincipal : IPrincipal
{
public RightPrincipal(IPrincipal user)
{
_user = user;
}

public RightPrincipal(IPrincipal user, List<Right> rights)
: this(user)
{
_rights = rights;
}

private IPrincipal _user = null;
private List<Right> _rights = new List<Right>();

public bool HasRight(String rightName)
{
bool result = false;
if (_user is RightPrincipal)
{
result = ((RightPrincipal)_user).HasRight(rightName);
}

if (!result)
{
result = _rights.Count(r => r.RightName == rightName) > 0;
}

return result;
}

#region IPrincipal Members

public IIdentity Identity
{
get { return _user.Identity; }
}

public bool IsInRole(string role)
{
return _user.IsInRole(role);
}

#endregion
}
}

There are a few things to talk about here.  There are two constructors, both take an IPrincipal object.  We are going to need to create an IHTTPModule later that builds our custom RightPrincipal and the it will pass the existing asp.net IPrincipal object into the constructor.

We store existing IPrincipal in a private field and use it to get the Identity object and implement the IsInRole function.  We also create a list of List<Right> and a HasRight method.  Has right checks the existing IPrincipal to see if it is a RightPrincipal and calls has right on it if it is.  If the IPrincipal is not a RightPrincipal or the right is not found it does a quick little check to see if the Right exists in the RightPrincipal's collection of rights.

We now have Right and a custom IPrincipal, but how do we attach them to the current request without doing something in every page?

Part two - the IHttpModule

In part one - IPrincipal, I talked about the right and IPrincipal objects needed to add "right" based security to asp.net applications in addition to role based security.

There are a few things we need to do to get ready to build the IHttpModule.  First we need something to give us a list of rights for a user.  To do this we will create a quick "RightManager" class.  We will create a static method "GetRightsByUserName" to return a list of Right objects for a given user name.  Really this class could also manage creating, editing and deleting rights along with assigning rights to roles, or users but that is outside of the scope of this article.  For a production system this class would likely use a provider model to connect to a data store and return the list of rights specific to the user base on their role membership.  For our purposes we will return the same right list for any user with a name starting with the letter J.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ObjectHelpDesk.Security
{
public static class RightManager
{
public static List<Right> GetRightsByUserName(string userName)
{
List<Right> result = new List<Right>();
Right someRight = new Right();
someRight.Id = new Guid();
someRight.RightName = "SVN Access";

if (userName.ToLower().StartsWith("j"))
result.Add(someRight);
return result;
}
}
}

The IHttpModule is where we do most of the heavy lifting. Because the code the gets the rights back from the user name is isolated in the Rights Manager class the amount of code in the module is really rather small.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;

namespace ObjectHelpDesk.Security
{
public class RightsHttpModule : IHttpModule
{
#region IHttpModule Members

public void Dispose()
{
}

public void Init(HttpApplication context)
{
            context.PostAuthenticateRequest += new EventHandler(context_PostAuthenticateRequest);
}

public void context_PostAuthenticateRequest(object sender, EventArgs e)
{
HttpApplication context = (HttpApplication)sender;
if ((context.User != null) && (context.User.Identity.IsAuthenticated))
{
List<Right> rights=RightManager.GetRightsByUserName(context.User.Identity.Name);
RightPrincipal newPrincipal = new RightPrincipal(context.User, rights);
HttpContext.Current.User = newPrincipal;
System.Threading.Thread.CurrentPrincipal = newPrincipal;
}
}

#endregion
}
}

When the When the module is loaded it will fire the Init method.  This wires the PostAuthenticateRequest event to our custom event handler "context_PostAuthenticateRequest".  The reason we use the PostAuthenticateRequest event is it allows us to alter the security principal after the membership and roles system has already processed and authenticated the user.  Because the user is already authenticated  at this point, the method used to authenticate them does not come into play and we can concentrate on getting the rights loaded.  Yes, this method of adding rights works with both Forms and windows authentication. We create the new RightPrincipal passing in the current principal and the list of rights for the user.  We than add the new principal to the Current HttpContext and the Thread.  The Principal of the thread is important and needs to be set as well.

Next we add the IHttpModule to the web.config.  The order you list the modules in the httpModules section of the web.config is important.  It determines the order of execution for the events in the modules. 

image

Now you can debug your web site project and login with various user accounts to check their rights.

While this code works, there are things I would take the time to clean up before using it in a production system. 

  1. The RightManager should be fleshed out and working with the ability to create, delete, edit and assign/revoke rights.
  2. The context_PostAuthenticateRequest method in the HttpModule should always create and hook in a RightPrincipal if it is not already one.

Part Three - Attributes

Part Three- Attributes.  Now that we have a Principal object with rights loaded as the current requests user we can begin assigning security to code by the users rights as well as their role membership.  The objective here was to be able to tag code to require a right and assign that right to a role.  Any user within the role would have the right and be able to execute the code.  We have everything in place to do this, except the Attributes.

Before e dive into Attributes let me recap where we have been; In part one of this series we covered the IPrincipal and right objects needed to secure code by rights under the asp.net membership and roles framework.  In part two we covered the IHttpModule that was required to make the custom RightPrincipal exist in the current request and we talked about a "RightManager".

What is an Attribute?  The short answer is they add meta data to the code that can affect it's behavior.  Here is the code for the RightAttribute. 

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

namespace ObjectHelpDesk.Security
{
public class RightAttribute : CodeAccessSecurityAttribute
{
private String _userName;
private String _rightName;
private Boolean _authenticated;

public RightAttribute(SecurityAction action)
: base(action)
{
_authenticated = true;
_userName = String.Empty;
_rightName = String.Empty;
}
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; }
}

public override System.Security.IPermission CreatePermission()
{
return new RightPermission(this._authenticated, this._userName, this._rightName);
}
}
}

There is not much to the Attribute code.  There is a UserName, RightName and Authenticated properties.  However, the CreatePermission() method returns a RightPermission.  This is what will check the current user to see if they have the needed right.

You can add a RightPermission to any method that you would like to secure.  For example you could add it to a button event handler in the code behind of a web page.  I added two buttons to a default web page in the project and secured them as follows

public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{

}

[RightAttribute(System.Security.Permissions.SecurityAction.Demand,RightName="SVN Access")]
protected void Button1_Click(object sender, EventArgs e)
{
...
}

[RightAttribute(System.Security.Permissions.SecurityAction.Demand,RightName="Manager Access")]
protected void Button2_Click(object sender, EventArgs e)
{
...
}
}

This will give us the ability to test everything after we have created the RightPermission.

Before we dive into the RightPermission we need to revisit the RightManager.

using System;
using System.Collections.Generic;

namespace ObjectHelpDesk.Security
{
public static class RightManager
{
public static List<Right> GetRightsByUserName(string userName)
{
List<Right> result = new List<Right>();
Right someRight = new Right();
someRight.Id = new Guid();
someRight.RightName = "SVN Access";

if (userName.ToLower().StartsWith("j"))
result.Add(someRight);
return result;
}

public static List<Right> GetRightsByRoleName(string roleName)
{
List<Right> result = new List<Right>();
Right someRight = new Right();
someRight.Id = new Guid();
someRight.RightName = "SVN Access";

if (roleName.ToLower()=="developer")
result.Add(someRight);
return result;
}
}
}

Notice we added a GetRightsByRoleName method.  Again it is a simple method and checks the RoleName to see if it equals "developer".   If it does the "SVN Access" right is added to the results.

Part Four - RightPermission

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;


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;
}

 

Check SubsetUserId and CheckSubsetAuthencation are fairly straight forward.  In Part three - Attributes we added another method to the RightManager.  That is used in the CheckSubsetRole(SecurityElement item). The important line of code is result = RightManager.GetRightsByRoleName(value).Count(r => r.RightName == value) > 0;

RightManager.GetRightsByRoleName(value) returns back a list of rights.  We use a Linq count function to see how many rights in the list have the RightName we are looking for.  While unlikely, it is possible you might have 2 rights with the same name (hey, plan for the unexpected and since we did not build a full RightManager in this article it is possible)So we check to see if the count is greater than 0.

 

        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;
}

 

If you setup the page with the test buttons back in Part three - Attributes you can now go click those buttons.  Now you can assign rights to your class, properties and methods that will raise a SecurityException if the current user does not have the right.

How flexible is this system?  We really once it is in place the only bits that need to be altered are that nagging bit of code in IHttpModule so that the user is always a RightPrincipal and the RightManager.

Ok, so here's a minor rewrite of the context_PostAuthenticateRequest for the IHttpModule

 

        public void context_PostAuthenticateRequest(object sender, EventArgs e)
{
HttpApplication context = (HttpApplication)sender;
if (context.User is RightPrincipal) return;
List<Right> rights = null;
if ((context.User != null) && (context.User.Identity.IsAuthenticated))
rights = RightManager.GetRightsByUserName(context.User.Identity.Name);
else
rights = new List<Right>();

RightPrincipal newPrincipal = new RightPrincipal(context.User, rights);
HttpContext.Current.User = newPrincipal;
System.Threading.Thread.CurrentPrincipal = newPrincipal;
}

 

The RightManager should be fully implemented; using a provider model to access the data store to update, create, delete rights as well as assigning them to roles.  In my full RightManager the GetRightsByUserName looks up all the roles assigned to the user and returns all the rights for those roles.  It is done in the data store to keep the workload off the web server and in the database. You could also assign rights directly to users if you wanted too.  The beauty of separating the system that manages rights from the system that secures code by rights is you can change how rights are stored, work or managed and the code security still functions. 

1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。
1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。
1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。
1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。
1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。
1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值