I am using Breeze.js, AngularJS, Web API and EF6 in a project which has 3 main security roles. Lets say High Level, Medium Level, and Low Level. In these examples I have Person, Company, LowLevelSecret, MediumLevelSecret, HighLevelSecret entities.
Security Problem 1: In the first example I want to be able to secure access to the entities as a whole. All security roles (low level, medium level and high level) should be able to access the Person entities. Only the users with a matching role level or higher should be able to access the secret entities which hold privileged information.
For example I might have.
class Person {
public int Id { get; set; }
public string Name { get; set; }
public LowLevelSecret LowLevelSecret { get; set; }
public MediumLevelSecret MediumLevelSecret { get; set; }
public HighLevelSecret HighLevelSecret { get; set; }
}
Where LowLevelSecret, MediumLevelSecret and HighLevelSecret would look something like this:
class LowLevelSecret {
public int Id { get; set; }
public string Secret { get; set; }
}
class MediumLevelSecret {
public int Id { get; set; }
public string Secret { get; set; }
}
class HighLevelSecret {
public int Id { get; set; }
public string Secret { get; set; }
}
I have a controller which an IQueryable per type for Person and Company:
class BreezeController : ApiController {
[HttpGet]
public string Metadata()
{
return _repository.Metadata;
}
[HttpPost]
public SaveResult SaveChanges(JObject saveBundle)
{
var result = _repository.SaveChanges(saveBundle);
return result;
}
[HttpGet]
public IQueryable<Person> People()
{
return _repository.People;
}
[HttpGet]
public IQueryable<Company> Companies()
{
return _repository.Companies;
}
}
I can't use an AuthorizeAttribute on the secret entities, they are not listed in the controller - I use expand on the breeze query client side to pull them in. My concern is that anyone could write their own client side code and use the expand function of breeze to first query for an entity they are allowed to access then expand it to other entities they should not be able to access.
I could override the expand depth on each controller action:
[HttpGet]
[BreezeQueryable(MaxExpansionDepth = 0)]
public IQueryable<Person> People()
{
return _repository.People;
}
This seems a little bit cumbersome and prone to error. Is there a better way to protect what gets sent to the client?
Security Problem 2 Say I have a Company entity as follows:
public Company {
// low level, medium level and high level access required
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
// medium level, high level access only - NOT low level
public string DirectorsName { get; set; }
public string DirectorsEmail { get; set; }
// high level access only - NOT low level, medium level
public string Profit { get; set; }
public string Turnover { get; set; }
}
How do I restrict access as described so that different user roles can access different parts of the same Company entity. I could write a projection query to only get the bits I want, but there is nothing to stop a user from writing their own query for the whole entity. I assume the key here may be to use different DTO's for each access level? How can this work so that it can all get pieced back together when saving etc. so it knows which entity to update.
Security Problem 3 The SaveChanges method in the controller accepts a change bundle. There does not appear to be anything to stop a user with lower level access from submitting changes to entities which they should not be able to change. Even if the normal client code does not allow this, they could simply write their own or create it manually and submit.
I believe the preferred solution to this is to override the BeforeSavingEntity method and inspect the changes to ensure they are acceptable for the users role? Is the only way to ensure security to manually inspect for changes to every medium or high level property and compare the users role? Is there a good scalable pattern which can be used to perform these checks and other business logic on a large number of entities. I can't find any good real world examples of how to properly enforce these kinds of business rules in a larger project scenario.
Thanks for all your help and suggestions,
This is a fairly controversial question because you are trying to limit exposing sensitive information by using querying with IQueryables and such. This is a terrible design flaw and you could get yourself in a lot of trouble.
Do not design your application to potentially expose information based on roles of the client that is not secured well. Just don't do it. Before you expose data in an IQueryable you need to restrict the data that the IQueryable has access to in your business logic.
Also DO NOT rely on saving to always be correct. Never. This is a very bad design and I hope you seek professional help from someone before putting this in to production!
For more information please see this question / answer - How is breeze.js handling security and avoiding exposing business logic
Good question ... well questions (plural)! I'm tasked to implement role-based security. Yesterday I implemented role-based security for saving. I did, in fact, implement checking in BeforeSaveEntity() ... in the way you described. It does seem like an expensive operation, but I don't know of a better way.
Well, you might be able to optimize a little by overriding BeforeSaveEntities() instead.
As for how to implement role-based security for querying ... I'm still working on that one :) I have a couple ideas. But, I have not been able to implement them yet to be able to say whether they work or not. None-the-less, here are my ideas:
1) much like the algorithm for saving, inspect the entities -- after the query has been executed but before the result is sent to the client. But, I don't know how to insert that checking code (via callback or something) at the point between execution and sending to the client :(
2) add smarts to the POCO to check for restricted entities and properties based on the user role. For example, instead of:
You'd have something like this:
That seems icky since a POCO is supposed to be dumb. The POCO would need to be able to read the user roles from somewhere ... maybe the HTTP session. I don't quite know how that will work.