I'm not sure i know the exact terminology for what i want to do, so i'll just describe what i want, and hopefully someone will give me the answer or direct me to the right place...
I want some (maybe all) of the models on my site to inherit from IGeneralSettings -
public interface IGeneralSettings
{
User CurrentUser { get; set; }
UserSettings PersonalSettings { get; set; }
}
and if the current user is authenticated/logged in, I will check if the current controller's model is of a type that implements IGeneralSettings, and if it is, I will fill the data with the current user data.
I am using Windsor Castle as an IoC container.
I don't want to use the property bag for this.
I don't want to call a method that will do this in each action in each controller, I want this to be injected automatically each request - meaning this code will be written once.
Is this possible ?
I would recommend adding your CurrentUser and PersonalSettings objects to a base controller and populating it each request (if they are authenticated). Then you can access them in a custom model binder because you have access to the controller context.
Example of one way to do it:
Base controller that your controllers inherit
public class BaseController : Controller
{
public BaseController()
{
//You can load the CurrentUser and Settings here
CurrentUser = new User
{
Id = 1
};
PersonalSettings = new UserSettings
{
Id = 1
};
}
public User CurrentUser { get; set; }
public UserSettings PersonalSettings { get; set; }
}
ModelBinder
public class ThingModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext,
ModelBindingContext bindingContext)
{
var model = (Thing)base.BindModel(controllerContext, bindingContext);
var baseController = controllerContext.Controller as BaseController;
if (baseController != null)
{
model.CurrentUser = baseController.CurrentUser;
model.PersonalSettings = baseController.PersonalSettings;
}
return model;
}
}
Add ModelBinder in Application_Start of Global.asax
ModelBinders.Binders.Add(typeof(Thing), new ThingModelBinder());
Controller
public class HomeController : BaseController
{
[HttpPost]
public ActionResult Save(Thing model)
{
//model has CurrentUser and PersonalSettings set
//without any values being posted.
//You can also access the values on the BaseController
//if you don't want to automatically bind it.
return View();
}
}
Another way I found in my last project:
Define a extension method for Controller, like:
public static Models.User GetUser(this Controller controller)
{
string name = controller.User.Identity.Name;
string alias = name.Substring(name.LastIndexOf('\\') + 1);
string domain = name.Substring(0, name.IndexOf('\\') - 1);
User user = User.ByAlias(alias);
if (user == null)
{
user = new User();
user.Name = controller.User.Identity.Name;
user.Alias = alias;
user.Domain = domain;
user.Description = "Automatically registered";
Guid userId = Models.User.Create(user, Guid.Empty);
user = User.ByAlias(alias);
}
return user;
}
That would not break your current class structure.
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)...
In a POST call to a WebApi I am trying to return a Created(newobject) thing. But there is no signature for Created in ApiController that can only take the object and do the rest.
It works fine if I return something like:
return Created(newobject.blahid.ToString(), newobject);
or if I do a
return CreatedAtRoute("DefaultApi", new { controller = ControllerContext.ControllerDescriptor.ControllerName, id = newobject.blahid.ToString()}, newobject);
I want to simplify this to:
return Created(newobject);
I would need to implement a method in a BaseController
public class BaseController : ApiController
{
protected new CreatedNegotiatedContentResult<T> Created<T>(T content)
{
var id = GetId(content);//need help here
return base.Created(id, content);
}
}
I don't want to worry about the Unique Identifier for an object being called differently in different models e.g. myobjguid, someblahguid etc. I would just want to find it out and mark it as "id".
say if my model is
public class Model_A
{
public List<Model_A> ChildModels { get; set; }
[LookForThisAttribute]//I want something like this
public Guid Model_AGuid { set; get; }
public Guid ? ParentGuid { set; get; }
public List<SomeOtherObject> OtherObjects { set; get; }
}
Is there an attribute([LookForThisAttribute]) or something I can set on all my models to specify that this is the guy to be assumed as THE unique identifier if I ever look for it.
Just like the [Key] attribute in Entity Framework. No matter what you call it, Entity Framework know its going to be the primary key.
So the GetId(T content) method can take the object and return the value of the property that has a [LookForThisAttribute] set?
I ended up writing my own Attribute and then looking up for it in the BaseController.
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public sealed class UniqueIdAttribute: Attribute
{
}
And in the BaseController Created method:
protected CreatedNegotiatedContentResult<T> Created<T>(T content)
{
var props =typeof(T).GetProperties().Where(
prop => Attribute.IsDefined(prop, typeof(UniqueIdAttribute)));
if (props.Count() == 0)
{
//log this
return base.Created(Request.RequestUri.ToString(), content);
}
var id = props.FirstOrDefault().GetValue(content).ToString();
return base.Created(new Uri(Request.RequestUri + id), content);
}
Mark Gravell's post here helped me with getting the value of the property that has my custom attribute:
How to get a list of properties with a given attribute?
Along with a corresponding unit test for the controllers works fine for me.
Now I can just call Created(anyobject); from all ApiControllers without bothering about the different names people put for their IDs as long as they decorate it with my custom attribute.
Update
I've managed to create something that is satisfactory. You can see the relevant parts of the code here on PasteBin. If there is there something that I could improve please let me know. I've got a nagging feeling this isn't very efficient.
Clarification
While it certainly seems to work with static dependencies as suggested by d_z, I was hoping, to avoid instantiating objects not utlilized, that I could use something similar to this:
public class HomeController : Controller
{
[Dependency]
protected IBaseData ActionData { get; set; }
public ActionResult Index()
{
return View(ActionData);
}
public ActionResult About()
{
return View(ActionData);
}
}
The data in the IndexData and AboutData instances in reality aren't static. The instance properties are set with data from a database. The DbProvider is injected into these classes.
In the end what I want is to minimize the memory footprint and database accesses.
Original
Let's say we have the following basic controller (with corresponding views):
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
public ActionResult About()
{
return View();
}
}
We also have two classes with the same interface:
public class IndexData : IBaseData
{
public string Name { get; set; }
public string Index { get; set; }
public IndexData()
{
Name = "Index";
Index = "This is an Index instance";
}
}
public class AboutData : IBaseData
{
public string Name { get; set; }
public string About { get; set; }
public AboutData()
{
Name = "About";
About = "This is an About instance";
}
}
What I'd like is for Unity to inject an instance of one of these classes, depending on which action is executed, into the corresponding view. I've tried to get my head around how to achieve this but I'm stumped. Is this even possible?
What you are requesting makes little sense. Dependency Injection is about injecting (design time) behavior (a.k.a. services). What you are trying to do however is to inject runtime data. So this is not a task that yout IoC container should handle.
Next the view should be completely ignorant of any dependency injection. The controller should return all data that the view needs from its action method. Make sure that your About and Index action methods return the proper instance.
To register several mappings for a type in Unity you have to create named registration like this:
myContainer.RegisterType<IBaseData, IndexData>("Index");
myContainer.RegisterType<IBaseData, AboutData>("About");
So after this in your actions you can resolve an instance accordingly:
Index:
IBaseData data = myContainer.Resolve<IBaseData>("Index");
About:
IBaseData data = myContainer.Resolve<IBaseData>("About");
Or for static dependencies it works like this:
[Dependency("Index")]
IBaseData data { get; set; }
Take a look here and here for details
I have a logging controller in my project that gets Log data from javascript client side and sends to server.
Log Model ils like this.
public class Log
{
public string Event { get; set; }
public string Message { get; set; }
public DateTime Date { get; set; }
public string Username { get; set; }
}
My controller action is like this.
[HttpPost]
public JsonResult Save(Log log)
{
log.Username = User.Identity.Name;
log.Date = DateTime.now;
return Json(new { message = "ok" }, JsonRequestBehavior.AllowGet);
}
when users sended the log data, I need to set the username and date. Is there another way with model binding or else? Automatically set the context.username and date time of log.
IModelBinder is the preferred way to go. You implement the interface and register it in Global.asax file
ModelBinders.Binders.Add(typeof(Log), new LogModelBinder());
What I don't like about this approach is that you have to be aware that there is a custom model binder for this type implemented somewhere and it can cause confusion if you forget about it, or you have a new developer working on the project and model starts behaving strange. It is just not obvious enough what is changing the object.
I like to have all my custom logic implemented in the controller itself. You can override OnActionExecuting and inject the properties there, better yet, have a BaseController that all your controllers inherit from and do your custom logic in there.
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
object model = filterContext.ActionParameters
.TryGetValue("model", out model)
? model : null;
if (model != null)
{
var logModel = model as Log;
if (logModel != null)
{
logModel.Date = DateTime.Now;
logModel.Username = filterContext.HttpContext.User.Identity.Name;
}
}
}
We're always told that a Controller should be skinny and that validation should be done in the Model, not the Controller. But consider the following example.
Here's is a simple Model and Controller for handling the POST from an edit screen, on which we can edit a Person object.
public class PersonEditModel
{
[Required(ErrorMessage = "No ID Passed")]
public int ID { get; set; }
[Required(ErrorMessage = "First name Required")]
[StringLength(50,ErrorMessage = "Must be under 50 characters")]
public string FirstName { get; set; }
[Required(ErrorMessage = "Last name Required")]
[StringLength(50,ErrorMessage = "Must be under 50 characters")]
public string LastName { get; set; }
}
public class PersonController : Controller
{
// [HttpGet]View, [HttpGet]Edit Controller methods omitted for brevity
[HttpPost]
public ActionResult Edit(PersonEditModel model)
{
// save changes to the record
return RedirectToAction("View", "Person", new { ID = model.ID});
}
}
The Model performs two kinds of validation here. It validates FirstName and LastName, but it also validates the private key (ID) used to access the record we wish to change. Should this validation be done in the Model also?
What if we then want to expand validation (as we should) to include a check to see if this record exists?
Normally, I would validate this in the controller:
[HttpPost]
public ActionResult Edit(PersonEditModel model)
{
using(DatabaseContext db = new DatabaseContext())
{
var _person = db.Persons.Where(x => x.ID == model.ID);
if(_person == null)
{
ModelState.AddError("This person does not exist!");
// not sure how we got here, malicious post maybe. Who knows.
// so since the ID is invalid, we return the user to the Person List
return RedirectToAction("List", Person");
}
// save changes
}
// if we got here, everything likely worked out fine
return RedirectToAction("View", "Person", new { ID = model.ID});
}
Is this bad practice? Should I be checking if the record exists in some kind of complex custom validation method in the model? Should I put it somewhere else entirely?
UPDATE
On a related note. Should a ViewModel contain the methods to populate the data?
Which of these is better practice - this
public class PersonViewModel
{
public Person person { get; set; }
public PersonViewModel(int ID){
using(DatabaseContext db = new DatabaseContext())
{
this.person = db.Persons.Where(x => x.ID == ID);
}
}
}
[HttpPost]
public ActionResult View(int ID)
{
return View("View", new PersonViewModel(ID));
}
Or this?
public class PersonViewModel
{
public Person person { get; set; }
}
[HttpPost]
public ActionResult View(int ID)
{
PersonViewModel model = new PersonViewModel();
using(DatabaseContext db = new DatabaseContext())
{
model.person = db.Persons.Where(x => x.ID == ID);
}
return View("View", model);
}
I generally prefer FluentValidation for all purposes. It also has a Nuget to install it out-of the box in VS.
Sample Validation Code from here:
using FluentValidation;
public class CustomerValidator: AbstractValidator<Customer> {
public CustomerValidator() {
RuleFor(customer => customer.Surname).NotEmpty();
RuleFor(customer => customer.Forename).NotEmpty().WithMessage("Please specify a first name");
RuleFor(customer => customer.Discount).NotEqual(0).When(customer => customer.HasDiscount);
RuleFor(customer => customer.Address).Length(20, 250);
RuleFor(customer => customer.Postcode).Must(BeAValidPostcode).WithMessage("Please specify a valid postcode");
}
private bool BeAValidPostcode(string postcode) {
// custom postcode validating logic goes here
}
}
Customer customer = new Customer();
CustomerValidator validator = new CustomerValidator();
ValidationResult results = validator.Validate(customer);
bool validationSucceeded = results.IsValid;
IList<ValidationFailure> failures = results.Errors;
See?? It is very easy to validate any kind of models with Fluent Validation with clean methods. You can consider going through FluentValidation Documentation.
Where to validate?
Suppose you have a model as below:
public class Category
{
public int ID { get; set; }
public string Name { get; set; }
virtual public ICollection<Image> Images { get; set; }
}
then, you will define another validator model in a similar class library or preferably a new class library that handles validation for all the models in the project.
public class CategoryValidator : AbstractValidator<Category>
{
public CategoryValidator()
{
RuleFor(x => x.Name).NotEmpty().WithMessage("Category name is required.");
}
}
So, you can do it in a separate validator model keeping your methods and domain models as clean as possible.
When we talk about Model, it includes your DAL and your business layer. For small apps or demos it is not unusual to see that kind of code in a controller, but normally you should give that role to the business or data layer :
[HttpPost]
public ActionResult Edit(PersonEditModel model)
{
// Validation round one, using attributes defined on your properties
// The model binder checks for you if required fields are submitted, with correct length
if(ModelState.IsValid)
{
// Validation round two, we push our model to the business layer
var errorMessage = this.personService.Update(model);
// some error has returned from the business layer
if(!string.IsNullOrEmpty(errorMessage))
{
// Error is added to be displayed to the user
ModelState.AddModelError(errorMessage);
}
else
{
// Update successfull
return RedirectToAction("View", "Person", new { ID = model.ID});
}
}
// Back to our form with current model values, as they're still in the ModelState
return View();
}
Here the goal is to free the controller from business logic validation and usage of the data context. It pushes submitted data and is notified if errors occurred. I used a string variable, but you can implement error management as you like. Evolving your business rules won't impact your controller at all.
There's absolutely nothing wrong with this. Your controllers are responsible for directing the flow of control when it comes to which view to be shown to your users. Part of doing that is ensuring that a view is getting a model that is in a usable state.
The controller doesn't care what the model is, or what the model contains, but it does care that it's valid. It's why ModelState.IsValid is so important, because the controller doesn't have to know how that validation is performed or what directly makes a model valid. Typically, any validation that needs to take place after ModelState.IsValid, can be pushed to another layer of your application, which again enforces separation-of-concerns.