Author |
Share Topic Topic Search Topic Options
|
jsobell
Groupie
Joined: 02-Apr-2009
Location: Australia
Posts: 80
|
Post Options
Quote Reply
Topic: Restricting access to retrieved data - How? Posted: 02-Apr-2009 at 5:32pm |
[Copied in from the Winforms EF forum:]
Originally posted by smi-mark
public class BOSFetch : IEntityServerFetching { public void OnFetching(IdeaBlade.EntityModel.v4.EntityServerFetchingEventArgs args) { if (args.Query.QueryableType is ActiveEntity) { //In here you can cast the args.Query to an EntityQuery<ActiveEntity> //and then you are able to do .Where and all your other query functions. } } }
I assume it's added to
my Thingy.Web project, but it appears to have no effect there. How
would it every get picked up? Does it go in an entity's partial class?
I'm interested in intercepting all client requests and filtering them based on our own per-entity permission set. In Linq to SQL or Ria we would simply add a '.Where' to the .GetClients, but what would be the correct manner in DF? I
only installed this yesterday, so please explain in simple terms, and
assume that I have not read the manual (which I have scanned through
several times but not found a solution). Also, how on earth does the Login feature work? How do I verify the login info? If
you could point me to any relevant doco that would be great, but the
videos on the site are all covering pretty fundamental stuff, so the
more technical customers could probably use a few more advanced ones :) I'm using the SL version, just in case that makes a difference, and it looks excellent.
Cheers, Jason
|
|
jsobell
Groupie
Joined: 02-Apr-2009
Location: Australia
Posts: 80
|
Post Options
Quote Reply
Posted: 03-Apr-2009 at 5:51am |
Ah, I finally got it to work by adding it to the namespaces to the correct parts of the Web.config file. Unfortunately, this doesn't appear to do what I hoped, as it receives the full query rather than the base entity collection being accessed. Should I be doing this restriction in IdeaBlade at all, should I modify the underlying EDM, or is there another approach?
Cheers, Jason
|
|
jsobell
Groupie
Joined: 02-Apr-2009
Location: Australia
Posts: 80
|
Post Options
Quote Reply
Posted: 03-Apr-2009 at 7:22am |
OK, perhaps it's time to go back to Linq2QL. I can't find any way of addressing this issue, it's holding us up, I know it's possible in RIA because we have a working version, and I'm tired of wading through PDF files. Then again, perhaps I'm just p*ssed off because I've spent two days trying to do this in DF and have had no assistance in the forum! Argh!
I'll have one last look tomorrow then I'll call it a day.
Jason
|
|
kimj
IdeaBlade
Joined: 09-May-2007
Posts: 1391
|
Post Options
Quote Reply
Posted: 03-Apr-2009 at 12:14pm |
Currently, there really isn't an easy way to intercept queries and filter based on user permissions. The IEntityQuery is passed into OnFetching, and you're right it's not easy to manipulate the query. In the May RC we will add a Filter property to the query to make it easy to append filtering logic on the server. An IPrincipal for the current user is also passed to OnFetching and is available to other interface implementations and events as well. You can use the Principal.IsInRole method to test the user's permissions. As a workaround for current limitations, adding the filter on the client at the time the query is built/run is probably the best approach.
As for how Login works - first, there's currently no tie-in to ASP.NET authorization services. You can implement the IEntityLoginManager interface on the server. The interface has a single method, Login, which is provided with a credential passed from the client in the client's call to EntityManager.LoginAsync(). Your Login implementation is responsible for taking that credential and validating as needed, and returning an IPrincipal of some sort. That IPrincipal is then used on both client and server for all subsequent requests.
It looks like we don't have a sample of this in our Silverlight or WinClient documentation or learning units, but we can provide one upon request.
|
|
jsobell
Groupie
Joined: 02-Apr-2009
Location: Australia
Posts: 80
|
Post Options
Quote Reply
Posted: 03-Apr-2009 at 3:43pm |
Hi, I'm not quite sure I'm clear on that workaround. Are you saying that I should filter the user's access permissions at the client end?? The idea of a security restriction is that the client is never sent any other users records, so adding 'where record.owner.clientid=thisuser.id' at the client end would be crazy. Does this mean that the system currently gives unrestricted read access to rows in every published table to every remote client? Is there any way a middle-layer can be introduced to modify these accesses, such as subclassing the ADO.NET EF to insert a filter layer?
Cheers, Jason
|
|
kimj
IdeaBlade
Joined: 09-May-2007
Posts: 1391
|
Post Options
Quote Reply
Posted: 03-Apr-2009 at 4:33pm |
DevForce does not give unrestricted access to every row in every table, it leaves determination of access restrictions to the application. The current security support in DevForce allows the application to determine how (and whether) users should be authenticated and what authorization they should receive. It's true that this support needs further enhancement, and in the next Release Candidate query filtering on the server will be provided. We are also looking at providing attribute-based security access similar to that provided in RIA, and other server events and interfaces which may help here.
At this time, with Release Candidate 1, your options in restricting access are unfortunately limited.
We welcome any constructive feedback on this, or other features you see which are either lacking or insufficient for your needs.
|
|
jsobell
Groupie
Joined: 02-Apr-2009
Location: Australia
Posts: 80
|
Post Options
Quote Reply
Posted: 03-Apr-2009 at 10:22pm |
OK, that clarifies things. Role based restrictions via attributes have limited use as it can only apply at column level, and row level is by far the most important, so we assumed that would be available. Allowing client-side joining and querying is a fantastic feature, but it has to work against filtered sets of data. WebServices obviously resolve this by adding their own Where clauses, but the link of ADO EF through to DF appears very hard-linked, so is there a practical workaround in the short term? I don't want to 'assume' a forthcoming solution to security in our product, as security is a core feature and not something to be retro-fitted.
It sounds as though calling this version RC1 may misleading, as you need to add fundamental functionality to make it usable in real-world scenarios, so this is a real disappointment to us. As our planned delivery is at the same time as SL3's release we're stuck choosing a suitable EF provider, so we have to make a call on who we believe will supply a complete working solution that we can base our system on.
The system DevForce uses is very nice indeed, the method of sharing source for entities is a clean and efficient, and functions such as caching entities are brilliant. We would much rather use this product than Ria, but are concerned that we appear to have hit major obstacles in the first few days, so I'm really looking for some positive arguments to say why and how we should stick with DF for our systems. For example, you mention that modifying the query in the IEntityServerFetching.OnFetching is not simple, but I would like to know if it is possible, and if so, how? Using MS Ria is often overly complex, so implementing a workaround for a couple of issues like this in DF is no sweat. Also, Is it feasible to add custom functionality to the webservice used by DF so the client can execute other functions without creating additional WCF services? e.g. Add an "ActivateSurvey(Guid)" function that is not a stored procedure?
Cheers, Jason
|
|
kimj
IdeaBlade
Joined: 09-May-2007
Posts: 1391
|
Post Options
Quote Reply
Posted: 03-Apr-2009 at 10:26pm |
One possible point of confusion which I'd like to clear up. In DevForce the entire LINQ expression (of any complexity) is sent to and executed on the server, and only the results of that query are returned to the client. So for example, a query like the following:
_mgr.Products.Where(p=> p.Id == 1)
means that a similar SQL query is sent to the database, and only the single retrieved entity is returned to the Silverlight client. The filtering is not performed on the client after the data is returned. This also means that application logic can build queries which restrict each query by user or tenant or similar so that only the requested data is shipped across the wire:
_mgr.UserInfo.Where(u=> u.Owner.ClientId == currentUser.ClientId)
The new filtering capability of IEntityServerFetching is intended to allow a developer to take an existing query from the client, with any number of clauses, and easily append additional criteria on the server immediately prior to query execution.
|
|
kimj
IdeaBlade
Joined: 09-May-2007
Posts: 1391
|
Post Options
Quote Reply
Posted: 03-Apr-2009 at 10:42pm |
Additional server-side operations can be added using the InvokeServerMethod(Async) call on the EntityManager.
As an addendum to the above post, in DevForce you choose where you want the query to execute - against the server or cache (or both) - via the QueryStrategy. So you could of course load all entities of a certain type into cache, and then execute LINQ queries locally to filter that data.
You might want to give your account rep a call next week to discuss your concerns or schedule a talk with an architect.
|
|
jsobell
Groupie
Joined: 02-Apr-2009
Location: Australia
Posts: 80
|
Post Options
Quote Reply
Posted: 04-Apr-2009 at 12:15am |
I understand that the entire Linq expression is passed, and the security risk here is that somebody is easily able to modify the client to remove the Where clause, returning every Product, whether we intended them to have access or not.
If we have a wholesaler system we might have the clause: _mgr.Products.Where(p=> p.Id == 1 && p.WholesalerId == mywholesalerid)
The client executing the query may be logged in as Wholesaler #123, so the query the client sends out will be: _mgr.Products.Where(p=> p.Id == 1 && p.WholesalerId == 123)
Now if I hack the client I can change this to _mgr.Products.Where(p=> p.Id == 1 && p.WholesalerId == 321)
giving another client's records.
The solution is to use security at the server end to record the wholesalerID, and ensure every response sent is *always* filtered by wholesaler, effectively giving the currently connected client a projection of the database that only shows their data, giving a hacked server-side query of: _mgr.Products.Where((p=> p.Id == 1 && p.WholesalerId == 321) && p.WholesalerId == 123)
As you say, adding a Filter property to IEntityServerFetching's EntityFetchingEventArgs would presumably modify the Linq expression to add the specified clause. My code might then be something like this (all ficticious, but you get the idea)
void OnFetching(object sender, EntityFetchingEventArgs e) { if (e.Query.ReturnType == typeof(Product)) { int wholesalerid = GetWhilesalerId(HttpContext.Current.User.Identity.Name); iRisk.DataModel.iRiskEntities de = new iRisk.DataModel.iRiskEntities(); e.Query.Filter = new Filter(p => p.Wholesaler.WholesalerID == wholesalerid); } }
In Linq2SQL you can easily override the accessors for the very bottom level collection, so even a complex join will have each low-level table filtered correctly, effectively allowing automatic server-side changing of Customers Join Orders Join Items to (Customers where xxx) join (Orders where yyy) join (Items where zzz)
My (failed) attempt was as follows: public void OnFetching(EntityServerFetchingEventArgs args) { if (args.Query.ReturnType == typeof(Portfolio)) { string uname = args.Principal.Identity.Name; args.Query = (args.Query as EntityQuery<Portfolio>).Include("Client").Where(p => p.Client.ClientID == 1); } }
Unfortunately this breaks when the system tries to load child entities: {value(IdeaBlade.EntityModel.v4.EntityGroupProxy`1[DomainModel.Portfolio]).Where(t => (t.PortfolioId = 1)).OfType().SelectMany(t => t.Securities)} :> Expression of type 'System.Linq.IQueryable`1[DomainModel.Security]' cannot be used for parameter of type 'System.Linq.IQueryable`1[DomainModel.Portfolio]' of method 'System.Linq.IQueryable`1[DomainModel.Portfolio] Where[Portfolio](System.Linq.IQueryable`1[DomainModel.Portfolio], System.Linq.Expressions.Expression`1[System.Func`2[DomainModel.Portfolio,System.Boolean]])'
I seem to be almost there by overriding the CreateQuery of the ADO.Net Entity provider:
public partial class iRiskEntities : global::System.Data.Objects.ObjectContext { public new ObjectQuery<T> CreateQuery<T>(string qry, params ObjectParameter[] parameters) { Debug.WriteLine(">>>>>" + qry); //if (typeof(T) == typeof(Portfolio)) //{ // ObjectQuery<T> oq = base.CreateQuery<T>(qry, parameters); // oq = from pf in oq where ((Portfolio)pf).Client.ClientID == 1) select pf; // return (ObjectQuery<T>)oq; //} //else return base.CreateQuery<T>(qry, parameters); } }
Again, it's just the modification of the ObjectQuery that has me stumped.
I'll experiment with the InvokeServerMethod tonight, and possibly subject you to further questions tomorrow :)
I think the questions I'm asking are not unusual, and the dilema all .NET developers face nowadays is that you frequently have no idea whether you're using a product as the developers intended, or if you've completely missed the point in the 2,800 object definitions and interfaces in a suppliers object model :) The abundance of "auto-generated documentation" (also known as "crap") is stifling, so I was very impressed with your extensive PDF manuals, and this is one aspect that attracted me to DF, so keep up the good work, and can I suggest that you release one soon with real-world example of security, both at basic role and row level, and include some nice demos of integration with MVVM approaches.
Cheers, Jason
|
|
jsobell
Groupie
Joined: 02-Apr-2009
Location: Australia
Posts: 80
|
Post Options
Quote Reply
Posted: 04-Apr-2009 at 12:38am |
As a quick prologue, the following (with very brief testing) seems to address the issue:
public partial class iRiskEntities : global::System.Data.Objects.ObjectContext {
public new ObjectQuery<T> CreateQuery<T>(string qry, params ObjectParameter[] parameters) { if (typeof(T) == typeof(Portfolio)) { ObjectQuery<Portfolio> oq = (ObjectQuery<Portfolio>)base.CreateQuery<Portfolio>(qry, parameters).Where(pf => pf.Client.ClientID == 1); return oq as ObjectQuery<T>; } else return base.CreateQuery<T>(qry, parameters); } }
It's a little crude, but I can't see any other means that maintains the flexibility of allowing client queries unless all client functions go via views with built-in filtering, and even then all updates and deletes would need to be protected via triggers, and the SQL server would need to know the current user's identity.
Cheers, Jason
|
|
jsobell
Groupie
Joined: 02-Apr-2009
Location: Australia
Posts: 80
|
Post Options
Quote Reply
Posted: 04-Apr-2009 at 3:43pm |
One flaw in this plan;there is no HttpContext available to find the currently logged in user in the DF webservice, so I can't see any way of deriving the currently logged in user (either DF identity or the ASP.NET logged-in user). Can you suggest either a way of retrieving this, or (better still) a workaround for the OnFetching problem I listed above?
Unfortunately, waiting until sometime in May to see what happens isn't a practical option for us.
Cheers, Jason
|
|
kimj
IdeaBlade
Joined: 09-May-2007
Posts: 1391
|
Post Options
Quote Reply
Posted: 04-Apr-2009 at 9:09pm |
The Thread.CurrentPrincipal should be set for the CurrentThread by the time CreateQuery is called. If you've implemented the IEntityLoginManager interface you can determine the concrete type of that IPrincipal.
If we had a good workaround to the OnFetching problem I would have told you by now. If you know how to build and manipulate expression trees, you can actually use OnFetching to modify the query. And that's all we've got until early May.
|
|