locked
Best approaches to row level security in LS HTML app RRS feed

  • Question

  • We're creating an HTML5 LS app that tracks HR incidents.  Each incident will have investigator(s) assigned to them, and a reasonable request has been raised by the business team-- can we set things up so that an investigator can only see those incidents they've been assigned to?

    Regrettably this request has come in after the app has been under development for some time, so no plumbing has been put in place to accommodate it.  We were planning to do screen-level security for the app, whereby certain people could only see certain information-- but they could at least see _something_ for every incident in the system.

    That's how it goes and now we have to figure out how to accomplish this.  I know that the data tables have their _CanRead methods, so that sounds promising here, though from what I see it appears to have an all-or-nothing 'result' value.

    Does that mean a user can see all rows in the Incident table, or none of them?  Is it possible to do something else, where you get a subset of the Incident rows based on the roles a user is in-- maybe something in the Incidents_Filter method?

    Also, it might make sense for us to break the app up a little, so that investigators don't have to go through all the screens but can instead have a quick-look version with the screens that make sense for them.  Is it possible to have two entry points into an LS HTML client, or can two HTML clients share the same backend?



    Friday, October 10, 2014 8:19 PM

Answers

  • Hi jim, about "so that an investigator can only see those incidents they've been assigned to" I think you could maybe customize incidents pre-process query.

    In a project of mine (just a POC so far) I have Activities and Participants, every participant is a Person who has to be previously mapped to a login user name by an app administrator.

    As I want participants being able to see only their activities I've customized _ApplicationDataService.lsml.vb as follows:

    Built a function to retrieve Person id starting from logged in user

    Private Function GetLoginPersonId() As Integer
         Dim id As Integer
         Dim loggedInPerson As List(Of Person) = Me.DataWorkspace.ApplicationData.PersonByUserNameQuery(Application.User.Name).Execute.ToList()
         If loggedInPerson.Count = 1 Then id = loggedInPerson(0).Id
         Return id
    End Function

    In the above code, PersonByUserNameQuery is just a Person query filtered by a User Name parameter which corresponds to a column in the Person table, compiled by the app admin.

    Coded pre-process query on Activities, something like:

    Private Sub Activities_PreprocessQuery(ByRef query As System.Linq.IQueryable(Of LightSwitchApplication.Activity))
          Dim personId As Integer = GetLoginPersonId()
          query = From a In query
                  Where a.Participants.Any(Function(p) p.Person.Id = personId)
    End Sub

    In your own Participants-like table you could additionally provide other columns to have further fine grained control (CanEditComments, CanMarkAsCompleted, ...) assigning to Person different rights over different activities.

    You can apply this kind of check in _Filter query method to avoid any indirect access and/or mix it with login level auth testing if Application.User.HasPermission.

    Far be it from me to know if this is a best-approach, anyway I hope this can help you to find a solution.


    Marco

    • Marked as answer by jim bancroft Sunday, October 12, 2014 4:45 PM
    Saturday, October 11, 2014 8:30 AM

All replies

  • Hi jim, about "so that an investigator can only see those incidents they've been assigned to" I think you could maybe customize incidents pre-process query.

    In a project of mine (just a POC so far) I have Activities and Participants, every participant is a Person who has to be previously mapped to a login user name by an app administrator.

    As I want participants being able to see only their activities I've customized _ApplicationDataService.lsml.vb as follows:

    Built a function to retrieve Person id starting from logged in user

    Private Function GetLoginPersonId() As Integer
         Dim id As Integer
         Dim loggedInPerson As List(Of Person) = Me.DataWorkspace.ApplicationData.PersonByUserNameQuery(Application.User.Name).Execute.ToList()
         If loggedInPerson.Count = 1 Then id = loggedInPerson(0).Id
         Return id
    End Function

    In the above code, PersonByUserNameQuery is just a Person query filtered by a User Name parameter which corresponds to a column in the Person table, compiled by the app admin.

    Coded pre-process query on Activities, something like:

    Private Sub Activities_PreprocessQuery(ByRef query As System.Linq.IQueryable(Of LightSwitchApplication.Activity))
          Dim personId As Integer = GetLoginPersonId()
          query = From a In query
                  Where a.Participants.Any(Function(p) p.Person.Id = personId)
    End Sub

    In your own Participants-like table you could additionally provide other columns to have further fine grained control (CanEditComments, CanMarkAsCompleted, ...) assigning to Person different rights over different activities.

    You can apply this kind of check in _Filter query method to avoid any indirect access and/or mix it with login level auth testing if Application.User.HasPermission.

    Far be it from me to know if this is a best-approach, anyway I hope this can help you to find a solution.


    Marco

    • Marked as answer by jim bancroft Sunday, October 12, 2014 4:45 PM
    Saturday, October 11, 2014 8:30 AM
  • I had a situation like this and WCF RIA Services solved it quite nicely. Basically when you create a WCF RIA service method, that is pointed to a LightSwitch table, you by pass any security you would normally have on the table.

    This is how I can make a Entity that shows only certain fields of a table but prevents a person from seeing or updating other fields of the same table.

    The best example I have of this is:

    LightSwitch Survey: Handling Complex Business Logic Using WCF RIA Services


    Unleash the Power - Get the LightSwitch 2013 HTML Client / SharePoint 2013 book

    http://LightSwitchHelpWebsite.com

    Sunday, October 12, 2014 2:21 PM
  • I'm a bit late to this thread, but this blog post by the LS team (if you've not already seen it) describes why the _Filter() method might be the best to achieve this kind of filtering and it should be able to be retrofitted quite easily: Filtering Data using Entity Set Filters

    At the moment there is not built-in support for multiple HTML clients inside of the same LS project. This is a heavily requested feature btw. Although there are some blogs posts around using "hacking" to achieve that, the easiest for now may be to just create a separate LS project and attach that HTML client to the OData service created in the first project. There are however some limitations to be aware of like not being able to access any Queries created over and above the standard tables on the original data source.

    You can also dynamically specify the startup screen inside the default.html file (in the javascript section) based on say a URL parameter. That will allow you multiple entry points into the same HTML client. I can't put my hand on a link where that was described but a bit of searching should reveal some sample code.


    Regards, Xander. My Blog

    Monday, October 13, 2014 9:11 PM
  • Hi Jim - a very simple solution for the basic case - i.e. a general user, and you've already been using the Application.CurrentUser.UserID (or similar) to assign Incidents. 

    • Add a AssignedUserID column to your Incident table if it doesn't already exist, and create a new parameterized query that requires a AssignedUserID match on a compulsory parameter. 
    • Bind this new query to your Incidents grid instead of the Incidents table. 
    • Create a screen variable called UserID. 
    • In the screen create event, set the screen variable to the UserID of the user as retrieved from your Application context (from memory - this will be Application.CurrentUser.UserID, but happy to be corrected).
    • Finally, assign the screen variable to the filter parameter in your data source.

    This will now filter the results for any logged in user to only see their own incidents (assuming you've assigned them to a particular UserID).

    For administrators/managers who need to see all incidents, simply create another screen bound to the table instead of the query.

    Finally, edit the screen navigation to allow people with admin/manager role to run the AllIncidents screen, and allow each user to run the MyIncidents screen. :)

    I know that all might sound a little too good to be true, but I don't think it has to be much more complicated than that.  The only real complexity is assigning the UserID to the Incident in the first place, and I'm assuming you've already done that or something similar. 

    Tuesday, October 14, 2014 4:54 AM
  • Hi Jeremy, just a little warning if you're using this approach in your solutions (and if I'm not missing something...)

    Moving filter logic into screens would still let any authenticated user to read all data by requesting incidents data directly, remember that LS backend is just an OData service:

    http://localhost:51905/ApplicationData.svc/Incidents

    Michael Simons post linked by novascape is about LS V1 but still valid and very clear.


    Marco

    Tuesday, October 14, 2014 6:04 AM
  • These are all great ideas, definitely appreciate everyone's input!
    Tuesday, October 14, 2014 6:19 AM
  • Fair point Marco. Although the users would have to be sophisticated enough to know how to interrogate the OData service without the screen, right?

    This might be an acceptable risk for an internal app compared with the additional complexity of creating a WCF RIA services (as per Michael's suggestion) or intercepting the data pipeline (as you suggested). 

    Tuesday, October 14, 2014 6:34 AM
  • Sure Jeremy, you're right, it was just a note about how authorization logic works.

    Anyway it's always nice and useful to exchange point of views in the forum, and I think we all hope that LS will still rock in the future, don't we?

    I invite every reader of this post to participate to  this question/discussion and to vote on it's related users' voice request.

    Have a nice day everybody


    Marco

    Tuesday, October 14, 2014 6:45 AM
  • Marco - no criticism of your comment was intended.  Not sure what you were trying to say in your most recent response.  *shrug* 

    Tuesday, October 14, 2014 7:51 AM
  • Jeremy, I actually understood there was no criticism at all in your comment :)

    Simply wanted to say that really appreciate learning through forum and exchanging information between users like we all did in the answers to Jim question.

    Very Sorry!! I'm sure the only reason about any possible misunderstanding has to be found in my very poor english, sometimes I'm just not able to make myself clear enough and to explain well my thoughts.

    Btw I just tried to seize the opportunity to point everyone attention to the linked post, started by joshbooker, one of the great LS forum contributors, cause I think it shows a very important and crucial step for LS future!!

    Sorry again, have a nice day :)


    Marco

    Tuesday, October 14, 2014 8:21 AM