C# ASP.NET MVC : how to block override from the method? - c#

I am creating a session control on my project and I need help at the moment.
Basically, my HomeController inherits from CustomController.
HomeController manages the methods and CustomController runs before methods to check session info.
public class HomeController : CustomController
{
public ActionResult Index()
{
}
}
public class CustomController : Controller
{
public OnActionExecuting()
{
// Check session
}
}
My problem is, I do not want to check Session before HomeController/Index method. Is this possible?

You could do it with a custom attribute class as follows:
/// <summary>
/// Indicates whether session checking is enabled for an MVC action or controller.
/// </summary>
public class CheckSessionAttribute : Attribute
{
public CheckSessionAttribute(bool enabled)
{
this.Enabled = enabled;
}
public bool Enabled { get; }
}
Then annotate the action method you want to exclude session checking with [CheckSession(false)].
Lastly, include the following in the OnActionExecuting method of the base class:
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
// Check if the CheckSession attribute is present and skip the session
// check if [CheckSession(false)] was explicitly provided.
bool checkSession = filterContext.ActionDescriptor.GetCustomAttributes(typeof(CheckSession), true)
.OfType<CheckSession>()
.Select(attr => attr.Enabled)
.DefaultIfEmpty(true)
.First();
if (checkSession)
{
// Check session
}
}
This checks for the presence of the [CheckSession(false)] attribute and disables the session check in that case. In this way, you can configure the methods that should not check the session info simply by annotating them with the new attribute. This also makes it immediately clear that the session is not checked for that specific action.

Related

Custom AuthorizeAttribute not being implemented

In our MVC solution we have two custom implementations of AuthorizeAttribute - one is named BasicHttpAuthorizeAttribute and has been used in production for years, and the other RoleAuthorizeAttribute, which was recently added.
When creating RoleAuthorizeAttribute I simply copied BasicHttpAuthorizeAttribute and modified some of the already-overridden methods.
Both attributes serve the purpose of authenticating the user, and the RoleAuthorizeAttribute of verifying that the user has the required role.
However, RoleAuthorizeAttribute never authenticates the user. It is simply not being called and instead our MVC controllers throw an exception when a non-logged-in user reaches the controller action and the code requests the context user.
Below is the outline for this custom AuthorizeAttribute. If I put breakpoints on all of those methods I find that none of them are hit when a request is made.
Can anyone explain why this class is not being used to authenticate users? Why is it that an unauthenticated user is not being redirected to the login page, but if I swap RoleAuthorize for BasicHttpAuthorize or simply the base Authorize then they are redirected?
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
public class RoleAuthorizeAttribute : System.Web.Http.AuthorizeAttribute
{
/// <summary>
/// Gets or sets the <see cref="Role"/> enumerations required for authorization.
/// </summary>
public Role[] RequiredRoles
{
get {...}
set {...}
}
public bool RequireSsl { get; set; };
public bool RequireAuthentication { get; set; }
public RoleAuthorizeAttribute(params Role[] requiredRoles)
{
// ...
}
public override void OnAuthorization(System.Web.Http.Controllers.HttpActionContext actionContext)
{
// ...
}
protected override void HandleUnauthorizedRequest(System.Web.Http.Controllers.HttpActionContext actionContext)
{
// ...
}
private bool Authenticate(System.Web.Http.Controllers.HttpActionContext actionContext)
{
// ...
}
public static bool TryGetPrincipal(string authHeader, out IPrincipal principal)
{
// ...
}
public static bool TryGetAuthCookie(out IPrincipal principal)
{
// ...
}
private static string[] ParseAuthHeader(string authHeader)
{
// ...
}
private static bool TryGetPrincipal(string username, string password, out IPrincipal principal)
{
// ...
}
}
And here is an example of its usage:
namespace MyProject.Areas.Customer.Controllers
{
[RoleAuthorize(Role.Customer, Role.CompanyAdmin)]
public partial class OrderController : MyCustomController
{
private static readonly ILog Log = LogManager.GetLogger(typeof (OrderController));
public ActionResult Index(int id)
{
// ...
}
}
}
We use Basic Authentication so there's a header set thus:
I've seen older questions asking about the same problem, but in those cases, they also override an AuthorizeCore method which no longer seems to be present on the AuthorizeAttribute class.
I figured out myself why this was happening.
There are two AuthorizeAttributes - one in the System.Web.Http namespace and the other in System.Web.Mvc. I wasn't aware of this and was trying to build a one-size-fits-all attribute, so my attribute was working for WebAPI requests but not for MVC controller requests.
The difference in these two attributes is in the OnAuthorize method where they each take a different context argument.
Once I had built two separate attributes (which are almost identical), each deriving from a different AuthorizeAttribute, everything worked as expected.

base method is not fired in mvc 5

I have the following base controller
public class BaseController : Controller
{
protected override void Execute(RequestContext requestContext)
{
base.Execute(requestContext);
}
}
Implementation is
public class HomeController : BaseController { }
we upgraded from mvc3 to mvc5 , in mvc5 this method is not getting called what needs to be done here ?
It's not entirely clear what your after but here's a few questions. Are your normal controllers inheriting your BaseController?
public class MyController : BaseController
I don't see what this line is meant to do....
var global = requestContext.HttpContext.Request.QueryString["global"] == null ? true : false;
You set the value then don't do anything with it. Are you meaning to store this value in the base controller so that you can access it from all your other controllers?
And surely if global is missing from the querystring then it should be false and not true.
public class BaseController : Controller
{
public bool isGlobal { get; private set; }
protected override void Execute(RequestContext requestContext)
{
this.isGlobal = requestContext.HttpContext.Request.QueryString["global"] == null ? false : true;
base.Execute(requestContext);
}
}
The title of this question led me here, but it's not really the same problem I was experiencing (i.e. the overridden Execute method in a base controller class not being called in an MVC 4/5 web site).
If that is the problem you are having too, this is the solution I found. It appears the way in which these methods are called has changed with the introduction of Async support. Instead of overriding the Execute method you should override the OnActionExecuted method instead.
protected override void OnActionExecuted(ActionExecutedContext filterContext)
{
}

How to allow [Authorize] inside a custom AuthorizeAttribute

I have a custom AuthorizeAttribute like this
public class DevMode : AuthorizationFilterAttribute
{
public override void OnAuthorization(HttpActionContext actionContext)
{
if (myConditionToAuthorize)
{
// how to allow [Authorize] ?
}
}
}
The problem is that it is used along with [Authorize] tag like this:
[Authorize, DevMode]
public class UserController : ApiController { ... }
I need to allow [Authorize] == true inside [DevMode]
Or it is better to put them all together inside a unique authorize class? But then I dont know tho to check Authorize data.
Or it is better to put them all together inside a unique authorize class?
Oh yes, that would indeed be better. You could simply derive from the AuthorizeAttribute and call the base method:
public class DevModeAttribute : AuthorizeAttribute
{
protected override bool IsAuthorized(HttpActionContext actionContext)
{
var authorize = base.IsAuthorized(actionContext);
if (!authorized)
{
// the user is not authorized, no need to go any further
return false;
}
// now apply your custom authorization logic here and return true or false
...
}
}
and then:
[DevMode]
public class UserController : ApiController { ... }
I used this to add a custom IsAdmin (based on claims) using this method
public class IsAdminAttribute : AuthorizeAttribute
{
protected override bool IsAuthorized(HttpActionContext actionContext)
{
IPrincipal principal = actionContext.RequestContext.Principal;
return principal.IsAdmin();
}
}
it kind of answers my own last comment so hope it helps someone else please note the .IsAdmin is an extension method on the IPrincipal that checks claims.

Override a Global Filter in MVC for One Method

In my filterConfig, I have registered a global attribute filter, which requires authorization on each of my methods.
However, I have one particular method where I want to apply a different authorization filter attribute. How does one accomplish this, if at all possible?
Note: I do not want to use the [AllowAnonymous] attribute (which works seamlessly and completely ignores my filter), since I want the request to be authorized, just through a different set of authorization logic on the method.
You can alter your filter to allow multiple by setting AllowMultiple = true in the AttributeUsage attribute on your attribute class, and add a check so that if the filter is present multiple times, the globally-applied one doesn't execute. The ActionExecutingContext that gets passed into OnActionExecuting() lets you get the filters applied via filterContext.ActionDescriptor.GetCustomAttributes(), so you can use that here.
Then, alter the constructor so that you can pass in a parameter (probably an enum) that it can use to decide which authorisation method to use - the normal one, or this other one. Give that parameter a default value that makes it select the normal auth method. Then, on that one method that needs a different auth method, you can apply the filter with the other value of the parameter. So it might look like this:
public class CustomAuthAttribute : AuthorizeAttribute
{
public CustomAuthAttribute(AuthMethod method = AuthMethod.StandardAuth)
{
//stuff
}
}
[CustomAuth(AuthMethod.WeirdAuth)]
public ActionResult MethodThatNeedsDifferentAuth()
{
//stuff
}
you can write your own version of the authorize attribute and pass specific parameter to depending on what action would you like your attribute to do for example
public class CustomAuthorizeAttribute : AuthorizeAttribute
{
public string currentAction { get; set; }
public override void OnAuthorization(AuthorizationContext filterContext)
{
if (currentAction != "notallowed")
{
HandleUnauthorizedRequest(filterContext);
}
}
}
protected override void HandleUnauthorizedRequest(AuthorizationContext context)
{
context.Result = new RedirectResult("/home/login");
}
and then apply it to your class or action
[CustomAuthorize(currentAction = "notallowed")]
public class HomeController : Controller

ASP.NET MVC: Register action filter without modifying controller

I'm working with nopCommerce and I need to add in my only Action Filter, however, I don't want to modify the core controllers to avoid my code being overwritten when a new update is released.
I've setup my Action Filter:
public class ProductActionFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
if (filterContext.Result is ViewResult)
{
...
}
base.OnActionExecuted(filterContext);
}
}
If I were to modify the controller, I could just add [ProductActionFilter] to the action I want it assigned to.
Is there a way I can register my custom Action Filter to a specific action without modifying the controller?
I think global filters is what you need.
Once you created the filter register it in the global.asax:
protected void Application_Start() {
AreaRegistration.RegisterAllAreas();
// Register global filter
GlobalFilters.Filters.Add(new MyActionFilterAttribute());
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
}
Add custom validation logic to filter if you want to apply it not to all actions.
If you want your filter to be registered for every action (or it is otherwise OK to do so), then MVC 3 allows you to apply Global action filters. Of course this requires that nopCommerce is built on MVC 3, which I believe the newest version is?
In NopCommerce 3.5 (the latest as of this answer, and newer than the question date), the best way I've found to add a global action filter is by creating a plugin with an IStartupTask implementation in it. This method completely avoids altering any NopCommerce core files.
The NopCommerce Application_Start event initializes the EngineContext, which creates the NopEngine instance. The NopEngine initialization finds all IStartupTask implementations, and executes them in their specified order. So an IStartupTask is the place to do anything that needs to happen on application start.
Sample code below:
public class Plugin : BasePlugin
{
public Plugin()
{
}
/// <summary>
/// Check to see if this plugin is installed
/// </summary>
public static bool IsInstalled(ITypeFinder typeFinder)
{
IEnumerable<Type> types = typeFinder.FindClassesOfType<IPluginFinder>(true);
if (types.Count() == 1)
{
IPluginFinder plugins = Activator.CreateInstance(types.First()) as IPluginFinder;
PluginDescriptor descriptor = plugins.GetPluginDescriptorBySystemName("MyPluginName");
if (descriptor != null && descriptor.Installed)
{
return true;
}
}
return false;
}
}
/// <summary>
/// Redirects to the 404 page if criteria not met
/// </summary>
public class FluffyTextureRequiredAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (Kitten.Texture != Textures.Fluffy)
{
var routeValues = new RouteValueDictionary();
routeValues.Add("controller", "Common");
routeValues.Add("action", "PageNotFound");
filterContext.Result = new RedirectToRouteResult(routeValues);
}
}
}
/// <summary>
/// Does application start event stuff for the plugin, e.g. registering
/// global action filters
/// </summary>
public class StartupTask : IStartupTask
{
private ITypeFinder _typeFinder;
public StartupTask()
{
//IStartupTask objects are created via Activator.CreateInstance with a parameterless constructor call, so dependencies must be manually resolved.
_typeFinder = EngineContext.Current.Resolve<ITypeFinder>();
}
public void Execute()
{
// only execute if plugin is installed
if (Plugin.IsInstalled(_typeFinder))
{
// GlobalFilters is in System.Web.Mvc
GlobalFilters.Filters.Add(new FluffyTextureRequiredAttribute());
}
}
public int Order
{
get { return int.MaxValue; }
}
}
This approach works for NopCommerce 4.10
This code will redirect "/Register" requests with "GET" Method to "YourCustomAction" action inside "YourCustomController".
Step 1:
implement INopStartup
public class NopStartup : INopStartup
{
public void ConfigureServices(IServiceCollection services, IConfiguration configuration)
{
services.Configure<MvcOptions>(config =>
{
config.Filters.Add<YourCustomActionFilter>();
});
}
public void Configure(IApplicationBuilder application)
{
}
public int Order => 0;
}
Step 2 :
public class YourCustomActionFilter : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
if (!(context.ActionDescriptor is ControllerActionDescriptor actionDescriptor)) return;
if (actionDescriptor.ControllerTypeInfo == typeof(CustomerController) &&
actionDescriptor.ActionName == "Register" &&
context.HttpContext.Request.Method == "GET")
{
string controllerName = nameof(YourCustomController).Replace("Controller", "");
string actionName = nameof(YourCustomController.YourCustomAction);
var values = new RouteValueDictionary(new
{
action = actionName,
controller = controllerName
});
context.Result = new RedirectToRouteResult(values);
}
}
}
With this approach, you can cut down the registration process and add some extra check/process then you can go on the registration process.
What about creating a partial class. As of version 2.60 all controllers are partials:
public partial class CatalogController : BaseNopController
You can put the filter to the class and then query the action name.

Categories