Check if API controller method has authorize attribute - asp.net-web-api

I'm trying to determine whether API method has authorize attribute and although I can easily find that using MethodInfo, I can't seem to find a way to handle check when Authorize is set on controller level.
Basically I need to know if either controller or method has Authorize attribute.
public class SecurityRequirementsOperationFilter : IOperationFilter
{
public void Apply(Operation operation, OperationFilterContext context)
{
var hasAuthorizeAttribute = context.MethodInfo
.GetCustomAttributes(true)
.OfType<AuthorizeAttribute>()
.Any();
if (!hasAuthorizeAttribute)
{
operation.Security = new List<IDictionary<string, IEnumerable<string>>>();
}
}
}

I've managed to find a way to find if method has either controller or method Authorize attribute.
var hasAuthAttribute = context.MethodInfo.DeclaringType.GetCustomAttributes(true)
.Union(context.MethodInfo.GetCustomAttributes(true))
.OfType<AuthorizeAttribute>()
.Any();

Related

To disable Get Method if Sensitive Information is passed

Our Application (MVC Based) accepts user payment information update request over GET method.Default method used by the application is POST.
Currently if we pass any sensitive information over a GET Method via Querystring, then Request sucessfully works.The reason is that it hits the same Edit Action method in Controller
[HttpGet]
[ValidateRequest(true)]
public ActionResult Edit (parameters)
But what we want is that Any request with sensitive information (like Credit Card etc.) sent over a GET method should be rejected by the application.
Anyhow can we reject GET method through Routing if sensitive information is passed? Please suggest valid approach.
My current route that calls Action is mentioned below:
routes.MapRoute("ChargeInformation", "ChargeInformationt.aspx/{seq}", new { controller = "Payment", action = "Edit", seq = UrlParameter.Optional });
Routing's only responsibility is to map URLs to route values and from route values back to URLs. It is a separate concern than authorizing the request. In fact, the built-in routing extension methods (MapRoute, MapPageRoute, and IgnoreRoute) completely ignore the incoming query string.
For request authorization, MVC has an IAuthorizationFilter interface that you can hook into. You can also (optionally) combine it with an attribute to make it run conditionally on specific action methods, as shown below.
In this case, you just want to reject specific query string key names that are passed into the request. It is unclear what action you wish to take in this case, so I am just setting to HTTP 403 forbidden as an example.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Web.Mvc;
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class DisallowQueryStringKeysAttribute : FilterAttribute, IAuthorizationFilter
{
private readonly IEnumerable<string> keysSplit;
public DisallowQueryStringKeysAttribute(string keys)
{
this.keysSplit = SplitString(keys);
}
public void OnAuthorization(AuthorizationContext filterContext)
{
var queryStringKeys = filterContext.HttpContext.Request.QueryString.AllKeys;
// If any of the current query string keys overlap with the non-authorized keys
if (queryStringKeys.Intersect(this.keysSplit, StringComparer.OrdinalIgnoreCase).Any())
{
filterContext.HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
// You must set the result property to a handler to run to tell the
// framework that the filter should do something other than run the
// action method. In this case, we just set it to an empty result,
// which implements the null object pattern. You could (if so inclined),
// make a class to set the status code or do something else
// (such as redirect) to indicate that the request is invalid.
filterContext.Result = new EmptyResult();
}
}
private string[] SplitString(string original)
{
if (String.IsNullOrEmpty(original))
{
return new string[0];
}
var split = from piece in original.Split(',')
let trimmed = piece.Trim()
where !String.IsNullOrEmpty(trimmed)
select trimmed;
return split.ToArray();
}
}
Usage
[HttpGet]
[ValidateRequest(true)]
[DisallowQueryStringKeys("creditCard, password")]
public ActionResult Edit (string creditCard, string password)

Mvc6 versioned api actions with custom constraints

I'm making a versioned api with mvc6 and to do that I want to be able to specify for an action on which api version it should work.
My api route is: /api/{version}/... and so I want at a certain action to inspect the version route value and to see if this action is available for that version.
I want to be able to specify that as an attribute on the api action, so for example:
// This is the base api controller
[Route("api/{version:regex(^v[[0-9]].[[0-9]]$)}/[controller]")]
public abstract class ApiControllerBase { ... }
// This is an action in one of the sub classes
[HttpGet("foo")]
[ApiVersion("0.1", "0.2")] // Here! (this is params string[])
public object Foo()
{
// return
}
// This is an action in another sub class
[HttpGet("foo")]
[ApiVersion("1.0")]
public object Foo()
{
// return
}
My question is what should ApiVersion implement or extend for this to work? I don't believe action filters work as I want because I don't want to return a 404 when this doesn't match because other actions inside other controllers might be able to handle this (Later I might have HomeController with common actions and Home2Controller with extended actions that work only for 1.0).
Note that I'm not asking for an implementation of ApiVersionAttribute, I just need to know what mvc infrastructure I should hook into (action filters, route constraints, ...) that will let me create an attribute that can look into route values and say if this action is a match.
It took 4 hours analyzing the mvc6 source but it was worth it. I solved this using an action attribute implementing Microsoft.AspNet.Mvc.ActionConstraints.IActionConstraint.
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
public class ApiVersionAttribute : Attribute, IActionConstraint
{
public ApiVersionAttribute(string version)
{
Version = version;
}
public string Version { get; }
public int Order => 0;
public bool Accept(ActionConstraintContext context)
{
var routeData = context.RouteContext.RouteData;
// return ...
}
}
And then on a certain action:
[HttpGet("foo")]
[ApiVersion("0.1")]
public object Foo()
{
// return ...
}

How to send an array via a URI using Attribute Routing in Web API?

I'm following the article on Attribute Routing in Web API 2 to try to send an array via URI:
[HttpPost("api/set/copy/{ids}")]
public HttpResponseMessage CopySet([FromUri]int[] ids)
This was working when using convention-based routing:
http://localhost:24144/api/set/copy/?ids=1&ids=2&ids=3
But with attribute routing it is no longer working - I get 404 not found.
If I try this:
http://localhost:24144/api/set/copy/1
Then it works - I get an array with one element.
How do I use attribute routing in this manner?
The behavior you are noticing is more related to Action selection & Model binding rather than Attribute Routing.
If you are expecting 'ids' to come from query string, then modify your route template like below(because the way you have defined it makes 'ids' mandatory in the uri path):
[HttpPost("api/set/copy")]
Looking at your second question, are you looking to accept a list of ids within the uri itself, like api/set/copy/[1,2,3]? if yes, I do not think web api has in-built support for this kind of model binding.
You could implement a custom parameter binding like below to achieve it though(I am guessing there are other better ways to achieve this like via modelbinders and value providers, but i am not much aware of them...so you could probably need to explore those options too):
[HttpPost("api/set/copy/{ids}")]
public HttpResponseMessage CopySet([CustomParamBinding]int[] ids)
Example:
[AttributeUsage(AttributeTargets.Parameter, Inherited = false, AllowMultiple = false)]
public class CustomParamBindingAttribute : ParameterBindingAttribute
{
public override HttpParameterBinding GetBinding(HttpParameterDescriptor paramDesc)
{
return new CustomParamBinding(paramDesc);
}
}
public class CustomParamBinding : HttpParameterBinding
{
public CustomParamBinding(HttpParameterDescriptor paramDesc) : base(paramDesc) { }
public override bool WillReadBody
{
get
{
return false;
}
}
public override Task ExecuteBindingAsync(ModelMetadataProvider metadataProvider, HttpActionContext actionContext,
CancellationToken cancellationToken)
{
//TODO: VALIDATION & ERROR CHECKS
string idsAsString = actionContext.Request.GetRouteData().Values["ids"].ToString();
idsAsString = idsAsString.Trim('[', ']');
IEnumerable<string> ids = idsAsString.Split(',');
ids = ids.Where(str => !string.IsNullOrEmpty(str));
IEnumerable<int> idList = ids.Select(strId =>
{
if (string.IsNullOrEmpty(strId)) return -1;
return Convert.ToInt32(strId);
}).ToArray();
SetValue(actionContext, idList);
TaskCompletionSource<object> tcs = new TaskCompletionSource<object>();
tcs.SetResult(null);
return tcs.Task;
}
}

Disable ApiController at runtime

I have a ASP.NET Web API (.NET 4) application which has a few controllers. We will run several instances of the Web API application on IIS with one difference. Only certain controllers will be available under certain IIS instances. What I was thinking is to disable/unload the controllers that are not applicable to an instance when the instance starts up.
Anyone got some information that could guide me in the right direction on this?
You can put your own custom IHttpControllerActivator in by decorating the DefaultHttpControllerActivator. Inside just check for a setting and only create the controller if allowed.
When you return null from the Create method the user will receive 404 Not Found message.
My example shows a value in App Settings (App.Config or Web.Config) being checked but obviously this could any other environment aware condition.
public class YourCustomControllerActivator : IHttpControllerActivator
{
private readonly IHttpControllerActivator _default = new DefaultHttpControllerActivator();
public YourCustomControllerActivator()
{
}
public IHttpController Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor,
Type controllerType)
{
if (ConfigurationManager.AppSettings["MySetting"] == "Off")
{
//Or get clever and look for attributes on the controller in controllerDescriptor.GetCustomAttributes<>();
//Or use the contoller name controllerDescriptor.ControllerName
//This example uses the type
if (controllerType == typeof (MyController) ||
controllerType == typeof (EtcController))
{
return null;
}
}
return _default.Create(request, controllerDescriptor, controllerType);
}
}
You can switch your activator in like so:
GlobalConfiguration.Configuration.Services.Replace(typeof(IHttpControllerActivator), new YourCustomControllerActivator());
Update
It has been a while since I looked at this question but if I was to tackle it today I would alter the approach slightly and use a custom IHttpControllerSelector. This is called before the activator and makes for a slightly more efficient place to enable and disable controllers... (although the other approach does work). You should be able to decorate or inherit from DefaultHttpControllerSelector.
Rather than unloading the controllers, I think I'd create a custom Authorize attribute that looked at the instance information in deciding to grant authorization.
You would add the following to each controller at the class level, or you could also add this to individual controller actions:
[ControllerAuthorize (AuthorizedUserSources = new[] { "IISInstance1","IISInstance2","..." })]
Here's the code for the Attribute:
public class ControllerAuthorize : AuthorizeAttribute
{
public ControllerAuthorize()
{
UnauthorizedAccessMessage = "You do not have the required access to view this content.";
}
//Property to allow array instead of single string.
private string[] _authorizedSources;
public string UnauthorizedAccessMessage { get; set; }
public string[] AuthorizedSources
{
get { return _authorizedSources ?? new string[0]; }
set { _authorizedSources = value; }
}
// return true if the IIS instance ID matches any of the AllowedSources.
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (httpContext == null)
throw new ArgumentNullException("httpContext");
//If no sources are supplied then return true, assuming none means any.
if (!AuthorizedSources.Any())
return true;
return AuthorizedSources.Any(ut => ut == httpContext.ApplicationInstance.Request.ServerVariables["INSTANCE_ID"]);
}
The IHttpControllerActivator implementation doesn't disable the routes defined using attribute routing , if you want to switch on/off a controller and have a default catch all route controller. Switching off using IHttpControllerActivator disables the controller but when the route is requested it doesn't hit the catch all route controller -it simply tries to hit the controller that was removed and returns no controller registered.

How do I get the MethodInfo of an action, given action, controller and area names?

What I have is the following extension method:
public MyCustomAttribute[] GetActionAttributes(
this Controller #this,
string action,
string controller,
string area,
string method)
{
}
How does ASP.NET MVC 3 find the action method, given the area, controller, action names and the method (GET, POST)?
To this moment I have nothing... no clues on how to do this.
I am currently looking for the stack trace inside a controller action, to find out how MVC dicovered it.
Why I need these attributes
My attributes contain information about whether a given user can or not access it... but depending on whether they can or not access it, I wan't to show or hide some html fields, links, and other things that could call that action.
Other uses
I have thought of using this to place an attribute over an action, that tells the css class of the link that will be rendered to call it... and some other UI hints... and then build an HtmlHelper that will render that link, looking at these attributes.
Not a duplicate
Yes, some will say this is possibly a duplicate of this question...
that does not have the answer I want:
How can i get the MethodInfo of the controller action that will get called given a request?
That's why I have specified the circumstances of my question.
I have looked inside MVC 3 source code, and tested with MVC 4, and discovered how to do it.
I have tagged the question wrong... it is not for MVC 3, I am using MVC 4. Though, as I could find a solution looking at MVC 3 code, then it may work with MVC 3 too.
At the end... I hope this is worth 5 hours of exploration, with a lot trials and errors.
Works with
MVC 3 (I think)
MVC 4 (tested)
Drawbacks of my solution
Unfortunately, this solution is quite complex, and dependent on things that I don't like very much:
static object ControllerBuilder.Current (very bad for unit testing)
a lot of classes from MVC (high coupling is always bad)
not universal (it works with MVC 3 default objects, but may not work with other implementations derived from MVC... e.g. derived MvcHandler, custom IControllerFactory, and so on ...)
internals dependency (depends on specific aspects of MVC 3, (MVC 4 behaves like this too) may be MVC 5 is different... e.g. I know that RouteData object is not used to find the controller type, so I simply use stub RouteData objects)
mocks of complex objects to pass data (I needed to mock HttpContextWrapper and HttpRequestWrapper in order to set the http method to be POST or GET... these pretty simple values comes from complex objects (oh god! =\ ))
The code
public static Attribute[] GetAttributes(
this Controller #this,
string action = null,
string controller = null,
string method = "GET")
{
var actionName = action
?? #this.RouteData.GetRequiredString("action");
var controllerName = controller
?? #this.RouteData.GetRequiredString("controller");
var controllerFactory = ControllerBuilder.Current
.GetControllerFactory();
var controllerContext = #this.ControllerContext;
var otherController = (ControllerBase)controllerFactory
.CreateController(
new RequestContext(controllerContext.HttpContext, new RouteData()),
controllerName);
var controllerDescriptor = new ReflectedControllerDescriptor(
otherController.GetType());
var controllerContext2 = new ControllerContext(
new MockHttpContextWrapper(
controllerContext.HttpContext.ApplicationInstance.Context,
method),
new RouteData(),
otherController);
var actionDescriptor = controllerDescriptor
.FindAction(controllerContext2, actionName);
var attributes = actionDescriptor.GetCustomAttributes(true)
.Cast<Attribute>()
.ToArray();
return attributes;
}
EDIT
Forgot the mocked classes
class MockHttpContextWrapper : HttpContextWrapper
{
public MockHttpContextWrapper(HttpContext httpContext, string method)
: base(httpContext)
{
this.request = new MockHttpRequestWrapper(httpContext.Request, method);
}
private readonly HttpRequestBase request;
public override HttpRequestBase Request
{
get { return request; }
}
class MockHttpRequestWrapper : HttpRequestWrapper
{
public MockHttpRequestWrapper(HttpRequest httpRequest, string httpMethod)
: base(httpRequest)
{
this.httpMethod = httpMethod;
}
private readonly string httpMethod;
public override string HttpMethod
{
get { return httpMethod; }
}
}
}
Hope all of this helps someone...
Happy coding for everybody!
You can achieve this functionality by using the AuthorizeAttribute. You can get the Controller and Action name in OnAuthorization method. PLease find sample code below.
public sealed class AuthorizationFilterAttribute : AuthorizeAttribute
{
/// <summary>
/// Use for validate user permission and when it also validate user session is active.
/// </summary>
/// <param name="filterContext">Filter Context.</param>
public override void OnAuthorization(AuthorizationContext filterContext)
{
string actionName = filterContext.ActionDescriptor.ActionName;
string controller = filterContext.ActionDescriptor.ControllerDescriptor.ControllerName;
if (!IsUserHasPermission(controller, actionName))
{
// Do your required opeation
}
}
}
if you have a default route configured like
routes.MapRoute(
"Area",
"",
new { area = "MyArea", controller = "Home", action = "MyAction" }
);
you can get the route information inside the controller action like
ht tp://localhost/Admin
will give you
public ActionResult MyAction(string area, string controller, string action)
{
//area=Admin
//controller=Home
//action=MyAction
//also you can use RouteValues to get the route information
}
here is a great blog post and a utility by Phil Haack RouteDebugger 2.0
This is a short notice! Be sure to use filterContext.RouteData.DataTokens["area"]; instead of filterContext.RouteData.Values["area"];
Good Luck.

Resources