I've a custom AuthorizeAttribute in my website. It has some logic about the Result created for unathorized requests.
In some cases, I want to trigger its validation manually*. I don't know if its possible. As I haven't found how to do that, I thought that I could extract the logic to get the Result to a diferrent method, and call it when I want. But then I don't know how to execute the ActionResult (outside de controllers).
How can I do to manually execute authorize validation? If not possible, how can I do to execute an ActionResult outside a controller?
*I need to trigger it manually because some request may pass the validation (because the session is created) and then, when accessing my services, found that the session was closed by someone else. I wouldn't like to add a call to the services in OnAuthorization to reduce services calls.
I'm not sure if its the best, but I've found a way to get it working (still listening for better answers).
When I call the services and notice that the work session has expired, all I do is removing the active user in the web session.
My custom authorize attribute also implements IResultFilter and IExceptionFilter.
In both OnResultExecuted and OnException I validate the active user once more. If the session was removed, then apply the same ActionResult that I would apply in OnAuthorization.
Here is the final class:
public class CustomAuthorizeAttribute : AuthorizeAttribute, IResultFilter, IExceptionFilter
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
ActionResult result = Validate(filterContext.HttpContext);
if (result != null)
filterContext.Result = result;
}
public void OnResultExecuted(ResultExecutedContext filterContext)
{
ActionResult result = Validate(filterContext.HttpContext);
if (result != null)
filterContext.Result = result;
}
public void OnResultExecuting(ResultExecutingContext filterContext)
{
}
public void OnException(ExceptionContext filterContext)
{
ActionResult result = Validate(filterContext.HttpContext);
if (result != null)
{
filterContext.Result = result;
filterContext.ExceptionHandled = true;
}
}
public static ActionResult Validate(HttpContextBase httpContext)
{
if (UserActiveInSession)
return null;
// Different rules to build an ActionResult for this specific case.
}
}
I did not get Diego answer's, But Just simply answering the title, I got it to work like that, You can use it as attribute on controllers actions and also trigger it manually at any place in C# or in Razor views.
namespace SomeNameSpace
{
public class CustomAuthorizeAttributeMVC : AuthorizeAttribute
{
private readonly string[] rolesParams;
public CustomAuthorizeAttributeMVC(params string[] roles)
{
this.rolesParams = roles;
}
public bool IsAuthorized { get {
//Do your authorization logic here and return true if the current user has permission/role for the passed "rolesParams"
string[] allowedRoles = new string[] {"role 1", "role 2", "role 3"};
return allowedRoles.Intersect(rolesParams).Any(); //for the example
}
}
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
return this.IsAuthorized;
}
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
//...
}
}
public class AuthorizeHelper
{
public static bool HasPermission(params string[] roles)
{
return new CustomAuthorizeAttributeMVC(roles).IsAuthorized;
}
}
}
Usage example:
[CustomAuthorizeAttributeMVC("role 2")]
public ActionResult SomeAction()
{
return Content("Authorized !");
}
public ActionResult SomeOtherAction()
{
if(AuthorizeHelper.HasPermission("role 2"))
{
return Content("Authorized !");
}
return Content("401 Not Authorized !");
}
And as said, it can be used in Razor views by calling it normally
#if(AuthorizeHelper.HasPermission("role 2")) {
//...
}
Thanks
Related
I have few custom action filters like
public class CustomFilter:ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
//Some task
}
}
public class CustomFilterNew:ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
//Some task
}
}
In my action method
[CustomFilter]
[CustomFilterNew]
public ActionResult Index()
{
return View();
}
Lets say CustomFilterNew exactly does opposite of CustomFilter.
In such case how to prevent one filter action from executing another filter action lets say CustomFilterNew should prohibit executing CustomFilter
Setting the filterContext.Result property to any non-null value will stop execution of later filters. So if your first filter sets filterContext.Result = new RedirectResult(...), the second filter and action method will never be run.
There are many ways to do that as follow
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
filterContext.Result = new RedirectResult("~/Home/Index");
}
OR
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
//Check your condition here
if (true)
{
//Create your result
filterContext.Result = new EmptyResult();
}
else
base.OnActionExecuting(filterContext);
}
OR
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
...
if (needToRedirect) //your condition here
{
...
filterContext.Result = new RedirectToAction(string action, string controller)
return;
}
...
}
Another thing here i have to mentioned how to set Order of filter works you can give an Order to Action Filters as follow to execute filters in order
[FilterOne(Order = 1), FilterTwo(Order = 2)]
i have ActionFilterAttribute like the following
class MyCustomRouteConstraint : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (something == 1) //i know there is no something variable
{
// do something
}
base.OnActionExecuting(filterContext);
}
}
as you can see in my code there is no variable named as something.
but i want to use the variable named as something in my action.
public ActionResult Index()
{
int something = 1;
return View();
}
public ActionResult About()
{
int something = 2;
return View();
}
public ActionResult Contact()
{
int something = 1;
return View();
}
I think what you need is OnActionExecuted. I have not tested.
class MyCustomRouteConstraint : ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
var something = Convert.ToInt32(filterContext.RouteData.Values["something"]);
if(something == 1)
{
//do something
}
base.OnActionExecuting(filterContext);
}
}
OnActionExecuting is called by the ASP.NET MVC framework BEFORE the action method executes. So it doesn't make sense to initialize your something variable in the action body. But still if you are going to override some method which is called AFTER the action method executes you can probably use ViewBag to init the variable in the controller and then get its value using filterContext.
You use ViewBag for this as well.
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
filterContext.Controller.ViewBag.SommeVariable = "Test";
}
And in your action
public ActionResult Index()
{
var variable = ViewBag.SommeVariable;
return View();
}
I use structuremap dependency injection to do that. See here some snippets:
namespace something.Infrastructure.ActionFilters {
public class PlatformAuthorizeAttribute : AuthorizeAttribute
{
public IRepository<User> UserRepo { get; set; }
public override void OnAuthorization(AuthorizationContext filterContext)
{
base.OnAuthorization(filterContext);
if (WebSecurity.Initialized && filterContext.HttpContext.User.Identity.IsAuthenticated)
{
if (filterContext.HttpContext.User.IsInRole("Banned"))
{
WebSecurity.Logout();
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary
{
{"Controller", "Home"},
{"Action", "Banned"},
{"Area", ""}
});
}
UserRepo.Dispose();
UserRepo = null;
}
}
}
}
And then initialize the following mapping in your IoC structuremap class:
public static class IoC
{
public static IContainer Initialize()
{
ObjectFactory.Initialize(x =>
{
x.Scan(scan =>
{
scan.TheCallingAssembly();
scan.WithDefaultConventions();
});
x.SetAllProperties(pset =>
{
pset.WithAnyTypeFromNamespace("something.Infrastructure.ActionFilters");
pset.OfType<IRepository<User>>();
});
});
return ObjectFactory.Container;
}
}
I think this is the cleanest way to provide your action filters with properties. Note that in my example I used a custom AuthorizeAttribute. You will need to change this to ActionFilterAttribute and use the OnActionExecuting method override to access the properties.
Use HttpContext.Items
HttpContext.Items["something"] = 1;
In ActionFilter, you can access it as:
var something = (int)filterContext.HttpContext.Items["something"];
However, your example of action filter is OnActionExecuting; this will execute before any of your Index/About actions executes. So you should initialize 'something' somewhere else as per your needs (for example, inside controller's OnActionExecuting method).
I made a functionality that prevents multiple-login for one username at the same time and I call it in Actions like this:
int userId = (int)WebSecurity.CurrentUserId;
if ((this.Session.SessionID != dba.getSessionId(userId)) || dba.getSessionId(userId) == null)
{
WebSecurity.Logout();
return RedirectToAction("Index", "Home");
}
So the point is that every time user logins I save his sessionID into database field. So if someone with same username logins over someone already logged in with same username it overwrites that database field with this new session. If sessionID in DB is not the same as current session ID of logined user it log him out.
Is there a possibility to put this part of code in 1 place or do I have to put it in every single Action in my application?
I tried in Global.asax:
void Application_BeginRequest(object sender, EventArgs e)
{
if (Session["ID"] != null)
{
int userId = Convert.ToInt32(Session["ID"]);
if ((this.Session.SessionID != db.getSessionId(userId)) || db.getSessionId(userId) == null)
{
WebSecurity.Logout();
}
}
}
But I can't use Session here nor WebSecurity class if I try like this:
void Application_BeginRequest(object sender, EventArgs e)
{
int userId = (int)WebSecurity.CurrentUserId;
if ((this.Session.SessionID != db.getSessionId(userId)) || db.getSessionId(userId) == null)
{
WebSecurity.Logout();
Response.RedirectToRoute("Default");
}
}
because I get null reference exception.
EDIT
I used this:
void IActionFilter.OnActionExecuting(ActionExecutingContext filterContext)
{
int userId = (int)WebSecurity.CurrentUserId;
using (var db = new UsersContext())
{
string s = db.getSessionId(userId);
if ((filterContext.HttpContext.Session.SessionID != db.getSessionId(userId)) || db.getSessionId(userId) == null)
{
WebSecurity.Logout();
filterContext.Result = new RedirectResult("/Home/Index");
}
}
}
I had to use using statement for context, otherwise db.getSessionId(userId) was returning old sessionId. Method is this:
public string getSessionId(int userId)
{
string s = "";
var get = this.UserProfiles.Single(x => x.UserId == userId);
s = get.SessionId;
return s;
}
Very strange, will have to read about why that happened.
Everything works fine, except one thing. I have one JsonResult action in a controller, which returns Json, but since event(its textbox on enter event) can't trigger POST(I assume it's because it logs out before) redirect doesn't work. It can't even post to that Json action to receive callback and redirect. Any clues on that?
success: function (data) {
if (data.messageSaved) {
//data received - OK!
}
else {
// in case data was not received, something went wrong redirect out
window.location.href = urlhome;
}
}
Before I used ActionFilterAttribute I used code to check different sessions inside of POST and of course it could make callback and therefore redirect if didn't receive the data.. But now since it can't even POST and go into method it just stucks there and doesn't redirect :)
I would derive from AuthorizeAttribute. No need to check this information if you don't need to authorize the request.
public class SingleLoginAuthorizeAttribute : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
bool isAuthorized = base.AuthorizeCore(httpContext);
if (isAuthorized)
{
int userId = (int)WebSecurity.CurrentUserId;
if ((filterContext.HttpContext.Session.SessionID != dba.getSessionId(userId))
|| dba.getSessionId(userId) == null)
{
WebSecurity.Logout();
isAuthorized = false;
filterContext.Result = new RedirectResult("/Home/Index");
}
}
return isAuthorized;
}
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
if (filterContext.HttpContext.Request.IsAjaxRequest())
{
filterContext.Result = new JsonResult()
{
Data = FormsAuthentication.LoginUrl,
JsonRequestBehavior = JsonRequestBehavior.AllowGet
};
}
else
{
base.HandleUnauthorizedRequest(filterContext);
}
}
}
I'd also mention that this allows you to short circuit other ActionFilters because they run after OnAuthorization.
Forward Order - OnAuthorization : AuthorizationFilter (Scope Controller)
Forward Order - OnActionExecuting : ActionFilter1 (Scope Global)
Forward Order - OnActionExecuting : ActionFilter2 (Scope Controller)
Forward Order - OnActionExecuting : ActionFilter3 (Scope Action)
Then as Rob Lyndon mentioned, you could in the FilterConfig (MVC4)
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new SingleLoginAuthorizeAttribute());
}
}
Then when you don't want to require any authorization, you can use the AllowAnonymouseAttribute on your ActionResult methods or Controller Class to allow anonymous access.
Update
I added a way for your ajax calls (Get or Post) to work with timeouts. You can do something like:
success: function (jsonResult)
{
if (jsonResult.indexOf('http') == 0)
{
window.location = jsonResult;
}
// do other stuff with the Ajax Result
}
This isn't exactly the best way, but if you want more information on how to do this better I would ask another question instead of appending more questions on this one.
The ActionFilterAttribute is the way to go.
We created an Action Filter called SeatCheck and decorate each controller like this:
[SeatCheck]
public class NoteController : BaseController
{
We use that to get a count of seats and other functions, but it makes it so much easier to control everywhere without thinking about it.
In the proejct ActionFilters folder we have the SeatCheck.cs file that looks like this:
namespace site.ActionFilters
{
public class SeatCheckAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
You can get the SessionID in the Action Filter like this
filterContext.HttpContext.Session.SessionID
Create a custom action filter, and put that code in the filter, then apply the filter to your controller.
Yes, indeed there is. You can use an attribute derived from ActionFilterAttribute.
I would write a class called SessionSecurityAttribute:
public class SessionSecurityAttribute : ActionFilterAttribute
{
public MyDbConn MyDbConn { get; set; }
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var session = filterContext.RequestContext.HttpContext.Session;
if (session["ID"] != null && WebSecurity.IsAuthenticated)
{
int userId = Convert.ToInt32(session["ID"]);
if ((sessionID != MyDbConn.getSessionId(userId)) || MyDbConn.getSessionId(userId) == null)
{
WebSecurity.Logout();
}
}
}
}
The question remains: how can you add these attributes to your actions whilst giving them access to your database? That's easy: in Global.asax you can call into the bootstrapping RegisterGlobalFilters method:
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
filters.Add(new SessionSecurityAttribute
{
MyDbConn = DependencyResolver.Current.GetService<MyDbConn>()
});
}
This adds your SessionSecurityAttribute, complete with DB connection, to every action by default, without a line of repeated code.
You might try implementing your own custom ISessionIDManager:
http://msdn.microsoft.com/en-us/library/system.web.sessionstate.isessionidmanager.aspx
In the validate, check to see if it's still valid, otherwise return false.
I have a service class UserService that gets an instance of IDocumentStore injected using AutoFac. This is working fine but now I'm looking at code like this:
public void Create(User user)
{
using (var session = Store.OpenSession())
{
session.Store(user);
session.SaveChanges();
}
}
Every action that writes to the db uses this same structure:
using (var session = Store.OpenSession())
{
dosomething...
session.SaveChanges();
}
What is the best way to eliminate this repetitive code?
The easiest way is implementing OnActionExecuting and OnActionExecuted on a base controller and use it.
let's imagine you create your RavenController like this:
public class RavenController : Controller
{
public IDocumentSession Session { get; set; }
protected IDocumentStore _documentStore;
public RavenController(IDocumentStore documentStore)
{
_documentStore = documentStore;
}
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
Session = _documentStore.OpenSession();
base.OnActionExecuting(filterContext);
}
protected override void OnActionExecuted(ActionExecutedContext filterContext)
{
using (Session)
{
if (Session != null && filterContext.Exception == null)
{
Session.SaveChanges();
}
}
base.OnActionExecuted(filterContext);
}
}
then all you need to do in your own controllers is inherit from RavenController like this:
public class HomeController : RavenController
{
public HomeController(IDocumentStore store)
: base(store)
{
}
public ActionResult CreateUser(UserModel model)
{
if (ModelState.IsValid)
{
User user = Session.Load<User>(model.email);
if (user == null) {
// no user found, let's create it
Session.Store(model);
}
else {
ModelState.AddModelError("", "That email already exists.");
}
}
return View(model);
}
}
Interesting enough, I have found a blog post showing exactly this technique ...
it does explain way more that what I did. I hope it helps you better
Building an ASP.NET MVC app using RavenDB as a Backing Store
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"})]