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")
}
}
Related
I have a web api with basic jwt authentication and role based authorization. Now I want to restrict certain fields from being edited by users that are in the role user, because the route based authorization is not enough.
class Account {
public int Id {get; set;}
public string Email {get; set;}
public string Password {get; set;}
public bool Enabled {get; set;} // <- this field should only be editable by an admin or manager
public int RoleId {get; set;} // <- this field should only be editable by an admin
}
When the user is in the role user he is only allowed to change his email address and his password, but only for his account. When he is in the role manager he should be able to edit the fields email, password and enabled but only for accounts that are in the user role. An admin can edit every field from every user.
Is there anything that would solve my problem, for example something like this:
class Account {
public int Id {get; set;}
public string Email {get; set;}
public string Password {get; set;}
[Authorize(Roles = "Admin,Manager")]
public bool Enabled {get; set;} // <- this field should only be editable by an admin or manager
[Authorize(Roles = "Admin")]
public int RoleId {get; set;} // <- this field should only be editable by an admin
}
More infos about my project:
- ASP.NET Core 3.1
- I use Entity Framework Core with a Postgres database
- For authentication I use basic jwt bearer authentication
So, I think you has incorrect understanding of Authtorize working.
This attribute uses for Controllers. You can create multiple controllers and set for each method different ROLES to specify what Roles can call this method.
It's not correct to specify it on Dto (Data Transfer Objects) classes.
But you can make some interesting solution with 2 controllers and inheritance.
//Account dto for edit
class AccountEditDto {
public int Id {get; set;}
public string Email {get; set;}
public string Password {get; set;}
}
//Controller to edit account
[Route("all/account_controller")]
public class AccountController : Controller
{
public ActionResult EditAccount(AccountEditDto accountDto)
{
//do something
}
}
Then for create manager roles setup something like this :
//Account dto for edit
class AccountManagerEditDto : AccountEditDto {
public bool Enabled {get; set;}
}
//Controller admin to edit account
[Area("Manager")]
[Route("manager/account_controller")]
public class AccountManagerController : AccountController
{
[Authorize(Roles = "Manager")]
public ActionResult EditAccount(AccountManagerEditDto accountDto)
{
//Do something
}
}
Then for create admin roles setup something like this :
//Account dto for edit
class AccountAdminEditDto : AccountManagerEditDto {
public int RoleId {get; set;}
}
//Controller admin to edit account
[Area("Admin")]
[Route("admin/account_controller")]
public class AccountAdminController : AccountController
{
[Authorize(Roles = "Admin")]
public ActionResult EditAccount(AccountAdminEditDto accountDtp)
{
//Do something
}
}
Then you can use than pattern of URL for call controller methods:
http://localhost/{role}/accont_controller/edit
jwt is token based, meaning that every user has it's unique token which he uses in order to access your system.
The easiest way for you to achieve this is to decode the token and check for the roles, thus enabling you to set role specific action for users.
You will not be able to do this via attributes because jwt does not support them.
There is a good guide to walk you through it as it will be too long for an answer here:
Role based JWT tokens
It requires basic understanding of how tokens work and the guide provides brief explanation.
Although I'm not really Fan of this answer but you might you need this answer duo to a internal policy on your workplace.
Disclaimer:: this question is treated as business rule validation because there is many ways technically to sperate them in a way that make since.
For me I like the First answer which made a very good segregation of the Apis....
Although this is the think you are looking for you MUST keep them in one api: :
[System.AttributeUsage(System.AttributeTargets.Property, Inherited = true)]
public class AuthorizeEdit : ValidationAttribute
{
private readonly string _role;
private IHttpContextAccessor _httpContextAccessor;
public AuthorizeEdit(string role) : base()
{
_role = role;
}
public override bool IsValid(object value)
{
return _httpContextAccessor.HttpContext.User?.IsInRole(_role) ?? false;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
_httpContextAccessor = (IHttpContextAccessor)validationContext
.GetService(typeof(IHttpContextAccessor));
return base.IsValid(value, validationContext);
}
}
and you can use it like this :
class Account {
public int Id {get; set;}
public string Email {get; set;}
public string Password {get; set;}
public bool Enabled {get; set;} // <- this field should only be editable by an admin or manager
[Authorize(Roles = "Admin")]
public int RoleId {get; set;} // <- this field should only be editable by an admin
}
THE DOWNSIDE : Any one whos not in role in the validator will not be able to change any failed what so ever
My recommendation: separate your Apis like the first answer suggest then add this filter to the admin api
Note you can change the filter to take as roles as u want
Unfortunately, this is not possible as it isn't something built-in to ASP.NET Core 3.1.
However, why not carry out your logic in the handler?
You can either create multiple endpoints for users (which I wouldn't recommend) or as per common convention, use 1 route and just validate the data based on the user's role before processing the data.
Get the current account, check to see what has changed and if the user has changed a property which they should have no permission to change, return HTTP 403 Forbidden without further processing their request.
If they have the right role for the action, continue as normal.
You can inherit AuthorizeAttribute and write your own like so:
public class myAuthorizationAttribute : AuthorizeAttribute
{
protected override bool IsAuthorized(HttpActionContext actionContext)
{
// do any stuff here
// it will be invoked when the decorated method is called
if (CheckAuthorization(actionContext))
return true; // authorized
else
return false; // not authorized
}
private bool CheckAuthorization(HttpActionContext actionContext)
{
bool isAuthorized = ... // determine here if user is authorized
if (!isAuthorized) return false;
var controller = (myController)actionContext.ControllerContext;
// add those boolean properties to myController
controller.isEnabledReadOnly = ... // determine here if user has the role
controller.isRoleIdReadOnly = ... // determine here if user has the role
return true;
}
}
In the function CheckAuthorization you can check roles available and then set flags in your code to decide whether the related fields should be allowed written to or not.
It can be simply used on any method like:
[myAuthorization]
public HttpResponseMessage Post(string id)
{
// ... your code goes here
response = new HttpResponseMessage(HttpStatusCode.OK); // return OK status
return response;
}
Since you've added the properties isEnabledReadOnly and isRoleIdReadOnly to your controller, you can directly access them in any (Get, Post, ...) method.
Note: You can use the actionContext to access Request, Response, ControllerContext etc (see here).
More information about the internals (how this works) can be found here.
You can do in an action. You sould code like this.
public class AccountController : Controller
{
[HttpPost("EditProfile")]
public async Task<IActionResult> EditProfile(User user)
{
var fields = new List<string>();
fields.Add("Id");
fields.Add("Email");
fields.Add("Password");
if (User.IsInRole("Admin")){
fields.Add("RoleId");
fields.Add("Enabled ");
}else if(User.IsInRole("Manager"))){
fields.Add("Enabled ");
}
var updateUser = context.Entry(user);
foreach (var field in fields)
{
updateUser.Property(field).IsModified = true;
}
await context.SaveChangesAsync();
}
}
Well, I would personally suggest using #TemaTre's answer but since you ask. I could mention another possibility other than using a custom serializer. Which is to use a custom model binder.
You need a few basic steps
Define a custom attribute
[AttributeUsage(AttributeTargets.Property, Inherited = false, AllowMultiple = false)]
public sealed class AuthorizePropertyAttribute : Attribute
{
readonly string roles;
public AuthorizePropertyAttribute(string roles)
{
this.roles = roles;
}
public string Roles
{
get { return roles; }
}
}
Annotate your model with that attribute
public class Account
{
public int Id { get; set; }
public string Email { get; set; }
public string Password { get; set; }
[AuthorizeProperty("Admin,Manager")]
public bool Enabled { get; set; } // <- this field should only be editable by an admin or manager
[AuthorizeProperty("Admin")]
public int RoleId { get; set; } // <- this field should only be editable by an admin
}
Define a custom ModelBinder
public class AuthorizedModelBinder : IModelBinder
{
public async Task BindModelAsync(ModelBindingContext bindingContext)
{
using var reader = new StreamReader(bindingContext.HttpContext.Request.Body);
var body = await reader.ReadToEndAsync();
var jObject = JsonConvert.DeserializeObject(body, bindingContext.ModelType); // get the posted object
var modelType = bindingContext.ModelType;
var newObject = Activator.CreateInstance(modelType); // this is for demo purpose you can get it in a way you want (like reading it from db)
var properties = modelType.GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
var user = bindingContext.HttpContext.User; // this is also for demo purpose only
foreach (var prop in properties)
{
var auth = prop
.GetCustomAttributes(typeof(AuthorizePropertyAttribute), true)
.OfType<AuthorizePropertyAttribute>().FirstOrDefault(); // check the property has that auth attribute
if (auth == null)
{
prop.SetValue(newObject, prop.GetValue(jObject)); // if not assign that property
}
else
{
var isInRole = auth.Roles.Split(",", StringSplitOptions.RemoveEmptyEntries).Any(user.IsInRole);
if (isInRole) // this guy has access
{
prop.SetValue(newObject, prop.GetValue(jObject));
}
}
}
}
}
And finally, if you want every Account object to go through this model binder you can annotate your class like this :
[ModelBinder(typeof(AuthorizedModelBinder))]
public class Account ...
Or you can specify it on the Action you want to use like this :
public IActionResult Sup([ModelBinder(typeof(AuthorizedModelBinder))]Account ACC)...
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.
In my App I have navbar with menu. In menu I have 3 dropdowns.
public
for normal users
for admins
Access is restricted but menus are visible for all. I want to hide unnecessary element for normal and public(anynymous) users.
for identyfication I'm using windows login names
To get a user role I'm asking database and query returns if user is normal user or admin.
My solution:
public bool CheckIfAdmin(string login)
{
bool admin = false;
EquipmentEntities db = new EquipmentEntities();
Tuple<string, string> credentials = GetName(login);
int RoleId = db.Users.Where(w => w.Name == credentials.Item1).Where(w => w.Surname == credentials.Item2).Select(s => s.RoleId).FirstOrDefault();
if(RoleId==1)
{
admin = true;
}
return admin;
}
and nearly same code for for checking if User
in methods:
if(CheckIfAdmin(login)){
ViewBag.Role=1;
}
else if(CheckIfUser(login)){
ViewBag.Role=2;
}
and finally in layout:
#if (ViewBag.Role==1)
{
<li class="dropdown">
Admin<b class="caret"></b>
<ul class="dropdown-menu">
//MEnu
</ul>
</li>
}
and nearly same code for second dropdown I want to hide.
This is working but I at this moment I need to put checks for role in each method. Its large amount of redundant code. Can anyone suggest me how to make it better?
I see your roles is static, because you check if(RoleId==1) so that user is admin. I think you can define roles like enum.
public enum UserRole
{
User = 1,
Manager = 2,
Admin = 3,
//SuperAdmin...etc.
}
Create base controller, add CurrentUser property. And when action execute take current user.
public class BaseController : Controller
{
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
CurrentUser = db.GetLoggedUserFromDatabase(); // to use in controller
ViewBag.CurrentUser = CurrentUser; // to use in views
}
public User CurrentUser { get; set; }
}
Finally your controller implements BaseController:
public class AnyController : BaseController
{
//in every action you have current user's details.
//Already you know current users role. you can use it. for example:
public ActionResult AnyAction()
{
if(CurrentUser != null) //if user logged
{
if (CurrentUser.Role == (int)UserRole.Admin)
{
//user is admin
}
}
}
}
In views you can use ViewBag.CurrentUser. Cast it first then check role as in controller.
When my user in the students Role login to the system, he can select various classes that he's enrolled. I already have a filter that'll redirect him to the select class page so he must select a class to access the system, and change it anytime he wants and the whole system's context will change.
As for now, i'm storing IdClass in the session variable, using the code below, and the system uses it to filter all the related queries and functions, like showing all the lessons from the current class. My question is: is this a good practice? Is this right or is there any better and efficient way? I'm trying to follow patterns.
[Serializable]
public sealed class Session
{
private const string SESSION_FOO = "STUDYPLATFORM_GUID";
private Session()
{
this.IdClass= 0; // Construct it to 0 so it evaluate as there's no Class selected.
}
/* This is the session's public IdClass that
i can get and set throughout the application. */
public int IdClass { get; set; }
public static Session Current
{
get
{
if (HttpContext.Current.Session[SESSION_FOO] == null)
{
HttpContext.Current.Session[SESSION_FOO] = new Session();
}
return HttpContext.Current.Session[SESSION_FOO] as Session;
}
}
}
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.).