lunes, 5 de mayo de 2008

Implementando roles y permisos en ASP.Net con una clase base

En aplicaciones web por más básicas que sean, seguramente se debe implementar un mecanismo de seguridad que permita o restrinja el acceso de ciertos usuarios a determinadas páginas. De esta manera, podemos tener un rol Administrador y otro rol por default Usuario, en el que el primero podrá ingresar a páginas que un usuario común no debería.

Implementar esto en ASP.Net sin usar Personalization, es relativamente fácil. Se debe verificar en el momento en el que se carga la página si el usuario actual está autenticado o autorizado para acceder a ella. Fácil, pero engorroso. Hacer lo mismo para CADA página no es muy divertido que digamos. Sí, se puede usar una clase que use un método estático al que se le pase el nombre de la página actual y devuelva un valor booleano que me indique si tengo acceso o no. Pero igualmente deberíamos llamarlo en cada página.

Existe otro forma, a mi parecer mil veces más convienente. Hacer una clase base que herede de System.Web.Page, de la cual van a heredar en el codebehind las páginas de la aplicación.

Básicamente, lo que hace esta clase es preguntar “este que quiere acceder, tiene los permisos?”. Si es cierto, accede por supuesto, si no ejecuta una acción predeterminada en la misma clase base, que será seteable con una enumeración. Vamos por partes:

Definmos la enumeración de la acción a ejecutar en caso de acceso denegado:

public enum AccessDeniedEnum
{
    NoDefined,
    RedirectToLoginPage,
    ThrowHttpAccessDeniedException
}


Tendremos un método se llamará en el caso de acceso denegado y que preguntará por el valor de la enumeración y realizará una acción distinta en cada caso. El primer valor es el que tomará por defecto:

private void AccessDeniedAction()
{
    if (this._accessDeniedActionEnm == AccessDeniedEnum.NoDefined)
    {
        // acción por defecto
        this._accessDeniedActionEnm = AccessDeniedEnum.ThrowHttpAccessDeniedException;
    }
 
    switch (_accessDeniedActionEnm)
    {
        case WebGenesis.Utils.AppEnums.AccessDeniedEnum.RedirectToLoginPage:
            Response.Redirect("~/loginPage.aspx", true);
            break;
        case WebGenesis.Utils.AppEnums.AccessDeniedEnum.ThrowHttpAccessDeniedException:
            throw new HttpException(403, "No tiene los permisos necesarios para acceder a esta página");
            break;                
    }
}


También un método virtual (sobreescribible :D) que permite devolver directamente un true en caso de que existan páginas que necesitemos que sean de acceso general y no queremos tener que definir los permisos necesarios para todos, ni tampoco queremos perder el tiempo verficándolo. Entonces nuestra página sobreescribirá este método, y solamente retornará true. Los valores de la colección pageForRole son los nombres de las páginas, del tipo “pagina.aspx”, que el usuario actual puede ver.

protected virtual bool IsAutorizedAccess()
{
    if (Session["PagesForRole"] == null)
    {
        LoadRolePermissions();
    }
 
    //Obtengo las páginas autorizadas para el rol            
    List<string> pagesForRole = (List<string>)Session["PagesForRole"];
 
    string currentPageName = GetPageName();
 
    // Si la página está en la lista, el usuario está autorizado
    if (pagesForRole.Contains(currentPageName))
    {
        return true;
    }
 
    return false;
}


Entonces tendremos un método LoadRolePermissions que cargará las páginas para cada rol. El método está completo en el código final, es lo que habría que modificar para cargar las páginas por rol. Tomando los datos de una base de datos, XML, o de lo que sea. Y toma el usuario suponiendo que está cargado en sesión.

Ahora como se implementa esta clase base en las páginas? Simplemente heredándola en vez de la clase System.Web.Page

public partial class ejemplo1Framework : BaseClasses.BasePage

Si quiero hacer que sea se de público acceso: sobreescribo el método IsAutorizedAccess y retorno true.

protected override bool IsAutorizedAccess()
{
return true;
}

Si quiero cambiar lo “que va a hacer” en caso de que tener permiso denegado, como loquearse nuevamente, o mostrar un mensaje de error, etc, seteo la propiedad AccessDeniedActionEnm en el constructor de la página:

public partial class ejemplo2Framework : WebGenesis.BaseClasses.AbstractGeneralPage
{
public ejemplo2Framework()
{
// si necesito cambiar la acción por default de página denegada, lo hago aquí.
this.AccessDeniedActionEnm = AccessDeniedEnum.RedirectToLoginPage;

Page.Init += new EventHandler(Page_Init);
}
}

Este método que implemento es muy fácil de utilizar, y lo quería compartir. Aquí solo se hizo uso de la autorización de usuarios en esta clase base, pero se puede extender a todas las funcionalidades comunes que tendrán las páginas de nuestra aplicación. Espero que me haya explicado bien, como este es un blog, cualquier duda, crítica o amenaza me comentan. Les dejo el código completo de la clase base abajo. Saludos!

using System;
using System.IO;
using System.Data;
using System.Configuration;
using System.Collections.Generic;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;
 
namespace BaseClasses
{
    /// <summary>
    /// Clase base de la cual heredarán todas las 
    /// páginas de la aplicación.
    /// Gestiona la autorización de usuarios.
    /// </summary>
    public abstract class BasePage : System.Web.UI.Page
    {
        // _enmAccessDenied se va a utilizar para realizar determinada 
        // una acción al intentar acceder a una página a la cual no se tiene acceso.         
        private AccessDeniedEnum _accessDeniedActionEnm;
        private string _pageName;
 
        public enum AccessDeniedEnum
        {
            NoDefined,
            RedirectToLoginPage,
            ThrowHttpAccessDeniedException
        }
 
        #region Properties
 
        public string PageName
        {
            get { return _pageName; }
        }
 
        public Utils.AppEnums.AccessDeniedEnum AccessDeniedActionEnm
        {
            get { return _accessDeniedActionEnm; }
            set { _accessDeniedActionEnm = value; }
        }
 
        #endregion
 
        #region Constructors
 
        public BasePage()
        {
            this.Init += new EventHandler(BasePage_Init);
            this.Load += new EventHandler(BasePage_Load);
        }
 
        #endregion
 
        #region PageEvents
        private void BasePage_Load(object sender, EventArgs e)
        {
        }
 
        private void BasePage_Init(object sender, EventArgs e)
        {
            if (!IsAutorizedAccess())
            {
                this.AccessDeniedAction();
            }
 
            _pageName = GetPageName();
        }
 
        #endregion
 
        #region Virtual Methods
 
        protected virtual bool IsAutorizedAccess()
        {
            if (Session["PagesForRole"] == null)
            {
                LoadRolePermissions();
            }
 
            //Obtengo las páginas autorizadas para el rol            
            List<string> pagesForRole = (List<string>)Session["PagesForRole"];
 
            string currentPageName = GetPageName();
 
            // Si la página está en la lista, el usuario está autorizado
            if (pagesForRole.Contains(currentPageName))
            {
                return true;
            }
 
            return false;
        }
 
        #endregion
 
        #region Private Methods
        /// <summary>
        /// Acción que se ejecuta al intentar ingresar a una página
        /// a la que no se tiene acceso.
        /// La acción se puede modificar en el constructor
        /// de cada página.
        /// </summary>
        private void AccessDeniedAction()
        {
            if (this._accessDeniedActionEnm == AccessDeniedEnum.NoDefined)
            {
                // acción por defecto
                this._accessDeniedActionEnm = AccessDeniedEnum.ThrowHttpAccessDeniedException;
            }
 
            switch (_accessDeniedActionEnm)
            {
                case WebGenesis.Utils.AppEnums.AccessDeniedEnum.RedirectToLoginPage:
                    Response.Redirect("~/loginPage.aspx", true);
                    break;
                case WebGenesis.Utils.AppEnums.AccessDeniedEnum.ThrowHttpAccessDeniedException:
                    throw new HttpException(403, "No tiene los permisos necesarios para acceder a esta página");
                    break;
            }
        }
 
        private void LoadRolePermissions()
        {
            //cargo en una lista las páginas autorizadas
            //para el rol
            List<string> pagesForRole = new List<string>();
 
            pagesForRole.Add("default.aspx");
            Session.Add("PagesForRole", pagesForRole);
 
            // implemento tomando los permisos desde la BD
            DataSet ds = new DataSet();
 
            //TODO hago algo inteligente aquí...
            if (ds.Tables.Count < 1)
                return;
            foreach (DataRow dr in ds.Tables[0].Rows)
            {
                pagesForRole.Add(dr["pageName"].ToString());
            }
            Session.Add("PagesForRole", pagesForRole);
            //
 
            return;
        }
 
        private string GetPageName()
        {
            //Obtengo el nombre de la página actual            
            FileInfo fi = new FileInfo(this.Page.Request.FilePath);
            return fi.Name.ToLower();
        }
 
        #endregion
    }
 
}

7 comentarios:

errr dijo...

Hey, vi un post tuyo en psicofxp, disculpame q te joda por este medio pero necesito consultarte una cosita!, podes agregarme al msn ? zoldi3r@hotmail.com

gracias!

abrozas dijo...

Muy buen post, he encontrado este sitio de casualidad, pero me parece muy interesante para los que empezamos con .NET y venimos de otras plataformas.

Una pena que haga mucho que no postees nada. Esperemos tener más cositas.

Anónimo dijo...

me podes explicar que es la webgenesis?..

Anónimo dijo...

Muchas gracias por la info! :):):):):):)

Anónimo dijo...

que significa el genesis?? :(

Oscar dijo...

Si, que es webgénesis? buen código, gracias

MLM Software dijo...

Nice..

Pooja

MLM Developers India

http://mlmdevelopers.com/products/mlm-software/corporate-mlm-soft/feature.html


Artículos relacionados