We have a fully working sitemap with many hundreds of nodes configured with sitemap attributes on the actions. These nodes are are security trimmed working perfectly based on claims. All working great and fast.
We now have a requirement that certain pages are inaccessible based on a route value. Basically hiding mvc menu links based on the value of personId in the route. Using the code below:
//Just proof of concept - people restricted will be from a service
public class PersonAuthorizeAttribute : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (!httpContext.Request.RequestContext.RouteData.Values.ContainsKey("personId"))
{
return base.AuthorizeCore(httpContext);
}
var personId = httpContext.Request.RequestContext.RouteData.Values["personId"].ToString();
int value;
int.TryParse(personId, out value);
if (value != 0 && value == 3708)
{
return false;
}
return base.AuthorizeCore(httpContext);
}
}
This works perfectly in terms of preventing access. However unlike the rest of our security trimming sitemap doesn't work with this. If I visit the person that is restricted first it hides the node for that person and everyone else. If I visit a non hidden person then the node is visible for everyone but I get a denied access request for the person if I try to visit their node.
I assume it is related to the fact that a node doesn't have a concept of trimming based on route values.
Any ideas how this could be implemented. We are trying to implement a more flexible security model.
There is a disconnect here because of the difference between the way MVC and MvcSiteMapProvider uses the AuthorizeAttribute.
In MVC, the AuthorizeAttribute is simply checked against the context of the current request. The current context contains everything that is needed to determine whether the user is authorized to view the current page.
MvcSiteMapProvider checks every node for each request. Therefore, we can't make the same assumptions that the current context is correct for determining that a node is accessible. There is a new temporary HttpContext created for each node (based on the node's generated URL) and the route values used during the check are derived from that context (not the current context).
This fake HttpContext is not perfect. Microsoft has created several redundant properties HttpContext, RequestContext, etc. that must be explicitly set or they will default to the current context, and not all of them have been set by the AuthorizeAttributeAclModule. This was done intentionally in this case because we want the current request (current user) to be checked when it comes to security.
As a result, your check is always using the route values from the current HttpContext, not the fake context that is created based on the node's URL. To use the fake context, you need to override OnAuthorization and use the RequestContext there.
public override void OnAuthorization(AuthorizationContext filterContext)
{
var personId = filterContext.RequestContext.RouteData.Values["personId"];
base.OnAuthorization(filterContext);
}
The real issue here is that (if you are using preserved route parameters) you are basing your security for the entire site on a route value which only applies to the currrent request. What is supposed to happen when your request changes to something that doesn't include the personId? Should the nodes all be invisible? Should they all be visible? What if the user changes the route value by changing the URL manually? There are a lot of cracks in this security model.
Important: Microsoft has said time and again the importance of not basing MVC security on the URL. The route values are just an abstraction of the URL, but don't really change the fact that they are based on the URL. It is virtually impossible to guarantee that an action method will only have a single route that can access it, which is why AuthorizeAttribute was created. AuthorizeAttribute secures the resource (action method) at its source so it can't be defeated by these alternate routes. Basing it on routes entirely defeats its purpose. In short, by including a route value in AuthorizeAttribute, you are opening up the possibility that your application can be hacked by an unintended alternate route to the action method. You are not simplifying security by basing AuthorizeAttribute on routes, you are making it more complex and nearly impossible to completely control over time.
It would be better to base your security on the IPrincipal and IIdentity interfaces that are part of every form of security that plugs into MVC. In this particular case, there is a user property that is already supported by the AuthorizeAttribute. There is no built-in way to add a not user, but that functionality could easily be added to your custom AuthorizeAttribute.
Related
In my MVC application I have Player and Coach objects, and a user can be one or the other. I also have Team objects and what I want to know is how I prevent a user who is in a list of Players or is the Coach of a Team gaining access to a route like /Teams/Details/2 where 2 is the id of a team other than that which he/she is part of.
Thanks in advance!
Since you want to restrict an id that they aren't a part of, this seems like a situation where you can Inherit from the AuthorizeAttribute and provide your implementation for AuthorizeCore
Your implementation could check their role/team id and decide what to do / redirect.
public class TeamAuthorize : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
return UserIsInTeam(httpContext); //whatever code you are using to check if the team id is right
}
}
You can now apply it like any other attribute.
[TeamAuthorize]
The very simplest solution would be to change the URLs from using IDs to random GUIDs. You would almost eliminate the chance of someone guessing another valid value. Of course, this is not secure by definition (mostly because someone could get the other URL from history or another source), but in some scenarios this is enough.
A better solution is to create a new attribute based on IActionFilter interface that implements OnActionExecuting method and checks the ID by using this.ControllerContext.HttpContext.User.Identity.Name and this.RouteData.Values["id"]. You will then apply this attribute to your controller methods.
In our current system we implemented row level security in controller methods by just adding the code that verifies the user permissions as the first line in each method. The checking code is the same as with the attribute and it requires the same amount of code to add. This approach has one additional benefit - it is easier to implement scenarios like where a coach would be able to see the details of other teams but not modify them (we have one action and view for both reading and updating depending on permissions).
You would also use the last approach if you need to go to the database to check the permissions and you are using IoC frameworks such as Ninject with constructor based injection - since you will not have access to those values in the attribute.
I have a problem with my MVC 4.0/Razor site.
It's a (not yet launched) public site that I recently inherited.
90% of all pages should be available to everyone, the rest are for superusers and need authentication.
This is handled via an AllowAnonymous attribute on the public facing pages, implemented like this;
public class RequireAuthenticationAttribute : AuthorizeAttribute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
var skipAuthorization = filterContext.ActionDescriptor.IsDefined(typeof(AllowAnonymousAttribute), true) ||
filterContext.ActionDescriptor.ControllerDescriptor.IsDefined(
typeof(AllowAnonymousAttribute), true);
if (!skipAuthorization)
base.OnAuthorization(filterContext);
}
}
Now, the problem is that I want a few customizations of the public facing sites (for the sake of argument, let's assume a "Currently logged in: XYZ"-label somewhere). What I've tried is using the User.Identity, but on all pages with AllowAnonymous, User.Identity.Name == "", even though a super user did log in. (And if he changes the url to a page with authentication, he's logged in again, and User.Identity.Name is correct).
Is there any way to both use Allow Anonymous and keep track of who's logged in?
I think I have solved this.
The problem was our custom subdomain routing. I had to override the "domain" setting so it points to .example.com instead of example.com for the cookies (mainly aspxauth).
The reason this took some time to realize was a number of other custom made parts, potentially interfering in the application, especially:
Custom membership provider
Custom AllowAnonymous attribute (even when there's a standard attribute now)
And the fact that I thought this was the normal behavior when in fact it's not.
Summary:
If implementing a subdomain routing rule AND need authentication that follows along the subdomains, you must change the base domain of the cookies.
I have the following ntier app: MVC > Services > Repository > Domain. I am using Forms authentication. Is it safe to use Thread.CurrentPrincipal outside of my MVC layer to get the currently logged in user of my application or should I be using HttpContext.Current.User?
The reason I ask is there seems to be some issues around Thread.CurrentPrincipal, but I am cautious to add a reference to System.Web outside of my MVC layer in case I need to provide a non web font end in the future.
Update
I have been following the advice recieved so far to pass the username into the Service as part of the params to the method being called and this has lead to a refinement of my original question. I need to be able to check if the user is in a particular role in a number of my Service and Domain methods. There seems to be a couple of solutions to this, just wondering which is the best way to proceed:
Pass the whole HttpContext.Current.User as a param instead of just the username.
Call Thread.CurrentPrincipal outside of my web tier and use that. But how do I ensure it is equal to HttpContext.Current.User?
Stick to passing in the username as suggested so far and then use Roles.IsUserInRole. The problem with this approach is that it requires a ref to System.Web which I feel is not correct outside of my MVC layer.
How would you suggest I proceed?
I wouldn't do either, HttpContext.Current.User is specific to your web layer.
Why not inject the username into your service layer?
Map the relevant User details to a new Class to represent the LoggedInUser and pass that as an argument to your Business layer method
public class LoggedInUser
{
public string UserName { set;get;}
//other relevant proerties
}
Now set the values of this and pass to your BL method
var usr=new LoggedInUser();
usr.UserName="test value "; //Read from the FormsAuthentication stuff and Set
var result=YourBusinessLayerClass.SomeOperation(usr);
You should abstract your user information so that it doesn't depend on Thread.CurrentPrincipal or HttpContext.Current.User.
You could add a constructor or method parameter that accepts a user name, for example.
Here's an overly simplified example of a constructor parameter:
class YourBusinessClass
{
string _userName;
public YourBusinessClass(string userName)
{
_userName = userName;
}
public void SomeBusinessMethodThatNeedsUserName()
{
if (_userName == "sally")
{
// do something for sally
}
}
}
I prefer option number 2( use Thread.CurrentPrincipal outside of web tier ). since this will not polute your service tier & data tier methods. with bonuses: you can store your roles + additional info in the custom principal;
To make sure Thread.CurrentPrincipal in your service and data tier is the same as your web tier; you can set your HttpContext.Current.User (Context.User) in Global.asax(Application_AuthenticateRequest). Other alternative location where you can set this are added at the bottom.
sample code:
//sample synchronizing HttpContext.Current.User with Thread.CurrentPrincipal
protected void Application_AuthenticateRequest(Object sender, EventArgs e)
{
HttpCookie authCookie = Request.Cookies[FormsAuthentication.FormsCookieName];
//make sure principal is not set for anonymous user/unauthenticated request
if (authCookie != null && Request.IsAuthenticated)
{
FormsAuthenticationTicket authTicket = FormsAuthentication.Decrypt(authCookie.Value);
//your additional info stored in cookies: multiple roles, privileges, etc
string userData = authTicket.UserData;
CustomPrincipal userPrincipal = PrincipalHelper.CreatePrincipal(authTicket.Name, authTicket.UserData, Request.IsAuthenticated);
Context.User = userPrincipal;
}
}
of course first you must implement your login form to create authorization cookies containing your custom principal.
Application_AuthenticateRequest will be executed for any request to server(css files, javascript files, images files etc). To limit this functionality only to controller action, you can try setting the custom principal in ActionFilter(I haven't tried this). What I have tried is setting this functionality inside an Interceptor for Controllers(I use Castle Windsor for my Dependency Injection and Aspect Oriented Programming).
I believe you are running into this problem because you need to limit your domains responsibility further. It should not be the responsibility of your service or your document to handle authorization. That responsibility should be handled by your MVC layer, as the current user is logged in to your web app, not your domain.
If, instead of trying to look up the current user from your service, or document, you perform the check in your MVC app, you get something like this:
if(Roles.IsUserInRole("DocumentEditorRole")){
//UpdateDocument does NOT authorize the user. It does only 1 thing, update the document.
myDocumentService.UpdateDocument(currentUsername, documentToEdit);
} else {
lblPermissionDenied.InnerText = #"You do not have permission
to edit this document.";
}
which is clean, easy to read, and allows you to keep your services and domain classes free from authorization concerns. You can still map Roles.IsUserInRole("DocumentEditorRole")to your viewmodel, so the only this you are losing, is the CurrentUserCanEdit method on your Document class. But if you think of your domain model as representing real world objects, that method doesn't belong on Document anyway. You might think of it as a method on a domain User object (user.CanEditDocument(doc)), but all in all, I think you will be happier if you keep your authorization out of your domain layer.
Let's suppose I don't want to use Membership and want to restrict user's access with ActionFilter.
I know I can create a filter/attribute and override the OnActionExecuting method and further I can put this attribute in a ActionResult.
And let's assume that I have a table named 'tbUsers', it has also an int field named 'certificate' and depending on this 'certificate' value, an user can access an ActionResult or not.
But, how can I, in a OnActionExecuting mehod, check this user's 'certificate' value and grant his access or redirect to a 'NotAllowed.aspx' page?
Thanks!!!
I would not do it this way. I would implement an IAuthorizationFilter. Authorization filters run before all action filters.
For example, suppose you later put an OutputCache attribute on the action method and it happens to run before your authentication filter. That would be bad! If the content is cached, the authentication filter would never run and people would see cached sensitive data.
The ActionExecutingContext has the HttpContext which would include the current User object. You can use this to get the User. You can also use it to access the Session if you wanted to store the information in the Session. You could also put it in an encrypted cookie and access those through the Request.Cookies on the context. You'd want to think about the security implications of that, though I don't see it as being more problematic than the auth cookie.
Checking it against the database and maintaining testability on your filter is a little more tricky. What I've done is to provide two constructors for each filter class. One provides a database factory that will create my data context. I use this in testing and supply a mock database factory that produces a mock or fake database. The normal, parameterless constructor calls the previous constructor with a null factory. When this happens the other constructor creates a new instance of the default factory.
private IDatabaseFactory Factory { get; set; }
public MyFilter( IDatabaseFactory factory )
{
this.Factory = factory ?? new DefaultDatabaseFactory();
}
public MyFilter() : this(null) { }
Where do I get information about the currently connected user? That is, how does shibboleth pass the information?
Can I set some restrictions on actions using [Authorize] attribute based on data acquired from shibboleth?
Shibboleth publishes user attributes associated with
sessions into HTTP request headers, based on header names defined
in Attribute Acceptance Policy (1.3.x) or Attribute Mapping (2.x)
files. These headers are transformed into CGI variables based
on mapping rules defined by the CGI specification.
You should be aware of this security advisory:
http://shibboleth.net/community/advisories/secadv_20090615.txt
I have never user shibboleth, but you can get information about the user from Controller.User property. It will return a generic principal of current thread. Using this principal you can check whether the user is authenticated and get a login name of the user. This is due to the reason that after logon an authentication cookie is set and this cookie contains limited amount of information. And on each request after logon only this cookie is checked (if it exists and valid - user is authenticated).
So if you need in some specific information you can manually load a user (it's better to use cache here) and check whatever you want.
Also you can create and attach your own principal with necessary information to the thread on start of a request (e.g. on start of a request load the user from db/cache using user name from base principal, create and set your own principal to thread). After this you can check all properties of the user you need.
Where would you attach your own principal? You say on the start of the request but what if you don't want every request authorizing?
You'll want to create a method in Global.asax.cs that has the following signature
protected void Application_PostAuthenticateRequest()
{
//Your code here.
}
This will be called automatically before almost anything else is done (MVC will call this method if it exists, you don't have to "turn it on" anywhere), and this is where you need to set the Principal. For instance, let's assume you have a header called RolesHeader that has a comma separated value of roles and another header called UserId that has (duh) the user ID.
Your code, without any error handling, might look something like:
protected void Application_PostAuthenticateRequest()
{
var rolesheader = Context.Request.Headers["RolesHeader"];
var userId = Context.Request.Headers["UserId"];
var roles = rolesheader.Split(',');
var principal = new GenericPrincipal(new GenericIdentity(userId), roles);
Context.User = principal;
}
It's the Principal/Identity that the [Authorize] attribute uses, so setting it here at the beginning of the request lifecycle means the [Authorize] attribute will work correctly.
The rest of this is optional, but I recommend it:
I like to create my own custom classes that implement IPrincipal and IIdentity instead of using the GenericPrincipal and GenericIdentity, so I can stuff more user information in it. My custom Principal and Identity objects then have much more rich information, such as branch numbers or email addresses or whatever.
Then, I create a Controller called BaseController that has the following
protected new CustomPrincipal User
{
get
{
return (base.User as CustomPrincipal) ?? CustomPrincipal.GetUnauthorizedPrincipal();
}
}
This allows me to access all my rich, custom Principal data instead of just what's defined in IPrincipal. All of my real controllers then inherit from BaseController instead of directly from Controller.
Obviously, when using a custom Principal like this, in the Application_PostAuthenticateRequest() method, you'd set the Context.User to be your CustomPrincipal instead of a GenericPrincipal.