I have a database table where all the company employees are listed. They have roles (a, b, c) defined to each employee. for e.g. employee 1 has role a, employe 2 has role b and so on.
Now, i want to check if employe has either of the 3 roles. if yes, provide that user access to website. if no roles mentioned to that user, deny access. The c# code should be able to take the windows login information and then query the database.
can anyone please let me know how to use C# code and start off with things
A Filter Attribute that extends AuthorizeAttribute. It gets the roles for the user in the database and compares with the roles assigned to each controller or method.
public class UserRoleAuthorize : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
//Data Repository. Getting data from database
var repository = new LoginRoleRepository();
//GetCharacterSeparator is an Extension method of String class
//It seperates the comma separated roles.
//The data comes from the controller
var roles = Roles.GetCharacterSeparator(',', true);
if (httpContext.User.Identity.IsAuthenticated)
{
//Here I check if the user is in the role, you can have your own logic. The data is gotten from DB.
var userRoles =
repository.All().Where(obj => obj.Login.Username == httpContext.User.Identity.Name).Single().Roles;
foreach (var role in roles)
if (userRoles.Any(obj => obj.Name == role))
return true;
}
return false;
}
}
Then you just define the attribute for each method or controller as bellow.
//Both Doctors and Receptionist have access to Patient controller.
[UserRoleAuthorize(Roles="Doctors, Receptionist")]
public class PatientController : Controller
{
//Both Doctors and Receptionist have access to Schedule an appointment for patients.
public ActionResult Schedule()
{
return View();
}
//Only Doctors have access to Treat patients.
[UserRoleAuthorize(Roles="Doctors")]
public ActionResult TreatPatient()
{
return View();
}
}
You need to add extra information as:
//Here seperate the roles as Doctor:ReadWrite, Receptionist:Read
//If you see Doctor:ReadWrite means the doctor has Read and Write and so on.
//This code is in AuthorizeCore
var roles = Roles.GetCharacterSeparator(',', true);
//And Add the bellow to the controllers and methods.
[UserRoleAuthorize(Roles="Doctors:Write, Employees:Read")]
Related
Describing University via software I faced an auth issue.
Previously I had only a Headmaster role, which can do and access anything.
But as for now, I need to integrate a Teacher role.
The Teacher role should have an option to access certain features, which could be easily restricted by Authorize attribute. But in some cases, I want to reduce the number of data allowed to access for this role, e.g. not all students of the universe, but the ones who study Teacher's Subject.
All of this is already described in EF, (e.g. teacher-subject, subject-students relationships). But now I struggle to refuse (return 403) request for subject or students which are not allowed to access by Teacher.
I thought about a Specification pattern usage for my Services, so the resulting data will be reduced with a Specification's filter, as it helps reduce the data amount, sometimes to no-data, but didn't help to fully refuse a request.
Could you kindly provide me a link or an architectural idea to satisfy expectations for both use-cases specified above?
// entity models
class Subject {
...
public Teacher Teacher { get; set; }
public List<Students> { get; set; }
...
}
class Teacher {
...
public List<Subject> Subjects { get; set; }
...
}
class Student {
...
public List<Subject> StudiedSubjects {get; set; }
...
}
// first use-case I want to solve
public ActionResult<List<Student>> GetStudent()
{
// previously I just did
return Ok(_studentsService.GetStudents());
// but as for now in case of Teacher role accessed the method I want to
// reduce the number of returned students
}
// second use-case I want to solve
public ActionResult<Subject> GetSubjectDetails(int subjectId)
{
// previously I just did
return Ok(_subjectService.GetSubject(subjectId);
// but as for now in case of Teacher role I need to check whether its
// allowed to get the subject and return Access denied in case its not
}
For your first case, because the Action have no parameters at all, it'll make more sense to return Students that are accessible for a Teacher, or no Students at all if no one take all the subjects of certain Teacher, so 403 are not needed in this case. You could pass the User from controller or inject HttpContextAssessor to StudentService and use it for filtering.
as for your second case, its a perfect situation to return 403 if the SubjectId is not related to the Teacher in context. If you dont mind getting data from database for each request, you can use Requirement combined AuthorizationHandler in a Policy-Based Authorization by retrieving any data you need for authorization from database thus determine if the teacher has access to certain Subject(s). Steps to achieve it:
First setup the policy for the Teachers-Subjects relation and the handlers in Startup.ConfigureServices :
services.AddAuthorization(options =>
{
options.AddPolicy("TeacherSubject", policy => policy.Requirements.Add( new TeacherSubjectRequirement() ));
});
services.AddScoped<IAuthorizationHandler, TeacherSubjectHandler>();
next create the AuthorizationHandler for that policy:
public class TeacherSubjectHandler : AuthorizationHandler<TeacherSubjectRequirement>
{
readonly IHttpContextAccessor _contextAccessor;
readonly UserManager<AppUser> _usermanager;
readonly UserToTeacherService _userToTeacherService;
public ThePolicyAuthorizationHandler(IHttpContextAccessor c, UserManager<AppUser> u, _userToTeacherService s)
{
_contextAccessor = c;
_userManager = u;
_userToTeacherService = s;
}
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext authHandlerContext, TeacherSubjectRequirement requirement)
{
var user = _userManager.GetUserAsync(_contextAccessor.HttpContext.User);
var teacher = _userToTeacherService(user); //I assume this service will also retrieve teacher's subjects
var subjectIds = teacher.Subjects.Select(s => s.SubjectId).ToList();
if (context.Resource is AuthorizationFilterContext filterContext)
{
var subjectIdStr = filterContext.RouteData.Values["id"].ToString();
if ( int.TryParse(subjectIdStr, out var subjectId) && subjectIds.Contains(subjectId) )
{
context.Succeed(requirement);
}
}
}
}
as for the Requirement class, its just an empty class:
public class TeacherSubjectRequirement: IAuthorizationRequirement
{
}
Since we're doing the authorization mechanism in AuthorizationHandler, we can leave this class empty. But it will still needed for the policy-based authorization to works.
And then for the policy to take effect, add attribute to the controller
[Authorize(Policy = "TeacherSubject")]
public ActionResult<Subject> GetSubjectDetails(int subjectId)
{
//existing code
}
But to be honest, I haven't tried putting policy-based attribute in an Action. If this doesn't work, putting the attribute in controller will surely works.
Hope this helps.
Your scenario is very practical which makes the question very interesting. But It would be nice for you to look at this documentation.
Use claims to give the teach access to the information they must be able to access. You can save a claim as .i.e. "TeacherName-TargetInforNameTheyCanAccess"; you can have as many claims as possible depending on how much information should be accessible by the teacher.
This goes the same for Students as well. You can create claims for Students that are under that Lecturers Class. Like say: "StudentName-LectureName" an you can then base your authentication by checking if the student is claimed to be under a specific lecturers class.
I have a bog-standard implementation of Users and Roles using ASP.NET Identity:
public class User : IdentityUserBase { }
public class Role : IdentityRoleBase { }
public class UserRole : IdentityUserRoleBase { }
I need a property or method in the User class that returns the (first) role of the user (in my app, each user can only have one):
public class User : IdentityUserBase
{
// ...
public Role GetRole()
{
return this.Roles.FirstOrDefault(); // this actually returns a UserRole object, with only a UserId and a RoleId available (no Role object)
}
}
Is there a way to do this here in the DAL?
I know it's possible at the UI layer using global objects like RoleManager, DbContext or HTTPContext but I don't have access to those in the User class (and don't want to have to pass them in as arguments).
I would have thought that this must be a standard use-case but I can't find an answer ... my question is basically the same as this one but I need access to the Role information within the User object itself.
First of all, you shouldn't put the GetRole method in the User class, keep those models simple and clean.
To get the roles for a user, you need the db context and the user ID, and you can do this:
var userId = ...; //Get the User ID from somewhere
var roles = context.Roles
.Where(r => r.Users.Any(u => u.UserId == userId))
.ToList();
This worked:
public class UserRole : IdentityUserRoleBase
{
public virtual Role Role { get; set; } // add this to see roles
public virtual User User { get; set; } // add this to see users
}
The User and Role properties are now exposed through the UserRole property that is available through the User object.
Looking back through the evolution of ASP.NET Identity here, these properties were present in IdentityUserRole by default in version 1 but then removed in version 2 in order to support generic primary keys, with a recommendation to use the UserManager class. However, this doesn't help cases like mine where the operations need to be done in the DAL and don't have easy access to this class (or the any of the context classes). Given that I will never have any other type of primary key, it feels quite safe to do this.
I'm trying to build an authorization system not only based in user -> permissions -> roles -> groups but also in entities -> properties.
So i want to limit the model binding in post and put requests so I can verify which properties the user has permission to update or create, and then let him/her update or create the entity ... else reject the request.
Maybe its a idea too complex but I wanted to have the same functionality as some CMS online.
I was reading and maybe this can be solved with a Custom Model Binder, I'm learning a lot about it but, I wanna know if this is "the right path" or maybe there is a faster or better way to do it
Thank you so much, I'll keep updating my question with code so, maybe can help someone in future with the same idea.
I'm working on exactly the same thing, and I do believe it's entirely possible to do this with custom [Attributes]. Here's a small implementation of how I've done it in dynamic select-statements:
Firstly, I have a custom attribute that takes an enum UserRoles as input:
[AttributeUsage(AttributeTargets.Property)]
public class RestrictUserRoles : Attribute
{
public RestrictUserRoles(UserRoles roles)
{
Roles = roles;
}
public UserRoles Roles { get; }
}
The UserRoles-enum can be implemented as such:
[Flags]
public enum UserRoles
{
[Description("Administrator")]
Admin = 1,
[Description("Employee")]
Employee = 2,
[Description("Head of a division")]
DivisionHead = 4,
[Description("Fired")]
Fired = 8
}
I use an enum because some employees can be admin, divisionheads (and even fired).
I then have an IQueryable extension where it gets all the properties that a user is authorized to see and intersects those properties with those selected. To do this I use dynamic Linq and reflection.
public static class QueryableExtensions
{
public static IQueryable SelectProperties<T>(this IQueryable<T> source, UserRoles roles, string criteria)
{
// get all the properties that a user is authorized to see
var authenticatedProperties = typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public)
.Where(prop => prop.CustomAttributes.Any(attr =>
attr.AttributeType == typeof(RestrictUserRoles) &&
(((RestrictUserRoles) Attribute.GetCustomAttribute(prop, typeof(RestrictUserRoles))).Roles & roles) !=
0))
.Select(prop => prop.Name)
.ToList();
// if there aren't any, then the user is not
// authorized to view any properties
// DISCLAIMER: or someone has forgotten to mark any properties
// with the RestrictUserRoles-attribute
if (!authenticatedProperties.Any()) throw new UnauthorizedAccessException();
// we get all the properties that the user wants to
// select from the string that was passed to the function in
// the form Prop1, Prop2, Prop3
var selectProperties = criteria
.Split(',')
.Select(property => property.Trim());
// Get the intersection between these properties, IE we
// select only those properties that the user has selected
// AND is authorized to view
var properties = authenticatedProperties
.Intersect(selectProperties)
.ToList();
// if there are none that intersect, return all those
// properties that a user is authorized to view
if (!properties.Any()) properties = authenticatedProperties;
// run the query using dynamic linq
return source.Select("new(" + properties.JoinToString(",") + ")");
}
}
This isn't field-tested, but it should do the trick and is easily extendable to mutations.
EDIT: forgot that I use an extension-function to join all properties that I define below:
public static string JoinToString(this IEnumerable<string> list, string delimiter = "")
{
return string.Join(delimiter, list);
}
Using this answer, I implemented below code to get list of ApplicationUsers in a specific role.
I need to mention that ApplicationUser is an extention of IdentityUser. I want to know are there any better methods for this?
ApplicationDbContext context = new ApplicationDbContext();
var store = new Microsoft.AspNet.Identity.EntityFramework.UserStore<ApplicationUser>(dbContext);
var manager = new Microsoft.AspNet.Identity.UserManager<ApplicationUser>(store);
List<ApplicationUser> users = new List<ApplicationUser>();
foreach (ApplicationUser user in manager.Users.ToList())
{
if (manager.IsInRole(user.Id,"Admin")){
users.Add(user);
}
}
You can query like this
ApplicationDbContext context = new ApplicationDbContext();
var role = context.Roles.SingleOrDefault(m => m.Name == "Admin");
var usersInRole = context.Users.Where(m => m.Roles.Any(r => r.RoleId != role.Id));
I am not sure if this is the optimal way, but does less queries to database than your code.
No, there isn't better way.
But assuming you are using that in your controller you could create a BaseController where every other controller is derived from.
Inside that BaseController you can instantiate the ApplicationManager and create a method that optionally receives an ID (UserId) and returns a bool.
Which you could call in your controller like this:
if(HasRole("Owner")) {} // CurrentUser
if(HasRole(Id, "Owner")) {} // Specific User
There are other ways, but that's a developer choise.
Note
Keep in mind that if you choose to statically instantiate the ApplicationManager, it will run only once which may do things that you don't want, like adding a user to a specific role and that ApplicationManager not showing the new role unless it is created again.
I suggest below method:
public static bool isInRole(IPrincipal User, string roleName, ApplicationDbContext dbContext)
{
try
{
var store = new Microsoft.AspNet.Identity.EntityFramework.UserStore<ApplicationUser>(dbContext);
var manager = new Microsoft.AspNet.Identity.UserManager<ApplicationUser>(store);
return manager.IsInRole(User.Identity.GetUserId(), roleName);
}
catch (Exception ex)
{
return false;
}
return false;
}
I am trying to populate a view model with User data that includes a list of the user's associated roles. Currently, my method is as follows:
[EnableQuery]
[AcceptVerbs("GET")]
public IEnumerable<UserViewModel> Get()
{
var clientId = this.GetClientId();
var users = this.UserManager.Users.Where(x => x.ClientId == clientId).ProjectTo<UserViewModel>().ToList();
foreach (UserViewModel user in users)
{
user.UserRoles = this.UserManager.GetRoles(user.Id);
}
return users;
}
Is there a better way to perform this operation? I cannot create a Linq query because EF will not allow me to create a class for the AspNetUserRoles table, and it will not allow me to edit the AspNetUsers table directly - so I am using the UserManager to get the results instead.