Checking Permissions in a LightSwitch Application

My sample application is in pretty good shape. Now I want to control who can work with the data. This is accomplished in LightSwitch by setting up permissions and then checking them in code. Beth Massi wrote about security in Implementing Security in a LightSwitch Application, and I don’t want to rehash what she said, so I am just going to focus on permissions.

NOTE: This information applies to LightSwitch Beta 1 ONLY.

We’ll start with a review of the data in the application. The application includes both remote and local data. The remote data consists of the Courses, Customers and Orders tables in the TrainingCourses SQL Server database and the KB articles on a SharePoint site. The local data consists of the Visits and CustomerNotes tables in a local SQL Server Express database.

Figure1

I want there to be two types of users: sales reps and support staff. Sales reps own the relationship with customers so they will have more access to data than support staff. I want to enforce the following rules for each user type:

Sales reps

  • Can view, add, edit and delete visits and customer notes.
  • Can view, add and edit but cannot delete customers and orders.
  • Can view but cannot add, edit or delete courses.
  • Can view, add, edit and delete KBs.

Support staff

  • Can view and add, but cannot edit or delete, visits.
  • Cannot view customer notes.
  • Can view customers but cannot view the year to date revenue for customers.
  • Cannot add or delete customers.
  • Can modify only a customer’s phone and fax number.
  • Can view but not add, edit or delete courses and orders.
  • Can view, add and edit but cannot delete KBs.

LightSwitch controls access to screens and data via permissions. Once you define permissions, you can write code to check the user’s permissions and control what the user can and cannot do or see. For example, if the user does not have permission to view data, you may not let them open a screen. Or you may create a query that only includes the information they can see.

Setting Up Permissions

The first step is to create permissions. Actually, the first step is to write out the rules, which I did above. Then you can decide how many different permissions you need. How granular do you want to get? For example, can I just create CanView and CanModify? Or do I need CanView, CanAdd, CanUpdate and CanDelete? If the ability to modify data is always all or nothing, then I can create a CanModify permission and leave it at that. But if I then need to distinguish between adding, modifying and deleting, I will have to add the three more granular permissions.

You also need to decide on what verbs you are using. What are the actions you are permitting or prohibiting? Is it based on entities or screens? Do I create CanViewCustomers or CanOpenCustomersScreen?

To create permissions, go into the project properties and select the Access Control tab. You then decide whether to use Windows or Forms authentication. The recommendation is to use Windows authentication if your users are on the network. They have logged onto the network and therefore don’t need to be authenticated by the application again. If you use Forms authentication, the application doesn’t know who your users are and so they will need to login. You would then have to create users and roles, just like in an ASP.NET application. LightSwitch uses the ASP.NET Membership and Role providers to perform Forms authentication.

I will use Windows authentication for this post. I will use the following permissions:

  • CanViewCustomers, CanAddCustomers, CanUpdateCustomers, CanDeleteCustomers
  • CanViewOrders, CanAddAndUpdateOrders, CanDeleteOrders
  • CanViewCourses, CanModifyCourses
  • CanViewKBs, CanModifyKBs
  • CanViewNotes, CanModifyNotes
  • CanViewVisits, CanModifyVisits
  • CanViewYTDRevenue
  • CanOnlyUpdateCustomerPhoneAndFax

Figure2

Permission names are one word. I made my display names multiple words. I chose to capitalize the display names not as the result of usability concerns, but because after copying and pasting, I didn’t want to take the time to lower case the non-first words! I made the descriptions more descriptive and more human readable.

I also checked Granted for debug for all of the permissions I want enabled when I test the application. If you leave the permissions unchecked you won’t have them when you press F5. For testing, I will play the role of Sales Rep, who can do anything except delete customers or orders and add, edit or delete a course.

Be aware that in Beta 1, if you click the grey space to the right of the authentication choices, you can select them by accident. Switching from Windows to Forms authentication does not clear out the Granted for debug check boxes, but if you accidentally select Do not enable authentication they are cleared out. Then you have to recheck Granted for debug all over again. This does not happen in later builds, so you shouldn’t see this in Beta 2.

Checking for Permissions at the Screen and Entity Level

Once you have defined permissions, you can now check them in code and control access to information. Let’s start with customers. When the application starts, users see a list of customers. That list includes the calculated year to date revenue for each customer. By clicking the Add button, the user can add a new customer.

Figure3

When the user selects a customer, the customer detail screen appears, with a built-in Save button and a Delete button that I added.

Figure4

I’m first going to check if the user can view customers. I open the CustomerList screen and select CanRunCustomerList from the Write Code button’s drop-down list.

Figure5

This method runs right before the customers list screen appears. The screen appears if this method returns true. So I add code to check if the user has permission to view customers.

 ‘ VB
Public Class Application
  Private Sub CanRunCustomerList(ByRef result As Boolean)
    result = Me.User.HasPermission(Permissions.CanViewCustomers)
  End Sub
End Class

// C#
public partial class Application
{
  partial void CanRunCustomerList(ref bool result)
  { result = this.User.HasPermission(Permissions.CanViewCustomers); }
}
 

I can test this by turning that permission off. Back to the Access Control tab of the Project Designer and uncheck Granted for debug for CanViewCustomers. When I run the application, I do not see the customers, which is the default view. I also don’t see Customer List in the menu.

Figure6

I want to add a CanView check to all of my screens, so I open the CustomerDetail screen and select CanRunCustomerDetail from the Write Code buttons drop-down list. CanRunCustomerList and CanRunCustomerDetail are both methods of the Application class so I can copy the code I just wrote, as long as I remember to add the CustomerId argument. This is required because I use the same screen for both adding and editing customers.

 ‘ VB
Private Sub CanRunCustomerDetail(
  ByRef result As Boolean, ByVal CustomerId As Int32?)
  result = Me.User.HasPermission(Permissions.CanViewCustomers)
End Sub

// C#
partial void CanRunCustomerDetail(ref bool result, int? CustomerId)
{ result = this.User.HasPermission(Permissions.CanViewCustomers); }
 

I can then create the appropriate methods for the remaining screens by copying, pasting and renaming. That is easier than opening each screen.

 ‘ VB
Public Class Application
  Private Sub CanRunCustomerList(ByRef result As Boolean)
    result = Me.User.HasPermission(Permissions.CanViewCustomers)
  End Sub
  Private Sub CanRunCustomerDetail(
    ByRef result As Boolean, ByVal CustomerId As Int32?)
    result = Me.User.HasPermission(Permissions.CanViewCustomers)
  End Sub
  Private Sub CanRunCustomerVisits(ByRef result As Boolean)
    result = Me.User.HasPermission(Permissions.CanViewVisits)
  End Sub
  Private Sub CanRunCourseList(ByRef result As Boolean)
    result = Me.User.HasPermission(Permissions.CanViewCourses)
  End Sub
  Private Sub CanRunCourseDetail(
    ByRef result As Boolean, ByVal CourseId As Int32?)
    result = Me.User.HasPermission(Permissions.CanViewCourses)
  End Sub
  Private Sub CanRunNewOrder(ByRef result As Boolean)
    result = Me.User.HasPermission(Permissions.CanAddAndUpdateOrders)
  End Sub
  Private Sub CanRunNewVisit(ByRef result As Boolean)
    result = Me.User.HasPermission(Permissions.CanModifyVisits)
  End Sub
End Class

// C#
public partial class Application
{
  partial void CanRunCustomerList(ref bool result)
  { result = this.User.HasPermission(Permissions.CanViewCustomers); }
  partial void CanRunCustomerDetail(ref bool result, int? CustomerId)
  { result = this.User.HasPermission(Permissions.CanViewCustomers); }
  partial void CanRunCustomerVisits(ref bool result)
  { result = this.User.HasPermission(Permissions.CanViewVisits); }
  partial void CanRunCourseList(ref bool result)
  { result = this.User.HasPermission(Permissions.CanViewCourses); }
  partial void CanRunCourseDetail(ref bool result)
  { result = this.User.HasPermission(Permissions.CanViewCourses); }
  partial void CanRunNewOrder(ref bool result)
  { result = this.User.HasPermission(
             Permissions.CanAddAndUpdateOrders); }
  partial void CanRunNewVisit(ref bool result)
  { result = this.User.HasPermission(Permissions.CanModifyVisits); }
}

So we have now covered whether or not users can open screens. What about whether or not they can modify data? Those permissions are going to be at the entity level. I open the Customer entity and I can select the Security Methods from the Write Code button’s drop-down list. I add permissions checks to CanRead, CanInsert, CanUpdate and CanDelete.

Figure7

 ‘ VB
Public Class TrainingCoursesDataService
  Private Sub Customers_CanRead(ByRef result As Boolean)
    result = Me.Application.User.HasPermission(
             Permissions.CanViewCustomers)
  End Sub
  Private Sub Customers_CanInsert(ByRef result As Boolean)
    result = Me.Application.User.HasPermission(
             Permissions.CanAddCustomers)
  End Sub
  Private Sub Customers_CanUpdate(ByRef result As Boolean)
    result = Me.Application.User.HasPermission(
             Permissions.CanUpdateCustomers)
  End Sub
  Private Sub Customers_CanDelete(ByRef result As Boolean)
    result = Me.Application.User.HasPermission(
             Permissions.CanDeleteCustomers)
  End Sub
End Class

 // C#
public partial class TrainingCoursesDataService
{
  partial void Customers_CanRead(ref bool result)
  { result = this.Application.User.HasPermission(
             Permissions.CanViewCustomers); }
  partial void Customers_CanInsert(ref bool result)
  { result = this.Application.User.HasPermission(
             Permissions.CanAddCustomers); }
  partial void Customers_CanUpdate(ref bool result)
  { result = this.Application.User.HasPermission(
             Permissions.CanUpdateCustomers); }
  partial void Customers_CanDelete(ref bool result)
  { result = this.Application.User.HasPermission(
             Permissions.CanDeleteCustomers); }
}

I already check if the user can open the customer screens, so why do I need the CanRead method? This method runs before a customer record is read. That includes running queries on it, whether they are default queries or queries I build myself. So here I am saying if you can’t view customers in screens, you shouldn’t be able to query customers or access them in code. Also, checking permissions at the entity level safeguards your data. If you forget to check for permissions, the user can edit data on the screen but the application will check permissions on the middle tier and prevent unauthorized actions.

I then create the corresponding methods for the Orders and Courses entities.

I have three data sources in my application: local SQL Express, remote SQL Server and remote SharePoint. The TrainingCoursesDataService class works with the remote SQL Server data. I need to add similar methods to the ApplicationDataService class to handle permissions for CustomerNotes and Visits and to the SPTrainingCoursesDataService class to handle permissions for KBs.

So I wind up with the following. I have left out the contents of each method, because you have seen that code already.

 ‘ VB
Public Class ApplicationDataService
  Private Sub CustomerNotes_CanRead(ByRef result As Boolean)
  Private Sub CustomerNotes_CanInsert(ByRef result As Boolean)
  Private Sub CustomerNotes_CanUpdate(ByRef result As Boolean)
  Private Sub CustomerNotes_CanDelete(ByRef result As Boolean)
  Private Sub Visits_CanRead(ByRef result As Boolean)
  Private Sub Visits_CanInsert(ByRef result As Boolean)
  Private Sub Visits_CanUpdate(ByRef result As Boolean)
  Private Sub Visits_CanDelete(ByRef result As Boolean)
  End Sub
End Class

Public Class TrainingCoursesDataService
  Private Sub Customers_CanRead(ByRef result As Boolean)
  Private Sub Customers_CanInsert(ByRef result As Boolean)
  Private Sub Customers_CanUpdate(ByRef result As Boolean)
  Private Sub Customers_CanDelete(ByRef result As Boolean)
  Private Sub Courses_CanRead(ByRef result As Boolean)
  Private Sub Courses_CanInsert(ByRef result As Boolean)
  Private Sub Courses_CanUpdate(ByRef result As Boolean)
  Private Sub Courses_CanDelete(ByRef result As Boolean)
    result = Me.Application.User.HasPermission(
             Permissions.CanModifyCourses)
  Private Sub Orders_CanRead(ByRef result As Boolean)
  Private Sub Orders_CanInsert(ByRef result As Boolean)
  Private Sub Orders_CanUpdate(ByRef result As Boolean)
  Private Sub Orders_CanDelete(ByRef result As Boolean)
End Class

Public Class SPTrainingCoursesDataService
  Private Sub KBs_CanRead(ByRef result As Boolean)
  Private Sub KBs_CanInsert(ByRef result As Boolean)
  Private Sub KBs_CanUpdate(ByRef result As Boolean)
  Private Sub KBs_CanDelete(ByRef result As Boolean)
End Class

// C#
public partial class ApplicationDataService
{
  partial void CustomerNotes_CanDelete(ref bool result)
  partial void CustomerNotes_CanInsert(ref bool result)
  partial void CustomerNotes_CanUpdate(ref bool result)
  partial void CustomerNotes_CanRead(ref bool result)
  partial void Visits_CanDelete(ref bool result)
  partial void Visits_CanInsert(ref bool result)
  partial void Visits_CanUpdate(ref bool result)
  partial void Visits_CanRead(ref bool result)
}
public partial class TrainingCoursesDataService
{
  partial void Customers_CanDelete(ref bool result)
  partial void Customers_CanInsert(ref bool result)
  partial void Customers_CanUpdate(ref bool result)
  partial void Customers_CanRead(ref bool result)
  partial void Courses_CanDelete(ref bool result)
  partial void Courses_CanInsert(ref bool result)
  partial void Courses_CanUpdate(ref bool result)
  partial void Courses_CanRead(ref bool result)
  partial void Orders_CanDelete(ref bool result)
  partial void Orders_CanInsert(ref bool result)
  partial void Orders_CanUpdate(ref bool result)
}
public partial class SPTrainingCoursesDataService
{
  partial void KBs_CanDelete(ref bool result)
  partial void KBs_CanInsert(ref bool result)
  partial void KBs_CanUpdate(ref bool result)
  partial void KBs_CanRead(ref bool result)
}

Now I have a full set of permissions checking for screens and entities.

Time to test the application. I have given myself the ability to view and update courses, but not the ability to add or delete them. When I view the course list, there is now no Add button. LightSwitch doesn’t include it on the screen because I don’t have add rights. When I select a course, the Save button is disabled because I don’t have update rights.

Figure8

Figure9

The course detail screen shows courses and related KBs from a SharePoint list. I covered how to do this in my Extending A LightSwitch Application with SharePoint Data post. What if I don’t have the ability to view KB items? To see what happens, I’ll go back to the Access Control tab and uncheck the KB related permissions. Now when I select a course, I do not see KBs. I’m also informed that the current user doesn’t have permission to view them.

Figure10

Checking for Permissions at the Property Level

Let’s get more granular. You can control not only screens and entities, but also individual entity properties. Next, I want to enforce the following rules for support staff:

  • Can view customers but cannot view the year to date revenue for customers.
  • Can modify only a customer’s phone and fax number.

Support staff can view customers but can only modify the phone and fax number. To enforce this, I can make all of the customer information read only except these two properties. I double-click Customers to open it in the Entity Designer. I click on CompanyName and then select CompanyName_IsReadOnly from the Write Code button’s drop-down list.

Figure11

If this method returns true then the property is read-only. So I add the following code:

 ‘ VB
Public Class Customer
  Private Sub CompanyName_IsReadOnly(ByRef result As Boolean)
    result = Me.Application.User.HasPermission(
             Permissions.CanOnlyUpdateCustomerPhoneAndFax)
  End Sub
End Class

// C#
public partial class Customer
{
partial void CompanyName_IsReadOnly(ref bool result)
{ result = this.Application.User.HasPermission(
           Permissions.CanOnlyUpdateCustomerPhoneAndFax);
}

If the user only has permission to update phone and fax, company name will be read only. I add the same code to the corresponding methods for contact name, the various address properties and email. I don’t need to use the PhoneNumber_IsReadOnly and Fax_IsReadOnly methods, because those will never be read only if the user can modify customers.

To test this, I will check both CanUpdateCustomers and CanOnlyUpdateCustomerPhoneAndFax. When I run the application, all of the customer information except phone and fax is read-only. Nice!

Finally, I want to prohibit support staff from seeing the year to date revenue for a customer. I can think of three options for doing this:

  1. I can have two versions of each customer screen, one with year to date revenue and one without. When the user views the customer list or an individual customer, I can check the permission and run the appropriate screen.
  2. I can have two versions of each customer query, one with year to date revenue and one without. When the user views the customer list or an individual customer, I can check the permission and then run the appropriate query.
  3. I can continue to use one version of each screen and query. When the user views the customer list or an individual customer, I can check the permission and then hide the year to date revenue field if necessary.

The third option is the easier one, so I will go with that. I open the CustomerDetail screen in the Screen Designer and I see that the year to date revenue control has an IsVisible property. All I need to do is set this to false if the user doesn’t have permission to see the data.

Figure12

To do that, I can simply add the following code to the screen’s Loaded event handler:

 ‘ VB
Public Class CustomerDetail
  Private Sub CustomerDetail_Loaded()
    Me.FindControl("YearToDateRevenue").IsVisible =
      Me.Application.User.HasPermission(
      Permissions.CanViewYTDRevenue)
End Class

// C#
public partial class CustomerDetail
{
  partial void CustomerDetail_Loaded()
  {
    this.FindControl("YearToDateRevenue1").IsVisible = 
      this.Application.User.HasPermission(
      Permissions.CanViewYTDRevenue);
  }
}

Year to date revenue appears in two screens: CustomerDetail and CustomerList. Visual Basic lets me name it YearToDateRevenue in both screens, but C# doesn’t. So in C# I named it YearToDateRevenue1 in the CustomerDetail screen and YearToDateRevenue2 in the CustomersList screen.

In the CustomerList screen, the year to date revenue control is contained in a grid. So I need to use FindControlInCollection rather than FindControl. FindControlInCollection takes two arguments: the name of the control and the row of data it is in. I will need to loop through all of the rows since year to date revenue is a column in the grid.

 ‘ VB
Public Class CustomerList
  Private Sub CustomerList_Loaded()
    For Each customerRow As IEntityObject In Me.CustomerCollection
      Me.FindControlInCollection(
        "YearToDateRevenue", customerRow).IsVisible =
        Me.Application.User.HasPermission(
        Permissions.CanViewYTDRevenue)
    Next
  End Sub
End Class

// C#
public partial class CustomerList
{
  partial void CustomerList_Loaded()
  {
    foreach (IEntityObject customerRow in this.CustomerCollection)
    {
      this.FindControlInCollection(
        "YearToDateRevenue2", customerRow).IsVisible =
        this.Application.User.HasPermission(
        Permissions.CanViewYTDRevenue);
    }
  }
}

I then uncheck CanViewYTDRevenue in the Access Control screen and run the application. I do not see year to date revenue on either customer screen.

Figure13

Figure14

Summary

Access control is a big, and important, feature in LightSwitch. As a developer, you have several ways to control what users can and can’t do. You can check permissions at a relatively high level. You can also check them at more granular levels as well. Hopefully, this post has provided you with good ideas on how to add access control to your LightSwitch applications.