I need to check privileges to specific field in specific object in database.
Let's make and example. I have Model called Employee
public class Employee {
[Key]
public int EmployeeID { get; set; }
public string JobTitle { get; set; }
public string Description { get; set; }
public int Salary { get; set; } // <---- Restricted
public int BossID { get; set; }
}
And I have a few cases:
I need to restrict access to specific field Salary
because I don't want anyone to see each other salary. But HR can see anyone Salary
and edit it. If I'm this employee I can see my own Salary
, but cannot edit it.
Everyone can see each other job titles, but only HR can edit it. And also boss of that employee, can edit, by employee himself cannot.
Use case:
I'm manager with RoleID 4. I want to see Salary
of my Employee
named John Smith with EmployeeID
5. I can do that.
I'm manager with RoleID 4. I want to see Salary
of 'Employeenamed Mark Twain with
EmployeeID` 8. Mark is not but my directly subordinate. He is from different branch. I cannot do that.
I'm employee with EmployeeID
5 and I want to see my Salary
. That's allowed.
I'm employee with EmployeeID
5 and I want to edit my own Salary
. It's forbidden. I get HTTP Error 401.
I'm from HR. I can see and edit Salary
of all Employees in company.
I though of something like this:
public class Access {
[Required]
public int RoleID { get; set; }
[Required]
public string TableName { get; set; }
[Required]
public string ColumnName { get; set; }
[Required]
public int RowID { get; set; }
}
And then check (by Authorize
attribute) if specific role (boss, HR or something) has access to specific field (for example Salary
) for specific data (for example Employee
with id 22). That's a lot of "specific"by the way.
How should I do it? Is my idea 'OK'?
In case when logic is less complicated or more generic, it's possible to set custom output formatter to prevent some fields to be written into the respose.
The approach has next problems:
Startup
, then it should be transferedLet's see an example. There could be a custom attrbute like
public class AuthorizePropertyAttribute : Attribute
{
public AuthorizePropertyAttribute(string role) => Role = role;
public string Role { get; set; }
}
Then output formatter could be like:
public class AuthFormatter : TextOutputFormatter
{
public AuthFormatter()
{
SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/json"));
SupportedEncodings.Add(Encoding.UTF8);
}
public override async Task WriteResponseBodyAsync(OutputFormatterWriteContext context,
Encoding selectedEncoding)
{
var settings = new JsonSerializerSettings
{
ContractResolver = new AuthorizedPropertyContractResolver(context.HttpContext.User)
};
await context.HttpContext.Response.WriteAsync(
JsonConvert.SerializeObject(context.Object, settings));
}
}
That would require
public class AuthorizedPropertyContractResolver : DefaultContractResolver
{
public AuthorizedPropertyContractResolver(ClaimsPrincipal user)
{
User = user;
}
public ClaimsPrincipal User { get; }
protected override JsonProperty CreateProperty(MemberInfo member,
MemberSerialization memberSerialization)
{
var result = base.CreateProperty(member, memberSerialization);
result.ShouldSerialize = e =>
{
var role = member.GetCustomAttribute<AuthorizePropertyAttribute>()?.Role;
return string.IsNullOrWhiteSpace(role) ? true : User.IsInRole(role);
};
return result;
}
}
Registration:
services.AddMvc(options =>
{
options.OutputFormatters.Insert(0, new AuthFormatter());
});
In that case Response for simple user will lack of the Salary field {"Id":1,"Name":"John"}
at the same time manager will see the full response
{"Id":1,"Name":"John","Salary":100000}
, ofcourse the property "Salary" should have attribute set
[AuthorizeProperty("Boss")]
public double Salary { get; set; }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With