Global method for all Controller classes ASP.NET MVC - c#

I have a lot of different controllers and each of them contains a lot of ActionResult methods which returns different views.
In my program you set a variable before you start using it. I store the variable in a Session.
For now, most of my Controllers, have methods with the same if-sentence.
Example:
if (Session["myVariable"] == null)
{
return RedirectToAction("/../Cons/SetVariable");
}
What i would like to is not to write this logic in all my controllers methods. Perhaps there is a way i could extend a new controller with the logic or something similar?
I hope somebody understands my problem and can help.

You can use MVC/WebAPI's filter attributes to execute the check and use context to perform the actions
public class VariableCheckerValidationAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext
filterContext)
{
if(filterContext.HttpContext.Session["myVariable"] == null)
{
filterContext.Result = new RedirectToRouteResult("/../Cons/SetVariable"‌, filterContext.RouteData.Values);
}
}
}
Then register the action filter as a global filter, and it will automatically apply to every single action in your application.
In WebApiConfig (in case of WebAPI project), in typically the Register method, add:
config.Filters.Add(new VariableCheckerValidationAttribute());
Or, instead, apply at controller or action levels as needed.
If you do decide to go the global application, and you have an action that doesn't need it, then you would need to create an attribute to 'exception' out of the policy, and update the above attribute code to test for presence of the 'exception' attribute and then not apply the check in that case.

Related

Injecting route values based on the current Principal

I've got a web api that accepts authentication through either an api key, a user access token, or a client access token. I've already written a DelegatingHandler for each case, that creates a new ClaimsPrincipal based on the given authentication details, and have confirmed that the principal is accessible within a controller action.
What I want to do now is inject either a company, a user, or a publisher into the route values, so I can create overloads on the controller for each case. What class/interface do I need to extend in order to plug into the pipeline with the following conditions:
Has access to the current principal
Has access to the route data
The action has not yet been selected
Edit
I'm not looking to sidestep routing here - I still want MVC to choose a route for me, based on the route values. I just want to add one more parameter to the route values before it chooses, and the parameter will have a different name and type depending on whether a user access token is used, or an api key is used.
I believe the subject of authentication should result in two distinct methods, because an api key can access all resources for a company, while a user access token can only access the resources they have been given permission to view.
I do not see a reason why you would want to go with a controller here. You would be sidestepping routing, a very opinionated piece of MVC. I would create middleware that runs before MVC (which is, itself, just middleware) instead.
If you're looking to affect RouteData inline, I would look into using a global IResourceFilter or IAsyncResourceFilter. Then, you can update the RouteData property on the ResourceExecutingContext based upon the conditions you specified in your question.
Any additional dependencies you need to determine how to populate the RouteData property can be injected into the resource filter's constructor as specified in the section on dependency injection.
public class SetRouteValueResourceFilter : IAsyncResourceFilter {
public async Task OnResourceExecutionAsync(ResourceExecutingContext context, ResourceExecutionDelegate next) {
var company = context.HttpContext.Identity.FindFirst("Company")?.Value;
if (company != null) {
context.RouteData.Values.Add("company", company);
}
await next();
}
}
This answer is just an idea. Even though this is handled before the action logic, I'm not sure it will affect which route is selected. According to this diagram, routes are selected before filters are ran.
I managed to get this going by making a custom ValueProviderFactory, which reads values from the current principal's claims and makes them available for parameter binding:
public class ClaimsPrincipalValueProviderFactory : ValueProviderFactory
{
public override IValueProvider GetValueProvider(HttpActionContext actionContext)
{
if (actionContext.RequestContext.Principal != null && actionContext.RequestContext.Principal is ClaimsPrincipal principal)
{
var pairs = principal.Claims.Select(claim => new KeyValuePair<string, string>(claim.Type, claim.Value));
return new NameValuePairsValueProvider(pairs, CultureInfo.CurrentCulture);
}
return null;
}
}
In order to use it, you can annotate the input parameters with the ValueProvider attribute:
public class FooController : ApiController
{
[HttpGet]
public void Bar([ValueProvider(typeof(ClaimsPrincipalValueProviderFactory))]ApiKey apiKey)
{
// ...
}
}
That's pretty ugly and unreadable, what I really wanted was something like the FromUri attribute, but for claims. ValueProviderAttribute and FromUriAttribute both inherit from ModelBinderAttribute, so I created a class which does the same:
/// <summary>
/// The action parameter comes from the user's claims, if the user is authorized.
/// </summary>
[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false)]
public class FromClaimsAttribute : ModelBinderAttribute
{
public override HttpParameterBinding GetBinding(HttpParameterDescriptor parameter)
{
return parameter.BindWithModelBinding(new ClaimsPrincipalValueProviderFactory());
}
}
Now the example method on the FooController looks a lot more readable:
[HttpGet]
public void Bar([FromClaims]ApiKey apiKey)
{
// ...
}
Update
Looks like this is still having problems with route selection and overloading, especially when some of the parameters are nullable and the value is null. I'm going to have to keep looking into this.
Update #2
I managed to simplify the value provider stuff a lot, after finding there is a built-in NameValuePairValueProvider

ASP.NET MVC - Optionally Redirect from a Base Class Method?

Consider the following block of code that reappears in many of my controller actions. (I'm chiefly concerned with the first 6 lines of the method body).
[HttpGet]
public ActionResult OptOut()
{
var user = this.SecurityPrincipal;
if (user.IsReadOnlyUser)
{
this.TempData["ViewModel"] = new AuthorizationModel { User = user };
return this.RedirectToAction("NotAuthorized", "Authorization");
}
var model = /* Elided for brevity */
return this.View(model);
}
My controllers derive from a base class, SecuredController which, in turn, derives from Controller. SecurityPrincipal is a property of SecuredController, and contains extensive Active Directory data about the current user.
In an effort to eliminate duplicate code, I'd ideally like to move the functionality contained in the if {...} block into a base class method, but I can't think of any way to do so, since the return type of the method would have to be ActionResult, resulting in something ungainly like this:
if ((var result = this.RequireReadWrite()) != null)
{
return result;
}
Can anyone suggest a way to do this, or am I simply out of luck here?
As mentioned in the comments, especially noting that security is a cross cutting concern we've suggested using MVC Action Filters to be applied in your use case and design.
Microsoft's documentation is pretty informative and there are more examples that can be found on the web on how to use MVC Filters. I'll try to provide an example, but this will be based on a lot of assumptions on your software architecture, since I simply don't have the knowledge of that.
You could create the following class:
public class SecuredFilterAttribute : AuthorizeAttribute
{
...
}
If using a Dependency Injection framework, you could inject the SecurityPrincipal service. But again I don't know the architecture of your application, so it's up to you how you create that dependency.
When overriding the AuthorizeCore, you could implement it like so:
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
return !this.SecurityPrinciple.IsReadOnlyUser;
}
And when not authorized override the HandleUnauthorizedRequest method to redirect:
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
var redirectRoute = ...; //your route to redirect to an unauthorized page
filterContext.Result = new RedirectToRouteResult(redirectRoute);
//do some other things, for example, setting some tempdata information
}
Again it's up to you on how you would use this Filter. You could register it globally, or apply it on a per controller or action basis. To register it globally, in your startup:
GlobalFilters.Filters.Add(new SecuredFilterAttribute());

Can an attribute be made that can redirect to an action?

I have an MVC3 web application with a front end and a backend. In the backend, each action is protected with an if..then..else redirecting to another action if the user is not logged in (it checks a session). It does this without windows roles.
I was wondering if it was possible to create an attribute that does the check and redirects the user to a login page without having to insert the same code over and over into each action?
EDIT #1
I've created a basecontroller which my admin controllers derived from and added the following member following on from what Virus said:
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
try
{
if (Session["loggedIn"] == null || (bool)Session["loggedIn"] != true)
{
TempData["targetAction"] = ControllerContext.ParentActionViewContext.RouteData.Values["action"] + "/" + ControllerContext.ParentActionViewContext.RouteData.Values["controller"];
RedirectToRoute("Admin/Login");
}
}
catch (NullReferenceException)
{
RedirectToRoute("Admin/Login");
}
}
But this code does'nt work. Can someone please tell me what is wrong?
you can do this easily.
No need to use attributes
Just create a common BaseController which is inherited from Controller class,
and inherit all your controllers from BaseController
override the OnActionExecuting event in BaseController and put the authorization logic on that.
the OnActionExecuting event will be executed every time when an action is invoked.
If you need to redirect to an Action, this code will help
filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary { { "action", "Index" }, { "controller", "Account" } });
That's what the built-in AuthorizeAttribute filter does.
You can use the AuthorizeAttribute filter at a global level, controller level or even at action level. If the built-in Authorize filter is not enough for you can easily create a custom one by inheriting it as explained in this post.

Overriding a global action filter

A handful of pages on my website need to use SSL, so I've added [RequireHttps] to the relevant controllers. However, I still want the majority of my pages to always use non-SSL so I successfully used code I found on SO to create a custom [DoNotUseHttps] filter.
To make things easier I'd like to include this non-SSL filter by default, so I added it to the global filters which are set in the Global.asax file. However, I seem to have now created an infinite loop with each filter redirecting to the other.
Which leads me to my question... is there anything I can add to my global filter to detect if the [RequireHttps] has been already applied to the controller?
Sure, you can interrogate anything you like about the actions and controllers. To check for RequireHttpsAttribute:
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
base.OnActionExecuted(filterContext);
bool requireHttps = filterContext.ActionDescriptor.ControllerDescriptor
.GetCustomAttributes(typeof(RequireHttpsAttribute), true).Length > 0
}

Process cookies before any controller action is invoked in ASP.NET MVC

Where can I put some code that will be executed before any controller action is executed ?
I'm trying to replace default ASP.NET Session and I need to do this before any controller actions take place:
get the cookies collection and check if I have new visitor with no cookies - than I'd add new "session ID" cookie that will than be available to the controllers. Otherwise (if there already is some "session ID" cookie) I will not do anything.
Where can I place the code that will do this ?
I do it in Global.asax.cs
protected void Application_BeginRequest(object sender, EventArgs e)
{
var c = Request.Cookies["lang"];
...
}
There are a number of places you could do this, I'd say that the best place would be in an ActionFilter, overriding the OnActionExecuting event. If you want it to happen first, then you'll want to add the Order setting when you apply it.
If you want all your controllers to have it, then you could apply that filter to a base class, or just override the base class's OnActionExecuting method.
As a side note, for maximum testability you probably should have your not directly access the cookies collection in the request; that information (if needed) should come into the action method as a parameter or as a property on the controller that you can set in tests.
If you need to wire into the lifecycle earlier than OnActionExecuting (for any reason) you could also create a custom ControllerFactory, but I think that's probably overkill bsed on your description.
A custom IHttpModule will do the trick.
ASP.NET MVC 3 now has the Global Action Filters which will run for every controller:
Scott Guthrie's Overview: http://weblogs.asp.net/scottgu/archive/2010/07/27/introducing-asp-net-mvc-3-preview-1.aspx
MSDN details on Filter Types: http://msdn.microsoft.com/en-us/library/gg416513(VS.98).aspx
On your global.asax:
protected void Application_Start()
{
GlobalConfiguration.Configure(WebApiConfig.Register);
RegisterWebApiFilters(GlobalConfiguration.Configuration.Filters);
}
public static void RegisterWebApiFilters(System.Web.Http.Filters.HttpFilterCollection filters)
{
filters.Add(new CredentialsActionFilter());
}
The CredentialsActionFilter class:
public override void OnActionExecuting(HttpActionContext actionContext)
{
var cookies = actionContext.Request.Headers.GetCookies("credentials").FirstOrDefault();
string username = cookies.Cookies.Where(c => c.Name == "username").FirstOrDefault().Value;
actionContext.Request.Properties.Add("username", username); // so you can access the value from within your actions
}

Categories