Custom authorization filter logs twice - c#

I created a custom authorization filter with some checks in it. When the check fails it is writing to a log file. The strange thing is that with every fail it writes the error text twice to the log. How to make sure it only logs the error once?
public class AuthorizationFilter : FilterAttribute, IAuthorizationFilter
{
public void OnAuthorization(AuthorizationContext filterContext)
{
var key = “wrong key”;
if (key != “correct key”)
{
DateTime DateTime = filterContext.HttpContext.Timestamp;
string path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, #"Logs\log.txt");
using (StreamWriter sw = File.AppendText(path))
{
sw.WriteLine(DateTime + “| error XYZ”);
}
filterContext.Result = new HttpUnauthorizedResult();
}
}
}

Assuming you have the filter registered globally...
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new AuthorizationFilter());
filters.Add(new HandleErrorAttribute());
}
}
It will fire once when the original action is run. Then it will return 401 unauthorized. This status is caught by ASP.NET and will automatically redirect to the login page. When the login page loads, your filter runs again (and presumably fails again).
To make it stop doing this, there are a couple of options.
Inherit from AuthorizeAttribute instead of FilterAttribute, IAuthorizationFilter. Override the AuthorizeCore method and return false when the login fails. Use the AllowAnonymousAttribute attribute on your login method (and any other methods you don't want to check).
Build your own logic to either check for AllowAnonymousAttribute or a custom attribute. Here is an example of checking for an attribute within a filter.
I suggest you use the first option. The reason is that in addition to automatically gaining the functionality of the AllowAnonymousAttribute there is also some code to deal with using output caching in conjunction with authorization.

Related

Overriding Global cache setting in Asp.net MVC

I have a ASP.Net MVC5 application. I disabled caching through out the application by applying global filter as follows:
public class CachingFilter : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
filterContext.HttpContext.Response.Cache.SetCacheability(HttpCacheability.NoCache); // HTTP 1.1.
filterContext.HttpContext.Response.Cache.SetRevalidation(HttpCacheRevalidation.AllCaches);
filterContext.HttpContext.Response.Cache.AppendCacheExtension("no-store, must-revalidate");
filterContext.HttpContext.Response.AppendHeader("Pragma", "no-cache"); // HTTP 1.0.
filterContext.HttpContext.Response.AppendHeader("Expires", "0"); // HTTP 1.0.
}
}
The filter above disables caching brilliantly. But now I have an action to populate some statistics as a PartialView. For test purposes I wanted to enable caching for 20 seconds, by applying OutputCacheAttribute as follows:
[AcceptVerbs(HttpVerbs.Get)]
[OutputCache(Location = OutputCacheLocation.Client, Duration = 20, VaryByParam = "*")]
public PartialViewResult Statistics()
{
var stats = GetStatistics();
return PartialView("~/Views/Shared/_Statistics.cshtml", stats);
}
No matter what I did, If CachingFilter is enabled in application global, Statistics() method is always called even though 20 second period isn't elapsed. If I disable CachingFilter from global, Statistics() method is cached properly.
I thought/read that applying cache filter to action is the final verdict for caching. How to bypass global caching properties in action level without adding action/controller name in if clauses in global cache filter?
You can create your own attribute to exclude the global filter on certain attributes, for example, create a stub attribute:
public class ExcludeCacheFilterAttribute : Attribute
{
}
Now in CachingFilter check for this attribute before running your code:
public class CachingFilter : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (filterContext.ActionDescriptor.GetCustomAttributes(typeof(ExcludeCacheFilterAttribute), false).Any())
{
return;
}
//Carry on with the rest of your usual caching code here
}
}

OnAuthorization being called multiple times

I have a custom filter:
public class SetAuthFilter : IAuthorizationFilter
{
public void OnAuthorization(AuthorizationContext filterContext)
{
//do something
}
}
In the Application_Start() under the Global.asax:
GlobalFilters.Filters.Add(new SetAuthFilter());
The above codes will be called everytime an action is invoked.
However, in my _Layout.cshtml, I have 2 different "BaseController", something like:
#Html.Action("SomeAction", "Base")
#Html.Action("SomeAction", "Base2")
When I set a break point, it appears that the SetAuthFilter is always being called three times, first before the page was launched, then second time when my break point hits the BaseController, then finally the third time when my break point hits the Base2Controller.
What should I do in order to avoid SetAuthFilter being called multiple times?
You simply cannot prevent it from being called if there are multiple actions that are interacting with the filter. It will be called every single request. However, you could cache your last request for that user's identity and, if it is the same request, immediately return without continuing onto the heavier authorization checks.
public class SetAuthFilter : IAuthorizationFilter
{
public void OnAuthorization(AuthorizationContext filterContext)
{
var key = CreateKey(filterContext);
var isCached = HttpRuntime.Cache.Get(key);
if (isCached != null) return;
HttpRuntime.Cache.Insert(key, true);
// Heavy auth logic here
}
private string CreateKey(AuthorizationContext context)
{
// Or create some other unique key that allows you to identify
// the same request
return
context.RequestContext.HttpContext.User.Identity.Name + "|" +
context.RequestContext.HttpContext.Request.Url.AbsoluteUri;
}
}
Note that this doesn't account for null identities or bad URIs. You'd want to add some additional defensive checks as well as a cache invalidation strategy.
This will not allow you to bypass your authentication, since each unique request will still need to be validated. However, it minimizes the number of times you call expensive authorization logic.
For every secure controller action, the OnAuthorization overload will get called.
If you dont want that to happen, you should decorate your function with AllowAnonymous attribute.
If you don't want to call custom filter on each method:
Then remove the following line from Application_Start() under the Global.asax:
GlobalFilters.Filters.Add(new SetAuthFilter());
Add [SetAuth] attribute as follows on those methods and Controllers which really needs authorization filter :
[SetAuth]
public ActionResult Index()
{
// your code
return View(yourModel);
}

Applying non-MVC related attributes to MVC actions

Our application has the notion of a PermissionAttribute. This attribute is defined in a base layer of our application and our commands and queries are decorated with that attribute. Since this attribute is defined in base layer we can't (and don't want to) to let it inherit from FilterAttribute or implement System.Web.Mvc.IActionFilter on it.
Still we would like to apply this attribute to controller actions as follows:
[Permission(Permissions.Administration.ManageUsers)]
public ActionResult Index()
{
return this.View();
}
Based on this attribute the proper security checks should be applied. I've been browsing through the MVC code base to find the proper hooks to customize MVCs behavior to allow adding these security checks based on this custom attribute. I though about creating a custom ControllerActionInvoker that returned a custom ReflectedControllerDescriptor from its GetControllerDescriptor method, which would return FilterAttribute that would be created based on the existence of the PermissionAttribute, but it feels like a lot of work, and I'm not sure this is the correct path to walk.
What would be am efficient and pleasant way to customize the MVC pipeline so that we can handle this non-MVC related attribute?
I would do it this way. First create your own implementation of the AuthorizeAttribtues like this:
public class PermissionAuthoriseAttribute : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
//Leaving the implementation of this to you, but you check if your
//PermissionAttribute is assigned and call it's methods.
if(...)
return true;
//You may want to check this first, depending on your requirements
return base.AuthorizeCore(httpContext);
}
}
Then apply this across your project by adding this line to the FilterConfig.cs file:
filters.Add(new PermissionAuthoriseAttribute());
I ended up doing the following:
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters) {
filters.Add(new PermissionAuthorizationFilter(
() => Global.Container.GetInstance<IUserPermissionChecker>()), 0);
filters.Add(new HandleErrorAttribute());
}
}
public sealed class PermissionAuthorizationFilter : IAuthorizationFilter
{
private readonly Func<IUserPermissionChecker> userPermissionCheckerFactory;
public PermissionAuthorizationFilter(
Func<IUserPermissionChecker> userPermissionCheckerFactory) {
this.userPermissionCheckerFactory = userPermissionCheckerFactory;
}
public void OnAuthorization(AuthorizationContext filterContext) {
var attribute = filterContext.ActionDescriptor
.GetCustomAttributes(typeof(PermissionAttribute), true)
.OfType<PermissionAttribute>()
.SingleOrDefault();
if (attribute != null) {
VerifyPermission(filterContext, attribute.PermissionId);
}
}
private static void VerifyPermission(AuthorizationContext filterContext,
Guid permissionId) {
var permissionChecker = userPermissionCheckerFactory.Invoke();
if (!permissionChecker.HasPermission(permissionId))
{
filterContext.Result = new HttpUnauthorizedResult();
}
}
}

ASP.NET MVC - what is the earliest point in the request cycle where you can detect a static resource request?

To give the question some context, I've written a profiler which is called on Application_BeginRequest but it's logging everything (ie javascripts, images etc). While it would be possible as a last resort to add filtering to the profiler client, I'd much rather only activate the profiler when it can be determined that the request requires routing. Ideally it would be in Application_BeginRequest but I don't think it would be possible without redundant processing of the incoming request for routing...
So in short, when is the earliest point in the request life cycle that I can determine if a request is for a static resource or not, and how would you go about it?
Is it perhaps possible to derive from or hook into System.Web.Routing.RouteTable and call my profiler code from there?
There are various options.
First - to determine the static file using Request.PhysicalPath - check out:
Check for a static file during Application_BeginRequest?
One alternative would be to have this as a handler and use the path to note the file types to include (*.aspx) for example in your web.config. Then you can have access to events pretty early on (see asp.net pipeline)
Or just use an httpmodule - check everything and only profile the non-physical items as you mention.
Or - use your current method with the first link to simply check Request.PhysicalPath and hope that works for you : )
I would rather use MVC Filters for profiling since MVC Filters allow to add pre- and post-processing behaviours and filterContext parameter should give you enough information.
For example, I would create ProfilerAttribute for profiling
public class ProfilerAttribute : FilterAttribute, IActionFilter, IResultFilter, IExceptionFilter {
public void OnActionExecuting(ActionExecutingContext filterContext) {
Debug.WriteLine("Before Action is executing");
}
public void OnActionExecuted(ActionExecutedContext filterContext) {
Debug.WriteLine("After Action is executed");
}
public void OnResultExecuted(ResultExecutedContext filterContext) {
Debug.WriteLine("After Action Result is executed");
}
public void OnResultExecuting(ResultExecutingContext filterContext) {
Debug.WriteLine("Before Action Result is executing");
}
public void OnException(ExceptionContext filterContext) {
Debug.WriteLine("oops! exception");
}
}
and register as GlobalFilter in Global.ascx....
public static void RegisterGlobalFilters(GlobalFilterCollection filters) {
filters.Add(new HandleErrorAttribute());
filters.Add(new ProfilerAttribute());
}
Hope it can help. Thanks.
update: Forgot to mention that the MVC Filter only executed after the routing is matched. So, you don't need to filter out for static resources since it was already done by MVC.
I've tried approaching it from a different angle and am much happier with the result. Basically - why detect static resource requests when you can just not 'route' them at all? In global.asax:
private readonly string[] STATIC_CONTENT_PATHS = new string[] { "css", "js", "img" };
public static void RegisterRoutes(RouteCollection routes)
{
foreach (string path in STATIC_CONTENT_PATHS) { routes.IgnoreRoute(path + #"/{*foo}"); }
// other MapRoute calls here
}
Now I no longer need to check for static requests, although if I do want to for whatever reason I can still do the following:
protected void Application_BeginRequest()
{
if (IsStaticRequest())
{
// do something here
}
}
private bool IsStaticRequest()
{
var result = Request.Url.AbsolutePath.Split('/');
if (result.Length < 2) return false;
return STATIC_CONTENT_PATHS.Contains(result[1]);
}
Since I know with absolute certainly what the only paths I'll be serving static content from are, this seems like a fairly robust solution. I'm still open to other ideas but I think this suits my needs well.

MVC RequireHttps and redirect if not https

I've read thru many of the questions on ASP.NET MVC [RequireHttps] - but can't find the answer to this question:
How do you make the [RequireHttps] attribute switch the url to https if it was not https to start with?
I have this code:
public ActionResult DoSomething()
{
return View("AnotherAction");
}
[RequireHttps]
public ActionResult AnotherAction()
{
return View();
}
But I get an error saying: "The requested resource can only be accessed via SSL."
The MVC futures project has a similar attribute [RequireSsl(Redirect = true)]. But that is outdated now ... What is the equivalent in MVC 2?
When someone types in the URL http://example.com/home/dosomething OR the url http://example.com/home/anotheraction, I need them to be automatically redirected to the url https://example.com/home/anotheraction
EDIT this is the sequence of events:
The URL http://example.com/home/dosomething is called from another website. They redirect their users to this url (with a response.redirect or similar).
DoSomething() then tries to return AnotherAction(), but fails with the error message "The requested resource can only be accessed via SSL."
The RequiresHttps attribute does automatically attempt to redirect to https://your-url. I verified this behavior on a site I have that uses that attribute, and also looking at the code in Reflector:
protected virtual void HandleNonHttpsRequest(AuthorizationContext filterContext)
{
if (!string.Equals(filterContext.HttpContext.Request.HttpMethod, "GET", StringComparison.OrdinalIgnoreCase))
{
throw new InvalidOperationException(MvcResources.RequireHttpsAttribute_MustUseSsl);
}
string url = "https://" + filterContext.HttpContext.Request.Url.Host + filterContext.HttpContext.Request.RawUrl;
filterContext.Result = new RedirectResult(url);
}
Are you sure you have your site set up to accept secure connections? What happens if you try to browse to https://your-url directly?
[mvc 4] short answer:
protected void Application_BeginRequest(Object source, EventArgs e)
{
if (!Context.Request.IsSecureConnection)
{
Response.Redirect(Request.Url.AbsoluteUri.Replace("http://", "https://"));
}
}
longer answer:
to move from http to https you cant send a redirect to https after the first packet,
therefor you need to catch the packet using Application_BeginRequest,
from the Global.asax add the function and it will override the default,
the code should be something like so (Global.asax on the class level):
protected void Application_BeginRequest(Object source, EventArgs e)
{
if (!Context.Request.IsSecureConnection &&
!Request.Url.Host.Contains("localhost") &&
Request.Url.AbsolutePath.Contains("SGAccount/Login"))
{
Response.Redirect(Request.Url.AbsoluteUri.Replace("http://", "https://"));
}
}
i strongly suggest putting a breakpoints and inspecting the Request.Url object for any url related need.
or visit the msdn page confused about request.url absoluteuri vs originalstring?
so am i you can go to dotnetperls for examples.
this function enables you to develop on localhost and deploying your code as is.
now for every page you want to make a https redirect you need to specify it in the if condition.
to move from https to http you can use regular Response.Redirect like so:
if (Request.Url.Scheme.Contains("https"))
{
Response.Redirect(string.Format("http://{0}", Request.Url.Authority), true);
}
notice this also support working on the same code when developing on local host by not interrupting the original course of things pre the https addition.
also i recommend thinking about implementing some return url convention (if not already implemented) in that case you should go something like so:
if (Request.Url.Scheme.Contains("https"))
{
Response.Redirect(string.Format("http://{0}{1}", Request.Url.Authority, returnUrl), true);
}
this will redirect to the requested page post login.
naturally you should protect every page that shows user data, register, login and more.
Http HEAD requests do not appear to be redirected. When reviewing our error logs we see lots of this message, googling lands here, but after looking more at the details they have a few interesting "features"
Request_method: HEAD
User Agent: curl/7.35.0
In other words all of the failed attempts were not customer facing...
(100% credit to comment from #arserbin3 for making me realize they were all HEAD requests)
MVC4 does now redirect
but not how you would expect.
http://www.example.com:8080/alpha/bravo/charlie?q=quux
will be redirect the client's browser to
https://www.example.com/alpha/bravo/charlie?q=quux
Notice the lack of a port number.
http://aspnetwebstack.codeplex.com/SourceControl/latest#test/System.Web.Mvc.Test/Test/RequireHttpsAttributeTest.cs
code test
[Fact]
public void OnAuthorizationRedirectsIfRequestIsNotSecureAndMethodIsGet()
confirms this is the desired behaviour.
If you would like to write a custom attribute that does include the PORT ... you can base your code on:
http://aspnetwebstack.codeplex.com/SourceControl/latest#src/System.Web.Mvc/RequireHttpsAttribute.cs
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
public class RequireHttpsAttribute : FilterAttribute, IAuthorizationFilter
{
public RequireHttpsAttribute()
: this(permanent: false)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="RequireHttpsAttribute"/> class.
/// </summary>
/// <param name="permanent">Whether the redirect to HTTPS should be a permanent redirect.</param>
public RequireHttpsAttribute(bool permanent)
{
this.Permanent = permanent;
}
/// <summary>
/// Gets a value indicating whether the redirect to HTTPS should be a permanent redirect.
/// </summary>
public bool Permanent { get; private set; }
public virtual void OnAuthorization(AuthorizationContext filterContext)
{
if (filterContext == null)
{
throw new ArgumentNullException("filterContext");
}
if (!filterContext.HttpContext.Request.IsSecureConnection)
{
HandleNonHttpsRequest(filterContext);
}
}
protected virtual void HandleNonHttpsRequest(AuthorizationContext filterContext)
{
// only redirect for GET requests, otherwise the browser might not propagate the verb and request
// body correctly.
if (!String.Equals(filterContext.HttpContext.Request.HttpMethod, "GET", StringComparison.OrdinalIgnoreCase))
{
throw new InvalidOperationException(MvcResources.RequireHttpsAttribute_MustUseSsl);
}
// redirect to HTTPS version of page
string url = "https://" + filterContext.HttpContext.Request.Url.Host + filterContext.HttpContext.Request.RawUrl;
filterContext.Result = new RedirectResult(url, this.Permanent);
}
}
To supplement the answer already given, this is the code from the MVC 5 implementation of HandleNonHttpsRequest
protected virtual void HandleNonHttpsRequest(AuthorizationContext filterContext)
{
// only redirect for GET requests, otherwise the browser might not propagate the verb and request
// body correctly.
...
}

Categories