Instantiation of a controller in OnActionExecuting (...not throwing a 404) in MVC Azure - c#

The objective is to add a maintenance batch on the same url of the administration of an Azure MVC site. The url should be something like:
https://admin.mysite.com/Batch?pass=HKE671
I decided to override OnActionExecuting and to capture the information I need in the url to trigger the maintenance method. I am not familiar with MVC projects, and this may not sound very conventional...
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
EmailUtility.SendSupportEmail("DEBUG - On result executing", ""); //I receive this email
int y = 14;
if (Request.Url.Query.Length > y)
{
string url = Request.Url.Query.Remove(0, y); // I remove ?ReturnUrl=%2f
if (url.StartsWith("Batch"))
{
mySite.Admin.Web.Controllers.TenantController controller = new mySite.Admin.Web.Controllers.TenantController();
EmailUtility.SendSupportEmail("DEBUG - starts maintenance", ""); // I don't receive this one
controller.maintenance(HttpUtility.ParseQueryString(url).Get("pass"));
};
}
base.OnActionExecuting(filterContext);
}
This code works as well as I need on local, the maintenance method is called and does the job. But when deployed on Azure, this modification throws a 404 error. Interestingly, I send two debug emails : I don’t receive the second one "DEBUG - starts maintenance", therefore my 404 error comes from the instantiation of a controller in OnActionExecuting.
First, I would like to understand why is the behavior different between my development machine and Azure?
Then, how can I make it work? Thanks,
EDITED on Jan 4:
I made a little progress, but this issue is still unsolved.
- About the difference between my dev machine and Azure: there are a few redirections on this site: https, 404 and non-existent domain. I assumed it was due to a 404 error. Encapsulating the code with a try/catch didn't sent me any error, so I am guessing that I can suppress the 404 from the hypothesis.
- I tried the code above on OnAuthorization without having more success.
- I noticed that the first email DEBUG - On result executing is in fact sent at the first test only. It is not sent the second time I run my test. This doesn't make any sense to me, because the session should be checked every time.
Conclusion for today: it seems to be more a routing/redirection problem.

Why don't you do:
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
try {
//your code here
} catch (Exception ex){
EmailUtility.SendSupportEmail("Execution failed: " + ex.Message , "");
}
}
If your code is throwing an exception, this should give you a better understanding on why it is failing.
But more important, a more robust way of checking for the correct URL would be:
if (filterContext.HttpContext.Request.Url.AbsolutePath.EndsWith("Batch")){
}
As far as i can tell your string variable "url" will not ever start with "Batch", because Request.Url.Query only conains the part after the "?" thus making your check always return false. Why this IS working on localhost is hard to tell, but in my opinion it shouldn't.
Actually, to work in all cases your check should be:
if (filterContext.HttpContext.Request.Url.AbsolutePath.ToLower().EndsWith("batch") || filterContext.HttpContext.Request.Url.AbsolutePath.ToLower().EndsWith("batch/")){
}
After your edit:
Ok, so what's happening is that you're requesting the action "Batch" in Home Controller, but because you're not authenticated you are redirected to LogOn in Account Controller. You should be seeing a login page, unless there's no associated LogOn view in your Views folder, or no AccountController at all, in both cases an exception is thrown.
Is the Batch method supposed to check for authentication? If not, remove the [Authorize] annotation from this method in HomeController and you should be fine.
You still have to adjust the if-check though, because it actually only evaluates to true on the LogOn page, and false if you actually get to the Batch page.

Shame on me, the problem was somewhere else!... The code above works well, but the routing of this site rejected an incomplete url. In this case, it had to contain the project name: https://admin.mysite.com/Admin/Batch?pass=HKE671 Then all is back to normal!
So the answer is: the behavior is different on my local computer because the local host of my development machine doesn't route projects of the solution the same way Azure does.
Many thanks for your help!

Related

Does anyone else have issues losing session variables in Firefox but not in Chrome or IE?

We are experiencing some issues with returning variables stored in session, which in turn is causing our controller authorization code to redirect some users back to the login page. The users only seem to be having this issue with the Firefox browser and the application works normally for them when using Chrome or Edge. It's almost as if their session is being dropped while navigating through the site (mostly immediately after login while being redirected to the home page, however sometimes the user can get through few links before being kicked out).The issue also only happens after if is running in our test or production sites, reproducing the issue locally troublesome. I was wondering if anyone else has experience this type of issue before and if they were able to find a work around or resolve it. Our sessionstate is set to InProc.
Based on other threads I have read on here I have tried the following so far:
Ensured that Session_Start is in Global.asax, Tried setting a "dummy" session variable in the Session_Start method "Session["Init"] = 1", added this to our web.config file under the section
, as well as some other solutions I have found that turned out not to resolve our issue.
This is the section of code in question.
public class AuthorizeSessionAttribute : AuthorizeAttribute, IAuthorizationFilter
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
HttpSessionStateBase session = filterContext.HttpContext.Session;
Controller controller = filterContext.Controller as Controller;
if (controller != null && session != null && session["CurrentRole"] == null)
{
controller.HttpContext.Response.Redirect("~/Account/Login");
}
base.OnAuthorization(filterContext);
}
}
I just can't see any obvious reason as to why this would only happen in Firefox but work correctly in Chrome or IE. Any input on this would be appreciated. Thanks.

Redirecting to login doesn't work if current url already contains ReturnUrl

Some background:
Our client wanted a number of actions to return to the previous action when done. For example, if he was on a list view of objects and he clicked on "create new object" button, he wanted to return to the list view after he fills and saves the form or after he cancels the action. We implemented it by mimicking the ReturnUrl behavior that is used with unauthorized access attempts (adding current address as an url encoded query parameter).
The problem:
If I'm not authorized (or in other words not logged in) and try to access an action that requires it I get redirected to login page (as I should) and current url gets put into ReturnUrl param. However, when the current address already contains ReturnUrl query param, I'm not being redirected anywhere and instead I get a blank page. Is there any reason why this happens?
The expected result would be being redirected to the login screen with the (url encoded version of) current url put into the ReturnUrl param (regardless of whether the current url contained its own ReturnUrl param or not)
Is there some way to make it work as expected? Sure in theory I could rename the "ReturnUrl" param (in my own actions) to something else, but we used such parameter in so many places already that renaming them is not going to be an easy task. Besides, I don't really understand why this doesn't work in the first place.
P.S. The problem only occurs if I name the parameter ReturnUrl, if it's returnUrl everything works as it should.
Edit: This question was previously called: Unauthorized access to restricted actions returns nothing if the url contains parameter called ReturnUrl. I changed the title to make it easier to understand.
Edit: This question might be a duplicate of Unauthorized request does not redirect to login page with returnUrl query string parameter. I'll have to investigate further if the solution provided there solves my issue or not. Update: The wording is similar, but the problem is different after all, so it's not a duplicate.
Thise proved a major pain to fix, but I managed to do that. In the quest to find why the redirect to login fails for some urls I had to answer myself an important question. What is actually responsible for doing the redirect and how can I override that?
This article put me on track (the thing responsible for the redirect was the [Authorize] filter) and I started searching for a solution. After a little bit of searching I found this simple custom Authorization filter. Of course it didn't do what I want out of the box (and what I want is basically for authorize to work like normally but not to break on urls that contain ReturnUrl param), so I altered the code and got this:
public class Authorize2 : AuthorizeAttribute
{
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
// 1. Get the default login url that was declared in web.config
string returnUrl = FormsAuthentication.LoginUrl;
// 2. Append current url as a return url to the login url
returnUrl += "?ReturnUrl=" + HttpUtility.UrlEncode(HttpContext.Current.Request.Url.PathAndQuery);
// 3. ...
// 4. Profit
filterContext.Result = new RedirectResult(returnUrl);
}
}
After writing this piece of code, I spent another hour trying to figure out why it doesn't work (breakpoints inside of HandleUnauthorizedRequest were never hit). Then I found this site and it suddenly made sense. A colleague of mine added a global Authorize filter to all actions for whatever reason and my own custom filter was never asked to authorize anything (\App_Start\FilterConfig.cs). After removing that line (I'll have to put my custom filter in its place eventually), the code above worked like a charm.
In a way the question is still open, I mean it's still a mystery why Authorize fails for those urls. The answer to that question undoubtedly lies in System.Web.Mvc.AuthorizeAttribute source code, but for now I'm satisfied with just having it work correctly.

Server.Transfer causing Session exception

In my global I have the following code to handle when an error occurs
//[..] code goes here
Server.Transfer("~/Error.aspx?ErrorID=" + errorId);
It used to be a Response.Redirect which worked perfectly except that it changed the url (which is why I want to use Server.Transfer)
Unfortunately, now when it tries to load the Error page, it crashes on the Masterpage when it tries to refer to the Session
HttpException:
Session state can only be used when enableSessionState is set to true,
either in a configuration file or in the Page directive. Please also
make sure that System.Web.SessionStateModule or a custom session state
module is included in the \\
section in the application configuration.
I do have enableSessionState in both my config and my page.
I also found some links which suggest using Context.RewritePath - that just causes a blank page to load for me.
Using Response.Redirect works perfectly and as expected, so I assume Server.Transfer is the issue here. What is it?
EDIT Code:
protected void Application_Error(object sender, EventArgs e)
{
lock (_lockMe)
{
Exception ex = Server.GetLastError();
if (ex != null)
{
if (ex.InnerException != null)
ex = ex.InnerException;
ErrorLoggingManager.AddError(ex, new MembershipData(), ...); //etc
}
Server.ClearError();
//Some other database code for cleaning up some stuff when an error happens
}
try
{
if (Response != null)
{
//Get the last error logged
MyDataContext db = new MyDataContext();
int errorId = db.LoggedErrors.OrderByDescending(le => le.ErrorId).Select(le => le.ErrorId).FirstOrDefault();
Server.Transfer("~/Error.aspx?ErrorID=" + errorId);
}
}
catch (Exception)
{
}
}
As you have not posted much code. So without seeing the actual implementation you have done. I could suggest you below points.
Point 1. First of all, you need to check if SessionState is enabled for pages. You could set them globally in web.config file. Try the snippet given below in web.config
<configuration>
<system.web>
<pages enableSessionState="true" />
</system.web>
</configuration>
Point 2. And put your Redirection in Application_Error in Global.asax.
public void Application_Error(object sender, EventArgs e)
{
HttpApplication app = (HttpApplication)sender;
app.Server.Transfer("~/Error.aspx?ErrorID=" + errorId,true);
}
Point 3. Also check if your SessionStateis set properly in IIS too.
Details are on MSDN to enable sessionstate
Hope this helps..!!!
From what I understand, Server.Transfer sends the content of another page to the client rather than the requested content. If that is the case, then I am wondering if it does not have something to do with applying a master page to the error page? I had a similar error years ago with earlier technology and it turned out that the master page did not like what I was trying to do.
I hope this helps at least point to a solution.
Here's what the problem is:
If there is a page render exception (ex. "File Not Found") then Server.Transfer screws up the session. This has something to do with it being called during the page render.
As long as you are not appending headers before the error occurs, Response.Redirect will work just fine; if you are, however, using Response.AppendHeader then Response.Redirect will not work during a page render.
Try using HttpContext.Current.RewritePath instead. That should fix all these problems. For whatever reason, RewritePath() does not care that the page hasn't finished rendering.
why not just use customErrors in web.config to do the redirect?
<customErrors mode="Off" defaultRedirect="~/Common/Error.aspx">
<error statusCode="403" redirect="~/SM_AccessDenied.aspx" />
<error statusCode="404" redirect="~/Common/FileNotFound.aspx" />
</customErrors>
I had the same problem in a different context a while ago. I don't know if it is your case, but if you're using IIS7 on Windows 2008, in addition to setting enableSessionState=true in your web.config, you have to put your modules inside the <system.webServer> section, instead of <system.web>. Changing this little thing solved it for me.
Why don't you try like this:
The Server.Transfer method also has a second parameter—"preserveForm". If you set this to True, using a statement such as Server.Transfer("WebForm2.aspx", True), the existing query string and any form variables will still be available to the page you are transferring to.
So I think doing like this your session will not expire.
Server.Transfer("~/Error.aspx?ErrorID=" + errorId,True);
The error you are encountering is because you are using a query string parameter. Per the msdn docs
However, the path parameter must not contain a query string, or ASP returns an error.
http://msdn.microsoft.com/en-us/library/ms525800%28v=vs.90%29.aspx
Its about 3/4 of the way down the page just above Requirements.
Even though the docs here are mentioning asp. and not asp.net, keep in mind the session state is a feature of IIS and is handled before asp.net is ever called.
#user2110845 : I had faced similar problem few months ago. the problem was with having an underscore in the website name. We were deploying a website in IIS with two different host names(adding two entries through the 'Edit Bindings' option on the website). The host names provided were abc_ts, abc_is. When the underscore was removed then the session problem got resolved.
It seems there are certain characters not allowed in a website host name. Check if that is your problem.
I found the answer here : link (check 'update 2' in the article)
You don't mention what version of ASP.NET you are using, but there were some changes between 2.0 and 3.5 in how unhandled exceptions bubbled their way up through an ASP.NET web app and then IIS.
Among some other possibles, while you are clearing the error you are not setting Context.Response.TrySkipIisCustomErrors = true; While this particular flag could have nothing to do with your issue (and is only available for 3.5+), it also could help deal with what is potentially two error pages behind the scenes that are obscuring the real issue. Regardless, it'll save you a lot of grief (at least if you are running 3.5+) with other potential issues. Check out two posts I wrote several years back that may be helpful: while they don't cover session handling, they do cover the multiple song-and-dance routines I had to follow to get proper 500 and 404 handling in various versions of ASP.NET. It's possible you will run into something that will get you further ahead, if not all the way there.
http://www.andornot.com/blog/post/Errors-Sending-the-Right-Message-(Redux-Covering-ASPNET-3540).aspx
http://www.andornot.com/blog/post/Errors-Sending-the-Right-Message.aspx

Redirect away from HTTPS with ASP.NET MVC App

I'm using ASP.NET MVC 2 and have a login page that is secured via HTTPS. To ensure that the user always accesses those pages via SSL, I've added the attribute [RequireHttps] to the controller. This does the job perfectly.
When they have successfully logged in, I'd like to redirect them back to HTTP version. However, there isn't a [RequireHttp] attribute and I'm struggling to get my head around how I might achieve this.
The added (potential) complication is that the website when in production is hosted at the route of the domain, but for development and testing purposes it is within a sub directory / virtual directory / application.
Am I over-thinking this and is there an easy solution staring me in the face? Or is it a little more complex?
After a bit of digging, I went along the lines of rolling my own as there didn't appear to be a good built-in solution to this (as mentioned, there is a great one for MVC2 applications in the form of [RequireHttps]). Inspired by çağdaş's solution to this problem and I adapated to come up with the following code:
public class RequireHttp : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
// If the request has arrived via HTTPS...
if (filterContext.HttpContext.Request.IsSecureConnection)
{
filterContext.Result = new RedirectResult(filterContext.HttpContext.Request.Url.ToString().Replace("https:", "http:")); // Go on, bugger off "s"!
filterContext.Result.ExecuteResult(filterContext);
}
base.OnActionExecuting(filterContext);
}
}
I can now add this to my Controller methods and it behaves (seemingly) as expected. If I redirect to the Index action on my controller from a HTTPS protocol, it will redirect to HTTP. It only allows HTTP access to the Index ActionResult.
[RequireHttp]
public ActionResult Index() {
return View();
}

CryptographicException: Padding is invalid and cannot be removed and Validation of viewstate MAC failed

Monitoring my global exception logs this error seems to be impossible to remove no matter what I do, I thought I finally got rid of it but it's back again. You can see a strack trace of the error on a similar post here.
Notes about the environment:
IIS 6.0, .NET 3.5 SP1 single server ASP.NET application
Steps already taken:
<system.web>
<machineKey validationKey="big encryption key"
decryptionKey="big decryption key"
validation="SHA1" decryption="AES" />
In my Page Base for all of my pages
protected override void OnInit(EventArgs e)
{
const string viewStateKey = "big key value";
Page.ViewStateUserKey = viewStateKey;
}
Also in the source of the page I can see that all of the ASP.NET generated hidden fields are correctly at the top of the page.
First of all lets start from the fact, that this error of view state happens on PostBack.
Also I must say that I have done all the things that every one suggest to do to avoid this problem. And I have single machine, but 2 pools that run the same Pages.
So someone do an action, ether a man, ether some other search machine by 'clicking' on your pages, or some hacker try to check your system for problems...
I have similar problems (rare but existing ones), and I finally found that people try to hack-test my pages. (from the same IP I have and dos attacks)
I modify the function LoadPageStateFromPersistenceMedium() that translate the viewstate, and see by logging what exactly was the input, and from what IPs... then I started monitor these results and see that the view state was changed by hand - or was totally empty.
On error I just redirect him to the same page...
Here is what I did...
public abstract class BasePage : System.Web.UI.Page
{
protected override object LoadPageStateFromPersistenceMedium()
{
try
{
.. return the base, or make here your decompress, or what ever...
return base.LoadPageStateFromPersistenceMedium();
}
catch (Exception x)
{
string vsString = Request.Form[__VIEWSTATE];
string cThePage = Request.RawUrl;
...log the x.ToString() error...
...log the vsString...
...log the ip coming from...
...log the cThePage...
// check by your self for local errors
Debug.Fail("Fail to load view state ! Reason:" + x.ToString());
}
// if reach here, then have fail, so I reload the page - maybe here you
// can place somthing like ?rnd=RandomNumber&ErrorId=1 and show a message
Responce.Redirect(Request.RawUrl, true);
// the return is not used after the redirect
return string.Empty;
}
}
Second Reason
Now there is one more reason why this can happen, and the reason is because some one click on your page before the __EVENTVALIDATION is loaded.
This eventValidation is placed on the last button-even that asp.net found, and if you have some of them on many place on the page, or near the button, then this go to the end of the page.
So even if you see the viewstate on the top of the page, where is the Validation ??? maybe this never loaded - page corrupt ?, too fast user click on page ?
<input type="hidden" name="__EVENTVALIDATION" id="__EVENTVALIDATION" ... >
To avoid this kind of problem I made a simple javascript that I do not let it press the button unless this input have been loaded !!!.
One more comment, the __EVENTVALIDATION is not always presents ! so is maybe safer not to search for this field if you make a general solution, but to make a javascript trick to just check if the full page is loaded, or something else that you think.
Here is my final solution with jQuery: (note that I check on PageLoad if eventvalidation exist !). I have this placed on my MasterPages.
<script language="javascript" type="text/javascript">
function AllowFormToRun()
{
var MyEventValidation = $("#__EVENTVALIDATION");
if(MyEventValidation.length == 0 || MyEventValidation.val() == ""){
alert("Please wait for the page to fully loaded.");
return false;
}
return true;
}
</script>
protected void Page_Load(object sender, EventArgs e)
{
// I do not know if Page can be null - just in case I place it.
if (Page != null && Page.EnableEventValidation)
{
Form.Attributes["onsubmit"] = "return AllowFormToRun();";
}
}
You can test by placing near the button of your page a delay.
<% System.Threading.Thread.Sleep(5000); %>
Update
Today I see in log this message again for WebResource and what I discover is that a bot getting the pages and make all the character on the links in lower case, including the parameters, so this was one more reason to not getting the correct encoded string, and throw a message like Padding is invalid and cannot be removed.
Hope this help you more.
A survey of the web pages found with several of the keywords from the error message indicate that this type of error is relatively common, usually random (at least in appearance) and unfortunately rarely inclusive of an explicit work-around or explanation...
The existence of many similar-yet-different situations is probably tied to the very many different architectures and underlying configurations which can somehow lead to the inability of the crypto layer to assert the authenticity of the MAC (Message Authentication Codes) in the request pages:
Server farm setup
Cross domain / syndicated pages
third party widget libraries and such
Actual ASP program logic (of course)
One relatively frequent "marker" around these bug reports is the mention of resource requests (eg. WebResource.axd).
Note that such requests are often not logged (lest they inflate the log files size with event of relative little interest). This absence from the log file and the fact they are often cached (and hence the relative random and infrequent occurrence of the bug) may explain how this possible origin of the bug go "under the radar". This also suggests that in trying to recreate the bug, (while tracking in the logs, in real time etc) it may be useful to prevent the web browser from caching (or for the least to clear it cache initially).
In short, here are a few ideas and things to look for:
start logging the *.axd requests
try and co-relate such axd requests with the error events in the exception log
look for pages with resource references
if in a Farm setting, ensure that all instances use the same key (apparently the snippet provided in the question hint at multiple IIS servers)
Be suspicious of pages with 3rd party tie-ins (search services, affiliate programs...)
Hope this helps ;-)
Are you sure your problem is cryptography related, and not caused by oversized ViewState?
If ViewState is the problem, you can chunk it - change the value of pages / MaxPageStateFieldLength in web.config

Categories

Resources