Access Control

Juan A. Reyes Paulino 20 Reputation points
2023-03-23T12:15:43.6866667+00:00

Good morning, I am working on a .NET Core 6, SQL Server project and I need some idea of how to implement permissions by fields, let me explain: in a form, for example, that has [Name, Surname, Date, Address, Gender] to be able to assign in an access screen the next.

Field Can View Can Create Can Edit
Name x x
Surname x x x
Date x
Address
Gender x

And then apply it to the Razor frontend

Thanks in advance

.NET
.NET
Microsoft Technologies based on the .NET software framework.
3,415 questions
ASP.NET Core
ASP.NET Core
A set of technologies in the .NET Framework for building web applications and XML web services.
4,207 questions
ASP.NET
ASP.NET
A set of technologies in the .NET Framework for building web applications and XML web services.
3,288 questions
{count} votes

Accepted answer
  1. Zhi Lv - MSFT 32,021 Reputation points Microsoft Vendor
    2023-03-29T09:40:25.79+00:00

    Hi @Juan A. Reyes Paulino

    You can use GetType() and GetProperties() method to get all properties from the Model, then store the Property Name and the permission in the database, when display the Model data, you can query the Property Permission table to decide whether display the property or not.

    For example, I create an Employee Model with the following property, then add the Properties and Permission class to store the Employee's properties and the permissions.

        public class Employee
        {
            [Key]
            public int EmpId { get; set; }
            public string Name { get; set; }
            public string Surname { get; set; }
            public DateTime Date { get; set; }
            public string Address { get; set; }
            public string Gender { get; set; }
        }
    
        //store all properties and the status.
        public class Properties
        {
            public int Id { get; set; }
            public string PropertyName { get; set; } 
            public bool HasPermission { get; set; } //use this property to enable or disable the permission.
    
            [ForeignKey("Employee")]
            public int EmpId { get; set; } 
            public Employee Employee { get; set; }
    
    
            [ForeignKey("Permission")]
            public int PermissionId { get; set; }
            public Permission Permission { get; set; }
        }
    
        //store read, create, edit, audit, print, etc.
        public class Permission
        {
            public int PermissionId { get; set; }
            public string Name { get; set; }
        }
    

    Then, enable migration to generate the relate table in the database, and use the following code to get the Employee's properties and then insert them into database:

                var employee = new Employee()
                {
                    Name="Tom",
                    Surname="D",
                    Address="Address a",
                    Gender="Male",
                    Date=DateTime.Now
                };
    
    
                var permissionlist = new List<Permission>()
                {
                    new Permission(){ Name = "CanCreate"},
                    new Permission(){ Name = "CanView"},
                    new Permission(){ Name = "CanEdit"},
                };
    
    
                var propertylist = employee.GetType().GetProperties();
    
                var proList = new List<Properties>();
                foreach (var item in propertylist)
                {
                    foreach (var per in permissionlist)
                    {
                        var newpro = new Properties()
                        {
                            Employee = employee,
                            PropertyName = item.Name,
                            Permission = per
                        };
                        proList.Add(newpro);
                    }
                }
    
                _dbContext.Employees.Add(employee);
                _dbContext.Permissions.AddRange(permissionlist);
                _dbContext.Properties.AddRange(proList);
                _dbContext.SaveChanges();
    

    After that, create a PropertyControlController as below:

        public class PropertyControlController : Controller
        {
            private readonly ApplicationDbContext _dbContext;
    
            public PropertyControlController(ApplicationDbContext applicationDbContext)
            {
                _dbContext=applicationDbContext;
            }
    
            public IActionResult Index()
            {
                var currentuserId = 1001;
    
                var result = _dbContext.Properties.Include(c => c.Permission)
                    .Where(c => c.EmpId == currentuserId).GroupBy(c=>c.PropertyName)
                    .Select(c => new PropertyViewModel()
                    {
                        EmployeeId = currentuserId, 
                        PropertyName = c.Key,
                        Permissions = c.Select(p=> new PermissionViewModel()
                        {
                             PermissionName = p.Permission.Name,
                              HasPermission = p.HasPermission
                        }).ToList(),
                    }).ToList();
    
                return View(result);
            }
    
            [HttpPost]
            public IActionResult Index(List<PropertyViewModel> properties)
            {
                if (ModelState.IsValid)
                {
                    foreach (var item in properties)
                    { 
                        foreach(var p in item.Permissions)
                        { 
                            var pro = _dbContext.Properties.
                                Where(c => c.PropertyName == item.PropertyName && c.EmpId == item.EmployeeId && c.Permission.Name == p.PermissionName)
                                .FirstOrDefault();
                            pro.HasPermission = p.HasPermission;
                        } 
                    } 
                    _dbContext.SaveChanges();
                }
              
                return RedirectToAction(nameof(Index), "Employee");
            }
        }
    

    And the Index page as below:

        @model List<WebApplication3.Models.PropertyViewModel>
    
        @{
            ViewData["Title"] = "Index";
        }
    
        <form asp-action="Index" method="post">
    
            @if (Model !=null && Model.Count>1)
            { 
                <table class="table"> 
                    @{
                        var i = 0;
                        var permissioncount = Model.First().Permissions.Count;
                    }
                    <thead> 
                        <tr>
                            <th>                        
                            </th>
                            @foreach (var ths in Model.First().Permissions.Select(c=>c.PermissionName).ToList())
                            {
                               <th>
                                   @ths
                               </th>
                            }
                        </tr>
                    </thead>
                    <tbody>
                        @foreach (var item in Model)
                        {
                            <tr> 
                                <td>
                                    @Html.DisplayFor(modelItem => item.PropertyName)
                                    <input type="hidden" name="[@i].PropertyName" value="@Model[i].PropertyName" />
                                    <input type="hidden" name="[@i].EmployeeId" value="@Model[i].EmployeeId" />
                                </td>
                                @for (var j = 0; j< permissioncount; j++)
                                {
                                    <td>
                                        <input type="checkbox" asp-for="@Model[i].Permissions[j].HasPermission" class="form-check-input" />
                                        <input type="hidden" asp-for="@Model[i].Permissions[j].PermissionName" />
                                    </td>
                                }
    
                            </tr>
                            i++;
                        }
                    </tbody>
                    <tfoot>
                        <tr>
                            <td colspan="5">
                                <input type="submit" value="Save" class="btn btn-primary" />
                            </td>
                        </tr>
                    </tfoot>
                    </table>
            }
        </form>
    

    The Employee Controller: In the Index page, View the Employee information based on the permission.

        public class EmployeeController : Controller
        {
            private readonly ApplicationDbContext _dbcontext;
    
            public EmployeeController(ApplicationDbContext applicationDbContext) {
                _dbcontext=applicationDbContext;
            }
            public IActionResult Index()
            {
                var currentuserId = 1001;
                var pagestatus = "CanView";
                var items = _dbcontext.Employees.ToList().Where(c => c.EmpId==currentuserId)
                    .GroupJoin(_dbcontext.Properties.Include(p =>p.Permission).Where(p=>p.Permission.Name == pagestatus).ToList(),
                u => u.EmpId,
                p => p.EmpId,
                    (u, p) => new EmployeeViewModel { Emp = u, Properties = p.ToList() })
                    .FirstOrDefault();
    
                return View(items);  
                return View();
            }
        }
    

    The Index page:

        @model WebApplication3.Models.EmployeeViewModel
    
        @{
            ViewData["Title"] = "Index";
        }
    
        <h1>Index</h1>
    
        <div>
            <h4>Employee</h4>
            <hr />
            <dl class="row">
                @foreach (var property in Model.Properties.ToList())
                {
                    if (property.HasPermission)
                    {
                        <dt class="col-sm-2">
                            @property.PropertyName
                        </dt>
                        <dd class="col-sm-10">
                            @Model.Emp?.GetType().GetProperty(property.PropertyName)?.GetValue(Model.Emp)
                        </dd>
                    }
                }
            </dl>
        </div>
        <div>
            @Html.ActionLink("Edit", "Edit", new { /* id = Model.PrimaryKey */ }) |
            <a asp-action="Index">Back to List</a>
        </div>
    
    

    The view model:

        public class EmployeeViewModel
        {
            public Employee Emp { get; set; }
            public List<Properties> Properties { get; set; } 
        }
         
        public class PropertyViewModel
        {
            public int EmployeeId { get; set; }
            public string PropertyName { get; set; }
    
            public List<PermissionViewModel> Permissions { get; set; }
        }
    
        public class PermissionViewModel
        { 
            public string PermissionName { get; set; }
            public bool HasPermission { get; set; }
        }
    

    The result as below:

    image2


    If the answer is the right solution, please click "Accept Answer" and kindly upvote it. If you have extra questions about this answer, please click "Comment".

    Note: Please follow the steps in our documentation to enable e-mail notifications if you want to receive the related email notification for this thread.

    Best regards,

    Dillion

    1 person found this answer helpful.
    0 comments No comments

1 additional answer

Sort by: Most helpful
  1. Bruce (SqlWork.com) 56,931 Reputation points
    2023-03-23T18:49:59.4966667+00:00

    one option is to use sqlserver built in support for column permissions and the execute as statement to run the query as the actual user.

    the other option is the app implements the access control. if the number of roles are small, then you could use attributes on the model fields.

    public class MyModel
    {
        [AccessRead(Role="ClientRead")
        [AccessCreate(Role="ClientCreate")
        [AccessEdit(Role="ClientEdit")
        public string Name {get; set;}
    
        [AccessRead(Role="ClientRead")
        [AccessCreate(Role="ClientCreate")
        [AccessEdit(Role="ClientEdit")
        public string Surname {get; set;}
    
    

    you could then make tag helpers that implement the generic role support of visible and readonly.

    your could also use the attribute roles when mapping the view model to the database model.