Search code examples
c#asp.net-mvcasp.net-authorization

Html element visibility based on permissions, not roles in MVC


I've seen many examples where html element visibility can be toggled in the cshtml file using something like:

if (User.IsInRole("Admin"))
{
    <input type="button" value="Save" />
}

However, is it possible to implement something like:

// User has update permission to User objects
User.HasPermission("User", "Update")
{
    <input type="button" value="Save" />
}

I'd want to use both of these in my MVC application, where roles are associated with a number of permissions and permissions can be individually given to users as well.

One way to do this is to overwrite System.Security.Principal and System.Security.Identity, and in Global.asax Application_AuthenticateRequest method set:

object user = HttpContext.Current.Cache.Get(authTicket.Name);

MyIdentity identity = new MyIdentity((MyUser)user, true);
MyPrincipal principal = new MyPrincipal(identity);
Context.User = principal;
Thread.CurrentPrincipal = principal;

And then in the cshtml file use:

MyIdentity identity = (MyIdentity)User.Identity;
if (identity.User.HasClaim("User", "Update"))
{
    <input type="button" value="Save" />
}

Problem with this approach is that I have to do that cast to MyIdentity everywhere I need to check for a permission.

Is there a better and more efficient way to do this? My target is MVC3 or MVC4.

--

EDIT:

Based on DavidG's suggestion I started looking at extension methods. This is what I came up with:

public static class PrincipalExtensions
{
    public static MyIdentity MyIdentity(this IPrincipal principal)
    {
        return principal.Identity as MyIdentity;
    }
}

How do I use it though? In some examples I saw people changing their web.config - tried - or meddling with controllers and controllerFactory, and global.asax code. So far I don't get the MyIdentity to be usable in my cshtml file.

--

EDIT2:

Some more progress: I used the instructions here: http://rizzo7.blogspot.fi/2012/04/mvc-30-razor-custom-principal-and.html

And got it - sort of - working, Now I can use a syntax like:

if (User.MyIdentity().User.HasClaim("User", "Update"))
{
    <input type="button" value="Save" />
}

However, in Visual Studio I get an error stating:

System.Security.Principal.IPrincipal' does not contain a definition for 'MyIdentity' and no extension method 'MyIdentity' accepting a first argument of type 'System.Security.Principal.IPrincipal' could be found (are you missing a using directive or an assembly reference?)

In my View's Web.config I have:

  <system.web.webPages.razor>
<host factoryType="System.Web.Mvc.MvcWebRazorHostFactory, System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
<pages pageBaseType="System.Web.Mvc.WebViewPage">
  <namespaces>
    <add namespace="System.Web.Mvc" />
    <add namespace="System.Web.Mvc.Ajax" />
    <add namespace="System.Web.Mvc.Html" />
    <add namespace="System.Web.Routing" />
    <add namespace="namespace.of.PrincipalExtensions"/>
  </namespaces>
</pages>

Despite the error, the project compiles and runs correctly. It would be a pain to develop and debug while getting an error for every visibility check. Is there anything I can do about this?

--

EDIT-Final!

Thanks everyone, I'll answer that last question I had myself; Just need to make sure the cshtml sees the namespace of the extension class. :)

I marked DavidG's answer as it got me searching from the right place and gave me most that I needed. Thanks!


Solution

  • You could tidy it up with an extension method:

    public static MyIdentity MyIdentity(this IPrincipal user)
    {
        return (MyIdentity)User.Identity;
    }
    

    Or an HTML helper:

    public static MyIdentity MyIdentity(this HtmlHelper helper)
    {
        return (MyIdentity)User.Identity;
    }