I have an HttpHandler that I'm trying to use to put a little security layer over a certain directory in my site, but it's behaving strangely.
I've got it registered like this in my Web.Config: no longer valid since I'm in IIS 7.5
<httpHandlers>
<add verb="*" path="/courses/*" type="CoursesAuthenticationHandler" />
I can't tell if it's actually being called or not, because regardless of the code, it always seems to do nothing. On the flip side, if there are any errors in the code, it does show me an error page until I've corrected the error.
Here's the handler itself:
using System;
using System.Web;
public class CoursesAuthenticationHandler : IHttpHandler
{
public bool IsReusable
{
get { return true; }
}
public void ProcessRequest(HttpContext context)
{
if (!context.Request.IsAuthenticated)
context.Response.Redirect("/");
}
}
So... that's pretty much it. The handler is being registered and analyzed at compile time, but doesn't actually do what it's expected to.
Edit: I realized that I'm using IIS 7.5 and that does indeed have an impact on this implementation.
For IIS 7, here's the Web.Config registration I used:
<handlers accessPolicy="Read, Execute, Script">
<add name="CoursesAuthenticationHandler"
verb="*"
path="/courses/*"
type="CoursesAuthenticationHandler"
resourceType="Unspecified" />
Edit 2: Progress! When not logged in, requests made to the /courses/ directory are redirected to the login page. However, authenticated requests to the /courses/ directory return empty pages...
Edit 3: Per #PatrickHofman's suggestion, I've switched to using an HttpModule.
The Web.Config registration:
<modules>
<add name="CourseAuthenticationModule" type="CourseAuthenticationModule" />
The code:
using System;
using System.Web;
public class CourseAuthenticationModule : IHttpModule
{
public void Dispose() { }
public void Init(HttpApplication context)
{
context.BeginRequest += new EventHandler(BeginRequest);
}
public void BeginRequest(Object source, EventArgs e)
{
HttpApplication app = (HttpApplication)source;
HttpContext context = app.Context;
HttpRequest request = context.Request;
HttpResponse response = context.Response;
if (request.Path.ToLower().StartsWith("/courses/") && !request.IsAuthenticated)
{
response.Redirect("/");
}
}
}
Now the problem is that !request.IsAuthenticated is always false. If I'm logged in, and navigate to the /courses/ directory, I'm redirected to the homepage.
What's the deal?
I think the last problem lies in the fact that a HttpHander handles stuff. It is the end point of a request.
Since you didn't add anything to the request, the response will end up empty.
Are you looking for HttpModules? They can be stacked.
As a possible solution when only files are necessary: read the files yourself in the request by either reading and writing to response or use TransmitFile. For ASP.NET pages you need modules.
Related
I am trying to write a asp.net httphandler for handling webdav requests with IIS.
I am extending the IHttpHandler interface and implementing the ProcessRequest.
public class clsMyHandler : IHttpHandler
{
public void ProcessRequest(System.Web.HttpContext context)
{
StreamWriter sw = new StreamWriter(#"C:\requestLog.txt",true);
sw.WriteLine("Got a request at " + DateTime.Now.ToString());
sw.Close();
}
public bool IsReusable
{
get
{
return true;
}
}
}
It's a simple handler for my test purpose to just log into a file when I get the webdav request for a file of given name. I can see the handler listed in the Handler mappings.
This is the web.config
<handlers>
<add name="testhandler" verb="*" path="*e.txt" type="MyPipeLine.clsMyHandler, MyPipeLine" />
</handlers>
It works fine when the request is a http from browser. My handler gets executed and logs in the file. But when the request is webdav(I have enabled webdav with IIS and mapped the website root as a network drive) editing a file of the pattern *e.txt fails which I would expect as I am overriding the webdav handler, but I do not see it getting logged in my file.
I am not sure if there is a server log that I can check to get some clue as to whether or not the handler was invoked and if there is an error in the handler. I am new to this.
I am not finding much materials/guides online regarding httphandlers for webdav.
Appreciate any help.
I would like to handle static file web requests through an HttpModule to show the documents in my CMS according to some policies. I can filter out a request, but I don't know how to directly process such a request as asp.net should do.
Is this what you're looking for? Assuming you're running in integrated pipeline mode, all requests should make it through here, so you can kill the request if unauthorized, or let it through like normal otherwise.
public class MyModule1 : IHttpModule
{
public void Dispose() {}
public void Init(HttpApplication context)
{
context.AuthorizeRequest += context_AuthorizeRequest;
}
void context_AuthorizeRequest(object sender, EventArgs e)
{
var app = (HttpApplication)sender;
// Whatever you want to test to see if they are allowed
// to access this file. I believe the `User` property is
// populated by this point.
if (app.Context.Request.QueryString["allow"] == "1")
{
return;
}
app.Context.Response.StatusCode = 401;
app.Context.Response.End();
}
}
<configuration>
<system.web>
<httpModules>
<add name="CustomSecurityModule" type="MyModule1"/>
</httpModules>
</system.web>
</configuration>
Is there a way to write some code that would be executed for each request to a .aspx or a .cshtml page in asp.net 4.5 apart from using a base page class. it is a very huge project and making changes to all pages to use a base page is a nightmare. Also i am not sure how would this be done for a cshtml page since they don't have a class.
Can we use the Application_BeginRequest and target only the aspx and cshtml files since the website is running in integrated mode.?
basically, i have to check if a user who is accessing the website has a specific ip address against a database and if yes then allow access otherwise redirect.
we are using IIS8 and ASP.Net 4.5 and ASP.Net Razor Web Pages
Also i am not sure how would this be done for a cshtml page since they don't have a class.
You could place a _ViewStart.cshtml file whose contents will get executed on each request.
Alternatively you could write a custom Http Module:
public class MyModule: IHttpModule
{
public void Init(HttpApplication app)
{
app.BeginRequest += new EventHandler(OnBeginRequest);
}
public void Dispose()
{
}
public void OnBeginRequest(object s, EventArgs e)
{
// this code here's gonna get executed on each request
}
}
and then simply register this module in your web.config:
<system.webServer>
<modules>
<add name="MyModule" type="SomeNamespace.MyModule, SomeAssembly" />
</modules>
...
</system.webServer>
or if you are running in Classic Mode:
<system.web>
<httpModules>
<add name="MyModule" type="SomeNamespace.MyModule, SomeAssembly" />
</httpModules>
</system.web>
basically, i have to check if a user who is accessing the website has
a specific ip address against a database and if yes then allow access
otherwise redirect.
Inside the OnBeginRequest method you could get the current user IP:
public void OnBeginRequest(object sender, EventArgs e)
{
var app = sender as HttpApplication;
var request = app.Context.Request;
string ip = request.UserHostAddress;
// do your checks against the database
}
Asp.net MVC filters are especially designed for that purpose.
You would implement ActionFilterAttribute like this (maybe put this new class in a Filters folder in your webapp solution):
public class IpFilter : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
string ip = filterContext.HttpContext.Request.UserHostAddress;
if(!testIp(ip))
{
if (true /* You want to use a route name*/)
filterContext.Result = new RedirectToRouteResult("badIpRouteName");
else /* you want an url */
filterContext.Result = new RedirectResult("~/badIpController/badIpAction");
}
base.OnActionExecuting(filterContext);
}
private bool testIp(string inputIp)
{
return true /* do you ip test here */;
}
}
Then you have to decorate any action that would perform the ipcheck with IpFilter like so :
[IpFilter]
public ActionResult AnyActionWhichNeedsGoodIp()
{
/* do stuff */
}
Here's the big picture. We're running a server in IIS 6 that hosts several web sites and applications, and we're in the process of moving the whole thing to a different data center with a slightly different setup. We've notified our users and updated our DNS info so that theoretically everyone will be happily hitting the new server from day 1, but we know that someone will inevitably fall through the cracks.
The powers that be want a "Listener" page/handler that will receive all requests to the server and log the entire request to a text file, including (especially) POST data.
That's where I'm stuck. I don't know how to implement a single handler that will receive all requests to the server. I vaguely understand IIS 6 redirection options, but they all seem to lose the POST data on the redirect. I also know a little about IIS 6's built-in logging, but it ignores POST data as well.
Is there a simple(ish) way to route all requests to the server so that they all hit a single handler, while maintaining post data?
EDIT: This is in WebForms, if that matters, but other solutions (if small) are definitely worth considering.
If all the requests are POST's to ASP.NET forms then you could plugin a HttpModule to capture and log this data.
You wouldn't have to rebuild all your applications to deploy this either. All it would take is to drop the HttpModule into each application's /bin folder and add it to the <httpModules> section of your web.config files. For example:
using System;
using System.Diagnostics;
using System.Web;
public class SimpleLogger : IHttpModule
{
private HttpApplication _application;
public void Dispose() { }
public void Init(HttpApplication context)
{
_application = context;
context.BeginRequest += new EventHandler(Context_BeginRequest);
}
void Context_BeginRequest(object sender, EventArgs e)
{
foreach (string key in _application.Request.Form.AllKeys)
{
// You can get everything on the Request object at this point
// Output to debug but you'd write to a file or a database here.
Debug.WriteLine(key + "=" + _application.Request.Form[key]);
}
}
}
In your web.config file add the logger:
<httpModules>
<add name="MyLogger" type="SimpleLogger, SimpleLogger"/>
</httpModules>
Be careful though. If your site captures credit card details or other sensitive data. You may need to ensure this is filtered out or have it encrypted and away from personel who should have no need to see this information.
Also if you're logging to files, make sure the log files are outside any public facing web folders.
Here is code of custom HTTP module we use to log HTTP POST request data.
using System;
using System.Web;
namespace MySolution.HttpModules
{
public class HttpPOSTLogger : IHttpModule
{
public void Dispose()
{
}
public void Init(HttpApplication context)
{
context.BeginRequest += new EventHandler(context_BeginRequest);
}
private void context_BeginRequest(object sender, EventArgs e)
{
if (sender != null && sender is HttpApplication)
{
var request = (sender as HttpApplication).Request;
var response = (sender as HttpApplication).Response;
if (request != null && response != null && request.HttpMethod.ToUpper() == "POST")
{
var body = HttpUtility.UrlDecode(request.Form.ToString());
if (!string.IsNullOrWhiteSpace(body))
response.AppendToLog(body);
}
}
}
}
}
Do not forget to register it in web.config of you application.
Use system.WebServer section for IIS Integrated Model
<system.webServer>
<modules>
<add name="HttpPOSTLogger" type="MySolution.HttpModules.HttpPOSTLogger, MySolution.HttpModules" />
</modules>
</system.webServer>
Use system.web section for IIS Classic Model
<system.web>
<httpModules>
<add name="HttpPOSTLogger" type="MySolution.HttpModules.HttpPOSTLogger, MySolution.HttpModules"/>
</httpModules>
</system.web>
IIS log Before applying module:
::1, -, 10/31/2017, 10:53:20, W3SVC1, machine-name, ::1, 5, 681, 662, 200, 0, POST, /MySolution/MyService.svc/MyMethod, -,
IIS log After applying module:
::1, -, 10/31/2017, 10:53:20, W3SVC1, machine-name, ::1, 5, 681, 662, 200, 0, POST, /MySolution/MyService.svc/MyMethod, {"model":{"Platform":"Mobile","EntityID":"420003"}},
Full article:
https://www.codeproject.com/Tips/1213108/HttpModule-for-logging-HTTP-POST-data-in-IIS-Log
I'm trying to get Session enabled in the GettHttpHandler method of my IRouteHandler classes but session is always null. Could someone tell me what I'm doing wrong?
In global.asax I have
RouteTable.Routes.Add("All", new Route("{*page}", new MyRouteHandler()));
The MyRouteHandler class where Session is null looks like this:
public class MyRouteHandler : IRouteHandler, IRequiresSessionState
{
public System.Web.IHttpHandler GetHttpHandler(RequestContext requestContext)
{
string test = HttpContext.Current.Session["test"].ToString();
return BuildManager.CreateInstanceFromVirtualPath("~/Page.aspx", typeof(Page)) as Page;
}
}
I made a small test app that shows the problem.
Could someone tell me what I'm doing wrong?
Edited to add:
Yes, I really need session data in the route handler. There are many reasons but one easily explainable is when the user can switch to browse the site in preview mode.
The site consists of a hierarchy of dynamic pages (/page1/page2...) in the database that can be published normally or to preview. A content producer browsing the site can choose to view just normal pages or also those published to preview. The browsing mode is stored in the user's session so therefor the route handler needs to know the browsing mode to be able to solve the requested page.
So I really need the session already at that stage.
I have explained reason behind this problem in this answer. And now I have found a solution to the problem!
You create a custom HttpHandler class:
class MyHttpHandler : IHttpHandler, IRequiresSessionState
{
public MyRequestHandler RequestHandler;
public RequestContext Context;
public MyHttpHandler(MyRequestHandler routeHandler, RequestContext context)
{
RequestHandler = routeHandler;
Context = context;
}
public void ProcessRequest(HttpContext context)
{
throw new NotImplementedException();
}
public bool IsReusable
{
get { throw new NotImplementedException(); }
}
}
It is important to add IRequiresSessionState interface, otherwise IIS does not load session for this request. We do not need to implement logic of ProcessRequest and IsReusable, but class must implement the IHttpHandler interface.
You change your RouteHandler implementation:
public class MyRequestHandler : IRouteHandler
{
public IHttpHandler GetHttpHandler(RequestContext requestContext)
{
return new MyHttpHandler(this, requestContext);
}
public IHttpHandler DelayedGetHttpHandler(RequestContext requestContext)
{
// your custom routing logic comes here...
}
}
You simply move your original, Session dependent logic to DelayedGetHttpHandler function and in the GetHttphandler function you return an instance of the helping MyHttpHandler class.
Then, you hook your handling logic to the HttpApplication.PostAcquireRequestState event, e.g. in the Global.asax:
public class Global : HttpApplication
{
public override void Init()
{
base.Init();
PostAcquireRequestState += Global_PostAcquireRequestState;
}
}
For more reference, check this page: https://msdn.microsoft.com/en-us/library/bb470252(v=vs.140).aspx. It explains the request lifecycle and why I use the PostAcquireRequestState event.
In the event handler, you invoke your custom RouteHandling function:
void Global_PostAcquireRequestState(object sender, EventArgs e)
{
if (HttpContext.Current.Handler is MyHttpHandler) {
var handler = HttpContext.Current.Handler as MyHttpHandler;
HttpContext.Current.Handler = handler.RouteHandler.DelayedGetHttpHandler(handler.Context);
}
}
And that's it. Works for me.
I am not sure that you can do this (although I may be wrong). My recollection is that IRequiresSessionState indicates that an HttpHandler needs session state, rather than the route handler (which is responsible for giving you a handler appropriate to the route).
Do you really need the session in the route handler itself and not the handler it gives out?
Well I know this is old thread but just putting up the answer here if anyone like me falls in the same scenario, I found an answer here
What you do is just add a runAllManagedModulesForAllRequests="true" attribute to your modules tag in web.config like below
<system.webServer>
.....
<modules runAllManagedModulesForAllRequests="true">
........
</modules>
......
</system.webServer>
However this is not a good solution as it calls managed module everytime, i am using
<remove name="Session" />
<add name="Session" type="System.Web.SessionState.SessionStateModule"/>
add it in modules section of web.config, this a better solution than the previous one.