Reputation: 27370
My MVC application consists of Admin and normal users. Admin users can register as different customers using the system, and they can add their respective users to the system.
The controllers already have Authorize filters on certain controllers to prevent unauthorised access to certain pages/json.
However, say I have an admin user logged in, what prevents them from inspecting the Json posts/gets in the JavaScript and manually calling a 'get' with the Id of the specific data to be viewed? e.g. /Users/1
Testing my application, I could post an AJAX get, to retrieve the details of another user in the system which was not under the management of the current authenticated user.
I could write access check methods whenever a service method is called to see if the user can view the data. Are they any good ways of solving this problem without littering the application with access check methods all over the place?
e.g. Current Implementation
public class PeopleController : ApplicationController
{
public ActionResult GetMemberDetails(int memberId)
{
var member = _peopleService.GetMemberById(memberId);
return Json(member, JsonRequestBehavior.AllowGet);
}
}
public class PeopleService : IPeopleService
{
public MemberModel GetMemberById(int memberId)
{
// Void function which throws Unauthorised exception
MemberAccessCheck(memberId);
var member = Mapper.Map<Member, MemberModel>(_memberRepository.GetById(memberId));
return member;
}
}
The MemberAccessCheck function is called lots of times in my service.
Upvotes: 0
Views: 1610
Reputation: 57479
You're going to have to write these checks inside your controller or service. I dont think you should use a custom attributefilter for this behaviour because you are basically filtering data from a service layer standpoint. Nor is Auhorize attribute suitable for this featire also.
What I suggest is that you have a service method that accepts the current userId from the controller (take the User.Identity) and send it to the service to get the list of user's or single user that they can view/modify. So its not necessarily littering with access checks, but it would be how your service operates (a business rule).
Eg of Service Method:
User GetAdminUser(int userId, int adminId);
List<User> GetAdminUsers(int adminId);
Or, just overload your previous service
User GetUser(int userId);
User GetUser(int userId, int adminId);
Upvotes: 1
Reputation: 26690
However, say I have an admin user logged in, what prevents them from inspecting the Json posts/gets in the JavaScript and manually calling a 'get' with the Id of the specific data to be viewed? e.g. /Users/1
Nothing, really.
You can use HTTPS for those requests so at least the data would be encrypted.
I could write access check methods whenever a service method is called to see if the user can view the data.
You probably will need to do this. On a web application you cannot trust that the request that comes to you is valid.
This code tends to be very application specific so there aren't many "frameworks" that handle it for you. Your best bet would be to write this code in a way that can easily be hooked into your application everywhere you need it.
Upvotes: 0
Reputation: 10924
You should consider placing all of your Admin functions in an Area. If you do, then you can use the Route for the Area to implement a RouteConstraint
, which can inspect the id submitted in the AJAX request to determine whther it is within the requesting user's scope. Your hypothetical Route in the AreaRegistration.cs file for the Area would look like the following:
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"Admin_default",
"Admin/{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional },
new { id = new IsActionAuthorized() }
);
}
Then, your RouteConstraint would look something like this:
public class IsActionAuthorized : IRouteConstraint
{
public IsActionAuthorized()
{ }
private MyEntities _db = new MyEntities();
public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
{
// return true if no id was submitted
if (String.IsNullOrEmpty(values[parameterName]))
return true;
// return true if action is authorized
var requestId = Convert.ToInt32(values[parameterName]);
var authorized = false;
// code to check against requester's id, to determine
// if requestedId is within requester's authority
// set authorized to true only if the request is authorized for this requester
return authorized;
}
}
This gives you a single entry point, where you can perform the necessary gatekeeping tests. If the tests fail, the request will fail routing and return a 404 error.
Upvotes: 1