marți, 5 ianuarie 2010

Using LINQ to XML to create an ASP.NET XML Roles Provider

Since Authorization and Authentication are important aspects of websites, ASP.NET provides good support for these: ASP.NET 2.0 introduced the Membership and Roles provider model, and offers some really useful built-in providers: SQL Membership Provider, SQL Roles Provider, Windows Token Roles Provider, etc.

However, not every website is that complex to make use of these heavy components: take for instance the proof of concepts.
For the SQL providers we need to setup a database (option that may not be always available), to create pages for users and roles management, that also require some time to be developed. The Windows/Active Directory providers would only work for enterprise applications that usually require some intranet infrastructure.
Therefore, sometimes we need a more simple solution to store users and roles, like a XML file, but also make use of the great Membership and Roles provider model.

The benefits of a XML Roles Provider are quite simple: simple to implement it, to manage roles and user-roles, to deploy and configure. It is very simple to manage roles and user-roles: just manually edit the XML file. As already mentioned before, this is only suited for simple websites, where there is a limited set of users. The XML Roles Provider would work with Windows Authentication, but also with Forms Authentication and a custom XML Membership Provider.

To implement the XML Roles Provider, just create a new Roles Provider, by inheriting from the System.Web.Security.RoleProvider abstract class and override the abstract methods. For minimum authorization functionality, just add some actual implementation to these basic methods: public override string[] GetRolesForUser(string username) and public override bool IsUserInRole(string username, string roleName).

The XML Roles Provider uses .NET 3.5 LINQ To XML for querying data from an XML file located in the “/App_Data” folder. The XML file is not accessible via HTTP to users, since IIS restricts access to this ASP.NET folder. Since we would want the XML Roles Provider to get updated when the XML file is edited, one option is to use the FileSystemWatcher to monitor the changes occurred on the XML file.

XML file structure used by the XML Roles Provider

<?xml version="1.0" encoding="utf-8" ?>
<
roles>
  <
role roleName="Administrator">
    <
user>Admin</user>
    <
user>Mike</user>
  </role>
  <
role roleName="Manager">
    <user>Lucy</user>
  </role>
  <
role roleName="Customer">
    <user>Alex</user>
    <user>George</user>
    <user>Jan</user>
    <user>Mary</user>
  </role>
</
roles>

The XML Roles Provider Implementation

public class XmlRoleProvider : RoleProvider
{
    public override void AddUsersToRoles(string[] usernames, string[] roleNames)
    {
        throw new NotImplementedException();
    }

    public override string ApplicationName
    {
        get { throw new NotImplementedException(); }
        set { throw new NotImplementedException(); }
    }

    public override voidCreateRole(string roleName)
    {
        throw new NotImplementedException();
    }

    public override bool DeleteRole(string roleName, bool throwOnPopulatedRole)
    {
        throw new NotImplementedException();
    }

    public override string[] FindUsersInRole(stringroleName, stringusernameToMatch)
    {
        throw new NotImplementedException();
    }

    public override string[] GetAllRoles()
    {
        throw new NotImplementedException();
    }

    public override string[] GetRolesForUser(string username)
    {
        var theResult = from p inDocument.Root.Elements("role").Elements("user")
                        where p.Value == username
                        select p.Parent.Attribute("roleName").Value;

        return theResult.ToArray();
    }

    public override string[] GetUsersInRole(string roleName)
    {
        throw new NotImplementedException();
    }

    public override bool IsUserInRole(string username, string roleName)
    {
        var userRoles = this.GetRolesForUser(username);
        var theResult = userRoles.Contains(roleName);
        return theResult;
    }

    public override void RemoveUsersFromRoles(string[] usernames, string[] roleNames)
    {
        throw new NotImplementedException();
    }

    public override bool RoleExists(string roleName)
    {
        throw new NotImplementedException();
    }

    private FileSystemWatcher _watcher;
    private XDocument _document;
    private object _docLock = new object();

    public XDocument Document
    {
        get
       
{
            if (this._document == null)
            {
                string path = HttpContext.Current.Server.MapPath("~/App_Data/roles.xml");

                //Perform some locking:
               
lock (this._docLock)
                {
                    if (this._document == null)
                    {
                        this._document = XDocument.Load(path);
                    }
                }

                if (this._watcher == null)
                {
                    this._watcher = new FileSystemWatcher(HttpContext.Current.Server.MapPath("~/App_Data"), "*.xml");
                    this._watcher.NotifyFilter = NotifyFilters.LastWrite;
                    this._watcher.Changed += new FileSystemEventHandler(watcher_Changed);
                    this._watcher.EnableRaisingEvents = true;
                }
            }

            return this._document;
        }
    }

    void watcher_Changed(object sender, FileSystemEventArgs e)
    {
        lock (this._docLock)
        {
            this._document = null;
        }
    }
}

2 comentarii: