Background:
I have a very large OData model that is currently using WCF Data Services (OData) to expose it. However, Microsoft has stated that WCF Data Services is dead and that Web API OData is the way they will be going.
So I am researching ways to get Web API OData to work as well as WCF Data Services.
Problem Setup:
Some parts of the model do not need to be secured but some do. For example, the Customers list needs security to restrict who can read it, but I have other lists, like the list of Products, that any one can view.
The Customers entity has many many associations that can reach it. If you count 2+ level associations, the are many hundreds of ways that Customers can be reached (via associations). For example Prodcuts.First().Orders.First().Customer. Since Customers are the core of my system, you can start with most any entity and eventually associate your way to the Customers list.
WCF Data Services has a way for me to put security on a specific entity via a method like this:
[QueryInterceptor("Customers")]
public Expression<Func<Customer, bool>> CheckCustomerAccess()
{
return DoesCurrentUserHaveAccessToCustomers();
}
As I look at Web API OData, I am not seeing anything like this. Plus I am very concerned because the controllers I am making don't seem to get called when an association is followed. (Meaning I can't put security in the CustomersController.)
I am worried that I will have to try to somehow enumerate all the ways that associations can some how get to customers and put security on each one.
Question:
Is there a way to put security on a specific entity in Web API OData? (Without having to enumerate all the associations that could somehow expand down to that entity?)
UPDATE: At this point in time I would recommend that you follow the solution posted by vaccano, which is based on input from the OData
team.
What you need to do is to create a new Attribute inheriting from EnableQueryAttribute for OData 4 (or QuerableAttribute depending on which version of Web API\OData you are talking with) and override the ValidateQuery (its the same method as when inheriting from QuerableAttribute) to check for the existence of a suitable SelectExpand attribute.
To setup a new fresh project to test this do the following:
Create a new ASP.Net project with Web API 2
Create your entity framework data context.
Add a new "Web API 2 OData Controller ..." controller.
In the WebApiConfigRegister(...) method add the below:
Code:
ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<Customer>("Customers");
builder.EntitySet<Order>("Orders");
builder.EntitySet<OrderDetail>("OrderDetails");
config.Routes.MapODataServiceRoute("odata", "odata", builder.GetEdmModel());
//config.AddODataQueryFilter();
config.AddODataQueryFilter(new SecureAccessAttribute());
In the above, Customer, Order and OrderDetail are my entity framework entities. The config.AddODataQueryFilter(new SecureAccessAttribute()) registers my SecureAccessAttribute for use.
SecureAccessAttribute is implemented as below:
Code:
public class SecureAccessAttribute : EnableQueryAttribute
{
public override void ValidateQuery(HttpRequestMessage request, ODataQueryOptions queryOptions)
{
if(queryOptions.SelectExpand != null
&& queryOptions.SelectExpand.RawExpand != null
&& queryOptions.SelectExpand.RawExpand.Contains("Orders"))
{
//Check here if user is allowed to view orders.
throw new InvalidOperationException();
}
base.ValidateQuery(request, queryOptions);
}
}
Please note that I allow access to the Customers controller, but I limit access to Orders. The only Controller I have implemented is the one below:
public class CustomersController : ODataController
{
private Entities db = new Entities();
[SecureAccess(MaxExpansionDepth=2)]
public IQueryable<Customer> GetCustomers()
{
return db.Customers;
}
// GET: odata/Customers(5)
[EnableQuery]
public SingleResult<Customer> GetCustomer([FromODataUri] int key)
{
return SingleResult.Create(db.Customers.Where(customer => customer.Id == key));
}
}
Apply the attribute in ALL actions that you want to secure. It works exactly as the EnableQueryAttribute. A complete sample (including Nuget packages end everything, making this a 50Mb download) can be found here: http://1drv.ms/1zRmmVj
I just want to also comment a bit on some other solutions:
Leyenda's solution does not work simply because it is the other way around, but otherwise was super close! The truth is that the builder will look in the entity framework to expand properties and will not hit the Customers controller at all! I do not even have one, and if you remove the security attribute, it will still retrieve the orders just fine if you add the expand command to your query.
Setting the model builder will prohibit access to the entities you removed globally and from everyone, so it is not a good solution.
Feng Zhao's solution could work, but you would have to manually remove the items you wanted to secure in every query, everywhere, which is not a good solution.
I got this answer when I asked the Web API OData team. It seems very similar to the answer I accepted, but it uses an IAuthorizationFilter.
In interest of completeness I thought I would post it here:
For entity set or navigation property appears in the path, we could define a message handler or an authorization filter, and in that check the target entity set requested by the user. E.g., some code snippet:
public class CustomAuthorizationFilter : IAuthorizationFilter
{
public bool AllowMultiple { get { return false; } }
public Task<HttpResponseMessage> ExecuteAuthorizationFilterAsync(
HttpActionContext actionContext,
CancellationToken cancellationToken,
Func<Task<HttpResponseMessage>> continuation)
{
// check the auth
var request = actionContext.Request;
var odataPath = request.ODataProperties().Path;
if (odataPath != null && odataPath.NavigationSource != null &&
odataPath.NavigationSource.Name == "Products")
{
// only allow admin access
IEnumerable<string> users;
request.Headers.TryGetValues("user", out users);
if (users == null || users.FirstOrDefault() != "admin")
{
throw new HttpResponseException(HttpStatusCode.Unauthorized);
}
}
return continuation();
}
}
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.Filters.Add(new CustomAuthorizationFilter());
For $expand authorization in query option, a sample.
Or create per user or per group edm model. A sample.
While I think that the solution provided by #SKleanthous is very good. However, we can do better. It has some issues which aren't going to be an issue in a majority of cases, I feel like it they were sufficient enough of a problem that I didn't want to leave it to chance.
The logic checks the RawExpand property, which can have a lot of stuff in it based on nested $selects and $expands. This means that the only reasonable way you can grab information out is with Contains(), which is flawed.
Being forced into using Contains causes other matching problems, say you $select a property that contains that restricted property as a substring, Ex: Orders and 'OrdersTitle' or 'TotalOrders'
Nothing is gaurenteeing that a property named Orders is of an "OrderType" that you are trying to restrict. Navigation property names are not set in stone, and could get changed without the magic string being changed in this attribute. Potential maintenance nightmare.
TL;DR: We want to protect ourselves from specific Entities, but more specifically, their types without false positives.
Here's an extension method to grab all the types (technically IEdmTypes) out of a ODataQueryOptions class:
public static class ODataQueryOptionsExtensions
{
public static List<IEdmType> GetAllExpandedEdmTypes(this ODataQueryOptions self)
{
//Define a recursive function here.
//I chose to do it this way as I didn't want a utility method for this functionality. Break it out at your discretion.
Action<SelectExpandClause, List<IEdmType>> fillTypesRecursive = null;
fillTypesRecursive = (selectExpandClause, typeList) =>
{
//No clause? Skip.
if (selectExpandClause == null)
{
return;
}
foreach (var selectedItem in selectExpandClause.SelectedItems)
{
//We're only looking for the expanded navigation items, as we are restricting authorization based on the entity as a whole, not it's parts.
var expandItem = (selectedItem as ExpandedNavigationSelectItem);
if (expandItem != null)
{
//https://msdn.microsoft.com/en-us/library/microsoft.data.odata.query.semanticast.expandednavigationselectitem.pathtonavigationproperty(v=vs.113).aspx
//The documentation states: "Gets the Path for this expand level. This path includes zero or more type segments followed by exactly one Navigation Property."
//Assuming the documentation is correct, we can assume there will always be one NavigationPropertySegment at the end that we can use.
typeList.Add(expandItem.PathToNavigationProperty.OfType<NavigationPropertySegment>().Last().EdmType);
//Fill child expansions. If it's null, it will be skipped.
fillTypesRecursive(expandItem.SelectAndExpand, typeList);
}
}
};
//Fill a list and send it out.
List<IEdmType> types = new List<IEdmType>();
fillTypesRecursive(self.SelectExpand?.SelectExpandClause, types);
return types;
}
}
Great, we can get a list of all expanded properties in a single line of code! That's pretty cool! Let's use it in an attribute:
public class SecureEnableQueryAttribute : EnableQueryAttribute
{
public List<Type> RestrictedTypes => new List<Type>() { typeof(MyLib.Entities.Order) };
public override void ValidateQuery(HttpRequestMessage request, ODataQueryOptions queryOptions)
{
List<IEdmType> expandedTypes = queryOptions.GetAllExpandedEdmTypes();
List<string> expandedTypeNames = new List<string>();
//For single navigation properties
expandedTypeNames.AddRange(expandedTypes.OfType<EdmEntityType>().Select(entityType => entityType.FullTypeName()));
//For collection navigation properties
expandedTypeNames.AddRange(expandedTypes.OfType<EdmCollectionType>().Select(collectionType => collectionType.ElementType.Definition.FullTypeName()));
//Simply a blanket "If it exists" statement. Feel free to be as granular as you like with how you restrict the types.
bool restrictedTypeExists = RestrictedTypes.Select(rt => rt.FullName).Any(rtName => expandedTypeNames.Contains(rtName));
if (restrictedTypeExists)
{
throw new InvalidOperationException();
}
base.ValidateQuery(request, queryOptions);
}
}
From what I can tell, the only navigation properties are EdmEntityType (Single Property) and EdmCollectionType (Collection Property). Getting the type name of the collection is a little different just because it will call it a "Collection(MyLib.MyType)" instead of just a "MyLib.MyType". We don't really care if it's a collection or not, so we get the Type of the Inner Elements.
I've been using this in production code for a while now with great success. Hopefully you will find an equal amount with this solution.
You could remove certain properties from the EDM programmatically:
var employees = modelBuilder.EntitySet<Employee>("Employees");
employees.EntityType.Ignore(emp => emp.Salary);
from http://www.asp.net/web-api/overview/odata-support-in-aspnet-web-api/odata-security-guidance
Would it be feasible to move this to your database? Assuming you're using SQL server, set up users which match the profiles you need for each client profile. Keeping it simple, one account with customer access and one without.
If you then map the user making a data request to one of these profiles and modify your connection string to include the related credentials. Then if they make a request to an entity they are not permitted to, they will get an exception.
Firstly, sorry if this is a misunderstanding of the problem. Even though I'm suggesting it, I can see a number of pitfalls most immediate being the extra data access control and maintenance within your db.
Also, I'm wondering if something can be done within the T4 template which generates your entity model. Where the association is defined, it might be possible to inject some permission control there. Again this would put the control in a different layer - I'm just putting it out there in case someone who knows T4s better than me can see a way to make this work.
The ValidateQuery override will help with detecting when a user explicitly expands or selects a navigable property, however it won't help you when a user uses a wildcard. For example, /Customers?$expand=*. Instead, what you likely want to do is change the model for certain users. This can be done using the EnableQueryAttribute's GetModel override.
For example, first create a method to generate your OData Model
public IEdmModel GetModel(bool includeCustomerOrders)
{
ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
var customerType = builder.EntitySet<Customer>("Customers").EntityType;
if (!includeCustomerOrders)
{
customerType.Ignore(c => c.Orders);
}
builder.EntitySet<Order>("Orders");
builder.EntitySet<OrderDetail>("OrderDetails");
return build.GetModel();
}
... then in a class that inherits from EnableQueryAttribute, override GetModel:
public class SecureAccessAttribute : EnableQueryAttribute
{
public override IEdmModel GetModel(Type elementClrType, HttpRequestMessage request, HttpActionDescriptor actionDescriptor)
{
bool includeOrders = /* Check if user can access orders */;
return GetModel(includeOrders);
}
}
Note that this will create a bunch of the same models on multiple calls. Consider caching various versions of your IEdmModel to increase performance of each call.
You can put your own Queryable attribute on Customers.Get() or whichever method is used to access the Customers entity (either directly or through a navigation property). In the implementation of your attribute, you can override the ValidateQuery method to check the access rights, like this:
public class MyQueryableAttribute : QueryableAttribute
{
public override void ValidateQuery(HttpRequestMessage request,
ODataQueryOptions queryOptions)
{
if (!DoesCurrentUserHaveAccessToCustomers)
{
throw new ODataException("User cannot access Customer data");
}
base.ValidateQuery(request, queryOptions);
}
}
I don't know why your controller isn't called on navigation properties. It should be...
Related
I'm currently trying to support API versioning using .NET Core API.
I have read many articles about the matter , Couldn't find a really good code examples for it.
Everyone is posting about the controller and how to add API version to each end point but none is actually talking about the headache afterwards. Meaning duplicating the models and the functions (service/handler)
Let's say I have a User controller which has more than 5 end points.
One of these end point is GET User. We needed to remove a field(age field) in the response and it's a breaking change. So we added 2 end point one support the default V1 and the other support V2
[ApiController]
[Route("api/User")]
[ApiVersion("1.0")]
[ApiVersion("2.0")]
public class UserController : ControllerBase {
[HttpGet("user")]
[MapToApiVersion("1.0")]
public async Task<IActionResult> GetUser([FromQuery] string id)
{
return Ok(await _service.GetUser(id));
}
[HttpGet("user")]
[MapToApiVersion("2.0")]
public async Task<IActionResult> GetUserV2([FromQuery] string id)
{
return Ok(await _service.GetUser(id));
}
}
assuming the GetUser() function has a heavy logic ( +30 lines of codes)
the only difference between V1 and V2 is in the model itself one will return the age
and one will not.
What is the better approach to handle such situation?
is it better to duplicate GetUser() as
GetUser(int id)
GetUserV2(int id)
Or pass a version number to the function and do the change accordingly
GetUser(int id , int version)
for my personal opinion. I prefer the duplication as it will be less complicated and easy to read. But duplicating all code also seems useless.
As this is my first time trying to support versioning. I would really appreciate some thoughts and ideas from you !
There is no "one size fits all" solution. What makes sense for your particular application will vary. Here are few ideas that may work for you. There is no preference in order nor is any one particular solution necessarily better than the other. Some options can even be combined together.
Option 1
Move as much logic as possible out of your controllers. Controllers are just a way to represent your API over HTTP. By delegating as much of the logic as possible into collaborators, you can likely reduce a lot of duplication.
Ideally, an action method should be less than 10 lines of code. Extension methods, custom results, and so on can help reduce duplication.
Option 2
Define a clear versioning policy; for example N-2. This can really help clamp down on duplication, but not necessarily eliminate it. Managing duplication across 3 versions is much more manageable if it's unbound.
It should be noted that sharing across versions also comes with some inherent risks (which you might be willing to accept). For example, a change or fix could affect multiple versions and in unexpected or undesirable ways. This is more likely to occur when interleaving multiple versions on a single controller. Some services choose a Copy & Paste approach for new versions to retain the same base implementation, but then allow the implementations to evolve independently. That doesn't mean you can't have shared components, just be careful what you share.
Option 3
Use nullable attributes and ensure your serialization options do not emit null attributes. This obviously doesn't work if you allow or use explicit null values.
For example, the age attribute can be removed using a single model like this:
public class User
{
// other attributes omitted for brevity
public int? Age { get; set; }
}
[HttpGet("user")]
[MapToApiVersion("2.0")]
public async Task<IActionResult> GetUserV2([FromQuery] string id)
{
var user = await _service.GetUser(id);
// if nulls are not emitted, then this effective 'removes' the
// 'age' member using a single model
user.Age = null;
return Ok(user);
}
Option 4
Use an adapter. This could get tedious if you don't have a fixed versioning policy, but is manageable for a limited number of versions. You could also using templating or source generators to render the code for you.
public class User2Adapter
{
private readonly User inner;
public User2Adapter(User user) => inner = user;
public FirstName => inner.FirstName;
public LastName => inner.LastName;
}
[HttpGet("user")]
[MapToApiVersion("2.0")]
public async Task<IActionResult> GetUserV2([FromQuery] string id)
{
return Ok(new User2Adapter(await _service.GetUser(id)));
}
This approach is used for serializing ProblemDetails using Newtonsoft.Json (see here)
This can also be achieved with anonymous types:
[HttpGet("user")]
[MapToApiVersion("2.0")]
public async Task<IActionResult> GetUserV2([FromQuery] string id)
{
var user = await _service.GetUser(id);
var userV2 = new
{
firstName = user.FirstName,
lastName = user.LastName,
};
return Ok(userV2);
}
Option 5
Use a custom OutputFormatter. The default implementation in SystemJsonTextOutputFormatter doesn't honor the specified object type unless the supplied object itself is null. You can change this behavior.
A complete implementation would be a bit verbose, but you can imagine that you might have something like this (abridged):
public class VersionedJsonOutputFormatter : TextOutputFormatter
{
private readonly Dictionary<ApiVersion, Dictionary<Type, Type>> map = new()
{
[new ApiVersion(1.0)] = new()
{
[typeof(User)] = typeof(User),
},
[new ApiVersion(2.0)] = new()
{
[typeof(User)] = typeof(User2),
},
}
public VersionedJsonOutputFormatter(
JsonSerializerOptions jsonSerializerOptions)
{
// TODO: copy SystemJsonTextOutputFormatter implementation
}
public override async Task WriteResponseBodyAsync(
OutputFormatterWriteContext context,
Encoding selectedEncoding)
{
// IMPORTANT: abridged with many assumptions; look at
// SystemJsonTextOutputFormatter implementation
var httpContext = context.HttpContext;
var apiVersion = httpContext.GetRequestedApiVersion();
var objectType = map[apiVersion][context.Object.GetType()];
var ct = httpContext.RequestAborted;
try
{
await JsonSerializer.SerializeAsync(
responseStream,
context.Object,
objectType,
SerializerOptions,
ct);
await responseStream.FlushAsync(ct);
}
catch (OperationCanceledException) when (ct.IsCancellationRequested)
{
}
}
}
This is just one approach. There are plenty of variations on how you can change the mapping.
Option 6
This one area where OData (or even EF) really shines. The use of an Entity Data Model (EDM) separates the model over the wire vs the code model. You can have a single, unified code model with a different EDM per API version that controls how that is serialized over the wire. I'm not sure you can yank only the specific bits that you want for EDM and serialization, but if you can, it just might get you what you want with minimal effort. This is approach is certainly useful for APIs outside of the context of OData.
The OData examples for API Versioning show this at work. I've never tried using things in a purely non-OData way, but that doesn't mean it can't be made to work.
I would prefer the
GetUser(int id , int version)
and add a few comments on why you're using this version varible and use a switch case inside rather than writing duplicate code.
For me personally, writing such duplicate code is not a very good practice as I find it redundant.
I'm creating a Web API with users having different roles, in addition as any other application I do not want User A to access User B's resources. Like below:
Orders/1 (User A)
Orders/2 (User B)
Of course I can grab the JWT from the request and query the database to check if this user owns that order but that will make my controller Actions' too heavy.
This example uses AuthorizeAttribute but it seems too broad and I'll have to add tons of conditionals for all routes in the API to check which route is being accessed and then query the database making several joins that lead back to the users table to return if the request Is Valid or not.
Update
For Routes the first line of defense is a security policy which
require certain claims.
My question is about the second line of defense that is responsible to
make sure users only access their data/resources.
Are there any standard approaches to be taken in this scenario ?
The approach that I take is to automatically restrict queries to records owned by the currently authenticated user account.
I use an interface to indicate which data records are account specific.
public interface IAccountOwnedEntity
{
Guid AccountKey { get; set; }
}
And provide an interface to inject the logic for identifying which account the repository should be targeting.
public interface IAccountResolver
{
Guid ResolveAccount();
}
The implementation of IAccountResolver I use today is based on the authenticated users claims.
public class ClaimsPrincipalAccountResolver : IAccountResolver
{
private readonly HttpContext _httpContext;
public ClaimsPrincipalAccountResolver(IHttpContextAccessor httpContextAccessor)
{
_httpContext = httpContextAccessor.HttpContext;
}
public Guid ResolveAccount()
{
var AccountKeyClaim = _httpContext
?.User
?.Claims
?.FirstOrDefault(c => String.Equals(c.Type, ClaimNames.AccountKey, StringComparison.InvariantCulture));
var validAccoutnKey = Guid.TryParse(AccountKeyClaim?.Value, out var accountKey));
return (validAccoutnKey) ? accountKey : throw new AccountResolutionException();
}
}
Then within the repository I limit all returned records to being owned by that account.
public class SqlRepository<TRecord, TKey>
where TRecord : class, IAccountOwnedEntity
{
private readonly DbContext _dbContext;
private readonly IAccountResolver _accountResolver;
public SqlRepository(DbContext dbContext, IAccountResolver accountResolver)
{
_dbContext = dbContext;
_accountResolver = accountResolver;
}
public async Task<IEnumerable<TRecord>> GetAsync()
{
var accountKey = _accountResolver.ResolveAccount();
return await _dbContext
.Set<TRecord>()
.Where(record => record.AccountKey == accountKey)
.ToListAsync();
}
// Other CRUD operations
}
With this approach, I don't have to remember to apply my account restrictions on each query. It just happens automatically.
Using [Authorize] attribute is called declarative authorization. But it is executed before the controller or action is executed. When you need a resource-based authorization and document has an author property, you must load the document from storage before authorization evaluation. It's called imperative authorization.
There is a post on Microsoft Docs how to deal with imperative authorization in ASP.NET Core. I think it is quite comprehensive and it answers your question about standard approach.
Also here you can find the code sample.
To make sure User A cannot view Order with Id=2 (belongs to User B). I would do one of this two things:
One:
Have a GetOrderByIdAndUser(long orderId, string username), and of course you take username from the jwt.
If the user does't own the order he wont see it, and no extra db-call.
Two:
First fetch the Order GetOrderById(long orderId) from database and then validate that username-property of the order is the same as the logged on user in the jwt.
If the user does't own the order Throw exception, return 404 or whatever, and no extra db-call.
void ValidateUserOwnsOrder(Order order, string username)
{
if (order.username != username)
{
throw new Exception("Wrong user.");
}
}
You can make multiple policies in the ConfigureServices method of your startup, containing Roles or, fitting to your example here, names, like this:
AddPolicy("UserA", builder => builder.RequireClaim("Name", "UserA"))
or replace "UserA" with "Accounting" and "Name" with "Role".
Then restrict controller methods by role:
[Authorize(Policy = "UserA")
Of course this in on the controller level again, but you don't have to hack around tokens or the database. This will give your a direct indicator as to what role or user can use what method.
Your statements are wrong, and you are also designing it wrong.
Over optimization is the root of all evil
This link can be summarized in "test the performance before claiming it won't work."
Using the identity (jwt token or whatever you configured) to check if the actual user is accessing the right resource (or maybe better to serve just the resources it owns) is not too heavy.
If it becomes heavy, you are doing something wrong.
It might be that you have tons of simultaneous access and you just need to cache some data, like a dictionary order->ownerid that gets cleared over time... but that doesn't seem the case.
about the design: make a reusable service that can get injected and have a method to access every resource you need which accept an user (IdentityUser, or jwt subject, just the user id, or whatever you have)
something like
ICustomerStore
{
Task<Order[]> GetUserOrders(String userid);
Task<Bool> CanUserSeeOrder(String userid, String orderid);
}
implement accordingly and use this class to sistematically check if the user can access the resources.
The first question you need to answer is "When I can make this authorisation decision?". When do you actually have the information needed to make the check?
If you can almost always determine the resource being accessed from route data (or other request context), then an policy with matching requirement and handler may be appropriate. This works best when you are interacting with data clearly silo'd out by resource - as it doesn't help at all with things like filtering of lists, and you'll have to fall back to imperative checks in these cases.
If you can't really figure out whether a user can fiddle with a resource until you've actually examined it then you are pretty much stuck with imperative checks. There is standard framework for this but it isn't imo as useful as the policy framework. It's probably valuable at some point to write an IUserContext which can be injected at the point you query you domain (so into repos/wherever you use linq) which encapsulates some of these filters (IEnumerable<Order> Restrict(this IEnumerable<Order> orders, IUserContext ctx)).
For a complex domain there won't be an easy silver bullet. If you use an ORM it may be able to help you - but don't forget that navigable relationships in your domain will allow code to break context, particularly if you haven't been strict on trying to keep aggregates isolated (myOrder.Items[n].Product.Orderees[notme]...).
Last time I did this I managed to use the policy-based-on-route approach for 90% of cases, but still had to do some manual imperative checks for the odd listing or complex query. The danger in using imperative checks, as I'm sure you are aware, is that you forget them. A potential solution for this is to apply your [Authorize(Policy = "MatchingUserPolicy")] at controller level, add an additional policy "ISolemlySwearIHaveDoneImperativeChecks" on the action, and then in your MatchUserRequirementsHandler, check the context and bypass the naive user/order matching checks if imperative checks have been 'declared'.
OK, Custom Policy Based Authorization in ASP.NET Core. I kinda of understood the idea of this new identity framework, but still not 100% clear what you can achieve with this. Assuming we have an Action in HomeController called List. This action will query and display a list of products from the database. The users that must access this list must be part of the Marketing division. Therefore in our policy we check if user has a claim called Division and the value of that is Marketing. If yes then he will be allowed to see the list otherwise not. We can decorate our action like this:
[Authorize(Policy = "ProductsAccess")]
public IActionResult List()
{
//do query and return the products view model
return View();
}
All good with this. It will work perfectly.
Scenario 1: What if I want to add the policy at the product level, and based on the policy the user will see only the products from his division. So marketing guy will see his products, R&D will see his and so forth. How can I achieve that? Can it be done with a policy? If yes how?
Scenario 2: What about access at the field level? Let's say maybe I want to hide certain fields? Example: all products will have certain columns that must be visible to Managers and hidden to the rest of users? Can this be done using custom policies? If yes how?
For Scenario 1 you can use resource based authorization.
In essence, you'd inject IAuthorizationService into your service or controller, then and have one or more authorization handlers which derive form AuthorizationHandler<TRequirement, TDocument> and then call
if(await _authorizationService.AuthorizeAsync(User, document, "MyPolicy"))
{
// Success, user has access to it
}
Downside: You have to fetch all products from database, then filter in memory, so it will work well for single documents or smaller data, where you don't need pagination. Pagination will break it, even on smaller data (i.e. if you request 50 products, but user don't have access to 40 of them, only 10 will be returned, despite the page size being 50).
an alternative will be possible with EF Core 2.0 (that's if you use EF Core as your ORM). You can add global filters, which will applied to all queries to a certain entity.
For more information, see Entity Framework Core 2.0 Announcement blog post:
public class BloggingContext : DbContext
{
public DbSet<Blog> Blogs { get; set; }
public DbSet<Post> Posts { get; set; }
public int TenantId {get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Post>()
.HasQueryFilter(p => !p.IsDeleted &&
p.TenantId == this.TenantId );
}
}
It may or may not be suitable for your case, it depends if you have a row-level data you can use (i.e. some kind of "resource owner" field).
Scenario 2 isn't possible out of the box with Identity as far as I know, you'd have to implement something on your own, but it's a very complex topic (if you ever worked with Dynamics CRM, you know what I mean).
Update
Just for an quick implementation, you can try to wrap your response around an ExpandoObject (that's what's used underlying when you use dynamic keyword) and the iterate over it, removing properties the user doesn't have access to before returning it from the controller action or write an Authorization filter, which will automatically do that for specific or all controllers.
For an rough idea (on how to construct/use the expando object), see my answer here.
I don't think policies were designed to solve the cases you have. I'm uncertain if it's possible, and even if it would, I feel like the code itself would suffer from the complexity and confusion it would bring. I wouldn't give my authorization filters that much responsibility.
As for your second scenario, you could just skip outputting certain data in your view based on the role, claim or whatever of the current user. Seems unnecessarily complex to solve that with policies.
Use policies for what it was made for, authorizing if the user is allowed to even run a method. Any differences to what is being returned? Handle it in the normal code flow.
I have an entity with a collection property that looks something like this:
public class MyEntity
{
public virtual ICollection<OtherEntity> Others { get; set; }
}
When I retrieve this entity via the data context or repository, I want to prevent others adding items to this collection through the use of MyEntity.Others.Add(entity). This is because I may want some validation code to be performed before adding my entity to the collection. I'd do this by providing a method on MyEntity like this:
public void AddOther(OtherEntity other)
{
// perform validation code here
this.Others.Add(other);
}
I've tested a few things so far, and what I've eventually arrived at is something like this. I create a private collection on my entity and expose a public ReadOnlyCollection<T> so MyEntity looks like this:
public class MyEntity
{
private readonly ICollection<OtherEntity> _others = new Collection<OtherEntity>();
public virtual IEnumerable<OtherEntity>
{
get
{
return _others.AsEnumerable();
}
}
}
This seems to be what I'm looking for and my unit tests pass fine, but I haven't yet started to do any integration testing so I'm wondering:
Is there a better way to achieve what I'm looking for?
What are the implications I'll face if I decide to go down this route (if feasible)?
Thanks always for any and all help.
Edit 1 I've changed from using a ReadOnlyCollection to IEnumerable and am using return _others.AsEnumerable(); as my getter. Again unit tests pass fine, but I'm unsure of the problems I'll face during integration and EF starts building these collections with related entities.
Edit 2 So, I decided to try out suggestions of creating a derived collection (call it ValidatableCollection) implementing ICollection where my .Add() method would perform validation on the entity provided before adding it to the internal collection. Unfortunately, Entity Framework invokes this method when building the navigation property - so it's not really suitable.
I would create collection class exactly for this purpose:
OtherEntityCollection : Collection<OtherEntity>
{
protected override void InsertItem(int index, OtherEntity item)
{
// do your validation here
base.InsertItem(index, item);
}
// other overrides
}
This will make much more rigid, because there will be no way to bypass this validation. You can check more complex example in documentation.
One thing I'm not sure is how to make EF create this concrete type when it materializes data from database. But it is probably doable as seen here.
Edit:
If you want to keep the validation inside the entity, you could make it generic through custom interface, that the entity would implement and your generic collection, that would call this interface.
As for problems with EF, I think the biggest problem would be that when EF rematerializes the collection, it calls Add for each item. This then calls the validation, even when the item is not "added" as business rule, but as an infrastructure behavior. This might result in weird behavior and bugs.
I suggest returning to ReadOnlyCollection<T>. I've used it in similar scenarios in the past, and I've had no problems.
Additionally, the AsEnumerable() approach will not work, as it only changes the type of the reference, it does not generate a new, independent object, which means that this
MyEntity m = new MyEntity();
Console.WriteLine(m.Others.Count()); //0
(m.Others as Collection<OtherEntity>).Add(new OtherEntity{ID = 1});
Console.WriteLine(m.Others.Count()); //1
will successfully insert in your private collection.
You shouldn't use AsEnumerable() on HashSet, because collection can be easily modified by casting it to ICollection<OtherEntity>
var values = new MyEntity().Entities;
((ICollection<OtherEntity>)values).Add(new OtherEntity());
Try to return copy of a list like
return new ReadOnlyCollection<OtherEntity>(_others.ToList()).AsEnumerable();
this makes sure that users will recieve exception if they will try to modify it. You can expose ReadOnlyCollection as return type enstead of IEnumerable for clarity and convenience of users. In .NET 4.5 a new interface was added IReadOnlyCollection.
You won't have big integration issues except some component depend on List mutation. If users will call ToList or ToArray, they will return a copy
You have two options here:
1) The way you are currently using: expose the collection as a ReadOnlyCollection<OtherEntity> and add methods in the MyEntity class to modify that collection. This is perfectly fine, but take into account that you are adding the validation logic for a collection of OtherEntity in a class that just uses that collection, so if you use collections of OtherEntity elsewhere in the project, you will need probably need to replicate the validation code, and that's a code smell (DRY) :P
2) To solve that, the best way is to create a custom OtherEntityCollection class implementing ICollection<OtherEntity> so you can add the validation logic there. It's really simple because you can create a simple OtherEntityCollection object that contains a List<OtherEntity> instance which really implements the collection operations, so you just need to validate the insertions:.
Edit: If you need custom validation for multiple entities you should create a custom collection which receives some other object that perform that validation. I've modified the example below, but it shouldn't be difficult to create a generic class:
class OtherEntityCollection : ICollection<OtherEntity>
{
OtherEntityCollection(Predicate<OtherEntity> validation)
{
_validator = validator;
}
private List<OtherEntity> _list = new List<OtherEntity>();
private Predicate<OtherEntity> _validator;
public override void Add(OtherEntity entity)
{
// Validation logic
if(_validator(entity))
_list.Add(entity);
}
}
EF can't map property without setter. or even private set { } requires some configuration. keep models as POCO, Plain-Old like DTO
the common approach is to create separated service layer that contain validation logic against your Model before save.
for sample..
public void AddOtherToMyEntity(MyEntity myEntity, OtherEntity otherEntity)
{
if(myService.Validate(otherEntity)
{
myEntity.Others.Add(otherEntity);
}
//else ...
}
ps. You could prevent compiler to do somethings but not other coders. Just made your code explicitly says "don't modify Entity Collection directly, until it passed validation"
Finally have a suitable working solution, here's what I did. I'll change MyEntity and OtherEntity to something more readable, like Teacher and Student where I want to stop a teacher teaching more students than they can handle.
First, I created an interface for all entities that I intend to validate in this way called IValidatableEntity that looks like this:
public interface IValidatableEntity
{
void Validate();
}
Then I implement this interface on my Student because I'm validating this entity when adding to the collection of Teacher.
public class Student : IValidatableEntity
{
public virtual Teacher Teacher { get; set; }
public void Validate()
{
if (this.Teacher.Students.Count() > this.Teacher.MaxStudents)
{
throw new CustomException("Too many students!");
}
}
}
Now onto how I invoke validate. I override .SaveChanges() on my entity context to get a list of all entities added and for each invoke validate - if it fails I simply set its state to detached to prevent it being added to the collection. Because I'm using exceptions (something I'm still unsure of at this point) as my error messages, I throw them out to preserve the stack trace.
public override int SaveChanges()
{
foreach (var entry in ChangeTracker.Entries())
{
if (entry.State == System.Data.EntityState.Added)
{
if (entry.Entity is IValidatableEntity)
{
try
{
(entry.Entity as IValidatableEntity).Validate();
}
catch
{
entry.State = System.Data.EntityState.Detached;
throw; // preserve the stack trace
}
}
}
}
return base.SaveChanges();
}
This means I keep my validation code nicely tucked away within my entity which will make my life a whole lot easier when mocking my POCOs during unit testing.
I am quite new to the FNH and NH world, so be gentle :P
I have created an application using FNH for data access which works good while not using lazy-loading, however once I enable lazy-loading everything goes pear shaped (as in, no sessions are open when I attempt to access the lazy-loaded properties etc).
The application layout I have created thus-far has a "Database" singleton which has various methods such as Save(), Refer() and List().
When calling Refer() a session is opened, the data is retrieved and the session is disposed; meaning there is no session available when attempting to access a lazy-loaded property from the returned object. Example: Database.Refer("username").Person since Person is lazy-loaded and the session has already closed.
I have read that Castle has a SessionManager that could be used for this very scenario but, either it's the late nights or lack of coffee, I can't seem to work out how to hook up FNH to use this manager as, in the spirit of castle, everything is defined in config files.
Am I missing something, or can this not be done? Are there any other session managers (or even more appropriate conventions) that I should look at?
Thanks for any help on this matter.
I don't think that your particular problem is connected with the SessionManager as you've already mentioned that you are capable of starting a new session and disposing it whenever needed.
From what I can understand of your post is that you are trying to expose an entity to your view (with some lazy-loaded properties) - which is already a bad idea because it leads to nasty LazyInitializationException(s).
You should consider making a distinguishion between your data-model and your domain model. The key concept has been described on this blog:
Ayende # Rahien
http://ayende.com/blog/4054/nhibernate-query-only-properties
If you say that you are writing a very simple 2-tier application then it probably will not harm if you will micro-manage your session in the data-layer (but keep in mind that this is not the best solution).
I would also look into the query that fetches your entity, as it seems to me that your are trying to obtain data that is just a part of your model - in this case Person. This can lead into serious problems like n+1 selects:
What is SELECT N+1?
So in general I think you should focus more on how things are structured in your application instead of searching for a SessionManager as it will not resolve all of your problems.
For any of you who are still looking for answers on this, I will share with you what I have so far.
This is only a very simple overview of the framework that I have decided to use, and is by far not the only solution for this problem.
The basic layout of my code is as follows:
NHibernate Repository
(references my model assembly and the UoW assembly)
Based on the HibernatingRhino's Repository implementation modified to suit my needs. Found here: http://ayende.com/Wiki/Rhino+Commons.ashx
public T Get(Guid Id)
{
return WrapUOW(() =>
{
using (Audit.LockAudit())
return (T)Session.Get(typeof(T), Id);
});
}
public void LoadFullObject(T partial)
{
if (partial == null)
throw new ArgumentNullException("partial");
if (partial.Id == Guid.Empty)
return;
WrapUOW(() =>
{
using (Audit.LockAudit())
{
LazyInitialiser.InitialiseCompletely(partial, Session);
}
});
}
public T SaveOrUpdate(T entity)
{
using (Audit.LockAudit())
{
With.Transaction(() =>
{
Enlist(entity);
Session.SaveOrUpdate(entity);
entity.HasChanged = false;
});
}
return entity;
}
protected void Enlist(T instance)
{
if (instance != null && instance.Id != Guid.Empty && !Session.Contains(instance))
using (Audit.LockAudit())
{
Session.Update(instance);
}
}
References a neat little helper class called 'Lazy Initializer for NHibernate' found here: http://www.codeproject.com/KB/cs/NHibernateLazyInitializer.aspx
This also contains Extension methods for Save, Delete and LoadFullObject
Have broken standards a little in this assembly by also creating a WrapUOW method to help simplify some of my code
protected static T WrapUOW(Func action)
{
IUnitOfWork uow = null;
if (!UnitOfWork.IsStarted)
uow = UnitOfWork.Start();
T result = action();
if (uow != null)
uow.Dispose();
return result;
}
NHibernate Unit of work
(references my model assembly)
Also based on the HibernatingRhino's UoW implementation and modified to suit
View - not important, just requried for MVVM implementation
Binds the values from the ViewModel
Model
Contains my entity classes and hibernate mapping files
ViewModel
Contains two main view base classes, ListPage and MaintenancePage
The ListPage base class just calls the Repository List method based on the object type we are listing. This loads a dehydrated list of entities.
The MaintenancePage takes an entity instance from the ListPage and calls the Repository.LoadFullObject method to rehydrate the entity for use on the screen.
This allows for the use of binding on the screen.
We can also safely call the Repository.SaveOrUpdate method from this page