I recently starded developing for MVC 3 but have experience in both C# and ASP.NET since earlier. So i'll start with what i'm trying to accomplish. I've developed a small site for hosting articles. I've implemented SQLServer based membership managament to the site. Now i want to create a credentials system that restricts and allows the right users to create, delete and update articles. There is one simple solution to this and that is to do it like this:
[Authorize(Roles="Admin")]
public ActionResult UpdateArticle(ArticleModel model, int articleid)
{
return View();
}
Now this is really simple. I simply say that only members that are in the role "Admin" are allowed to update an article. But that's just to static. So i created a credentials table in my database that in the end tells me that "Article 5 can be edited by roles 1,2,3 & 4 and by users A, b & C". So far so good. But how would i implement that with the Authorize solution?
I would like to do something like this:
[Authorize(getAuthorizedusers("update",this.articleid))]
where getAuthorizedusers returns which users and roles are authorized to update the article with the articleid that was passed to it.
So I have (at least) two problems here:
-Getting the Authorize method to accept multiple users and roles.
-Passing the supplied articleid, that was sent to the UpdateArticle method, to the getAuthorizedusers method.
You can create your own custom attribute that inherits from AuthorizeAttribute and override the OnAuthorize method to do what you need.
This should get you started:
public class ArticleAuthorizeAttribute : AuthorizeAttribute
{
public enum ArticleAction
{
Read,
Create,
Update,
Delete
}
public ArticleAction Action { get; set; }
public override void OnAuthorization(AuthorizationContext filterContext)
{
base.OnAuthorization(filterContext);
//do custom authorizization using Action and getting ArticleID
//from filterContext.HttpContext.Request.QueryString or
//filterContext.HttpContext.Request.Form
}
}
The usage would look like this:
[ArticleAuthorize(Action=ArticleAuthorizeAttribute.ArticleAction.Update)]
Edit: After looking into this a bit more, it looks like you can't pass this.articleID in to the attribute. However, you do have access to the parameters from filterContext.HttpContext.Request through the QueryString property or the Form property, depending on how you are passing the values. I have updated the code sample appropriately.
A more complete example can be found here
To check for authorization using user role and user list you would do something like this:
var allowedUsers = new List<string>();
//populate allowedUsers from DB
If (User.IsInRole("Update") || allowedUsers.Contains(User.Identity.Name))
{
//authorized
}
Alternatively, you can do both checks against the DB directly in a single method to keep from making two calls.
Here's a much easier way to accomplish the same thing:
[Authorize]
public ActionResult UpdateArticle(ArticleModel model, int articleid)
{
// if current user is an article editor
return View();
// else
return View("Error");
}
I got it working as I wanted when I overrode the AuthorizeCore method and authorizes the way I want to.
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (httpContext == null)
{
throw new ArgumentNullException("httpContext");
}
IPrincipal user = httpContext.User;
if (!user.Identity.IsAuthenticated)
{
return false;
}
if ((_usersSplit.Length > 0 && !_usersSplit.Contains(user.Identity.Name, StringComparer.OrdinalIgnoreCase)) && (_rolesSplit.Length > 0 && !_rolesSplit.Any(user.IsInRole)))
{
return false;
}
return true;
}
Related
I am making an asp.NET MVC5 intranet website for my company and as I have just finished implementing authorization and a dynamic menu according to user roles in Active Directory, I need to apply restrictions for access. I have read on MSDN / Technet that you can apply authorizations by using [Authorize(Role="<YourRole>")] and I make it work perfectly. Now my problem is that I have a 20 differents roles, and each Role is linked with Documents and Categories in my MSSQL Database. This means that if you want to access a document or a ressource, you must first find the corresponding entry in the DB (If you need I can explain this further). So If I implement Authorizations with the [Authorize] attribute, I will have to check my DB to see if the row exists, and if does I add it. I have started to do that with a static class :
public static class CustomRoles
{
public const string Role1 = "Role1";
public const string Role2 = "Role2";
//And so on ...
}
Then in my controller action methods:
[Authorize(Roles=CustomRoles.Role1+","+CustomRoles.Role2)]
public ActionResult Index(){}
You can imagine doing this for each role will be long and tedious.
So my question : do you know of any better / simpler way to do this? Because I have to manually check each document (thousands!) and look then in another table what profiles are associated, and then apply the corresponding authorization. And technically, my dynamic menu is supposed to take care of this since you cannot see what is not available to you, but then again, using the URL you can access anything in this way if authorizations aren't implemented
And also : Not all roles are registered in my DB, most users have around 140 roles, but there are likely just 1 or 2 that are registered in the database. Is this going to create some performance issues? I know I can handle this when I create my Claims and filter out the ones not belonging to the DB but I'd prefer not to.
One workaround you could do is by using overidden OnActionExecuting method in the ActionFilter instead of decorators AuthorizeAttribute to check if a user is authorized to do certain action or not.
Yep, you still need to carefully check of your role authorization, but not sparsely, as you only need to check all of them in one place, namely your action filter (or, to be more precise, your single switch). Also, putting everything in one place enable you to avoid redundancy and make the logic more compact whenever possible.
Example:
The ActionFilter:
public class AuthorizeActionFilterAttribute : ActionFilterAttribute {
public override void OnActionExecuting(ActionExecutingContext filterContext) {
IPrincipal user = HttpContext.Current.User; //get the current user
//Get controller name and action
string controllerName = filterContext.ActionDescriptor.ControllerDescriptor.ControllerName;
string actionName = filterContext.ActionDescriptor.ActionName;
//All roles will be defined here!
List<string> rolesAccepted = new List<string>();
switch (controllerName){
case "Controller1": //suppose these three happen to have the same rules
case "Controller2":
case "Controller3":
//Describe roles accepted for certain controller and action
rolesAccepted = new List<string> { "Role1", "Role2" };
break;
case "Controller4": //suppose these have specific restrictions only for some actions
if (actionName == "action1") {//can also use switch
rolesAccepted = new List<string> { "Role3", "Role4" };
} else {
rolesAccepted = new List<string> { "Role5", "Role6", "Role7" };
}
break;
....
}
//Redirect to login if non of the user role is authorized
if (!rolesAccepted.Any(x => user.IsInRole(x)){
filterContext.Result = redirectToLogin();
return;
}
}
private ActionResult redirectToLogin() {
return new RedirectToRouteResult(
new RouteValueDictionary(new { controller = "Account", action = "Login" })
);
}
}
Then you only need to put the AuthorizeActionFilterAttribute in every controller (and not roles in every single action) as you have handled all authorization in the single place:
[AuthorizeActionFilter]
public class Controller1 : Controller {
...
}
[AuthorizeActionFilter]
public class Controller2 : Controller {
...
}
... and so on
We are trying to implement security in with our predefined set of permissions, which will serves the purpose whether to execute action method, show the view OR not, hide specific control(Like button,textbox etc) etc. So, while user getting logged in into the application we have the data of users role and there permissions.
So, my question is whether we should go for ActionFilter OR Authorize Filter? Initially we have tried with ActionFilter, but my action filter is getting called though the particular action is NOT executed/called.
Action Filter
using Microsoft.AspNetCore.Mvc.Filters;
namespace LMS.Web.Core.Classes
{
public class SecurityFilter : ActionFilterAttribute
{
private string permissionName;
private Permissions permissions;
public SecurityFilter(string m_permissionName)
{
permissionName = m_permissionName;
}
public override void OnActionExecuted(ActionExecutedContext context)
{
base.OnActionExecuted(context);
}
public override void OnActionExecuting(ActionExecutingContext context)
{
base.OnActionExecuting(context);
}
}
}
and this action filter I have referred on one action method
[Route("Course/Info")]
[SecurityFilter("some permission name")]
public ActionResult CourseDetails()
{
return View();
}
So, while logging into the application the action filter is getting called. why this is so ?
We want to use the filter on view and controller side. So, basically we are looking like this
[Route("Course/Info")]
[SecurityFilter(PermissionName = "some permission")]
public ActionResult CourseDetails()
{
return View();
}
public class SecurityFilter : ActionFilterAttribute
{
public string PermissionName { get; set; }
public SecurityFilter(SessionServices _session)
{
session = _session;
}
public SecurityFilter()
{
//Unable able to remove the default constructor
// because of compilation error while using the
// attribute in my controller
}
public override void OnActionExecuting(ActionExecutingContext context)
{
if (session.GetSession<List<OrganizationUserRolePermission>>("OrganizationUserRolePermission") != null)
{
List<OrganizationUserRolePermission> permissionList = session.GetSession<List<OrganizationUserRolePermission>>("OrganizationUserRolePermission");
checkPermission = permissionList.Any(m => m.PermissionName == PermissionName);
if(!checkPermission)
{
// Redirect to unauthorized access page/error page
}
}
base.OnActionExecuting(context);
}
}
and whatever the permission we passed to the filter will check whether user has the permission OR not. Also, we are trying to inject session service into filter, but getting session null.
I'm not sure about your use case to pass the SessionServices
instance to filter attribute constructor but this is not possible as any
argument to Attribute invocation should be a compile-time constant
value.
Reference
Attribute parameters are restricted to constant values of the following types:
- Simple types (bool, byte, char, short, int, long, float, and double)
- string
- System.Type
- enums
- object (The argument to an attribute parameter of type object must be
a constant value of one of the above types.)
- One-dimensional arrays of any of the above types
Rather you could retrieve the stored session data inside the OnActionExecuting method directly to check the needed permissions.
Ideally Authorize attribute would be more appropriate in your case to check the user permissions to allow access to any view. I believe ActionFilter might be more suitable in case of any logging before/after the action execution.
Regarding the below
So, while logging into the application the action filter is getting called. why this is so ?
Please check the Filter Registration in your application code. Ideally if the filter is applied to any specific action (e.g. CourseDetails in your case) then it will be called only on that particular action execution.
Alternatively please include the Login action in your question so that we could check for the problem if any.
I hope this would help find a solution in your case!
I have a controller method which returns a list of resources like this:
[HttpGet, Route("events/{id:int}/rosters")]
public RosterExternalList List(int id, int page = 1, int pageSize = 50) {
return Repository.GetRosters(id).ToExternal(page, pageSize);
}
And the repository method is:
public IQueryable<EventRoster> GetRosters(int id) {
return EventDBContext.EventRosters.Where(x => x.eventID == id).OrderBy(x => x.EventRosterID).AsQueryable();
}
This is the same pattern for multiple list methods. The problem is that when no items are retrieved from the db, the api sends a 200 with an empty response body even if the id passed in is invalid.
What I want is if id = invalid, send appropriate response (404?). Otherwise, if the id is valid, and there are no records, send the 200 with empty body.
My question is - is this the right way to handle this? Can this be done via an Action Filter so it will be implemented across all the methods like this? How?
It is possible with an action filter like this:
public class EventsFilter : ActionFilterAttribute
{
public EventDBContext EventDBContext { get; set; }
public override void OnActionExecuting(HttpActionContext actionContext)
{
bool exists = false;
var routeData = actionContext.Request.GetRouteData();
object value;
if (routeData.Values.TryGetValue("id", out value))
{
int id;
if (int.TryParse(value, out id))
{
exists = EventDBContext.EventRosters.Where(x => x.eventID == id).Any();
}
}
if (exists == false)
{
var response = actionContext.Request.CreateErrorResponse(HttpStatusCode.NotFound, "Event not found");
throw new HttpResponseException(response);
}
}
}
You would also need to configure a dependency resolver to set EventDBContext property.
Is it the right way?
It depends on your solution design:
If your business layer is integrated and depends on the Web API (as it appears from your example), then yes it is the way to go.
If Web API (service layer) is not the only one who uses your business layer, then you would want to avoid duplication of event checking in other places and move it to a more generic class than Web API action filter and call this class from your business layer instead of Web API to make sure that regardless of the caller, you would always check if event exists. In that class you would throw something like BusinessLogicNotFoundException with information about event. And on the Web API, you would need to create an ExceptionFilterAttribute that handles BusinessLogicNotFoundException and creates an appropriate 404 response.
I've got a AuthorizeAttribute class that intercepts calls to my Web Api. There I valdiate users from the session that is given.
If the user has the right credentials, I would like to append the request body with the userId that was fetched during credential checks. I've tried some stuff but it seem that I can't access the request body in IsAuthorized?
I'm trying to do something like this:
public class AuthorizeUserAttribute : AuthorizeAttribute
{
protected override bool IsAuthorized(HttpActionContext httpContext)
{
// Pick up session
var sessionKey = httpContext.Request.Headers.FirstOrDefault(h => h.Key == "session").Value;
// If session vas valid, get userid from session and add it to the request body somehow
// so the controller gets userid attached.
return true;
}
}
After, the target controller gets called:
public Response GetCandy( CandyRequest candy )
{
// Wohoo, the user was accepted and i've got the user id already in the argument object.
var userId = candy.UserId;
}
I'm not sure I entirely understand what you're seeking here. If you're trying to get access to candy and set the UserId from within your attribute, you're probably going to have a rough time. You maybe able to add it to do something with httpContext.ActionArguements before model binding happens to inject your user ID.
But, I've always understood AuthorizeAttribute's as something that is a gatekeeper and shouldn't be setting data inside of it. With that being said, one workaround you could use HttpContext.Items to set items for the request, so your resulting code would look like
protected override bool IsAuthorized(HttpActionContext httpContext)
{
// code to map the session to the user.
HttpContext.Current.Items["UserId"] = 23423; // set the user id to the Items collection
return true;
}
then in your controller
public Response GetCandy( CandyRequest candy )
{
// Wohoo, the user was accepted and i've got the user id already in the argument object.
var userId = HttpContext.Items["UserId"];
}
I'm looking into how to implement authorization in MVC 4 (.NET 4.5), and have specifically been reading about SimpleMembership. Is there any typical or accepted way in MVC to have roles that have additional properties aside from a name?
For example, suppose you were designing a CMS, and wanted to be able to have a role called something like Writer that let a user make modifications. However, you also want the role to be restrictive to a single page. The only way that I know of to do that would be to have a separate role for each page, where each role might be named something like Writer_<PageID>. Is there any pattern that's nicer than this, or is that pretty much all we can do?
Ideally, I'm wondering if there'd be some way to be able to have something remotely like:
public ActionResult EditPage(Page page) {
WriterRole role = new WriterRole(page);
if (!User.IsInRole(role)) {
return NotAuthorized();
}
// Edit...
}
Instead of:
public ActionResult EditPage(Page page) {
string role = "Writer_" + page.Id;
if (!User.IsInRole(role)) {
return NotAuthorized();
}
// Edit...
}
What I would do is have one Writer role then check the UserId to see if the person owns the editable resource.
[Authorize(Roles = "Writer")]
public ActionResult EditPage(Page page) {
if (User.UserId == page.UserId) { ... }
}