I'm in the process of upgrading a legacy app that I inherited from MVC3 to MVC5 and somehow I broke the HTTPContext.User object.
The app has a custom authentication mechanism that seems to be working correctly as it returns a proper User object to the rest of the pipeline. Specifically, in Global.asax.cs:
protected void Application_BeginRequest(object sender, EventArgs args)
{
if (HttpContext.Current.User != null)
{
if (HttpContext.Current.User.GetType().FullName.Equals("Citation.AMS.Users.UserContext", StringComparison.OrdinalIgnoreCase))
{
try
{
using (IAuditor auditor = CoreFactory.AuditorCreate())
{
auditor.WriteAudit(HttpContext.Current, "Compliance");
}
}
catch (Exception ex)
{
using (ILogger log = CoreFactory.LoggerCreate())
{
log.WriteFatalError("Exception in Application_BeginRequest::Audit.", ex);
}
}
The check for HTTPContext.Current.User returns the correct object above. But by the very next method call:
protected void Application_AcquireRequestState(object sender, EventArgs args)
{
CultureInfo ci = null;
HttpCookie cookie = Request.Cookies[CookieHelper.CookieName];
if (cookie != null && !string.IsNullOrWhiteSpace(cookie.Values[CookieHelper.CultureName]))
{
ci = new CultureInfo(cookie.Values[CookieHelper.CultureName]);
}
else
{
The object is converted then to an RolePrincipal instead of the correct User type.
There must be some processing done between those two method calls in the pipeline but I can't figure out what's happening between them that would change the user object.
In the original code base, this doesn't happen. I've also created a new branch based on the MVC3 branch and upgraded all the nuget packages and updated the web.config files as I found issues and that seems to work better (don't want to go that route because my other branch has a lot of other changes that would be challenging to replicate.)
Can someone tell me what I'm missing or how to find out why the object is changing between these method calls?
Found it, it was custom javascript that was redirecting my controllers.
Related
I am building a multi tenant application using ASP.NET MVC. Now, noticed a bug but at completely random intervals, sometimes data from the wrong tenant is fetched for another tenant.
So for instance, Tenant1 logs in, but they see information from Tenant2. I am using same database from all the tenants but with TenantId.
I boot application from Global > Application_AcquireRequestState as given below:
namespace MultiTenantV1
{
public class Global : HttpApplication
{
public static OrganizationGlobal Tenant;
void Application_Start(object sender, EventArgs e)
{
ViewEngines.Engines.Clear();
ViewEngines.Engines.Add(new RazorViewEngine());
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
UnityWebActivator.Start();
}
void Application_AcquireRequestState(object sender, EventArgs e)
{
// boot application
HttpApplication app = (HttpApplication)sender;
HttpContext context = app.Context;
// dependency runner
Tenant = new Tenant().Fetch(context.Request.Url);
// third party api calls
var session = HttpContext.Current.Session;
if (session != null)
{
if (string.IsNullOrEmpty(Session["CountryCode"] as string))
{
string isDevelopmentMode = ConfigurationManager.AppSettings["developmentmode"];
if (isDevelopmentMode == "false")
{
// api call to get country
}
else
{
// defaults
}
}
Tenant.CountryCode = Session["CountryCode"].ToString();
}
}
}
}
Now in the entire application I use 'Tenant' object as starting point and use this to query database for further data. I noticed, sometimes a tenant sees another tenant name (not sure if other other data is also visible same way).
I'm initializing 'Tenant' based on HttpContext.Request.Url. So there is no way to load other tenant data.
Can anyone see anything in the above code, or in my use of HttpContext.Request.Url that could result in the wrong tenant being extracted for any specific request?
Each request will override the static Tenant object, therefore on concurrent requests, the wrong tenant will be used.
The key is to store tenant per request, for example in the HttpContext.Current. I usually use a tenancy resolver, which contains code like this:
public string CurrentId {
get {
return (string)HttpContext.Current.Items["CurrentTenantId"];
}
set {
string val = value != null ? value.ToLower() : null;
if (!HttpContext.Current.Items.Contains("CurrentTenantId")) {
HttpContext.Current.Items.Add("CurrentTenantId", val);
} else {
HttpContext.Current.Items["CurrentTenantId"] = val;
}
}
}
In the Application_AcquireRequestState I set the CurrentId based on the url.
The tenancyresolver is then used in the classes that need to know the tenant, by getting the CurrentId.
I spent a long time trying to get the ASP.NET MVC [HandleError] attribute to work in my websites. It seemed like a good idea to go with the solution offered by the framework, but I just couldn't get it to do anything useful. Then I tried writing my own attribute (mainly so that I could step in to the code with the debugger), but although my code seemed to be doing all the right things, after it executed the framework took over and did mysterious things. Finally I tried the MVC Contrib's [Rescue] attribute, which was better but I still couldn't get it to do what I wanted.
One problem is that exceptions thrown in code embedded in aspx / ascx pages get wrapped in HttpException's and WebHttpException's.
Another problem for me was that the system is very opaque. I was essentially poking inputs in to a black box with some desired outputs in mind, but with no idea (other than the documentation, which doesn't seem very accurate / thorough) what the relationship was between them.
So, what to do?
I went for Dynamic Proxies in Castle Windsor, using the code below, which tries to handle Database errors, for which I have a specific Exception (AccessDBException).
The _alreadyAttemptedToShowErrorPage is to stop infinite recursion in the case where the error page throws an Exception.
The GetAccessDBException(...) method finds the relevant exception anywhere in the Exception stack, for the case when there are problems in aspx / ascx code.
The code requires that there is a BaseController class that all controllers derive from. This class is used to add a CreateErrorView(...) method (being as the standard View(...) method is protected)
public class AccessDBExceptionHandlingDynamicProxy : IInterceptor
{
private bool _alreadyAttemptedToShowErrorPage;
public AccessDBExceptionHandlingDynamicProxy()
{
_alreadyAttemptedToShowErrorPage = false;
}
public void Intercept(IInvocation invocation)
{
Contract.Requires(invocation.Proxy is BaseController);
try
{
invocation.Proceed();
}
catch (HttpException e)
{
if (_alreadyAttemptedToShowErrorPage == true) throw e;
_alreadyAttemptedToShowErrorPage = true;
var dbException = GetAccessDBException(e);
if (dbException != null)
{
var baseController = (invocation.Proxy as BaseController);
var view = baseController.CreateErrorView("AccessDBException", new AccessDBExceptionViewModel(dbException));
baseController.Response.Clear();
baseController.Response.StatusCode = (int) HttpStatusCode.InternalServerError;
view.ExecuteResult(baseController.ControllerContext);
baseController.Response.End();
}
else
{
throw e;
}
}
}
private static AccessDBException GetAccessDBException(HttpException e)
{
AccessDBException dbException = null;
Exception current = e;
while (dbException == null && current != null)
{
if (current is AccessDBException) dbException = (current as AccessDBException);
current = current.InnerException;
}
return dbException;
}
}
Our security team requires that all the cookies are set to Secure=true.
To set the secure property for MVC AntiForgery, we are using the following code:
protected void Application_BeginRequest(object sender, EventArgs e)
{
AntiForgeryConfig.RequireSsl = HttpContext.Current.Request.IsSecureConnection;
}
But now we have a problem on our test server which is not using SSL. Sometimes we have spontaneous errors
The anti-forgery system has the configuration value AntiForgeryConfig.RequireSsl = true, but the current request is not an SSL request.
When looking in the ASP.NET MVC code to pinpoint the location of the exception, we found the following
private void CheckSSLConfig(HttpContextBase httpContext)
{
if (_config.RequireSSL && !httpContext.Request.IsSecureConnection)
{
throw new InvalidOperationException(WebPageResources.AntiForgeryWorker_RequireSSL);
}
}
It seems correct and it should work because the execution sequence is
AntiForgeryConfig.RequireSsl = HttpContext.Current.Request.IsSecureConnection;
// ... something happens in between
if (_config.RequireSSL && !httpContext.Request.IsSecureConnection)
{
throw new InvalidOperationException(WebPageResources.AntiForgeryWorker_RequireSSL);
}
But it seems that for some requests HttpContext.Current.Request.IsSecureConnection is returning true although we are not using SSL on our test server.
What's going on there? Why do we get this exception?
I was searching for information about AntiForgeryConfig.RequireSsl and found your question.
In your following code:
protected void Application_BeginRequest(object sender, EventArgs e)
{
AntiForgeryConfig.RequireSsl = HttpContext.Current.Request.IsSecureConnection;
}
You modify an application level value (AntiForgeryConfig.RequireSsl) with a local value (Request.IsSecureConnection).
If you have 2 requests with different Request.IsSecureConnection value, what do you think will happen ?
- first request set AntiForgeryConfig.RequireSsl to false
- second request set AntiForgeryConfig.RequireSsl to true
- first request is evaluated by CheckSSLConfig (true)
- second request is evaluated by CheckSSLConfig (true)
You must avoid modifying global application setting this way and write your own filter to handle that kind of behavior.
I'm facing a strange problem with ASP.NET MemoryCaching in a MVC 3 ASP.NET application.
Each time an action is executed, I check if its LoginInfo are actually stored in the MemoryCache (code has been simplified, but core is as follow):
[NonAction]
protected override void OnAuthorization(AuthorizationContext filterContext) {
Boolean autorizzato = false;
LoginInfo me = CacheUtils.GetLoginData(User.Identity.Name);
if (me == null)
{
me = LoginData.UserLogin(User.Identity.Name);
CacheUtils.SetLoginInfo(User.Identity.Name, me);
}
// Test if the object is really in the memory cache
if (CacheUtils.GetLoginData(User.Identity.Name) == null) {
throw new Exception("IMPOSSIBLE");
}
}
The GetLoginInfo is:
public static LoginInfo GetLoginData(String Username)
{
LoginInfo local = null;
ObjectCache cache = MemoryCache.Default;
if (cache.Contains(Username.ToUpper()))
{
local = (LoginInfo)cache.Get(Username.ToUpper());
}
else
{
log.Warn("User " + Username + " not found in cache");
}
return local;
}
The SetLoginInfo is:
public static void SetLoginInfo (String Username, LoginInfo Info)
{
ObjectCache cache = MemoryCache.Default;
if ((Username != null) && (Info != null))
{
if (cache.Contains(Username.ToUpper()))
{
cache.Remove(Username.ToUpper());
}
cache.Add(Username.ToUpper(), Info, new CacheItemPolicy());
}
else
{
log.Error("NotFound...");
}
}
The code is pretty straightforward, but sometimes (totally randomly), just after adding the LoginInfo to the MemoryCache, this results empty, the just added Object is not present, therefore I got the Exception.
I'm testing this both on Cassini and IIS 7, it seems not related to AppPool reusability (enabled in IIS 7), I've tested with several Caching policies, but cannot make it work
What Am I missing/Failing ?
PS: forgive me for my bad english
Looking at the code for MemoryCache using a decomplier there is the following private function
private void OnUnhandledException(object sender, UnhandledExceptionEventArgs eventArgs)
{
if (!eventArgs.IsTerminating)
return;
this.Dispose();
}
There is an unhandled exception handler setup by every MemoryCache for the current domain Thread.GetDomain() so if there is ever any exception in your application that is not caught which may be common in a website it disposes the MemoryCache for ever and cannot be reused this is especially relevant for IIS apps as apposed to windows applications that just exit on unhanded exceptions.
The MemoryCache has limited size. For the Default instance, is't heuristic value (according to MSDN).
Have you tried to set Priority property on CacheItemPolicy instance to NotRemovable?
You can have race-condition because the Contains-Remove-Add sequence in SetLoginInfo is not atomic - try to use Set method instead.
Btw. you are working on web application so why not to use System.Web.Caching.Cache instead?
I believe you are running into a problem that Scott Hanselman identified as a .NET 4 bug. Please see here: MemoryCache Empty : Returns null after being set
I have written an HTTPModule for the redirection purpose and installed in GAC and referenced in root web.config file. It is working for Team sites very well.
I am using PreRequestHandlerExecute to see the request is page or not and calling
public void Init(HttpApplication context)
{
this.app = context;
this.app.PreRequestHandlerExecute += new EventHandler(Application_PreRequestHandlerExecute);
}
void Application_PreRequestHandlerExecute(object source, EventArgs e)
{
Page page = HttpContext.Current.CurrentHandler as Page;
if (page != null)
{
page.PreInit += new EventHandler(Perform_Redirection);
}
}
and in the Perform_Redirection method I am doing the redirection stuff.
void Perform_Redirection(object source, EventArgs e)
{
//logic goes here for redirection
}
The above code working fine for Teamsites but not for Publishing sites. The Page.PreInit is not firing for publishing sites.
Please help me to solve this problem!
I am using PreRequestHandlerExecute, because I need session object and other details otherwise I would have used BeginRequest.
I solved it by moving the redirection code into the PreRequestHandlerExecute event handler