Dynamically extract information from FluentSecurity configuration - asp.net-mvc

I'm working on an ASP.NET MVC Website that uses FluentSecurity to configure authorizations. Now we need a custom ActionLink Helper which will be displayed only if the current user has access to the targeted action.
I want to know if there is a way to know dynamically, from FluentSecurity Configuration (using SecurityConfiguration class for example), if the current logged user has access to an action given her name (string) and her controller name (string). I spend a lot of time looking in the source code of FluentSecurity https://github.com/kristofferahl/FluentSecurity but with no success.
For example:
public bool HasAccess(string controllerName, string actionName) {
//code I'm looking for goes here
}

Finally, i will answer myself may this will help another one.
I just emulate the OnAuthorization method of the HandleSecurityAttribute class. this code works well:
public static bool HasAccess(string fullControllerName, string actionName)
{
ISecurityContext contx = SecurityContext.Current;
contx.Data.RouteValues = new RouteValueDictionary();
var handler = new SecurityHandler();
try
{
var result = handler.HandleSecurityFor(fullControllerName, actionName, contx);
return (result == null);
} catch (PolicyViolationException)
{
return false;
}
}

Related

How to count number of hits to the website using MVC [duplicate]

what is the best way to capture page views by person without slowing down performance on the site. I see that stackoverflow show page views all over the place. Are they doing an insert into a db everytime i click on a page?
In asp.net-mvc, Is there any recommended way to track page view per user (my site has a login screen) so i can review which pages people are going to and how often
First off.. if what you really care about is how are customers using my site then you most likely want to look into Google Analytics or a similar service.
But if you want a quick and dirty page view record and you are using ASP.Net MVC 3 then as Chris Fulstow mentioned you're going to want to use a mix of global action filters and caching. Here is an example.
PageViewAttribute.cs
public class PageViewAttribute : ActionFilterAttribute
{
private static readonly TimeSpan pageViewDumpToDatabaseTimeSpan = new TimeSpan(0, 0, 10);
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var calledMethod = string.Format("{0} -> {1}",
filterContext.ActionDescriptor.ControllerDescriptor.ControllerName,
filterContext.ActionDescriptor.ActionName);
var cacheKey = string.Format("PV-{0}", calledMethod);
var cachedResult = HttpRuntime.Cache[cacheKey];
if(cachedResult == null)
{
HttpRuntime.Cache.Insert(cacheKey, new PageViewValue(), null, DateTime.Now.Add(pageViewDumpToDatabaseTimeSpan) , Cache.NoSlidingExpiration, CacheItemPriority.Default,
onRemove);
}
else
{
var currentValue = (PageViewValue) cachedResult;
currentValue.Value++;
}
}
private static void onRemove(string key, object value, CacheItemRemovedReason reason)
{
if (!key.StartsWith("PV-"))
{
return;
}
// write out the value to the database
}
// Used to get around weird cache behavior with value types
public class PageViewValue
{
public PageViewValue()
{
Value = 1;
}
public int Value { get; set; }
}
}
And in your Global.asax.cs
public class MvcApplication : HttpApplication
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new PageViewAttribute());
}
}
For pre-ASP.Net MVC 3 ONLY you are going to have to apply the same attribute manually to all of your actions.
[PageView]
public ActionResult CallOne()
{
}
[PageView]
public ActionResult CallTwo()
{
}
The best way would probably be a global action filter that intercepts requests to all actions on all controllers, then increments a counter in the database for the current user and page. To save hitting the database too hard, you could cache these values and invalidate them every few minutes, depending on how much traffic you're dealing with.
We use the open source Piwik: http://piwik.org/, which is setup on it's own server. One line of Javascript in the _Layout page makes a call to Piwik after the page has loaded (put the JS at the end) and does not affect page load performance at all.
In addition to just counts, you'll get a ton of info about where your users are coming from, browser, screen resolutions, installed plugins. Plus you can track conversions and use the same tool to track marketing campaigns, etc.
<soapbox>
I cannot think of a situation where you'd be better off implementing this in MVC or in your web app in general. This stuff simply does not belong in your web app and is a meta-concern that should be separated out. This approach has enabled us to track analytics for all of our apps (32 of them: mvc 2/3, webforms, php...) in a unified manner.
If you really don't want to use another tool for this purpose, I would recommend tapping into your IIS log and getting your stats from there. Again, to get any real decision making power out of it, you'll need to put a good analyzer on it. I recommend Splunk: http://www.splunk.com/
</soapbox>
I wanted to post an updated version of Shane's answer for those who are interested. Some things to consider:
You have to set the action attribute up as a service when decorating your
methods using syntax like the following :
[ServiceFilter(typeof(PageViewAttribute))]
As far as I can tell, HttpRuntime.Cache.Insert isn't a thing in .NET Core, so I used a simple implementation of IMemoryCache (You may need to add this line to your startup.cs in order to use the interface):
services.AddMemoryCache();
Because we are injecting IMemoryCache into a class that is not a controller, we need to register our attribute as a service in startup.cs, like so:
services.AddScoped<[PageViewAttribute]>(); - without brackets!
Whatever object you return when creating a cacheKey will be assigned to the 'value' parameter of the OnRemove method.
Below is the code.
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var controllerActionDescriptor = filterContext.ActionDescriptor as ControllerActionDescriptor;
var arguments = filterContext.ActionArguments;
ActionId = arguments["id"].ToString();
var calledMethod = string.Format("{0} -> {1}",
controllerActionDescriptor.ControllerName,
controllerActionDescriptor.ActionName);
var cacheKey = string.Format("PV-{0}", calledMethod);
var cachedResult = _memoryCache.Get(cacheKey);
if (cachedResult == null)
{
//Get cacheKey if found, if not create cache key with following settings
_memoryCache.GetOrCreate(cacheKey, cacheKey =>
{
cacheKey.AbsoluteExpirationRelativeToNow
= pageViewDumpToDatabaseTimeSpan;
cacheKey.SetValue(1);
cacheKey.RegisterPostEvictionCallback(onRemove);
return cacheKey.Value;
});
}
else
{
_memoryCache.Get(cacheKey);
}
}
//Called when Memory entry is removed
private void onRemove(object key, object value, EvictionReason reason, object state)
{
if (!key.ToString().StartsWith("PV-"))
{
return;
}
// write out the value to the database
SaveToDataBase(key.ToString(), (int)value);
}
As a point of reference, this was done for a .NET Core 5 MVC App.
Regards.

Derived from AuthorizeAttribute but User.Identity.Name is null unless using AuthorizeAttribute

So I've created a custom authorize attribute I use in a few places that is derived from an abstract base class which is derived from AuthorizeAttribute:
CustomAuthorizeAttributeBase.cs
public abstract class CustomAuthorizeAttributeBase : AuthorizeAttribute
{
public abstract string GetUsers();
public abstract string GetRoles();
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
if (filterContext.IsChildAction)
{
return;
}
filterContext.Result =
new RedirectToRouteResult(new RouteValueDictionary
{
{"controller", "NotAuthorized"},
{"action", "Index"},
});
}
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (GetUsers().IndexOf(httpContext.User.Identity.Name, StringComparison.CurrentCultureIgnoreCase) >= 0 ||
GetRoles().Split(',').Any(s => httpContext.User.IsInRole(s)))
{
return true;
}
return false;
}
}
AreaLevelReadonly.cs
public class AreaLevelReadOnly : CustomAuthorizeAttributeBase
{
public override string GetUsers()
{
return ConfigurationManager.AppSettings["AreaReadonlyUsers"];
}
public override string GetRoles()
{
return ConfigurationManager.AppSettings["AreaReadonlyRoles"];
}
}
I also have some fairly simple code that gets me the currently logged in user:
UserIdentity.cs
public class UserIdentity : IUserIdentity
{
public string GetUserName()
{
return HttpContext.Current.User.Identity.Name.Split('\\')[1];
}
}
However, when I add my AreaLevelReadonly attribute to my controllers, getUserName fails and returns an exception that Name is null. I agonized over it for about an hour before putting authorize attribute on there as well, at which point it magically started working again. So, what is so different on the implementation level that my attribute deriving from authorizeattribute doesn't cause the Name to be populated.
Note: Windows authentication is on for the area, and the code works, but I don't understand why the Readonly attribute isn't enough to trigger authorization and population of the HttpContext.Current.User.Identity.Name.
Edit: Working:
[AreaLevelReadonly]
[Authorize]
public class DeleteAreaDataController : Controller {
//etc
var username = _userIdentity.GetUserName(HttpContext);
//etc
}
Exception on name:
[AreaLevelReadonly]
public class DeleteAreaDataController : Controller {
//etc
var username = _userIdentity.GetUserName(HttpContext);
//etc
}
More likely than not, you're accessing User.Identity.Name before it's populated. By including the standard Authorize attribute, as well, your code is then only running after the user has been authorized already and User.Identity.Name has been populated.
EDIT
Sorry, I misunderstood where the code attempting to call User.Identity.Name was running. Based on the belief that it was happening in your custom attribute, I was suggesting that you're trying to access it too early. However, I now see that you're calling it in your controller (although an explanation of what happens in GetUserAccount(HttpContext) would have helped.)
Anyways, your custom attribute obviously adds extra conditions on whether a user is authorized or not. When you return false, there is no user. It's not a situation where the user is "logged in" but not allowed to see the page. It's either there or it isn't. So the user is failing authorization based on your custom attribute (User.Identity.Name is null) but is authorized when you include Authorize (User.Identity.Name has a value).
Long and short, your GetUserName or GetUserAccount or whatever code needs to account for when the user has failed authorization. Or, if the user shouldn't be failing authorization, you'll need to look into why your custom attribute isn't working. Though, either way, you should still account for User.Identity.Name being null.
Your custom attribute is probably reading User.Identity.Name before you check that the user is authenticated.
In other words, in IsAuthorized(), before you read User.Identity.Name, you should be doing something like this:
if (!user.Identity.IsAuthenticated)
{
// Your custom code...
return false;
}
The reason you need this is because Windows Authentication (at least for NTLM) is a 2-step negotiation process between the client and server (see https://support.citrix.com/article/CTX221693). There will be 2 requests - the first with no name, and the second with a name. You can test this yourself - the source code for AuthorizeAttribute is provided here. Copy/paste that into your code and put a breakpoint in IsAuthorized - you will see that the breakpoint is hit twice. First time, the name is null, second time, it's set to your username.
So I think the solution is to either check user.Identity.IsAuthenticated at the start of your method, if you need to run custom code (as shown above), or alternatively if you only need to return false, simply replace the above code with base.IsAuthorized() which should do it for you.

Session is null in RouteHandler

I have spent quite some time going through similar questions here and have not found any that answer my question - apologies if this is a duplicate however I'm pretty sure it's not..
I have an website where the aim is for visitors to complete a form. I am interested in testing different type of forms to ascertain which get filled out more consistently. My idea is that each form has it's own controller and when the user first requests the url it is picked up by a custom route handler which picks 1 form at random and set the relevant controller in RouteData. The chosen formid is then stored in the Session so on subsequnt requests instead of a form being picked at random it will just use the one from the session.
The probem is that I cannot seem to access the Session data in the routehandler - requestContext.Httpcontext.Session is always null. Is this because it is too early in the pipeline? if so how could I achieve this approach?
The first code I tried looked like this:
int FormID = 0;
string FormName = "";
RepositoryManager mgr = new RepositoryManager();
if (requestContext.HttpContext.Session["Form_ID"] != null && requestContext.HttpContext.Session["Form_Name"] != null)
{
int.TryParse(requestContext.HttpContext.Session["Form_ID"].ToString(), out FormID);
FormName = requestContext.HttpContext.Session["Form_Name"].ToString();
}
if (FormID == 0)
{
List<Form> forms = mgr.FormRepository.Get(f => f.FormType.Code == "").ToList();
int rnd = new Random().Next(0, forms.Count - 1);
FormID = forms[rnd].ID;
FormName = forms[rnd].FormName;
requestContext.HttpContext.Session["Form_ID"] = FormID;
requestContext.HttpContext.Session["Form_Name"].ToString();
}
requestContext.RouteData.Values["controller"] = FormName;
return new MvcHandler(requestContext);
This always errored as requestContext.HttpContext.Session is null
I have tried with a custom routehandler then passing off to a custom http handler as follows:
Routehandler
requestContext.HttpContext.SetSessionStateBehavior(GetSessionStateBehavior(requestContext));
IHttpHandler handler = new FormMvcHandler(requestContext);
return handler;
FormMVCHandler
public class FormMvcHandler : MvcHandler, IRequiresSessionState
{
public FormMvcHandler(RequestContext requestContext)
: base(requestContext)
{
}
protected override void ProcessRequest(HttpContext httpContext)
{
//for testing setting form manually - session will be used here as in original routehandler
RequestContext.RouteData.Values["controller"] = "1Stage";
base.ProcessRequest(httpContext);
}
}
In this second approach changing the controller name has no effect. I have tried changing the controller name in the constructor of the HTTPHandler which does have an effect however If I try and access the session from there using RequestContext.HttpContext.Session it is still null. I have tried setting a breakpoint in ProcessRequest however it is never hit.
Edit 2
This now works by overriding both ProcessRequest(HttpContext httpContext) and BeginProcessRequest(HttpContext httpContext) in the HttpHandler - even when not using an async controller BeginProcessRequest is called by the framework (v3)
In your RouteHandler you have an function GetHttpHandler which return an IHttpHandler. That custom HttpHandler must use IRequiresSessionState and then you can access the Session in the ProcessRequest function in the HttpHandler.
Look into this post:
IRequiresSessionState - how do I use it?
I think you need to use IRequiresSessionState interface
It's too early to using Session in router hander.
you can achieve what you want by using action filter.
Create a Controller named FormController, an action named FormPickerAttribute. In the ActionExecuting of attribute, you can check cookie or session, where your set form id. let's say the form id is "Form1"(create one if null), then you change the action methods to "Form1".
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary{
{ "controller", "Form" },
{ "action", "Form1" },
{ "area", ""}, #your area name
{ "parameter", "parameter value"} #passing any parameter to the action
}
);
you can also create a controller for each form, just updated the
{"controller", "FormIdController"}
to the correct one.

Best approach to don't request same info over and over

On my controller I have it inherit a MainController and there I override the Initialize and the OnActionExecuting.
Here I see what is the URL and by that I can check what Client is it, but I learned that for every Method called, this is fired up again and again, even a simple redirectToAction will fire the Initialization of the same controller.
Is there a better technique to avoid this repetition of database call? I'm using Entity Framework, so it will take no time to call the DB as it has the result in cache already, but ... just to know if there is a better technique now in MVC3 rather that host the variables in a Session Variable
sample code
public class MyController : MainController
{
public ActionResult Index()
{
return View();
}
}
public class MainController : Controller
{
public OS_Clients currentClient { get; set; }
protected override void Initialize(System.Web.Routing.RequestContext requestContext)
{
// get URL Info
string url = requestContext.HttpContext.Request.Url.AbsoluteUri;
string action = requestContext.RouteData.GetRequiredString("action");
string controller = requestContext.RouteData.GetRequiredString("controller");
object _clientUrl = requestContext.RouteData.Values["cliurl"];
if (_clientUrl != null && _clientUrl.ToString() != "none")
{
// Fill up variables
this.currrentClient = db.FindClientById(_clientUrl.ToString());
}
base.Initialize(requestContext);
}
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
// based on client and other variables, redirect to Disable or Login Actions
// ... more code here like:
// filterContext.Result = RedirectToAction("Login", "My");
base.OnActionExecuting(filterContext);
}
}
is it still best to do as:
public OS_Clients currentClient {
get {
OS_Clients _currentClient = null;
if (Session["CurrentClient"] != null)
_currentClient = (OS_Clients)Session["CurrentClient"];
return _currentClient;
}
set {
Session["CurrentClient"] = value;
}
}
It seems that you dealing with application security in that case I would suggest to create Authorization filter, which comes much early into the action. You can put your permission checking code over there and the framework will automatically redirect the user to login page if the permission does not meet AuthorizeCore.
Next, if the user has permission you can use the HttpContext.Items as a request level cache. And then you can create another ActionFilter and in action executing or you can use the base controller to get the user from the Httpcontext.items and assign it to controller property.
If you are using asp.net mvc 3 then you can use the GlobalFilters to register the above mentioned filters instead of decorating each controller.
Hope that helps.
In your base controller, you need to cache the result of the first call in a Session variable.
This makes sure the back-end (DB) is not called unnecessarily, and that the data is bound to the user's Session instead of shared across users, as would be the case with the Application Cache.

Questions regarding HttpContext, HttpContextBase, and Action Filters

I'm try to build a static property on a static class that will basically return a cookie value, to be used across my MVC site (MVC 3, if it matters). Something like this:
public static class SharedData
{
public static string SomeValue
{
get
{
if (HttpContext.Current.Request.Cookies["SomeValue"] == null)
{
CreateNewSomeValue();
}
return HttpContext.Current.Request.Cookies["SomeValue"].Value.ToString();
}
}
}
I need to access this from within controller actions, global.asax methods, and action filters. But the problem is, when action filters run, HttpContext is not available. Right now, I have to have a separate static method just to pull the cookie from the filter context that I pass in, which seems awkward.
What is the best solution for building such a static method for retrieving a cookie value like this that works from both controller actions and action filters? Or is there a better approach for doing something like this?
Thanks in advance.
The call to the static HttpContext.Current is not good design. Instead, create an extension method to access the cookie from an instance of HttpContext and HttpContextBase.
I wrote a little helper for you. You can use it to perform your functionality from within an action filter.
public static class CookieHelper
{
private const string SomeValue = "SomeValue";
public static string get_SomeValue(this HttpContextBase httpContext)
{
if(httpContext.Request.Cookies[SomeValue]==null)
{
string value = CreateNewSomeValue();
httpContext.set_SomeValue(value);
return value;
}
return httpContext.Request.Cookies[SomeValue].Value;
}
public static void set_SomeValue(this HttpContextBase httpContext, string value)
{
var someValueCookie = new HttpCookie(SomeValue, value);
if (httpContext.Request.Cookies.AllKeys.Contains(SR.session))
{
httpContext.Response.Cookies.Set(someValueCookie);
}
else
{
httpContext.Response.Cookies.Add(someValueCookie);
}
}
}
Note: You could easily make these methods work on HttpContext instead just by replacing the HttpContextBase parameter with HttpContext.
As JohnnyO pointed out above, I had access to HttpContext from within my action filter all along. At least, in the particular action filter method where this was needed. There may have been some other filter/method that did not have access at one point, but for now, this is working as I need it to.

Resources