In the system I am building there is a complex and continuously changing resource-based authorization. There is a total of six roles at the moment.
The system is handling members where all members can edit basic info on their own profile, another person in another role can edit even more info on their profile and so on.
I cannot figure out which is the best way to design this with endpoints / actions for posts like the edit member action. What I ended up doing, but dislike, is that each role has one controller action, view and view model. The main reason for doing this instead of having one view model is that I felt it did not make sense to have all the properties that someone cannot even edit, that's over-posting right?
I am not quite happy with the result. 6 view models, 6 views, 6 madly similar controller actions, 6 validators etc.
My idea now is that I will just have one edit action and then have a bunch of if statements when mapping back to the domain object, in the view and on the validator classes. The overposting is still there but managed with if statements. I'm also thinking like this - what if the system would become an API? api/members/1/edit/ makes more sense than api/members/1/editAsTreasurer?
What do you think? Anyone has another solution I have not thought of?
Some code parts, example of duplicated code, of course there's more in validator classes, views, and mapping, not sure how much to include:
[HttpPost]
public IActionResult EditAsSecretary(EditMemberAsSecretaryViewModel viewModel)
{
if (!ModelState.IsValid)
{
viewModel.Init(_basicDataProvider, _authorizationProvider.GetAuthorizedLogesForManageMember());
return View("EditAsSecretary", viewModel);
}
var member = _unitOfWork.Members.GetByMemberNumber(viewModel.MemberNumber, true);
if (member == null) return NotFound();
// Authorize
if (!_authorizationProvider.Authorize(viewModel.MemberInfo.LogeId, AdminType.Sekreterare))
return Forbid();
var user = _unitOfWork.Members.GetByUserName(User.Identity.Name);
var finallyEmail = viewModel.MemberContactInfo.Email != null && member.Email == null &&
!member.HasBeenSentResetPasswordMail && member.MemberNumber != user.MemberNumber;
_domainLogger.UpdateLog(viewModel, member, user);
UpdateMember(viewModel, member, user.Id);
_unitOfWork.Complete();
if (finallyEmail) SendUserResetPasswordMail(member).Wait();
TempData["Message"] = "Member has been updated.";
return RedirectToAction("Details", "Members", new { memberNumber = member.MemberNumber });
}
[HttpPost]
public IActionResult EditAsManager(EditMemberAsManagerViewModel viewModel)
{
if (!ModelState.IsValid)
{
viewModel.Init(_basicDataProvider, _authorizationProvider.GetAuthorizedLogesForManageMember());
return View("EditAsManager", viewModel);
}
var member = _unitOfWork.Members.GetByMemberNumber(viewModel.MemberNumber, true);
if (member == null) return NotFound();
// Authorize
if (!_authorizationProvider.Authorize(member.LogeId, AdminType.Manager))
return Forbid();
var user = _unitOfWork.Members.GetByUserName(User.Identity.Name);
var finallyEmail = viewModel.MemberContactInfo.Email != null && member.Email == null &&
!member.HasBeenSentResetPasswordMail && member.MemberNumber != user.MemberNumber;
_domainLogger.UpdateLog(viewModel, member, user);
UpdateMember(viewModel, member, user.Id);
_unitOfWork.Complete();
if (finallyEmail) SendUserResetPasswordMail(member).Wait();
TempData["Message"] = "Member has been updated.";
return RedirectToAction("Details", "Members", new { memberNumber = member.MemberNumber });
}
private void UpdateMember(EditMemberAsSecretaryViewModel viewModel, Member member, string userId)
{
_mapper.Map(viewModel, member);
MapGodfathers(viewModel.MemberInfo, member);
AfterUpdateMember(member, userId);
_userManager.UpdateNormalizedEmailAsync(member).Wait();
}
private void UpdateMember(EditMemberAsManagerViewModel viewModel, Member member, string userId)
{
_mapper.Map(viewModel, member);
MapGodfathers(viewModel.MemberInfo, member);
AfterUpdateMember(member, userId);
_userManager.UpdateNormalizedEmailAsync(member).Wait();
}
My idea now is that I will just have one edit action and then have a bunch of if statements when mapping back to the domain object, in the view and on the validator classes. The overposting is still there but managed with if statements
Don't.
Besides making the code much less readable, it also poses a security risk. Every Action should take as little parameters as it needs. It does not cost you anything to have more Actions so there is no reason for doing that.
There are some issues though with your code, that help in that duplication:
You seem to be making security validations against what you receive from the user, instead of using the currently authenticated user. This is a big issue, as you are trusting the data that comes from a user.
Instead of that, create a custom(s) Authorization Policy that checks for the type of user using your business logic. Those can be then added to the built-in container and you can use:
[Authorize(Policy = "EnsureManager")]
public IActionResult EditAsManager(...)
This would allow you to remove all that duplicated code and be closer to the SRP.
Your duplicated UpdateMember looks like your models are unrelated. In a case like this, it would be far better to have a base model and then children with the required properties:
public abstract class EditMemberBaseViewModel
{
[Required]
public Something Something { get; set; }
}
public class EditMemberAsSecretaryViewModel : EditMemberBaseViewModel
{
[Required]
public AnotherThing AnotherThing { get; set; }
}
That would allow you to have a single UpdateMember since the logic is based on EditMemberBaseViewModel and not their children, as far as you have shown that is:
private void UpdateMember(EditMemberAsManagerViewModel viewModel, Member member, string userId)
{
_mapper.Map(viewModel, member);
MapGodfathers(viewModel.MemberInfo, member);
AfterUpdateMember(member, userId);
_userManager.UpdateNormalizedEmailAsync(member).Wait();
}
As a last and probably most important point, there is a problem with this code:
_userManager.UpdateNormalizedEmailAsync(member).Wait();
That's really bad. You are making ASP.NET Core hang an entire thread waiting for that action to complete. That's synchronous, 2000s code.
You need to learn to use asynchronous code for every IO-related operation (like database calls) in your application, otherwise performance will suffer lots. As an example:
public async Task<IActionResult> EditAsManager(...)
{
.....
await UpdateMemberAsync(...);
}
public async Task UpdateMemberAsync(...)
{
await _userManager.UpdateNormalizedEmailAsync(member);
}
Related
I am using https://learn.microsoft.com/en-us/aspnet/core/tutorials/first-web-api?view=aspnetcore-2.2&tabs=visual-studio as a reference and am interested in the way updates should be handled.
Given the following code for an update method:
public class Todo
{
public int Id { get; set; }
public string Title { get; set; }
public bool Completed { get; set; }
}
public class UpdateTodoRequest
{
public string Title { get; set; }
public bool Completed { get; set; }
}
public async Task<ActionResult> UpdateAsync([FromRoute] id, UpdateTodoRequest todoUpdateRequest)
{
if (todo == null)
{
return BadRequest();
}
var result = await _todoService.UpdateAsync(id, todoUpdateRequest);
// Here I can have three possible results
// 1. Todo with that Id was not found - 404
// 2. Todo didn't pass validation rules - 400 (together with list of rules that didn't pass)
// 3. Everything was successful - 204
}
How should (can) I model _todoService.UpdateAsync method signature so that it could handle all of three cases I've listed?
I could refactor that as follows:
public async Task<ActionResult> UpdateAsync([FromRoute] id, UpdateTodoRequest todoUpdateRequest)
{
if (todo == null)
{
return BadRequest();
}
// Getting the whole Todo by id coming from route
var todo = await _todoService.GetById(id);
if (todo == null)
{
// Returning 404 here
return NotFound();
}
// Updating values coming from request to todo taken from service
todo.Title = todoUpdateRequest.Title;
todo.Completed = todo.Completed;
// Result = boolean for success/failure
var result = await _todoService.UpdateAsync(todo);
return result ? NoContent() : BadRequest();
}
In this version I can almost handle cases I wanted:
I can get 404 if there's no todo with that Id
I can return 400 for validation issues (HOWEVER without validation criteria)
I can get 204 if update was successful
So how could I model my service so that controller could handle all of these three cases above? Also I'm open to any kind of suggestions to do things differently or links to SO questions where this has been discussed to death.
I've also thought that it would make sense to use a Either Monad from functional world: https://mikhail.io/2016/01/validation-with-either-data-type-in-csharp/ to indicate success/validation errors and return correct response type based on its' result.
Because there are multiple return types and paths in the action, liberal use of the [ProducesResponseType] attribute is necessary. This attribute produces more descriptive response details for API help pages generated by tools like Swagger. [ProducesResponseType] indicates the known types and HTTP status codes to be returned by the action. Refer to here.
You could use below code to return three kinds of responses' type.
[HttpPut("{id}")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult> UpdateAsync([FromRoute] id, UpdateTodoRequest todoUpdateRequest)
As I'm in the progress of learning ASP.NET MVC, I ran into a question and into some trouble
I'm trying to create a simple blog, just to test out what I have learned so far. But when it comes to editing and leaving a field i run into a problem.
I'm trying to edit an already submitted post on my blog, the post contains few fields: Id, Headline, Message, Author and Date for the submission which should not be edited, just left as it is.
Here is some code:
My post model:
namespace MyBlock.Models
{
public class Post
{
public int Id { get; set; }
[Required]
public string Author { get; set; }
[Required]
public string Headline { get; set; }
[Required]
public string Message { get; set; }
public DateTime Date { get; set; }
}
}
My edit:
[HttpGet]
public ActionResult Edit(int id = 0)
{
Post post = db.Posts.Find(id);
if (post != null) {
return View(post);
}
return HttpNotFound();
}
[HttpPost]
public ActionResult Edit(Post post)
{
if (ModelState.IsValid) {
db.Entry(post).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index", "Home");
}
return View(post);
}
And my view for edit:
#model MyBlock.Models.Post
#{
ViewBag.Title = "Edit";
}
<h2>Rediger "#Model.Headline"</h2>
#using (Html.BeginForm()) {
#Html.LabelFor(u => u.Author)
#Html.TextBoxFor(u => u.Author)
#Html.LabelFor(u => u.Headline)
#Html.TextBoxFor(u => u.Headline)
#Html.LabelFor(u => u.Message)
#Html.TextAreaFor(u => u.Message)
<input type="submit" value="Gem" />
}
I know I could throw in a #HiddenFor(u => u.Date) and the same date would be submitted. But I bet there is another way than having it as a hidden field in the source code? I mean this isn't that secure in another example? So I want something else than hidden field here. Can you guys help me out?
If I try to run this as it is. I'm getting an error which is my Date isn't set, which is logic because it want to update that one aswell. But I dont want it to. I want to leave it optional if you could say that.
Don't take candy from strangers
In other words, don't take the information from the client and directly update the DB. You should enforce your business rules on the server side and not trust the client to do it for you.
[HttpPost]
public ActionResult Edit(Post post)
{
if (ModelState.IsValid) {
var dbPost = db.Posts.FirstOrDefault(p => p.Id == post.Id);
if (dbPost == null)
{
return HttpNotFound();
}
dbPost.Author = post.Author;
dbPost.Message = post.Message;
dbPost.Headline = post.Headline;
db.SaveChanges();
return RedirectToAction("Index", "Home");
}
return View(post);
}
[HttpPost]
public ActionResult Add(Post post)
{
if (ModelState.IsValid) {
var dbPost = db.Create<Post>();
dbPost.Author = post.Author;
dbPost.Message = post.Message;
dbPost.Headline = post.Headline;
dbPost.Date = DateTime.Now(); // Don't trust client to send current date
db.SaveChanges();
return RedirectToAction("Index", "Home");
}
return View(post);
}
In my own project I enforce rules like this at the domain layer by adding custom validation rules to the ValidateEntity method.
DateTime is a value type, and cannot be null. Thus, it can never be optional.
You need to make a it a nullable type. ie.
public DateTime? Date {get;set;}
In general, most value types in a ViewModel should be nullable, then you use Required attributes to enforce that they contain a value. This allows you to tell whether they failed to enter a value, or whether it's a default value.
In your controller, you can then check if the Date has a value with Date.HasValue and if so, then save the date.
In regards to security, in this case it's not raelly an issue. Assuming someone has access to the page (they pass authorization) and they have the right to update the date, then it doesn't matter if the user can bypass it. All they can do is submit a valid date format. Unless you want to add logic to ensure that the date is within a specific time period, then you don't have to worry. The ModelBinder will not bind to a non-valid date format.
If you want to control whether the user can update the date, say based on role, then you could add logic to your controller to check if the date has a value and the user is in the correct role, otherwise issue an error.
UPDATE:
I think the easiest solution here is to do two things. The first is to make Date nullable, as I mention above. Although this is not strictly necessary if you do not have a form field for Date in your view, if you were to add a form field later then you would get a validation error if you left the textbox empty. I like to prevent future errors from occurring if possible. Also, should someone be posting values to your Edit action manually, and they include a blank Date field, it will fail to validate, rather than simply ignore it. Making the value nullable allows the value to be completely ignored regardless of its value.
Second, is do what #p.s.w.g suggests, and only update the fields that you want updated. Retrieve the post from the database, then update all fields except Id and Date. Then call SaveChanges().
Just my 2cents here. I know this is a simple situation and the answer given is nice and straightforward. But as that list of attributes grows then it could get difficult.
So a different approuch would be along these lines
var t = _db.Blog.Where(x => x.ID == id).FirstOrDefault();
var info = typeof(Blog).GetProperties();
//properties you don't want to update
var properties = info.Where(x => x.Name != "xxx" && x.Name != "xxxx").ToList();
foreach(var p in properties)
{
p.SetValue(t, p.GetValue(temp.Volunteer));
}
_db.Entry(t).State = EntityState.Modified;
_db.SaveChanges();
But if you are just doing a few fields then the above makes sense.
Just use your noggin!
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.).
Just curious if someone can shed some light on if this is a good practice or not?
Currently I am working on a C# project that performs and Inserts a record and runs through 4 or 5 methods to validate that the record can be added and returns a string that tells the presentation layer if the record has been submitted or not.
Is this a good practice? Pros/Cons?
The call from the presentation is:
protected void btnProduct_Click(object sender, EventArgs e)
{
lblProduct.Text = ProductBLL.CreateProduct(txtProductType.Text, txtProduct.Text, Convert.ToInt32(txtID.Text);
}
The BLL method is:
public class AccountBLL
{
// Create The Product w/ all rules validated
public static string CreateProduct(string productType, string product, int id)
{
// CHECK IF PRODUCT NAME IN DB
else if (ValidateIfProductNameExists(product) == true)
{
return "Invalid Product Name";
}
// CHECK IF 50 PRODUCTS CREATED
else if (ValidateProductCount(id) == true)
{
return "Max # of Products created Can't add Product";
}
// CHECK IF PRODUCT TYPE CREATED
else if (ValidateProductType(productType) == false)
{
return "No Product Type Created";
}
// NOW ADD PRODUCT
InsertProduct(productType, product,id);
return "Product Created Successfully";
}
As mentioned in the previous post, use Enum types.
Below is a sample code that could be used in your application.
public struct Result
{
public Result(ActionType action, Boolean success, ErrorType error) :
this()
{
this.Action = action;
this.HasSuceeded = success;
this.Error = error;
}
public ActionType Action { get; private set; }
public Boolean HasSuceeded { get; private set; }
public ErrorType Error { get; private set; }
}
public enum ErrorType
{
InvalidProductName, InvalidProductType, MaxProductLimitExceeded, None,
InvalidCategoryName // and so on
}
public enum ActionType
{
CreateProduct, UpdateProduct, DeleteProduct, AddCustomer // and so on
}
public class ProductBLL
{
public Result CreateProduct(String type, String name, Int32 id)
{
Boolean success = false;
// try to create the product
// and set the result appropriately
// could create the product without errors?
success = true;
return new Result(ActionType.CreateProduct, success, ErrorType.None);
}
}
Don't use hardcoded strings.
Use an Enum for the return value, you can do much more and more efficiently with enums.
Validations must be done, only thing you can improve is to put the whole validation process in a single method.
After you call the method, you can have a single if sentence in the main method to check the enum returned.
if (IsValidated(productType, product,id) == MyEnumType.Success) { }
I'd use exceptions rather than a string or a enum...
I would recommend looking at the Validation framework used by Imar Spaanjaar in his N-Layer architecture series. The framework he uses if very versatile and it even supports Localization through using Resource files for the validation strings.
It is not a best practice to return a string with the status of the method.
The main reason is that it violates the separation of concerns between the UI layer and the business layer. You've taken the time to separate out the business logic into its own business layer; that's a good thing. But now the business layer is basically returning the error message directly to the UI. The error message to display to the user should be determined by the UI layer.
With the current implementation the business layer also becomes hard to use (for anyone without explicit knowledge of the implementation) because there is no contract. The current contract is that the method will return a string that you should display to the user. This approach makes reuse difficult. Two common scenarios that could cause headaches are if you want to support a new language (localization) or if you want to expose some of these business methods as a service.
I've been bitten when trying to use some old code like this before. The scenario is that I want to reuse the method because it does exactly what I want but that I want to take some action if a specific error occurs. In this case you end up either rewriting the business logic (which is sometimes not possible) or you end up having to hard code a horrible if statement into your application. e.g.
if (ProductBLL.CreateProduct(productType, product, ID) ==
"Max # of Products created Can't add Product")
{
...
}
Then a requirement comes down that the message should be changed to something different ("The maximum number of products has been exceeded. Please add less products and try again."). This will break the above code. In production. On a Saturday night.
So in summary: don't do it.
This is a very hard to explain question and I hope my code extract explains most of it.
Let's say you have the following database design:
musicstyle relations http://img190.yfrog.com/img190/2080/musicstylerelations.jpg
And you want to build one generic interface to modify the musicstyle relations between all three entities. Currently I have created a MusicStyleController which requires the type of Entity it is related to (Member, Event or Band).
[AcceptVerbs(HttpVerbs.Post)]
public JsonResult DeleteMusicStyle(int id, string type, int typeid)
{
if (!(Session["MemberLoggedIn"] is Member)) return Json(string.Empty);
Member member = (Member)Session["MemberLoggedIn"];
switch (type) {
case "member":
_memberService.DeleteMusicStyle(member, id);
break;
case "band":
Band band = _bandService.GetBand(typeid);
_bandService.DeleteMusicStyle(band, id);
break;
case "event":
Event #event = _eventService.GetEvent(typeid);
_bandService.DeleteMusicStyle(#event, id);
break;
}
return SelectedMusicStyles();
}
I make myself sick writing such code, but can't find another, more elegant way.
Note that this function is called using jquery.post().
The question
How would you refactor this code, and would you normalize the database even more? Keep in mind that I'm using the Entity Framework as a data model.
Assuming that id represents the member's id, you could create 3 separate functions to handle each type, thus separating your concerns more than they are now.
Example:
[AcceptVerbs(HttpVerbs.Post)]
public JsonResult DeleteMusicStyleByMember(int id)
{
if (!(Session["MemberLoggedIn"] is Member)) return Json(string.Empty);
Member member = (Member)Session["MemberLoggedIn"];
_memberService.DeleteMusicStyle(member, id);
return SelectedMusicStyles();
}
[AcceptVerbs(HttpVerbs.Post)]
public JsonResult DeleteMusicStyleByBand(int id, int typeid)
{
Band band = _bandService.GetBand(typeid);
_bandService.DeleteMusicStyle(band, id);
return SelectedMusicStyles();
}
[AcceptVerbs(HttpVerbs.Post)]
public JsonResult DeleteMusicStyleByEvent
(int id, int typeid)
{
Event event = _eventService.GetEvent(typeid);
_bandService.DeleteMusicStyle(event, id);
return SelectedMusicStyles();
}
Then you would just modify your jquery post to go to the respective methods depending on what you're trying to do.
How would you refactor this code?
1) The code which checks the user is logged in should be moved:
if (!(Session["MemberLoggedIn"] is Member)) return Json(string.Empty);
Member member = (Member)Session["MemberLoggedIn"];
This is a cross cutting concern, which should be applied using a security framework, Spring pops to mind as an example.
2) I would avoid using a singleton pattern to represent this use-cases, they can quickly turn into a collection of scripts which when grow large can be difficult to know where to place code. Consider using the Command Pattern instead.
This pattern will allow you to return the results as JSON, XML or any other format based on the interfaces you which your command to conform too.
class DeleteMusicStyleByBandCommand : JsonResultModelCommand, XmlResultModelCommand {
public DeleteMusicStyleByBand(int id, int typeid) {
//set private members
}
public void execute() {
..
}
public JsonResult getJsonResult() { .. }
public XmlResult getXmlResult() { .. }
}
The Command pattern IMHO is much better at representing use-cases than many methods in a Service..