I don't have a lot of experience with this and I am really hoping to get a good suggestion from you guys. I need to implement the following security scenario and I would like to know the best way to do it.
Imagine we have Employees, Supervisors and Department managers.
Both Employees and Supervisors have ManagerId assigned based off and pointing to the department manager they belong to.
When a supervisor user logs in I want him to only see records for employees that belong to the same ManagerId as his.
If another supervisor with another ManagerId user logs in and manually punches other employee's information in url (ex: wwww.domain.com/employee/details/{id} ),
because his ManagerId != employee's ManagerId I would like the access to be restricted.
Does it make sense ?
I started typing out checks on all ActionMethods such as:
public ActionResult Details(int id)
{
var employee = employeeRepository.Get(id)
var user = (CustomIdentity)ControllerContext.HttpContext.User.Identity;
if(employee.managerId == user.managerId)
{
Do whatever...
}
else
{
Not allowed
}
}
But typing that out in all ActionMethods seems redundant and just..ehh... I know there must be a better way.
Here is a stab at a solution. It needs a bit of cleanup but should give you everything you need.
Create a custom ActionFilter, and then decorate your methods with it.
[ManagerIdAuthentication]
public ActionResult Details(int id)
{
// Gets executed if the filter allows it to go through.
}
The next class can be created in a separate library so you can include it in all your actions that require this validation.
public class ManagerIdAuthentication : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
// the next line needs improvement, only works on an httpGet since retrieves
// the id from the url. Improve this line to obtain the id regardless of
// the method (GET, POST, etc.)
var id = filterContext.HttpContext.Request.QueryString["id"];
var employee = employeeRepository.Get(id);
var user = filterContext.HttpContext.User.Identity;
if (employee.managerId == user.managerId)
{
var res = filterContext.HttpContext.Response;
res.StatusCode = 402;
res.End();
filterContext.Result = new EmptyResult(); //may use content result if want to provide additional info in the error message.
}
else
{
// OK, let it through.
}
}
}
I had a similar issue in the past, what I would consider per-object permissions. What I did was add a member to the object similar to:
public bool CanUserAccess(User user) {
return managerId == user.managerId;
}
Then, at the top of each action providing access to a controlled resource:
public ActionResult Details(int id)
{
var employee = employeeRepository.Get(id)
var user = (CustomIdentity)ControllerContext.HttpContext.User.Identity;
if(!employee.CanUserAccess(user))
return new HttpUnauthorizedResult();
// Normal logic here
}
It's certainly not perfect, but it does centralize the permission handling and allows you to easily increase the complexity in the future (allow access up the chain, special rules for HR, etc.). You could also write another overload/extension to access the User.Identity property for a bit more automation (or at least handle the type conversions).
Since I was dealing with ACL's, I would have additional methods/parameters to specify the basic nature of the action (e.g. Read, Write, Delete, Create, etc.).
Related
In the system I am building there is a complex and continuously changing resource-based authorization. There is a total of six roles at the moment.
The system is handling members where all members can edit basic info on their own profile, another person in another role can edit even more info on their profile and so on.
I cannot figure out which is the best way to design this with endpoints / actions for posts like the edit member action. What I ended up doing, but dislike, is that each role has one controller action, view and view model. The main reason for doing this instead of having one view model is that I felt it did not make sense to have all the properties that someone cannot even edit, that's over-posting right?
I am not quite happy with the result. 6 view models, 6 views, 6 madly similar controller actions, 6 validators etc.
My idea now is that I will just have one edit action and then have a bunch of if statements when mapping back to the domain object, in the view and on the validator classes. The overposting is still there but managed with if statements. I'm also thinking like this - what if the system would become an API? api/members/1/edit/ makes more sense than api/members/1/editAsTreasurer?
What do you think? Anyone has another solution I have not thought of?
Some code parts, example of duplicated code, of course there's more in validator classes, views, and mapping, not sure how much to include:
[HttpPost]
public IActionResult EditAsSecretary(EditMemberAsSecretaryViewModel viewModel)
{
if (!ModelState.IsValid)
{
viewModel.Init(_basicDataProvider, _authorizationProvider.GetAuthorizedLogesForManageMember());
return View("EditAsSecretary", viewModel);
}
var member = _unitOfWork.Members.GetByMemberNumber(viewModel.MemberNumber, true);
if (member == null) return NotFound();
// Authorize
if (!_authorizationProvider.Authorize(viewModel.MemberInfo.LogeId, AdminType.Sekreterare))
return Forbid();
var user = _unitOfWork.Members.GetByUserName(User.Identity.Name);
var finallyEmail = viewModel.MemberContactInfo.Email != null && member.Email == null &&
!member.HasBeenSentResetPasswordMail && member.MemberNumber != user.MemberNumber;
_domainLogger.UpdateLog(viewModel, member, user);
UpdateMember(viewModel, member, user.Id);
_unitOfWork.Complete();
if (finallyEmail) SendUserResetPasswordMail(member).Wait();
TempData["Message"] = "Member has been updated.";
return RedirectToAction("Details", "Members", new { memberNumber = member.MemberNumber });
}
[HttpPost]
public IActionResult EditAsManager(EditMemberAsManagerViewModel viewModel)
{
if (!ModelState.IsValid)
{
viewModel.Init(_basicDataProvider, _authorizationProvider.GetAuthorizedLogesForManageMember());
return View("EditAsManager", viewModel);
}
var member = _unitOfWork.Members.GetByMemberNumber(viewModel.MemberNumber, true);
if (member == null) return NotFound();
// Authorize
if (!_authorizationProvider.Authorize(member.LogeId, AdminType.Manager))
return Forbid();
var user = _unitOfWork.Members.GetByUserName(User.Identity.Name);
var finallyEmail = viewModel.MemberContactInfo.Email != null && member.Email == null &&
!member.HasBeenSentResetPasswordMail && member.MemberNumber != user.MemberNumber;
_domainLogger.UpdateLog(viewModel, member, user);
UpdateMember(viewModel, member, user.Id);
_unitOfWork.Complete();
if (finallyEmail) SendUserResetPasswordMail(member).Wait();
TempData["Message"] = "Member has been updated.";
return RedirectToAction("Details", "Members", new { memberNumber = member.MemberNumber });
}
private void UpdateMember(EditMemberAsSecretaryViewModel viewModel, Member member, string userId)
{
_mapper.Map(viewModel, member);
MapGodfathers(viewModel.MemberInfo, member);
AfterUpdateMember(member, userId);
_userManager.UpdateNormalizedEmailAsync(member).Wait();
}
private void UpdateMember(EditMemberAsManagerViewModel viewModel, Member member, string userId)
{
_mapper.Map(viewModel, member);
MapGodfathers(viewModel.MemberInfo, member);
AfterUpdateMember(member, userId);
_userManager.UpdateNormalizedEmailAsync(member).Wait();
}
My idea now is that I will just have one edit action and then have a bunch of if statements when mapping back to the domain object, in the view and on the validator classes. The overposting is still there but managed with if statements
Don't.
Besides making the code much less readable, it also poses a security risk. Every Action should take as little parameters as it needs. It does not cost you anything to have more Actions so there is no reason for doing that.
There are some issues though with your code, that help in that duplication:
You seem to be making security validations against what you receive from the user, instead of using the currently authenticated user. This is a big issue, as you are trusting the data that comes from a user.
Instead of that, create a custom(s) Authorization Policy that checks for the type of user using your business logic. Those can be then added to the built-in container and you can use:
[Authorize(Policy = "EnsureManager")]
public IActionResult EditAsManager(...)
This would allow you to remove all that duplicated code and be closer to the SRP.
Your duplicated UpdateMember looks like your models are unrelated. In a case like this, it would be far better to have a base model and then children with the required properties:
public abstract class EditMemberBaseViewModel
{
[Required]
public Something Something { get; set; }
}
public class EditMemberAsSecretaryViewModel : EditMemberBaseViewModel
{
[Required]
public AnotherThing AnotherThing { get; set; }
}
That would allow you to have a single UpdateMember since the logic is based on EditMemberBaseViewModel and not their children, as far as you have shown that is:
private void UpdateMember(EditMemberAsManagerViewModel viewModel, Member member, string userId)
{
_mapper.Map(viewModel, member);
MapGodfathers(viewModel.MemberInfo, member);
AfterUpdateMember(member, userId);
_userManager.UpdateNormalizedEmailAsync(member).Wait();
}
As a last and probably most important point, there is a problem with this code:
_userManager.UpdateNormalizedEmailAsync(member).Wait();
That's really bad. You are making ASP.NET Core hang an entire thread waiting for that action to complete. That's synchronous, 2000s code.
You need to learn to use asynchronous code for every IO-related operation (like database calls) in your application, otherwise performance will suffer lots. As an example:
public async Task<IActionResult> EditAsManager(...)
{
.....
await UpdateMemberAsync(...);
}
public async Task UpdateMemberAsync(...)
{
await _userManager.UpdateNormalizedEmailAsync(member);
}
I'm trying to implement this architecture for the first time in a Winform. So I have a simple but very important question for me.
Take a simple example. I want the form to retrieve a user list and to allow a modification of the phone number.
I have this for the first step (simplified and I normally use interfaces)
public Form1()
{
InitializeComponent();
UserService _userService = new UserService();
listBoxUsers.DataSource = _userService.GetAllUsers();
}
class User
{
public int Id { get; set; }
public string Name { get; set; }
public string Phone { get; set; }
}
class UserService
{
UserRepository _userRepository=new UserRepository();
public Dictionary<int, string> GetAllUsers()
{
DataTable dtbl= _userRepository.AllUsers();
//Some code here
return dict;
}
}
class UserRepository
{
public DataTable AllUsers()
{
//Sql query
return dtbl;
}
}
Now by selecting a user in the lisbox, I'm able to display some information as the Phone number. When I'm changing the phone number, I need a method called UpdatePhoneNumber to update the SQL database.
But, where to place it? User or UserService (I don't talk about the SQL query, just the logic)
And after that, how to access (here or somewhere else in the app) to this user property to display it in the form? Directly with _user.Id (User must be instantiated in the form) or implement a _userService.The id which retrieves User.ID (in this case Form knows only UserService class).
Many thanks for your precious help
Put all Methods working on the User's data in the user class. Ask yourself the question what the user can do? Put all the logic which controlls the users in UserService like GetUserById, GetAllUsers, CreateUser and so..
Put all the method which the user can perform in the User class.
Or lately i was building such kind of thing and i merged User and UserServices into one and made the UserServices class method static so i can access them without instantion of the User.
Hope this help.
Here is what your basic 3-layered app looks like.
UI (your form and ui supporting objects)
BLL (GetAllUsers, SaveUser, DeleteUser, etc)
Data (ADO, EF, etc)
In your particular case, you really looking for Master-detail concept. A master usually the one where you display list of users
// Master
var _userList = Service.GetAllUsers(); // List<UserModel>
userGrid.Datasource = _userList;
I will not discuss it here but you can set bindings so that click on grid will result in detail control being populated. Or manually
// detail
UserModel model = master._userList[currIndex];
txtFirstName.Text = model.FirstName;
txtPhone.Text = model.Phone;
// . . . .
Now, of course, you're about to change the text box and save user...
// detail
UserModel model = master._userList[currIndex];
Service.SaveUser(model);
Master.Reload();
This is general idea, how this is done. If you follow, you have distinct layers. UI calls Service, which calls Data. For example, you have BLL
// Service
private IUserDataProvider _provider;
public List<UserModel> GetAllUsers()
{
var data = _provider.Get<User>();
// massage your 'data' and return List<UserModel>
. . . .
}
your provider might return some unwanted data, so you can use BLL to trim it and return only appropriate data. But you don't know what provider is doing inside. May be it is doing Ado.net or Entity Framework. Hence a true separation of layers.
Does anyone have an actual example of how to use int RefId as proposed in this question?
I am trying to get authentication going but I need to marry up the userAuth data with my own user table. The problem is I have no idea how to pass an additional parameter to the "/register" method. I guess I'm looking for an event like "OnAddUser" which will allow me to throw some additional parameters into the mix.
I managed to get the user registration working pretty quickly, it was super easy. Maybe the problem is that it was too easy? I can see it work but I can't figure out how to get between it and the database.
Either the dictionary approach or the RefId approach will probably work for me, it's just no obvious to me how use either.
Is it possible to override the create user altogether? I found this code:
MyServices.cs
which looks like it's doing the create user in place of "/register" but there are some other articles that suggest that you can't override the ServiceStack DTOs, you have to use the default tables.
You could include your own Register Service by using a copy of the RegisterService source code and modify it to suit your needs, e.g. Use a custom Register DTO with the additional properties you want.
But you can easily pass additional params without changing the existing Register DTO by adding it to the ?querystring which you can access inside your Services with:
var myParam = base.Request.QueryString["myParam"];
Otherwise the way to add your Custom Logic during registration or Authentication is to tap into the existing Session or Auth Events.
TechStacks has an example of this in its CustomAuthUserSession:
public class CustomUserSession : AuthUserSession
{
public string DefaultProfileUrl { get; set; }
public string GithubProfileUrl { get; set; }
public string TwitterProfileUrl { get; set; }
public override void OnAuthenticated(IServiceBase authService,
IAuthSession session,
IAuthTokens tokens,
Dictionary<string, string> authInfo)
{
base.OnAuthenticated(authService, session, tokens, authInfo);
var appSettings = authService.TryResolve<IAppSettings>();
var userAuthRepo = authService.TryResolve<IAuthRepository>();
var userAuth = userAuthRepo.GetUserAuth(session, tokens);
var dbConnectionFactory = authService.TryResolve<IDbConnectionFactory>();
foreach (var authTokens in session.ProviderOAuthAccess)
{
if (authTokens.Provider.ToLower() == "github")
{
GithubProfileUrl = session.GetProfileUrl();
}
if (authTokens.Provider.ToLower() == "twitter")
{
TwitterProfileUrl = session.GetProfileUrl();
if (appSettings.GetList("TwitterAdmins").Contains(session.UserName)
&& !session.HasRole(RoleNames.Admin))
{
userAuthRepo.AssignRoles(userAuth, roles:new[]{RoleNames.Admin});
}
}
DefaultProfileUrl = GithubProfileUrl ?? TwitterProfileUrl;
using (var db = dbConnectionFactory.OpenDbConnection())
{
var userAuthInstance = db.Single<CustomUserAuth>(x =>
x.Id == this.UserAuthId.ToInt());
if (userAuthInstance != null)
{
userAuthInstance.DefaultProfileUrl = this.DefaultProfileUrl;
db.Save(userAuthInstance);
}
}
}
}
}
Which fetches the Profile Url of the User when they login via GitHub or Twitter. Will assign the Admin role to users in the TwitterAdmins AppSetting, which is a way to assign admin rights to known Twitter users. Finally the retrieved Profile Url is added to the CustomUserAuth POCO Table and saved.
TechStacks tells ServiceStack to use its own CustomUserAuth table instead by registering a generic OrmLiteAuthRepository:
var authRepo = new OrmLiteAuthRepository<CustomUserAuth, UserAuthDetails>(dbFactory);
container.Register<IUserAuthRepository>(authRepo);
authRepo.InitSchema();
Where it will now Save User Information in the CustomUserAuth instead of the default UserAuth table.
I'm building a prototype for a RESTful API using ASP.NET Web API 2. Fo simplictiy let's assume I have three entities: customers, licences and users. Each customer has a set of licences and users. Semantically it seems to me the resource URIs should look like this:
myurl/api/customers for accessing all customers
myurl/api/customers/{cid} for accessing the customer {cid}
myurl/api/customers/{cid}/licences for accessing all licences of customer {cid}
myurl/api/customers/{cid}/licences/{lid} for accessing the licence {lid} of customer {cid}
The same goes for the users. The intended semantics allow for example two users to have the same id if they belong to separate customers. Apart from maybe the licences entities (decision not final yet) each customer will have a dedicated database, so there is no overlapping in this domain and resource paths like
myurl/api/users
make only sense in the way "join all user tables from all customers' databases.
Using attribute routing this setup is quite easily achieved. However, all methods have to be implemented in the same controller since methods from different controllers cannot share the same prefix AFAIK.
The actual application will contain many more entities than just three, so I expect the controller's implementation to get quite huge. My question now is, how can I split the method into different controllers? I thought about using one main controller which just dispatches the work to be done to another controller. For example
[Route("{customer:int}/licences/{licence:int}")]
public HttpResponseMessage GetLicence(int customer, int licence)
{
// pretend the called method is static
return LicenceController.GetLicence(customer, licence);
}
However, I do not know how to implement this properly: Should I create a new LicenceController for each call? Or have a property of this type and call it's method? Actually implement some static methods?
Another downside is that this introduces hard-coded dependencies between the selector and the implementing controller classes which I feel is not a clean solution.
I came up with a workaround which uses resource paths like this:
myurl/api/licences/customer-{cid} for accessing all licences of customer {cid}
myurl/api/licences/customer-{cid}/{lid} for accessing the licence {lid} of customer {cid}
This works quite well but messes up the homogeneous semantics IMO. I know I can write a custom selector class but that seems to be quite some work to get it right.
So my question is, what is the best (perhaps most efficient) way to split the code which deals with incoming HTTP messages into separate controllers so that there is loose coupling and the resource semantics are coherent?
You would have two controllers. One to return the customers and one to return the licences. For the Customer there is no need to use attributes as the defaults are fine:
public class CustomersController : ApiController
{
// GET: api/Customers
public IEnumerable<Customer> Get()
{
return new List<Customer>
{
new Customer { Id = 1, Name = "Wayne" },
new Customer { Id = 2, Name = "John" }
};
}
// GET: api/Customers/5
public Customer Get(int id)
{
return new Customer { Id = 1, Name = "Wayne" };
}
}
Then you can you RoutePrefix attribute on the controller to add for api/Customers/1/licences and the rest can be handled by Route on the actions. I named the Controller CustomerLicencesController as you probably want to have a Licences controller to fetch a particular licence or all licences such as api/licences or api/licences/1.
[RoutePrefix("api/customers/{customer}/licences")]
public class CustomerLicencesController : ApiController
{
// GET: api/Customers/1/licences
[Route("")]
public IEnumerable<Licence> Get(int customer)
{
return new List<Licence>
{
new Licence { Id = 1, Name = "Test" },
new Licence { Id = 2, Name = "Test2" }
};
}
// GET: api/Customers/1/licences/1
[Route("{id}")]
public Licence Get(int customer, int id)
{
return new Licence { Id = 1, Name = "Test" };
}
}
For more information about Route attributes take a look at this.
I have a system where all pages (views) and all controls (buttons, links, menu itens ...) have security roles applied to them.
So I have an admin interface where all pages and controls are registered. And each user has a set of individual permissions.
So, for example:
I have a View EditCar, with 3 buttons: "New", "Delete" and "Back".
So the user X have permission to see View EditCar, and only the button "Back"
So each new view must be registered, and the users associated with. There is no roles, because each user is 100% configurable.
So, I have a FilterAttribute:
public class CustomAuthorize : FilterAttribute, IAuthorizationFilter
{
public void OnAuthorization(AuthorizationContext filterContext)
{
if (filterContext.HttpContext.Request.IsAuthenticated)
{
var userPermissions = repository.GetAll().Where(x => x.Name.Equals(User.Identity.Name);
// if (!userPermissions.Pages.Any(x => x.NamePage.Contains(???))))
}
else
{
filterContext.Result = new HttpUnauthorizedResult();
}
}
}
So my question is :
- What should I keep in database to identify each View(Action) ? Maybe 3 values? Area-Controller-Action?
Is it the best option? Any other idea about that solution?
Thanks
I have same scenario in my web-application and it is working in the following way:
we have in database:
Permission contains View, Add, Edit, Delete
Feature contains all the feature which can be set over role
FeaturePermission bind the feature with permission like which feature has what permisssion
UserRole has the role of a user
RoleFeaturePermission shows that which role has what permission to allowed
Now in code I do when a user authenticate I generate the list of permission assigned to it with features then I defined an Enum like:
public enum FeatureValue
{
Custom = 1,
Schedule = 2,
Export=3
}
public enum PermissionValue
{
View = 1,
Add = 2,
Edit = 3,
Delete = 4
}
and the UserPermission static class to get authorization:
public static bool VerifyPermission(FeatureValue feature, PermissionValue permission, int id) {
return getFeaturePermissionsForReport(feature, permission, id);
}
private static bool getFeaturePermissionsForReport(FeatureValue feature, PermissionValue permission, int id) {
SessionHelper sessionHelper = new SessionHelper(null);
UserModel userModel = sessionHelper .getUser()//get user from session.
if (userModel != null && userModel.IsAuthorized == false) return false;
UserProfile userProfile = sessionHelper.Get<UserProfile> ();
if (userProfile != null && userProfile.AssignedRoleList != null) {
List<Core.Entities.FeaturePermission> featurePermission = userProfile.AssignedRoleList.SelectMany(b => b.RoleFeaturePermission).ToList();
if (featurePermission != null) {
if (featurePermission.Count(f = > f.Feature.Id == (int) feature && f.Permission.Id == (int) permission) > 0) {
bool isAllowed= false;
int featurePermissionId = featurePermission.Where(f = > f.Feature.Id == (int) feature && f.Permission.Id == (int) permission).Select(i = > i.Id).FirstOrDefault();
isAllowed = (reports.Count(r = > (r.FeaturePermissionId == featurePermissionId && r.Id == id)) > 0) ? true : false;
return isAllowed;
}
}
}
return false;
}
and now one each link, button or action use:
#if (UserPermission.VerifyPermission(FeatureValue.Custom, PermissionValue.Edit))
{
//action link to edit custom view
}
and for action custom attribute is:
[AttributeUsage(AttributeTargets.All,AllowMultiple=true)]
public class CustomFeaturePermissionAttribute : ActionFilterAttribute
{
private FeatureValue[] feature;
private PermissionValue[] permission;
private bool excludeParamId;
/// <summary>
/// Set values of featurelist and permission list
/// </summary>
/// <param name="featureList"></param>
/// <param name="permissionList"></param>
public CustomFeaturePermissionAttribute(object featureList,object permissionList, int excludeParamId)
{
FeatureList = (FeatureValue[])featureList;
PermissionList = (PermissionValue[])permissionList;
ExcludeParamId = excludeParamId;
}
public FeatureValue[] FeatureList
{
get
{
return feature;
}
set
{
feature = value;
}
}
public bool ExcludeParamId
{
get
{
return excludeParamId;
}
set
{
excludeParamId = value;
}
}
public PermissionValue[] PermissionList
{
get
{
return permission;
}
set
{
permission = value;
}
}
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);
bool isAccessAllowed = false;
FeatureValue feature;
PermissionValue permission;
for (int i = 0; i < FeatureList.Count(); i++)
{
feature = FeatureList[i];
permission = PermissionList[i];
isAccessAllowed = UserPermission.VerifyPermission(feature, permission, Convert.ToInt16(ExcludeParamId));
if (isAccessAllowed)
break;
}
if (!isAccessAllowed)
{
filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary(new { action = "UnauthorizedAccess", controller = "Security" }));
}
}
}
and on actions allow role having view permission over Custom and Export:
[CustomFeaturePermission(new FeatureValue[] { FeatureValue.Custom, FeatureValue.Export }, new PermissionValue[] { PermissionValue.View, PermissionValue.View},pageId)]
public ActionResult Custom()
{
//action body
}
I would create an abstract way of defining each permission, such as an enum. For example:
public enum UserPermissions
{
ViewCars,
EditCars,
DeleteCars,
ViewUsers,
EditUsers,
DeleteUsers
}
You could create these in the database in a table called Permissions, then create a many-to-many mapping where each user can be assigned to any number of permissions.
Then you would create a custom authorization attribute by deriving from AuthorizeAttribute and override the OnAuthorization method to load the user from the database. This is exactly what you have done in your question except the key part is that you want to add some property where you can define the permission(s) needed for an action, like so:
public class UserPermissionsAttribute : AuthorizeAttribute
{
public IEnumerable<UserPermissions> PermissionsRequired { get; set; }
public UserPermissionsAttribute()
{
}
public UserPermissionsAttribute(params UserPermissions[] permissionsRequired)
{
PermissionsRequired = permissionsRequired;
}
public override void OnAuthorization(AuthorizationContext filterContext)
{
var user = filterContext.HttpContext.User; // get user from DB
if (PermissionsRequired.All(x => user.Permissions.Any(y => x == y)))
{
// all permissions are met
base.OnAuthorization(filterContext);
}
else
{
throw new UnauthorizedAccessException();
}
base.OnAuthorization(filterContext);
}
}
Now you can decorate each action or controller with a permission or list of permissions:
[UserPermissions(UserPermissions.ViewCars, UserPermissions.EditCars)]
public ActionResult Index()
{
ViewBag.Title = "Home Page";
return View();
}
This way you separate your permission system from MVC controller/action logic.
Although I'd advise against this method of storing each permission on an individual basis. The role system keeps things much simpler and will improve performance. I really think you could do this with a number of fine-grained roles instead of fine-grained permissions.
Note that Authorizing users to see specific page elements differs from Authorizing for CRUD or other database operations, unless the elements point to operational Actions in Controller. Consider that you may have some elements that there's no need to be saw by a specific user, and don't have specific database operation. Till now we conclude that we need the following permissions :
Permission to See
Permission to Command
I believe that you can use Microsoft Role Provider for both parts. According to MSDN Documentation Considering that :
The Authorize attribute lets you indicate that authorization is
restricted to predefined roles or to individual users. This gives you
a high degree of control over who is authorized to view any page on
the site.
In The next step/question is how to do that?
I think 3 ways are available to meet our purpose:
Solution 1: Creating separate Views with specific page elements due to forwarding each user to related View. In this scenario we must
create separate controller actions too. we have to check user types
before each action like [Authorise(Roles="Administrator")]. We
forced to have static (Pre-defined) Roles and Accessibility. And in
one sentence Not a good solution because of redundancy and
instability.
Solution 2: Creating pages Dynamically simply by adding some if conditions for each access restricted element in One Page(for
example Edit Page). That is like employing #if
(User.IsInRole("Admin")) to authorize specific users and show
related page elements like buttons. In Controller side we can use
if conditions (not as FilterAttribute due to add dynamic
functionality based on generated/added new roles) and control valid
transactions against database. Although FilterAttributes add some great functionalists (like performance optimization). In one sentence A moderate solution.
Solution 3: Act like solution 2, just fix Controller problem by
creating our own custom FilterAttribute for authorization. That will
inherited from AuthorizeAttribute and overrides the OnAuthorize
method to do what you need only for Operations.
For Example :
public class TableAuthorizeAttribute : AuthorizeAttribute
{
public enum TableAction
{
Read,
Create,
Update,
Delete
}
public TableAction Action { get; set; }
public override void OnAuthorization(AuthorizationContext filterContext)
{
base.OnAuthorization(filterContext);
//do custom authorizization using Action and getting TableEntryID
//from filterContext.HttpContext.Request.QueryString or
//filterContext.HttpContext.Request.Form
}
}
And its usage will be like this :
[TableAuthorize(Action=TableAuthorizeAttribute.TableAction.Update)]
Here is complete example about above concept. Here is complete example for creating dynamic AuthorizeAttribute for authorizing new roles added to application.
Solution 3 in one sentence A perfect but Complex Solution.
Note that by using FilterAttribute before Actions we have limited our application to static/predefined roles. No need to use another Data Structure or generate tables in Database.
I've seen a similar implementation in the past which utilized a token concept.
Each Action method is represented by a token. A selection of tokens define a role. A role is assigned to a user.
I used a simple console application to reflect my MVC application and look for all Controllers and determine every action method within them.
Store these "Tokens" in your database along with your roles.
The implementation kept it simple and just used the fully qualified name with namespaces etc to identify them. This way the data has to be specific to your application which can increase security
I would take Trevor's approach, but It wouldn't use an attribute.
I would create a common action permission enum like :
[Flags]
internal enum PermissionsEnum
{
listbutton = 1,
editbutton = 2,
deletebutton = 4,
savebutton = 8,
createbutton = 16,
action03 = 32,
action04 = 64,
action05 = 128,
action06 = 256,
action07 = 512,
action08 = 1024,
action09 = 2048,
action10 = 4096,
action11 = 8192,
action12 = 16384,
action13 = 32768
}
Such a permission object I store for every area/controller and user in the database like with some additional constraints
permission value -1 not allowed to call the action and permission value 0 to call the action but no other permissions:
Controller/Action UserId Permission
================= ====== =========
cars/delete User0001 -1
cars/edit User0001 8
cars/index User0001 0
cars/list User0001 16
cars/show User0001 2
The apply the permissions I would create a base controller. When ever an action is called, the base controller retrieves the permissions for the called controller:
var currentController = this.Url.RouteData["controller"];
var currentAction = this.Url.RouteData["action"];
var currentUserPermissons = GetUserPermissonForController(string.Format("{0}/{1}",currentController,currentAction), userId);
if( 0 > currentUserPermissons ) RedirectToAction("PermissonDenied","Error");
ViewBag.UserPermissons = (PermissionsEnum)currentUserPermissons;
In each view I would check the ViewBag.UserPermissons before create a protected item like:
#{ if((ViewBag.UserPermissons & PermissionsEnum.listbutton) == PermissionsEnum.listbutton)
{
#Html.ActionLink("Listitems","List")
}
}