I have an authorization requirement to have my security roles based on actions methods, which can not be achieved using the default asp.net mvc authorization. so i have created the following action filter, to implement my custom authorization requirments:-
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class CheckUserPermissionsAttribute : ActionFilterAttribute
{
Repository repository = new Repository();
public string Model { get; set; }
public string Action { get; set; }
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
// var user = User.Identity.Name; // or get from DB
string ADusername = filterContext.HttpContext.User.Identity.Name.Substring(filterContext.HttpContext.User.Identity.Name.IndexOf("\\") + 1);
if (!repository.can(ADusername,Model,Action)) // implement this method based on your tables and logic
{
filterContext.Result = new HttpUnauthorizedResult("You cannot access this page");
}
base.OnActionExecuting(filterContext);
}
}
which is calling the following Repository method:-
public bool can(string user, string Model, string Action)
{
bool result;
bool result2;
int size = tms.PermisionLevels.Where(a5 => a5.Name == Action).SingleOrDefault().PermisionSize;
var securityrole = tms.SecurityroleTypePermisions.Where(a => a.PermisionLevel.PermisionSize >= size && a.TechnologyType.Name == Model).Select(a => a.SecurityRole).Include(w=>w.Groups).Include(w2=>w2.SecurityRoleUsers).ToList();//.Any(a=> a.SecurityRoleUsers.Where(a2=>a2.UserName.ToLower() == user.ToLower()));
foreach (var item in securityrole)
{
result = item.SecurityRoleUsers.Any(a => a.UserName.ToLower() == user.ToLower());
var no = item.Groups.Select(a=>a.TMSUserGroups.Where(a2=>a2.UserName.ToLower() == user.ToLower()));
result2 = no.Count() == 1;
if (result || result2)
{
return true;
}}
return false;
i am calling the action filter inside my controller class as follow:-
[CheckUserPermissions(Action = "Read", Model = "Server")]
but i have the following concerns:-
inside my repository i will be retrieving all the users and groups (when calling the .Tolist()), and then check if the current login user is inside these values. which will not be very extensible when dealing with huge number of users?
each time the user call an action method the same security code will run (of course ideally the user permission might chnage during the user session),,, which might cause performance problems ?
So can anyone adice how i can improve my current implementation, taking the two concerns in mind ?
Thanks
I would change your approach and use claims based authentication.
This way you have a lot more granular control over authorization (it can be driven by resource and actions).
You can use a ClaimsAuthorizationManager to check access at every level in a central place.
This article expains how to implement this and also use secure session tickets to save accessing the database everytime.
http://dotnetcodr.com/2013/02/25/claims-based-authentication-in-mvc4-with-net4-5-c-part-1-claims-transformation/
Related
We're using ASP.Net Web API to generate a feed and it includes the ability to do paging.
myfeed.com/afeed?page=2
My boss says "let's also allow users to use 'paged', because that's what WP uses." In addition, we're also using pageIndex in some of our older feeds. So what I'd like to do is accept all three.
myfeed.com/afeed?page=2
myfeed.com/afeed?paged=2
myfeed.com/afeed?pageIndex=2
I'd like to do is be able to write a clean Web API method, such as
public Foo Get(int page = 1)
{
//do some stuff
return foo;
}
without cluttering the method with page 'plumbing'. So I tried creating an ActionFilter
public override void OnActionExecuting(HttpActionContext actionContext)
{
object pageParam = new object(); //query["page"]
if (pageParam == null)
{
var altPageParam = GetPageParamUsingAlternateParams(actionContext);
if (altPageParam != null){}
//SetPageParam here
}
base.OnActionExecuting(actionContext);
}
private object GetPageParamUsingAlternateParams(HttpActionContext actionContext)
{
object result = new object();
object pageIndexParam = new object(); //Query["pageIndex"]
object pagedParam = new object(); ////Query["paged"]
if (pagedParam != null)
result = pagedParam;
else if (pageIndexParam != null)
result = pageIndexParam;
return result;
}
I didn't finish. As I was looking for the best way to get the query params, I stumbled into a big mistake!
OnActionExecuting is executed after int page = 1. Sure, I could override it in an ActionFilter, but that would lead to confusion down the road. I really want to be able to do a simple flow through the URI query parameters that goes from
page -> paged -> pageIndex -> default value in method
I have found a lot of articles on custom binding to a an object. Also, I found articles about "parameter binding", however those dealt with FromUri and FromBody. I didn't find anything that I felt had a direct parallel to what I'm facing.
You could achieve what you want by defining 3 different GET method with parameters matched with the query segment of the Url like the code snippet below:
public class ProductsController : ApiController
{
//Matched api/products?page=1
public IHttpActionResult Get(int page)
{
return GetPagedData(page);
}
//Matched api/products?paged=1
public IHttpActionResult GetPaged(int paged)
{
return GetPagedData(paged);
}
//Matched api/products?pagIndex=1
public IHttpActionResult GetPageIndex(int pageIndex)
{
return GetPagedData(pageIndex);
}
//Do the real paging here
private IHttpActionResult GetPagedData(int page =1)
{
return Ok("Data Pages");
}
}
What is the correct way to restrict access to a controller?
For instance, I might have "ProductReviewController", I want to be able to check that this controller is accessible in the current store and is enabled. I'm not after the code to do that but am interested in the approach to stopping the user getting to the controller should this criteria not be met. I would like the request to just carry on as if the controller was never there (so perhaps throwing a 404).
My thoughts so far:
A data annotation i.e [IsValidController]. Which Attribute class would I derive from - Authorize doesn't really seem to fit and I would associate this with user authentication. Also, I'm not sure what the correct response would be if the criteria wasn't met (but I guess this would depend on the Attribute it's deriving from). I could put this data annotation against my base controller.
Find somewhere lower down in the page life cycle and stop the user hitting the controller at all if the controller doesn't meet my criteria. i.e Create my own controller factory as depicted in point 7 here: http://blogs.msdn.com/b/varunm/archive/2013/10/03/understanding-of-mvc-page-life-cycle.aspx
What is the best approach for this?
Note: At the moment, I am leaning towards option 1 and using AuthorizeAttribute with something like the code below. I feel like I am misusing the AuthorizeAttribute though.
public class IsControllerAccessible : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (!CriteriaMet())
return false;
return true;
}
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary(
new
{
controller = "Generic",
action = "404"
})
);
}
}
I think you are confused about AuthorizeAttribute. It is an Action Filter, not a Data Annotation. Data Annotations decorate model properties for validatioj, Action Filter's decorate controller actions to examine the controller's context and doing something before the action executes.
So, restricting access to a controller action is the raison d'etre of the AuthorizeAttribute, so let's use it!
With the help of the good folks of SO, I created a customer Action Filter that restricted access to actions (and even controllers) based on being part of an Access Directory group:
public class AuthorizeADAttribute : AuthorizeAttribute
{
public string Groups { get; set; }
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (base.AuthorizeCore(httpContext))
{
/* Return true immediately if the authorization is not
locked down to any particular AD group */
if (String.IsNullOrEmpty(Groups))
return true;
// Get the AD groups
var groups = Groups.Split(',').ToList<string>();
// Verify that the user is in the given AD group (if any)
var context = new PrincipalContext(ContextType.Domain, "YOURADCONTROLLER");
var userPrincipal = UserPrincipal.FindByIdentity(context,
IdentityType.SamAccountName,
httpContext.User.Identity.Name);
foreach (var group in groups)
{
try
{
if (userPrincipal.IsMemberOf(context, IdentityType.Name, group))
return true;
}
catch (NoMatchingPrincipalException exc)
{
var msg = String.Format("While authenticating a user, the operation failed due to the group {0} could not be found in Active Directory.", group);
System.ApplicationException e = new System.ApplicationException(msg, exc);
ErrorSignal.FromCurrentContext().Raise(e);
return false;
}
catch (Exception exc)
{
var msg = "While authenticating a user, the operation failed.";
System.ApplicationException e = new System.ApplicationException(msg, exc);
ErrorSignal.FromCurrentContext().Raise(e);
return false;
}
}
}
return false;
}
}
Note this will return a 401 Unauthorized, which makes sense, and not the 404 Not Found you indicated above.
Now, the magic in this is you can restrict access by applying it at the action level:
[AuthorizeAD(Groups = "Editor,Contributer")]
public ActionResult Create()
Or at the controller level:
[AuthorizeAD(Groups = "Admin")]
public class AdminController : Controller
Or even globally by editing FilterConfig.cs in `/App_Start':
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
filters.Add(new Code.Filters.MVC.AuthorizeADAttribute() { Groups = "User, Editor, Contributor, Admin" });
}
Complete awesome sauce!
P.S. You mention page lifecycle in your second point. There is no such thing in MVC, at least not in the Web Forms sense you might be thinking. That's a good thing to my mind, as things are greatly simplified, and I don't have to remember a dozen or so different lifecycle events and what the heck each one of them is raised for!
I have the following ActionFilter class, to implement my custom authorization system:-
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class CheckUserPermissionsAttribute : ActionFilterAttribute
{
Repository repository = new Repository();
public string Model { get; set; }
public string Action { get; set; }
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
string ADusername = filterContext.HttpContext.User.Identity.Name.Substring(filterContext.HttpContext.User.Identity.Name.IndexOf("\\") + 1);
if (!repository.can(ADusername,Model,Action))
{
filterContext.Result = new HttpUnauthorizedResult("You cannot access this page");
}
base.OnActionExecuting(filterContext);
}
}
The above class will call the following repository method:-
public bool can(string user, string Model, string Action)
{
bool result;
bool result2;
int size =tms.PermisionLevels.Where(a5 => a5.Name == Action).SingleOrDefault().PermisionSize;
var securityrole = tms.SecurityroleTypePermisions.Where(a => a.PermisionLevel.PermisionSize >= size && a.TechnologyType.Name == Model).Select(a => a.SecurityRole).Include(w=>w.Groups).Include(w2=>w2.SecurityRoleUsers).ToList();
foreach (var item in securityrole)
{
result = item.SecurityRoleUsers.Any(a => a.UserName.ToLower() == user.ToLower());
var no = item.Groups.Select(a=>a.TMSUserGroups.Where(a2=>a2.UserName.ToLower() == user.ToLower()));
result2 = no.Count() == 1;
if (result || result2) {
return true;
}}
return false;
}
But inside my repository method , I am doing the following:-
Query the database and include all the Groups & SecurityRoleUsers when executing the .tolist()
Then filter the returned records insdie the server, based on the foreach loop.
But this will cause the following drawbacks:-
If I have many Groups and SecurityRoleUsers, then I will be getting them all from the DB, and then filter the result on the server.
And since this code will be executed whenever an action method is called, as it Is a security attribute at the begging of the controller class. So this might not be very efficient.
So my question is whether I can join all the queries inside the repository method to be single query , and do all the work on the Database and just return true or false to the server ?
The associated tables looks as follow:-
Ideally remove this foreach.
Try riding with Linq to Sql.
You should be more comfortable because it resembles SQL.
This link has several examples.
http://code.msdn.microsoft.com/101-LINQ-Samples-3fb9811b
Att
Julio Spader
wessolucoes.com.br
Use linq.
Ideally you should only have one line of code after you got the size value. e.g.
int size =tms.PermisionLevels.Where(a5 => a5.Name == Action).SingleOrDefault().PermisionSize;
var result = //One line of code to determine user authenticity
return result;
I think you should design you database in the way that join queries are easy to do. So you don't have to perform more than one select.
Try code-first EF, which links tables very easily.
You need to take care with Lazy Loading. If not used correctly, it will make a query to the database each object segmentation, especially in your foreach. With that already has a good improvement.
Take a look at this article. I think it will help you too.
http://www.sql-server-performance.com/2012/entity-framework-performance-optimization/
Att
Julio Spader
wessolucoes.com.br
Looking for ideas to guard against malicious data changes: userA manipulating (editing or deleting) data that belongs to userB. Since we are creating entities on the client, we need to assign them (or at least some of them) to the authenticated user.
For example:
var newItem = ds.createNewItem();
newItem.OwnerId(22); //this is the problem that I see.
newItem.Name("New Item");
newItem.Description("I just changed your item!");
... //and so on
ds.saveChanges();
Assuming we know the identity of the user calling SaveChanges on our API, how do we validate our entities (new or modified) against this user?
The first thought that comes to mind is to subclass EFContextProvider, override BeforeSaveEntity and examine the entities OwnerId property against the identity of our user. For example:
if (entityInfo.Entity.GetType() == typeof(Item)
&& (entityInfo.EntityState == EntityState.Added
|| entityInfo.EntityState == EntityState.Modified)
&& ((Item)entityInfo.Entity).OwnerId != _currentUserId) {
return false
... //and so on
If using this approach, does it make sense to establish _currentUserId in the constructor of our new EFContextProvider class?
An ideas or perhaps a better way to approach this problem?
I think you are on the right track. I've been noodling this myself and have gone down much the same path.
Let's assume you've handled authentication and there's an IPrincipal available. You've got yourself a custom IIdentity too (call it AppIdentity) where you can stash the UserId for the authenticated user.
The Web Api's base ApiController class makes the ambient IPrincipal available via its User property. We will leverage that in your custom Breeze Web Api controller which might begin like this:
[Authorize]
[JsonFormatter, ODataActionFilter]
public class BreezeApiController : ApiController
{
private readonly AppContextProvider _context;
public BreezeApiController() {
// pass 'User' IPrincipal to the context ctor
_context = new AppContextProvider(User);
}
...
// one of the Query action methods
[HttpGet]
public IQueryable<Foo> Foos() {
return _context.Foos
}
...
Your custom EFContextProvider might begin like this:
public class AppContextProvider : EFContextProvider<AppDbContext>
{
public AppContextProvider(IPrincipal user)
{
UserId = ((AppIdentity) user.Identity).UserId;
}
public int UserId { get; private set; }
...
Now you probably want to prevent UserB's entities from being seen by UserA. So instead of allowing every Foo to go out the door, your custom EFContextProvider could filter accordingly.
public DbQuery Foos
{
get
{
// Here the 'Context' is your EF DbContext
return (DbQuery) Context.Foos
.Where(f => f.UserId == UserId);
}
}
Looking back at the controller, we see that its Foos GET action method is oblivious to the filter ... as it should be. We want our controllers to be light and move the business logic to the custom EFContextProvider and its helpers.
Finally, a highly simplified, general purpose BeforeSaveEntity could look like this:
private bool BeforeSaveEntity(EntityInfo info)
{
var entity = info.Entity;
if (info.EntityState == EntityState.Added)
{
entity.UserId = UserId;
return true;
}
return UserId == entity.UserId || throwCannotSaveEntityForThisUser();
}
...
private bool throwCannotSaveEntityForThisUser()
{
throw new SecurityException("Unauthorized user");
}
Notice that the custom context provider on the server is responsible for setting the UserId of added entities. We wouldn't trust the client to do that anyway. And of course it is responsible for verifying the UserId of modified and deleted entities.
Hope this helps. Remember, this is only a sketch. The real deal would have greater sophistication and be refactored into helpers.
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.