Missing HttpContext.Items for some actions - asp.net-mvc

Background:
MVC3 intranet application using windows authentication. After windows authentication completes, a HttpModule looks up the user's network id from an HR database and returns the user's employee information and sets it in HttpContext.Items. I have a base controller that looks for this information and sets a ViewBag property by overriding OnActionExecuting.
My question is that this HttpContext.Items["UserInfo"] information only seems to be available on Home/Index only and not available when I click to Home/About or Home/Help although HomeController inherits BaseController. Can anyone shed light on why this is happening?
protected override void OnActionExecuting(ActionExecutingContext ctx)
{
if (this.HttpContext.Items["UserInfo"] != null)
{
UserInfo User = (UserInfo)this.HttpContext.Items["UserInfo"];
ViewBag.CurrentUser = User;
}
base.OnActionExecuting(ctx);
}

HttpContext.Items is per request only; it is not retained when you redirect to another view or even post back within the current view. So you need to use Session or something else to persist it.

Related

MVC Limit / Redirect Role to Area

I'm trying to figure out how to do this and I can't seem to arrive at an idea to try.
I have already created a filter to redirect authenticated requests, so an authenticated user cannot revisit the login page for instance. That's all fine and dandy.
However there is one role in the system that only has access to a single area within the application, any requests to other controllers in the main area or other areas should redirect the user to this area.
I don't just want to show an unauthorised message to these users if they attempt to view anything outside of their area I would prefer to just redirect them back to their area.
what are the different ways to achieve this goal (please include pros and cons)?
EDIT
Just to be clear about this the reason I'm looking to do this is mop up potential edge cases where a user of a particular role enters a url from browser history or manually which coule take them to another area of the application they shouldn't be accessing.
For the default area of the application where the base role is User it is sufficient to just use the basic AuthorizeAttribute which just ensures requests are authorized (as most users will be granted a user role on registration.
For other areas there are specific roles which will correctly show a 401 unarthorized page to a user without the given role.
Where in this special case a certain type of user is created with a specific role and NOT provided with the basic User role but at the same time their requests will be deemed as Authorized.
Probably the easiest and most effective way is to create a custom AuthorizationAttribute that is specific to your needs:
public class ThatSpecificRoleAttribute : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
// not sure what is EXACTLY needed so here is all the stuff I
// think someone might need
var rd = httpContext.Request.RequestContext.RouteData;
string currentAction = rd.GetRequiredString("action");
string currentController = rd.GetRequiredString("controller");
string currentArea = rd.Values["area"] as string;
if (httpContext.User != null
&& httpContext.User.Identity != null
&& httpContext.User.Identity.IsAuthenticated
&& httpContext.User.IsInRole("ThatSpecificRole")
&& currentController != "Home")
{
// alternatively you could also...
// httpContext.Controller.TempData["ThatSpecificRoleError401"] = true;
return false;
}
return true
}
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
filterContext.Result = new RedirectToAction("Index", Home);
}
}
Then register it globally (making sure it is the first auth attribute added):
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new ThatSpecificRoleAttribute());
}
}
I know someone else already mentioned something like this, but this is very specific (and multiple authorization attributes are supported by MVC, fyi).
Pros:
You don't need to worry if someone added this to controllers or methods, it is global
It does only what you need, everyone else (anonymous and people without the role) use the system normally.
This will redirect them, and you could see if the TempData exists and give them a nice message letting them know they were redirected (option).
Cons:
Every request will check this authorization. It should be cached so the performance hit is very minimal but still exists.
I've had a similar problem, and I ended up using a little hack.
When you return a HttpUnauthorized result, the user is automatically redirected to the Login-page, defined in the Web.config (in the section
However, in stead of redirecting to the login-page, I made a redirect to an different action. In this action, I check if the user is actually logged in. If so, I check what area he can view, and redirect the user to that area. If no area is found, or if the user is not logged in at all, I manually redirect to the login-page.
Hope this helps!

Prevent Url Tampering to access another users data

I just wanted to gauge opinions on how I should approach this problem and ultimately looking for a quick win (wrong way to think about things nut time pressures mean I have to think and act quickly!
I've been given a website that has a bit of an issue.
I login using standard forms authentication as User1234 and my url is as follows:
www.mywebsite.co.uk/1234/Contact.
This will take me to User1234's details.
You can put two and two together and correctly assume that 1234 is a user id of some sort.
Once authenticated, I can access the views with [Authorize] attribute present, any anonymous/unathenticated users get redirected.
However, once logged in as User1234, I can then tinker with the url like so:
www.mywebsite.co.uk/1235/Contact.
So I am authenticated as User1234 but can see User1235's data. This is BAD for obvious reasons.
When I log in, I actively set the login ID in session so in theory, I could do a check whenever a user hits an ActionResult, I could cross check the ID present in the URL against the session login ID. However, it is a rather project with lots of action results and as such, I'm reluctant to spend my Saturday afternoon adding something to each and every ActionResult.
Is there an event in the global.asax I could use that is hit on each ActionResult request where I can compare Session login ID with url ID?
Alternatively, can anyone offer some suggestions about how I can achieve this or restrict URL tampering?
You can try and do a base controller
public class BaseController : Controller
{
protected override void OnActionExecuted(ActionExecutedContext filterContext)
{
//Do your stuff here
base.OnActionExecuted(filterContext);
}
}
I assume that you don't want to change your URL routes, as you could retrieve the user id also from the session. A quick solution would be to use an ActionFilter which you can place on the affected controllers or action methods:
public class VerifyUserIdAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var sessionUserId = filterContext.HttpContext.Session["UserId"];
var routeUserId = filterContext.RouteData.Values["UserId"];
if (routeUserId != null && sessionUserId == routeUserId)
filterContext.Result = new RedirectResult("<<url to redirect to>>");
}
}
I don't understand why the URL contains a data entry point. This appears to be a design flaw. I would remove all code that uses a URL parameter and instead make sure the controller looks up what the ID is based on the logged in user.

Supply user specific dependencies to MVC controller with Ninject

I have some controllers that require a web service connection (an instance of MS Dynamics CRM CrmService) and I would like the controllers to receive this through their constructors. The CRM service has to be set up with a token that is based on who the currently logged in user is (when the user logs in the application authenticates against the CRM and can store the returned token in Session).
I'm not sure how best to supply this instance using Dependency Injection and Ninject. It seems a bit rubbish for the Ninject ToMethod() Func<> to access FormsAuth/Session for the current request (to obtain the token if authenticated) to create the appropriate instance. I'm also not sure what should happen if the user is not authenticated - I don't need these users be able to access the controller but the controller will be instantiated before any filters like [Authorize] will run so the dependency will always have to be met. From what I have read returning null is not ideal and I would have to change the Ninject configuration to do this anyway.
I was thinking that maybe the controller could get an instance of ICrmServiceFactory or something but that doesn't help me if the controllers end up having other dependencies which also rely on CrmService directly (and don't want to be passed a factory).
Any advice on how to solve this would be appreciated.
I usually set up a binding for IPrincipal:
kernel.Bind<IPrincipal>().ToMethod(c => HttpContext.Current.User);
Never really had a problem with this approach.
If I understand your question correctly then your controller has a dependency to CrmService and the CrmService requires some token stored in the session.
In that case you could inject a CrmTokenProvider to CrmService and add a property to that class which gets the value from the session whenever it is requested by the CrmService.
public class CrmService
{
public CrmService(CrmTokenProvider tokenProvider)
{
this.tokenProvider = tokenProvider;
}
public void DoSomeWork()
{
...
this.tokenProvider.Token;
...
}
}
I have ended up implementing this as follows:
private static void RegisterServices(IKernel kernel)
{
kernel.Bind<CrmService>()
.ToMethod(context =>
{
//return unauthenticated instance if user not logged in.
if (!HttpContext.Current.User.Identity.IsAuthenticated) return new CrmService();
return GetConnection(HttpContext.Current);
})
.InRequestScope();
}
private static CrmService GetConnection(HttpContext ctx)
{
//get stuff out of session and return service
}

What is Webform's "UrlAuthorizationModule.CheckUrlAccessForPrincipal" equivalent for MVC?

I got a problem as i am writing a custom SSO solution for my company. To mkae it simple, i've made a custom authentication httpmodule that intercepts all requests so as to check user authentication state. If not authenticated, user is redirected to my custom sso login page.
The thing is, when user is not authenticated, i'd like to check if he can access the requested page/resource... With Webforms, no problem, i add an authorization block in web.config, and i use UrlAuthorizationModule.CheckUrlAccessForPrincipal with an anonymous user. Everything works fine...
But when i apply my module to an MVC (3) web site, this does not work anymore (for obvious reasons, like the possibility to access the same controller and/or action from differents urls when using routing, and because authorizations are made through controller attributes).
How can I achieve this ?? I've been searching all day long, didn't find anything about that :/
ASP.NET MVC 3 Internet Application template includes a basic AccountController which implements the following actions (along with the associated models and views):
LogOn
Register
ChangePassword / ChangePasswordSuccess
You simply need the [Authorize] attribute on the Actions or classes you wish to secure. But if you need something really custom you can do something like I've done.
I created a custom class to override security in my application.
public class AuthorizeActivityAttribute : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
HttpContext currentContext = HttpContext.Current;
//Do your custom authentication stuff here and return true or false depending on results
Return true;
}
}
And now in my Controller I have the following:
[AuthorizeActivity]
public ActionResult Index()
{
ViewBag.Message = "Welcome";
return View();
}
I had the same problem.
See solution here: MVC equivalent of Webforms "UrlAuthorizationModule.CheckUrlAccessForPrincipal"
You would have to read the information from the other controller. This
can be done by instantiating its context and the Descriptor, then
instantiating the AuthorizationContext for that controller and read
the filter info.

Obtaining the current Principal outside of the Web tier

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.

Resources