Jhipster, prevent user to update entities created by other users - spring-security

I have been implementing Jhipster at my work and loving it. I was asked to implement a security validation that one user should not be allowed to edit the entity created by other user. For this I need two things:
First, in all entities, add a ManytoOne relation with User entity.
In Backend put a validation in controller while updating the entity to check if current logged in user is same as what is stored in DB. In front end also same logic to show/hide edit button.
I have done a POC for this and it works but is little ugly, check the code:
public ResponseEntity<Entry> updateEntry(#RequestBody Entry entry) throws URISyntaxException {
log.debug("REST request to update Entry : {}", entry);
if (entry.getId() == null) {
throw new BadRequestAlertException("Invalid id", ENTITY_NAME, "idnull");
}
//here is my logic
Optional<Entry> entry_db = entryRepository.findOneWithEagerRelationships(entry.getId());
if(!entry_db.get().getUser().getId().equals(userService.getUserWithAuthorities().get().getId())) {
//throw someexception
}
//
Entry result = entryRepository.save(entry);
return ResponseEntity.ok()
.headers(HeaderUtil.createEntityUpdateAlert(ENTITY_NAME, entry.getId().toString()))
.body(result);
}
Is there any better or OOTB way of doing this??? may be something in spring security i am not aware of??
Thanks for help!!

This is a job for Spring Security Expression-Based Access Control, in particular you could annotate your method with #PreFilter and a custom PermissionEvaluator you would implement with similar logic as in your POC. The PermissionEvaluator could be generic and applied to several entity types if you define an Owned interface that models the ownership with a method like User getOwner() and that all your entity classes would implement.
See https://docs.spring.io/spring-security/site/docs/current/reference/html5/#el-access
The annotated method should be in a service rather than in a resource controller.
Also, UserService alone will not help you in finding the current authenticated user, you should use JHipster's SecurityUtils first and then ÙserService if you need more data about it.

Related

.NET MVC preventing forged POST

SITUATION:
I have a Model and based on a users Role I want to allow the user to only update certain parts of the model. Lets say the Model has three fields. (My Model is obviously more complex than this)
MyObject
Field1
Field2
Field3
My View looks something like this:
Html.TextBoxFor(#Model.Field1)
Html.TextBoxFor(#Model.Field2)
#if(UserIsAdmin())
Html.TextBoxFor(#Model.Field3)
else
#Model.Field3
Bearing with me on the syntax (and the poor design of the example), you can see what I'm trying to do. Upon the user posting the form my controller would just take the MyObject and save it back to the database, we are using EF.
QUESTION:
My question is, is there a way to stop a user from forging a POST to be able to save data he/she should not be able to. My current idea would be to do a check in the controller to see if the user modified values he should not have. Or I could save fields individually, but neither is a convient solution.
Is there a better one?
Thanks!
Additional Info:
Not sure if this artical is relevant at all: http://blog.stevensanderson.com/2008/09/01/prevent-cross-site-request-forgery-csrf-using-aspnet-mvcs-antiforgerytoken-helper/
All three fields are from the same database table and I'm using EF to get and save the entity.
You want to make sure the user is only able to update permitted fields.
You decided that the way to achieve this is to prevent the user "forging" a response using e.g. firebug, or F12 developer tools, or GreaseMonkey, and have asked how to do this.
But the correct/best method is to check which fields the user is attempting to update, and only update those which he is permitted to update. Then it doesn't matter if they forge the request or not, they still won't be able to access anything they shouldn't. In other words, check access rights at the point of access.
Anti-forgery tokens are there to solve a separate problem, namely XSRF.
Use a viewmodel that accepts only the fields that should be updated and then populate the model with those values. You could use something like AutoMapper for mapping between the two.
My current idea would be to do a check in the controller to see if the user modified values he should not have. Or I could save fields individually, but neither is a convient solution.
You're on the right track with that idea. A typical Add() operation would look like this:
public class FooController : Controller
{
public ActionResult Add(FooViewModel viewModel)
{
if (ModelState.IsValid)
{
FooDataModel dataModel = FooMapper.MapToDataModel(viewModel, User);
FooRepository.Add(dataModel);
}
}
}
Like #VimalStan said, your FooViewModel is then a model that contains only the fields you want to let the user update. Also this still doesn't solve your problem, which should be done in your mapper (in this case FooMapper) and still check every field as #Ben suggested:
public static class FooMapper
{
public static FooDataModel Map(FooViewModel viewModel, IPrincipal user)
{
var dataModel = new FooDataModel();
dataModel.Field1 = viewModel.Field1;
dataModel.Field2 = viewModel.Field2;
if (IsAllowedToUpdateField3(user))
{
dataModel.Field3 = viewModel.Field3;
}
return dataModel;
}
public static bool IsAllowedToUpdateField3(IPrincipal user)
{
return false; // your logic
}
}

Restricting the creation of new entities in Breeze.js

Let's say I want to use Breeze to create a Task entity (I'm using EF), but because this is a real task application, the task must be associated to the currently logged in user. Tasks have UserId foreign keys that need to be populated during save and that's where I'm stuck. Doing this in a mostly client-side application is a foreign concept to me. I can't very well specify the UserId foreign key on the client side because I don't want a clever user might create tasks for another user. Where do I go about hooking into the save process to populate the UserId FK with the currently logged in user (System.Web.HttpContext.Current.User.Identity.Name in this case).
Feel free to point me in the direction of another post or documentation. As I said, this is a new concept to me so I don't really know what to search for.
And to whoever answer this question, I love you with all my heart. :)
Take a look at the documentation on the Breeze website about the Custom EFContextProvider's Save Interceptor. You will need to create your own provider and implement the BeforeSaveEntity method. In that method you can set the UserId of the task to the current user just like you did with MVC4.
public class TasksContextProvider: EFContextProvider<YourExistingDbContext>
{
public override bool BeforeSaveEntity(EntityInfo entityInfo)
{
if (entityInfo.Entity.GetType() == typeof(YourTask)
&& entityInfo.EntityState == EntityState.Added)
{
// add your logic here to set the user and/or do validation
}
}
}

Obtaining the current Principal outside of the Web tier

I have the following ntier app: MVC > Services > Repository > Domain. I am using Forms authentication. Is it safe to use Thread.CurrentPrincipal outside of my MVC layer to get the currently logged in user of my application or should I be using HttpContext.Current.User?
The reason I ask is there seems to be some issues around Thread.CurrentPrincipal, but I am cautious to add a reference to System.Web outside of my MVC layer in case I need to provide a non web font end in the future.
Update
I have been following the advice recieved so far to pass the username into the Service as part of the params to the method being called and this has lead to a refinement of my original question. I need to be able to check if the user is in a particular role in a number of my Service and Domain methods. There seems to be a couple of solutions to this, just wondering which is the best way to proceed:
Pass the whole HttpContext.Current.User as a param instead of just the username.
Call Thread.CurrentPrincipal outside of my web tier and use that. But how do I ensure it is equal to HttpContext.Current.User?
Stick to passing in the username as suggested so far and then use Roles.IsUserInRole. The problem with this approach is that it requires a ref to System.Web which I feel is not correct outside of my MVC layer.
How would you suggest I proceed?
I wouldn't do either, HttpContext.Current.User is specific to your web layer.
Why not inject the username into your service layer?
Map the relevant User details to a new Class to represent the LoggedInUser and pass that as an argument to your Business layer method
public class LoggedInUser
{
public string UserName { set;get;}
//other relevant proerties
}
Now set the values of this and pass to your BL method
var usr=new LoggedInUser();
usr.UserName="test value "; //Read from the FormsAuthentication stuff and Set
var result=YourBusinessLayerClass.SomeOperation(usr);
You should abstract your user information so that it doesn't depend on Thread.CurrentPrincipal or HttpContext.Current.User.
You could add a constructor or method parameter that accepts a user name, for example.
Here's an overly simplified example of a constructor parameter:
class YourBusinessClass
{
string _userName;
public YourBusinessClass(string userName)
{
_userName = userName;
}
public void SomeBusinessMethodThatNeedsUserName()
{
if (_userName == "sally")
{
// do something for sally
}
}
}
I prefer option number 2( use Thread.CurrentPrincipal outside of web tier ). since this will not polute your service tier & data tier methods. with bonuses: you can store your roles + additional info in the custom principal;
To make sure Thread.CurrentPrincipal in your service and data tier is the same as your web tier; you can set your HttpContext.Current.User (Context.User) in Global.asax(Application_AuthenticateRequest). Other alternative location where you can set this are added at the bottom.
sample code:
//sample synchronizing HttpContext.Current.User with Thread.CurrentPrincipal
protected void Application_AuthenticateRequest(Object sender, EventArgs e)
{
HttpCookie authCookie = Request.Cookies[FormsAuthentication.FormsCookieName];
//make sure principal is not set for anonymous user/unauthenticated request
if (authCookie != null && Request.IsAuthenticated)
{
FormsAuthenticationTicket authTicket = FormsAuthentication.Decrypt(authCookie.Value);
//your additional info stored in cookies: multiple roles, privileges, etc
string userData = authTicket.UserData;
CustomPrincipal userPrincipal = PrincipalHelper.CreatePrincipal(authTicket.Name, authTicket.UserData, Request.IsAuthenticated);
Context.User = userPrincipal;
}
}
of course first you must implement your login form to create authorization cookies containing your custom principal.
Application_AuthenticateRequest will be executed for any request to server(css files, javascript files, images files etc). To limit this functionality only to controller action, you can try setting the custom principal in ActionFilter(I haven't tried this). What I have tried is setting this functionality inside an Interceptor for Controllers(I use Castle Windsor for my Dependency Injection and Aspect Oriented Programming).
I believe you are running into this problem because you need to limit your domains responsibility further. It should not be the responsibility of your service or your document to handle authorization. That responsibility should be handled by your MVC layer, as the current user is logged in to your web app, not your domain.
If, instead of trying to look up the current user from your service, or document, you perform the check in your MVC app, you get something like this:
if(Roles.IsUserInRole("DocumentEditorRole")){
//UpdateDocument does NOT authorize the user. It does only 1 thing, update the document.
myDocumentService.UpdateDocument(currentUsername, documentToEdit);
} else {
lblPermissionDenied.InnerText = #"You do not have permission
to edit this document.";
}
which is clean, easy to read, and allows you to keep your services and domain classes free from authorization concerns. You can still map Roles.IsUserInRole("DocumentEditorRole")to your viewmodel, so the only this you are losing, is the CurrentUserCanEdit method on your Document class. But if you think of your domain model as representing real world objects, that method doesn't belong on Document anyway. You might think of it as a method on a domain User object (user.CanEditDocument(doc)), but all in all, I think you will be happier if you keep your authorization out of your domain layer.

Disable Model Validation in Asp.Net MVC

How do I disable Model validation for a single Action in a Controller ?
Or can I do it per model by registering the model type at startup somewhere ?
I want the ModelBinder to bind to the model, but afterwards it should not perform the model validation.
The reason why i dont want validation to happen is because i am trying to move the logic from the controllers to a service layer which will be responsible for validating the models as i cant assume that models being passed to a service contains valid data.
As I understand this is the recommend approach (to not have logic in the controllers), so I find it a bit strange that i cant seem to find anything about how the model validation can be disabled (per action or per model type).
Please notice that I dont want to disable model validation for the entire webapplication (by removing the validation providers), and i dont want to disable the input validation that checks for malicious code being posted.
UPDATE
I am using .Net 4.0 and MVC 3 Preview 1
Just remove the items you don´t need before checking if the model is valid
ModelState.Remove("Email");
if (ModelState.IsValid)
{
// your logic
}
I've solved this problem with this code:
public ActionResult Totals(MyModel model)
{
ModelState.Clear();
return View(model);
}
Not sure it's the right way though.
Unfortunately there seems to be no easy way to disable the model validation happening in the ModelBinder except for registering every single model type you don’t want to validate (including nested complex types) with a specific ModelBinder. It can be done with the following code:
ModelBinders.Binders[typeof(MyModelType)] = new NonValidatingModelBinder();
Creating a SkipValidationAttribute that can be attached to action methods or action method parameters doesn’t seem possible as it should be implemented in the ControllerActionInvoker, but there is no way of letting the ModelBinder know that it should do any validation in the SetProperty() and OnModelUpdated methods when calling BindModel() in the GetParameterValue() method.
I definitely dislike this addition in the 2.0 version, because, as you stated in your question, validation makes more sense in the Service layer. In this way you can reuse it in other non-web applications, and test it more easily without having to mock the auto-validation mechanism.
Validation in Controller layer is pointless because in this part you can only verify model data and not business rules. For example, think of a service responsible of adding new comments and a user that wants to post a new one, the data in the comment he/she is posting may be valid, but what happens if the user is banned to comment because of misbehavior in the past? You should do some validation in the Service layer to ensure this is not happening, and if it does, throwing an exception. In short, validation must be done in the Service layer.
I use xVal as my Validation framework because it's compatible with the DataAnnotationModel, allows me to place validation wherever I want and performs client-side validation without extra-effort, even remote-client side. This is how I use it at the beginning of each of my services, for example, a login service:
public void SignIn(Login login) {
var loginErrors = DataAnnotationsValidationRunner.GetErrors(login);
// Model validation: Empty fields?
if (loginErrors.Any())
throw new RulesException(loginErrors);
// Business validation: Does the user exist? Is the password correct?
var user = this._userRepository.GetUserByEmail(login.Email);
if (user == null || user.Password != login.Password)
throw new RulesException(null, "Username or password invalids");
// Other login stuff...
}
Simple, web-independent and easy... then, in the Controller:
public RedirectResult Login(Login login) {
// Login the user
try {
this._authenticationRepository.SignIn(login);
} catch (RulesException e) {
e.AddModelStateErrors(base.ModelState, "Login");
}
// Redirect
if (base.ModelState.IsValid)
return base.Redirect(base.Url.Action("Home"));
else return base.Redirect(base.Url.Action("Login"));
}
I would recommend you perform validation in both places rather than trying to turn off validation in the UI. I understand your point that the service cannot assume that it's being passed valid data - that is a valid concern and is the reason your service should have validation. But you should also have validation in your UI. This is also nice because you can have client-side validation to prevent user errors and give you users a better experience.
I know that this already been answered but what you really needed was to extend the DataAnnotationsValidatorProvider and override the GetValidators method.
Then, on startup, you would remove the DataAnnotationsValidatorProvider from ModelValidatorProviders.Providers and add your new one.
Needless to say, if you simply don't want any validation at all, you can simply have the GetValidators method returning an empty collection.
In my case, I need to remove validation only when submitting the forms while still keeping the client-side validation, hence the following:
public class DynamicModelValidatorProvider : DataAnnotationsModelValidatorProvider
{
GetValidators(ModelMetadata metadata, ControllerContext context, IEnumerable<Attribute> attributes)
{
if (context.HttpContext.Request.HttpMethod == "POST")
{
return new ModelValidator[] { };
}
return base.GetValidators(metadata, context, attributes);
}
}
I use
[ ValidateInput( false )]
Not sure if this prevents model validation, or only IIS submit validation.

Access Control in ASP.NET MVC depending on input parameters / service layer?

Preamble: this is a bit of a philosophical question. I'm looking more for the "right" way to do this rather than "a" way to do this.
Let's imagine I have some products, and an ASP.NET MVC application performing CRUD on those products:-
mysite.example/products/1
mysite.example/products/1/edit
I'm using the repository pattern, so it doesn't matter where these products come from:-
public interface IProductRepository
{
IEnumberable<Product> GetProducts();
....
}
Also my Repository describes a list of Users, and which products they are managers for (many-many between Users and Products). Elsewhere in the application, a Super-Admin is performing CRUD on Users and managing the relationship between Users and the Products they are permitted to manage.
Anyone is allowed to view any product, but only users who are designated as "admins" for a particular product are allowed to invoke e.g. the Edit action.
How should I go about implementing that in ASP.NET MVC? Unless I've missed something, I can't use the built-in ASP.NET Authorize attribute as first I'd need a different role for every product, and second I won't know which role to check for until I've retrieved my Product from the Repository.
Obviously you can generalise from this scenario to most content-management scenarios - e.g. Users are only allowed to edit their own Forum Posts. StackOverflow users are only allowed to edit their own questions - unless they've got 2000 or more rep...
The simplest solution, as an example, would be something like:-
public class ProductsController
{
public ActionResult Edit(int id)
{
Product p = ProductRepository.GetProductById(id);
User u = UserService.GetUser(); // Gets the currently logged in user
if (ProductAdminService.UserIsAdminForProduct(u, p))
{
return View(p);
}
else
{
return RedirectToAction("AccessDenied");
}
}
}
My issues:
Some of this code will need to be repeated - imagine there are several operations (Update, Delete, SetStock, Order, CreateOffer) depending on the User-Products relationship. You'd have to copy-paste several times.
It's not very testable - you've got to mock up by my count four objects for every test.
It doesn't really seem like the controller's "job" to be checking whether the user is allowed to perform the action. I'd much rather a more pluggable (e.g. AOP via attributes) solution. However, would that necessarily mean you'd have to SELECT the product twice (once in the AuthorizationFilter, and again in the Controller)?
Would it be better to return a 403 if the user isn't allowed to make this request? If so, how would I go about doing that?
I'll probably keep this updated as I get ideas myself, but I'm very eager to hear yours!
Thanks in advance!
Edit
Just to add a bit of detail here. The issue I'm having is that I want the business rule "Only users with permission may edit products" to be contained in one and only one place. I feel that the same code which determines whether a user can GET or POST to the Edit action should also be responsible for determining whether to render the "Edit" link on the Index or Details views. Maybe that's not possible/not feasible, but I feel like it should be...
Edit 2
Starting a bounty on this one. I've received some good and helpful answers, but nothing that I feel comfortable "accepting". Bear in mind that I'm looking for a nice clean method to keep the business logic that determines whether or not the "Edit" link on the index view will be displayed in the same place that determines whether or not a request to Products/Edit/1 is authorised or not. I'd like to keep the pollution in my action method to an absolute minimum. Ideally, I'm looking for an attribute-based solution, but I accept that may be impossible.
First of all, I think you already half-way figured it, becuase you stated that
as first I'd need a different role for every product, and second I won't know which role to check for until I've retrieved my Product from the Repository
I've seen so many attempts at making role-based security do something it was never intended to do, but you are already past that point, so that's cool :)
The alternative to role-based security is ACL-based security, and I think that is what you need here.
You will still need to retrieve the ACL for a product and then check if the user has the right permission for the product. This is so context-sensitive and interaction-heavy that I think that a purely declarative approach is both too inflexible and too implicit (i.e. you may not realize how many database reads are involved in adding a single attribute to some code).
I think scenarios like this are best modeled by a class that encapsulates the ACL logic, allowing you to either Query for decision or making an Assertion based on the current context - something like this:
var p = this.ProductRepository.GetProductById(id);
var user = this.GetUser();
var permission = new ProductEditPermission(p);
If you just want to know whether the user can edit the product, you can issue a Query:
bool canEdit = permission.IsGrantedTo(user);
If you just want to ensure that the user has rights to continue, you can issue an Assertion:
permission.Demand(user);
This should then throw an exception if the permission is not granted.
This all assumes that the Product class (the variable p) has an associated ACL, like this:
public class Product
{
public IEnumerable<ProductAccessRule> AccessRules { get; }
// other members...
}
You might want to take a look at System.Security.AccessControl.FileSystemSecurity for inspiration about modeling ACLs.
If the current user is the same as Thread.CurrentPrincipal (which is the case in ASP.NET MVC, IIRC), you can simplyfy the above permission methods to:
bool canEdit = permission.IsGranted();
or
permission.Demand();
because the user would be implicit. You can take a look at System.Security.Permissions.PrincipalPermission for inspiration.
From what you are describing it sounds like you need some form of user access control rather than role based permissions. If this is the case then it needs to be implemented throughout your business logic. Your scenario sounds like you can implement it in your service layer.
Basically you have to implement all functions in your ProductRepository from the perspective of the current user and the products are tagged with permissions for that user.
It sounds more difficult than it actually is. First off you need a user token interface that contains the user information of uid and role list (if you want to use roles). You can use IPrincipal or create your own along the lines of
public interface IUserToken {
public int Uid { get; }
public bool IsInRole(string role);
}
Then in your controller you parse the user token into your Repository constructor.
IProductRepository ProductRepository = new ProductRepository(User); //using IPrincipal
If you're using FormsAuthentication and a custom IUserToken then you can create a Wrapper around the IPrincipal so your ProductRepository is created like:
IProductRepository ProductRepository = new ProductRepository(new IUserTokenWrapper(User));
Now all your IProductRepository functions should access the user token to check permissions. For example:
public Product GetProductById(productId) {
Product product = InternalGetProductById(UserToken.uid, productId);
if (product == null) {
throw new NotAuthorizedException();
}
product.CanEdit = (
UserToken.IsInRole("admin") || //user is administrator
UserToken.Uid == product.CreatedByID || //user is creator
HasUserPermissionToEdit(UserToken.Uid, productId) //other custom permissions
);
}
If you wondering about getting a list of all products, in your data access code you can query based on permission. In your case a left join to see if the many-to-many table contains the UserToken.Uid and the productId. If the right side of the join is present you know the user has permission to that product and then you can set your Product.CanEdit boolean.
Using this method you can then use the following, if you like, in your View (where Model is your Product).
<% if(Model.CanEdit) { %>
Edit
<% } %>
or in your controller
public ActionResult Get(int id) {
Product p = ProductRepository.GetProductById(id);
if (p.CanEdit) {
return View("EditProduct");
}
else {
return View("Product");
}
}
The benefit to this method is that the security is built in to your service layer (ProductRepository) so it is not handled by your controllers and cannot be bypassed by your controllers.
The main point is that the security is placed in your business logic and not in your controller.
The copy paste solutions really become tedious after a while, and is really annoying to maintain. I would probably go with a custom attribute doing what you need. You can use the excellent .NET Reflector to see how the AuthorizeAttribute is implemented and perform your own logic to it.
What it does is inheriting FilterAttribute and implementing IAuthorizationFilter. I can't test this at the moment, but something like this should work.
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = true, AllowMultiple = true)]
public class ProductAuthorizeAttribute : FilterAttribute, IAuthorizationFilter
{
public void OnAuthorization(AuthorizationContext filterContext)
{
if (filterContext == null)
{
throw new ArgumentNullException("filterContext");
}
object productId;
if (!filterContext.RouteData.Values.TryGetValue("productId", out productId))
{
filterContext.Result = new HttpUnauthorizedResult();
return;
}
// Fetch product and check for accessrights
if (user.IsAuthorizedFor(productId))
{
HttpCachePolicyBase cache = filterContext.HttpContext.Response.Cache;
cache.SetProxyMaxAge(new TimeSpan(0L));
cache.AddValidationCallback(new HttpCacheValidateHandler(this.Validate), null);
}
else
filterContext.Result = new HttpUnauthorizedResult();
}
private void Validate(HttpContext context, object data, ref HttpValidationStatus validationStatus)
{
// The original attribute performs some validation in here as well, not sure it is needed though
validationStatus = HttpValidationStatus.Valid;
}
}
You could probably also store the product/user that you fetch in the filterContext.Controller.TempData so you can fetch it in the controller, or store it in some cache.
Edit: I just noticed the part about the edit link. The best way I can think of is factoring out the authorization part from the attribute and make a HttpHelper for it that you can use in your view.
I tend to think that authorization is part of your business logic (or at least outside of your controller logic anyway). I agree with kevingessner above, in that the authorization check should be part of the call to fetch the item. In his OnException method, you could show the login page (or whatever you have configured in the web.config) by something like this:
if (...)
{
Response.StatusCode = 401;
Response.StatusDescription = "Unauthorized";
HttpContext.Response.End();
}
And instead of making UserRepository.GetUserSomehowFromTheRequest() calls in all the action methods, I would do this once (in an override of the Controller.OnAuthorization method for example), then stick that data somewhere in your controller base class for later use (e.g. a property).
I think that it's unrealistic, and a violation of the separation of concerns, to expect to have controller/model code control what the view renders. The controller/model code can set a flag, in the view model, that the view can use to determine what it should do, but I don't think that you should expect a single method to be used by both controller/model and view to control both access to and rendering of the model.
Having said that you could approach this in either of two ways -- both would involve a view model that carries some annotations used by the view in addition to the actual model. In the first case, you can use an attribute to control access to the action. This would be my preference, but would involve decorating each method independently -- unless all of the actions in a controller have the same access attributes.
I've developed a "role or owner" attribute for just this purpose. It verifies that the user is in a particular role or is the owner of the data being produced by the method. Ownership, in my case, is controlled by the presence of a foreign key relationship between the user and the data in question -- that is, you have a ProductOwner table and there needs to be a row containing the product/owner pair for the product and current user. It differs from the normal AuthorizeAttribute in that when the ownership or role check fails, the user is directed to an error page, not the login page. In this case, each method would need to set a flag in the view model that indicates that the model can be edited.
Alternatively, you could implement similar code in the ActionExecuting/ActionExecuted methods of the controller (or a base controller so that it applies consistently across all controllers). In this case, you would need to write some code to detect what kind of action is being executed so you know whether to abort the action based on the ownership of the product in question. The same method would set the flag to indicate that the model can be edited. In this case, you'd probably need a model hierarchy so you could cast the model as an editable model so that you can set the property regardless of the specific model type.
This option seems more coupled to me than using the attribute and arguably more complicated. In the case of the attribute you can design it so that it takes the various table and property names as attributes to the attribute and uses reflection to get the proper data from your repository based on the attribute's properties.
Answering my own question (eep!), Chapter 1 of Professional ASP.NET MVC 1.0 (the NerdDinner tutorial) recommends a similar solution to mine above:
public ActionResult Edit(int id)
{
Dinner dinner = dinnerRepositor.GetDinner(id);
if(!dinner.IsHostedBy(User.Identity.Name))
return View("InvalidOwner");
return View(new DinnerFormViewModel(dinner));
}
Asides from making me hungry for my dinner, this doesn't really add anything as the tutorial goes on to repeat the code implementing the business rule immediately in the matching POST Action Method, and in the Details view (actually in a child partial of the Details view)
Does that violate SRP? If the business rule changed (so that e.g. anyone who had RSVP'd could edit the dinner), you'd have to change both GET and POST methods, and the View (and the GET and POST methods and View for the Delete operation too, although that's technically a seperate business rule).
Is pulling the logic out into some kind of permissions arbitrator object (as I've done above) as good as it gets?
You're on the right track, but you can encapsulate all of the permission check into a single method like GetProductForUser, which takes a product, user, and the required permission. By throwing an exception that's caught in the controller's OnException handler, the handling is all in one place:
enum Permission
{
Forbidden = 0,
Access = 1,
Admin = 2
}
public class ProductForbiddenException : Exception
{ }
public class ProductsController
{
public Product GetProductForUser(int id, User u, Permission perm)
{
Product p = ProductRepository.GetProductById(id);
if (ProductPermissionService.UserPermission(u, p) < perm)
{
throw new ProductForbiddenException();
}
return p;
}
public ActionResult Edit(int id)
{
User u = UserRepository.GetUserSomehowFromTheRequest();
Product p = GetProductForUser(id, u, Permission.Admin);
return View(p);
}
public ActionResult View(int id)
{
User u = UserRepository.GetUserSomehowFromTheRequest();
Product p = GetProductForUser(id, u, Permission.Access);
return View(p);
}
public override void OnException(ExceptionContext filterContext)
{
if (typeof(filterContext.Exception) == typeof(ProductForbiddenException))
{
// handle me!
}
base.OnException(filterContext);
}
}
You just have to provide ProductPermissionService.UserPermission, to return a user's permission on a given product.By using a Permission enum (I think I've got the right syntax...) and comparing permissions with <, Admin permissions imply Access permissions, which is pretty much always right.
You can use a XACML based implementation. This way you can externalize authorization and also have a repository for your policies outside of your code.

Resources