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
Related
I'm trying to pull out of database objects where current user Id is in list of those objects.
My model:
public class Procedure
{
...
public IList<User> Lawyers{ get; set; }
...
}
And in controller:
[HttpGet]
public async Task<IActionResult> MyProcedures()
{
var user = await userManager.GetUserAsync(User);
var procedures = context.Procedures.Where(x => x.Lawyers.Contains(user));
return View(procedures);
}
This only selects one object.
EDIT:
Problem is in my User class, it takes only one Id from Procedure and that is why its showing my only one (last added). Thanks for help guys!
The way you wrote will work if you have icomparable or something implemented that can tell that a lawyer is “the” user if user id matches.
Without that you would need to do lawyers.Any(x=> x.UserId == user.UserId)
You are missing something, which user ??
Either pass something into your function signature or get it from HTTPContext you need to get the user you want to find
If you are using ASP core its bit different from the old ways
First this:
var userId = _httpContextAccessor.HttpContext.User.FindFirst(ClaimTypes.NameIdentifier).Value;
And now..
// make sure you can access/inject it if you want
// private.. _httpContextAccessor
// then in your action
[HttpGet]
public async Task<IActionResult> MyProcedures()
{
// again make sure you can access the context _httpContextAccessor
var userId = _httpContextAccessor.HttpContext.User.FindFirst(ClaimTypes.NameIdentifier).Value;
var procedures = context.Procedures.Where(x => x.Lawyers.Contains(userId).FirstOrDefault());
//... fill in whatever logic you want..
return View(parnice);
}
Update 2 based on question/comments:
Do this in two Steps
Step 1: Get the Current User (with claims or HTTPContext as shown below), for e.g. System.Security.Claims.ClaimsPrincipal currentUser = this.User;
Step 2: Using the user, find all the related Lawyers etc. context.Procedures.Where(x => x.Lawyers.Contains(userId)
Make sure to register HttpContextAccessor in your startup... double check this.. to register in your Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
}
In the original Core version, I have to double check if it changed now, assuming your code is inside an MVC controller:
public class YourController : Microsoft.AspNetCore.Mvc.Controller
Now, since you have the Controller base class, you can get the IClaimsPrincipal from the User property
System.Security.Claims.ClaimsPrincipal currentUser = this.User;
You can check the claims directly (without a round trip to the database):
var userId = _userManager.GetUserId(User); // Get user id:
To your second comment, you can get either the UserId or UserName
// user's userId
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier)
// user's userName
var userName = User.FindFirstValue(ClaimTypes.Name)
A nice reference for you hope it helps :)
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!
Hello :) I am building an MVC5/EF6 system that has stores information about students with a number of user types . When the user logs in certain information about the user is stored in Session; UserID, UserTypeID etc. Different users have different privileges, and I often need to get the user information from Session within my ActionResult methods in each controller:
private Student GetCurrentStudentInfo()
{
var currentuser = (SessionModel)Session["LoggedInUser"];
var student = _db.Student.Find(currentuser.UserID);
return student;
}
I have this method at the bottom of my controllers, which I call from each method depending on when I need this information. It gets the userID from the current logged in user and returns the profile information. I would like to be able to either:
Make this method available to all my controllers
Or create a class variable that I can use at the top of my controller, which would return this info:
public class RegistrationWizardController : Controller
{
private readonly DefaultContext _db = new DefaultContext();
private UserInfo _userInfo = new UserInfo();
}
I am very new to MVC and coding in general, so any help/opinions/other suggestions are welcome. Thanks!
You have a couple of options.
The first (and easier) of the two is to make all of your controllers inherit from a common base controller. To do this, make a base controller that extends from the default controller:
public class BaseController : Controller
{
protected Student GetCurrentStudentInfo() //protected so we can access this method from derived classes
{
var currentuser = (SessionModel)Session["LoggedInUser"];
var student = _db.Student.Find(currentuser.UserID);
return student;
}
}
Now, change your controllers to inherit the base controller you just created:
public class RegistrationWizardController : BaseController
{
public ActionResult AnAction()
{
var student = this.GetCurrentStudentInfo(); //calls the method inherited from BaseController
}
}
The other option you have is to use Dependency Injection. This is a bit more complicated, and much more abstract than the previous method. There are a bunch of Dependency Injection frameworks, my favorite is Ninject (http://www.ninject.org/). This would probably be closer to the "Industry Standard" of doing something like this, and I would encourage you to at least look into it, but I think a sample would be a little out of scope for this question (do some side reading first). If you do decide to implement it and get stuck, post another question.
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) { ... }
}
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;
}