How to get RouteData.Values["action"].ToString() from User Attribute - c#

[CheckAccessMethod(RouteData.Values["action"].ToString())]
[HttpGet]
public IActionResult Get(){...}
class CheckAccessMethodAttribute : Attribute
{
string MethodName { get; set; }
public CheckAccessMethodAttribute(string methodName)
{
MethodName = methodName;
}
}
I can’t get the current request route.
I want to create method access logic for users

One option would be to use the ActionFilterAttribute, then you'd have access to the ResultExecutingContext which has the route data you need. More information here
public class MyActionFilterAttribute : ActionFilterAttribute
{
public override void OnResultExecuting(ResultExecutingContext context)
{
var action = context.RouteData.Values["action"];
//do something with action here
base.OnResultExecuting(context);
}
}

Related

How to get Post parameters on IsAuthorized of AuthorizeAttribute ASP.NET Web.API MVC 5

I need to get values of my Post Parameters at the time of Authorization. Searchers on web but no solution is working. ActionArguments count always showing 0 and not able to find values in ActionDescriptor.GetParameters()
Here is my code:
POST model -
public class XyzModel
{
public int Prop1 { get; set; }
public string Prop2 { get; set; }
}
Custom Authorize Attribute -
public class CustomAuthorizeAttribute : AuthorizeAttribute
{
protected override bool IsAuthorized(HttpActionContext actionContext)
{
bool conditions = // here I need to check value of my model (XyzModel) properties
if(conditions)
{
return true;
}
return false;
}
}
Code in controller -
[HttpPost]
[CustomAuthorizeAttribute]
public IHttpActionResult MyAction(XyzModel model)
{
// my work here
}
Any suggestion?
You can access model property of ActionArguments it will return XyzModel object. than you can perform any operation on its properties:
XyzModel model = (XyzModel)actionContext.ActionArguments["model"];
In your code it will be like this:
public class CustomAuthorizeAttribute : AuthorizeAttribute
{
protected override bool IsAuthorized(HttpActionContext actionContext)
{
var prop1 = HttpContext.Current.Request.Params["Prop1"];
var prop2 = HttpContext.Current.Request.Params["Prop2"];
bool conditions = // add conditions based on above properties
if(conditions)
{
return true;
}
return false;
}
}
I believe, you will not get post parameter value in AuthorizeAttribute as AuthorizeAttribute methods are called before action's parameter binding.
For your scenario, you can use ActionFilterAttribute which executes only after action's parameter binding. You can create your custom filter attribute by using ActionFilterAttribute
using System.Web.Http.Controllers;
using System.Web.Http.Filters;
public class CheckMyPostDataFilter : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
XyzModel model = (XyzModel )actionContext.ActionArguments["model"]; // you will get data here
base.OnActionExecuting(actionContext);
}
}
You can simply decorate above CheckMyPostDataFilter filter in your action :
[HttpPost]
[CheckMyPostData]
public IHttpActionResult MyAction(XyzModel model)
{
// my work here
}
You can use Request Body Input Stream to read the entire Body content as below
public sealed class CustomAuthorizeAttribute : AuthorizeAttribute
{
protected override bool IsAuthorized(HttpActionContext actionContext)
{
var req = HttpContext.Current.Request.InputStream;
string body = new StreamReader(req).ReadToEnd();
}
}

Authorizing access to controller based on user claims

I know I can restrict access to a controller (or it's members) by decorating it with the AuthorizeAttribute().
With the advent of ASP identity and moving toward a more "claims based" world I would like to find the equivalent attribute. Something like:
[ClaimAuthorize(Permission="CanCreateCustomer")]
public ActionResult CreateCustomer()
{
return View();
}
Although I'm sure this would come built in to identity, all my searching has drawn a blank.
If it doesn't exist how do I roll my own?
You have to Roll your Own. From then you can customize it as you want.
You have to Extend Authorize Attribute.
public class ClientAuthorize : AuthorizeAttribute
{
public new String Roles { get; set; }
public String RequiredRights { get; set; }
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
return CustomAuthorizeLogicReturnsBool(Roles, RequiredRights);
}
protected override void HandleUnauthorizedRequest(System.Web.Mvc.AuthorizationContext filterContext)
{
if (!filterContext.HttpContext.User.Identity.IsAuthenticated)
{
//filterContext.Result = new HttpUnauthorizedResult();
base.HandleUnauthorizedRequest(filterContext);
}
else
{
filterContext.Result = new System.Web.Mvc.HttpStatusCodeResult((int)System.Net.HttpStatusCode.Forbidden);
}
}
}
Usage
[ClientAuthorize(Roles = "ClientUser", RequiredRights = "SaveAdmin,KillAdmin")]
public class AdminController : Controller
{
}
You should probably put in a documentation request for this, but to get you started you can implement IAuthenticationFilter, register it and then decorate your controllers with something like: [Authorize(Roles = "CanCreateCustomer")]
public class CustomAuthenticationAttribute : Attribute, System.Web.Http.Filters.IAuthenticationFilter
{
public bool AllowMultiple
{
get
{
return true;
}
}
public async Task AuthenticateAsync(HttpAuthenticationContext context, CancellationToken cancellationToken)
{
context.Principal = //get principal here, based on your implementation
}
public async Task ChallengeAsync(HttpAuthenticationChallengeContext context, CancellationToken cancellationToken)
{
await Task.FromResult(0);
}
}
register it:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API routes
config.MapHttpAttributeRoutes();
config.Filters.Add(new CustomAuthenticationAttribute ());
}
}

Restrict action filter attribute for one action method [duplicate]

I have set up a global filter for all my controller actions in which I open and close NHibernate sessions. 95% of these action need some database access, but 5% don't. Is there any easy way to disable this global filter for those 5%. I could go the other way round and decorate only the actions that need the database, but that would be far more work.
You could write a marker attribute:
public class SkipMyGlobalActionFilterAttribute : Attribute
{
}
and then in your global action filter test for the presence of this marker on the action:
public class MyGlobalActionFilter : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (filterContext.ActionDescriptor.GetCustomAttributes(typeof(SkipMyGlobalActionFilterAttribute), false).Any())
{
return;
}
// here do whatever you were intending to do
}
}
and then if you want to exclude some action from the global filter simply decorate it with the marker attribute:
[SkipMyGlobalActionFilter]
public ActionResult Index()
{
return View();
}
Though, the accepted answer by Darin Dimitrov is fine and working well but, for me, the simplest and most efficient answer found here.
You just need to add a boolean property to your attribute and check against it, just before your logic begins:
public class DataAccessAttribute: ActionFilterAttribute
{
public bool Disable { get; set; }
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (Disable) return;
// Your original logic for your 95% actions goes here.
}
}
Then at your 5% actions just use it like this:
[DataAccessAttribute(Disable=true)]
public ActionResult Index()
{
return View();
}
In AspNetCore, the accepted answer by #darin-dimitrov can be adapted to work as follows:
First, implement IFilterMetadata on the marker attribute:
public class SkipMyGlobalActionFilterAttribute : Attribute, IFilterMetadata
{
}
Then search the Filters property for this attribute on the ActionExecutingContext:
public class MyGlobalActionFilter : IActionFilter
{
public override void OnActionExecuting(ActionExecutingContext context)
{
if (context.Filters.OfType<SkipMyGlobalActionFilterAttribute>().Any())
{
return;
}
// etc
}
}
At least nowadays, this is quite easy: to exclude all action filters from an action, just add the OverrideActionFiltersAttribute.
There are similar attributes for other filters: OverrideAuthenticationAttribute, OverrideAuthorizationAttribute and OverrideExceptionAttribute.
See also https://www.strathweb.com/2013/06/overriding-filters-in-asp-net-web-api-vnext/
Create a custom Filter Provider. Write a class which will implement IFilterProvider. This IFilterProvider interface has a method GetFilters which returns Filters which needs to be executed.
public class MyFilterProvider : IFilterProvider
{
private readonly List<Func<ControllerContext, object>> filterconditions = new List<Func<ControllerContext, object>>();
public void Add(Func<ControllerContext, object> mycondition)
{
filterconditions.Add(mycondition);
}
public IEnumerable<Filter> GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
{
return from filtercondition in filterconditions
select filtercondition(controllerContext) into ctrlContext
where ctrlContext!= null
select new Filter(ctrlContext, FilterScope.Global);
}
}
=============================================================================
In Global.asax.cs
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
MyFilterProvider provider = new MyFilterProvider();
provider.Add(d => d.RouteData.Values["action"].ToString() != "SkipFilterAction1 " ? new NHibernateActionFilter() : null);
FilterProviders.Providers.Add(provider);
}
protected void Application_Start()
{
RegisterGlobalFilters(GlobalFilters.Filters);
}
Well, I think I got it working for ASP.NET Core.
Here's the code:
public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
// Prepare the audit
_parameters = context.ActionArguments;
await next();
if (IsExcluded(context))
{
return;
}
var routeData = context.RouteData;
var controllerName = (string)routeData.Values["controller"];
var actionName = (string)routeData.Values["action"];
// Log action data
var auditEntry = new AuditEntry
{
ActionName = actionName,
EntityType = controllerName,
EntityID = GetEntityId(),
PerformedAt = DateTime.Now,
PersonID = context.HttpContext.Session.GetCurrentUser()?.PersonId.ToString()
};
_auditHandler.DbContext.Audits.Add(auditEntry);
await _auditHandler.DbContext.SaveChangesAsync();
}
private bool IsExcluded(ActionContext context)
{
var controllerActionDescriptor = (Microsoft.AspNetCore.Mvc.Controllers.ControllerActionDescriptor)context.ActionDescriptor;
return controllerActionDescriptor.ControllerTypeInfo.IsDefined(typeof(ExcludeFromAuditing), false) ||
controllerActionDescriptor.MethodInfo.IsDefined(typeof(ExcludeFromAuditing), false);
}
The relevant code is in the 'IsExcluded' method.
You can change your filter code like this:
public class NHibernateActionFilter : ActionFilterAttribute
{
public IEnumerable<string> ActionsToSkip { get; set; }
public NHibernateActionFilter(params string[] actionsToSkip)
{
ActionsToSkip = actionsToSkip;
}
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (null != ActionsToSkip && ActionsToSkip.Any(a =>
String.Compare(a, filterContext.ActionDescriptor.ActionName, true) == 0))
{
return;
}
//here you code
}
}
And use it:
[NHibernateActionFilter(new[] { "SkipFilterAction1 ", "Action2"})]

WebApi BaseController - Checking User

I have the following BaseApiController:
public class BaseApiController : ApiController
{
public readonly Current _current { get; private set; }
}
All my ApiControllers inherit from this one.
I need to do a validation in all of my methods inside my ApiControllers, that checks if the userId passed match the current HttpContext.Current.User.Identity.
[Route("{userId}/cars")]
public HttpResponseMessage GetCars(int userId)
{
if (userId != _current.UserId)
{
return Request.CreateErrorResponse(HttpStatusCode.Forbidden, "Unauthorized");
}
}
Is there anyway to do it on the BaseApiController, so that I can avoid doing this validation on all of the endpoints that receive the userId as argument?
You can create custom validation attribute based on ActionFilterAttribute to achieve what you need.
For your case it can look like this:
public class UserAccessCheckAttribute: ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
var controller = actionContext.ControllerContext.Controller as BaseApiController;
object requestUserIdObj;
if (controller != null && actionContext.ActionArguments.TryGetValue("userId", out requestUserIdObj))
{
var userId = (int) requestUserIdObj;
if (userId != controller._current.UserId)
{
actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.Forbidden, "Unauthorized");
}
}
}
}
After that you can decorate either controllers or specific actions where you need to perform user id check with this attribute:
[Authorize]
[RoutePrefix("api/Account")]
[UserAccessCheck] //check user id for all actions in controller
public class AccountController : BaseApiController
{
//....
}
public class ValuesController : BaseApiController
{
//....
[UserAccessCheck] //check user id for specific action only
public IEnumerable<string> Get()
{
//...
}
}
After that no additional code in your actions required

ActionFilterAttribute - apply to actions of a specific controller type

I'm using an ActionFilterAttribute to do custom authentication logic. The Attribute will only be used on a derived Controller class that contains my authentication logic.
Here's my Controller, derived from my custom controller class, and a sample attribute:
public class MyController : CustomControllerBase
{
[CustomAuthorize(UserType = UserTypes.Admin)]
public ActionResult DoSomethingSecure()
{
return View();
}
}
Here's an example of my ActionFilterAttribute:
public class CustomAuthorizeAttribute : ActionFilterAttribute
{
public MyUserTypes UserType { get; set; }
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
myUser user = ((CustomControllerBase)filterContext.Controller).User;
if(!user.isAuthenticated)
{
filterContext.RequestContext.HttpContext.Response.StatusCode = 401;
}
}
}
Works great.
Here's the question: Can I demand that this attribute ONLY be used on Actions in my custom controller type?
You can put the ActionFilter on the class itself. All actions in the class will realize the ActionFilter.
[CustomAuthorize]
public class AuthorizedControllerBase : CustomControllerBase
{
}
public class OpenAccessControllerBase : CustomControllerBase
{
}
public class MyRealController : AuthorizedControllerBase
{
// GET: /myrealcontroller/index
public ActionResult Index()
{
return View();
}
}
Based on the comments and the constraints of my system, I took a hybrid approach. Basically, if the request comes through via a cached route or the "User" is not set for any reason, authentication fails in the proper way.
public class CustomAuthorizeAttribute : AuthorizeAttribute
{
private MyUser User { get; set; }
public override void OnAuthorization(AuthorizationContext filterContext)
{
//Lazy loads the user in the controller.
User = ((MyControllerBase)filterContext.Controller).User;
base.OnAuthorization(filterContext);
}
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
bool isAuthorized = false;
string retLink = httpContext.Request.Url.AbsolutePath;
if(User != null)
{
isAuthorized = User.IsValidated;
}
if (!isAuthorized)
{
//If the current request is coming in via an AJAX call,
//simply return a basic 401 status code, otherwise,
//redirect to the login page.
if (httpContext.Request.IsAjaxRequest())
{
httpContext.Response.StatusCode = 401;
}
else
{
httpContext.Response.Redirect("/login?retlink=" + retLink);
}
}
return isAuthorized;
}
}

Categories