共用方式為


Prevent a Security Hole with Access Control - Securing Logic Tier

I recently joined the Visual Studio LightSwitch team and so far have spent most of my time learning about everything that LightSwitch has to offer. With all that I’ve been learning, I thought this blog would be a perfect opportunity to share some of my new found knowledge. I can’t help but be reminded of my childhood and the lessons taught by the Saturday morning cartoon Schoolhouse Rock! Granted, my blog may be missing the colorful animations, upbeat songs, and unforgettable lyrics, but I do promise to share my experiences as a LightSwitch newbie with an occasional attempt at rhyming; hence the title LightSwitch Rocks!

Inspired by Schoolhouse Rock! episodes with catchy titles such as “Conjunction Junction” and “My Hero, Zero”, my first blog post is appropriately called “Prevent a Security Hole with Access Control.”  This is the first post focuses on securing data on the logic tier.Scenario: Expense Report Tracker

Let’s begin by creating a LightSwitch project called “ExpenseReportTracker” with two entities that are used to track the status of expense reports; note that ExpenseReport has a one-to-many relationship with ExpenseItems. 

Two types of users will use this application:

  • Employees that create and save expense reports for approval
  • Managers that approve or reject employees’ expense reports

When an employee creates and saves an expense report, the report’s Status is automatically defaulted to “Pending.” The assigned Approver (e.g. manager) can then change the Status of the report to either “Approved” or “Rejected.”

image

image

Based on the above scenario, let’s look at the security rules that we want to enforce depending on the type of user:

  1. An employee cannot delete an expense report
  2. An employee cannot modify an expense report’s Status; this prevents employees from approving\rejecting expense reports
  3. Once a manager has set an expense report’s Status to “Approved” or “Rejected”, an employee can no longer make modifications to the report

Creating Permissions

Since we have two types of users, there are two permissions that we need to create in the Application Properties designer; one that will be assigned to employees and another for managers.

image

Enforcing Security on the Logic Tier

Now that we have permissions created for employees and managers, let’s look at how to use these permissions to enforce the security rules for our application.

Using Entity Set “Can” Methods

Our first security rule means:

  • When the current user is assigned to a role with ManageExpenseReportPermission, then the user can delete expense reports; otherwise, the user should be prevented from deleting an expense report

This type of rule applies to all expense reports; not a particular instance of an expense report. Based on this, we can add this permission check to the ExpenseReport entity set’s CanDelete method.  This method is added by opening ExpenseReports in the entity designer and selecting “ExpenseReports_CanDelete” from the “Write Code” drop-down menu.

image

Here’s the code that is written in the “ExpenseReports_CanDelete” method:

C#

partial void ExpenseReports_CanDelete(ref bool result)
{
result = this.Application.User.HasPermission(Permissions.ManageExpenseReportPermission);
}

VB
 Private Sub ExpenseReports_CanDelete(ByRef result As Boolean)
    result = Me.Application.User.HasPermission(Permissions.ManageExpenseReportPermission)
End Sub 

The benefit of using the “Can” method is two-fold; when it returns false, the user is prevented from performing the Delete operation. In addition, the application will automatically disable the Delete button on a screen that is displayed to the user.

If you need to enforce security for a specific entity instance or check a data value on an entity, then you will need to instead use the save pipeline’s methods.

Using Save Pipeline Methods

Our second and third security rules require us to check data on specific entity instances, therefore we will need to use the save pipeline methods. These security rules can be interpreted as follows:

  • When the current user is assigned to a role with EmployeeExpenseReportPermission, the user cannot modify the Status property
  • Similarly, if the user is assigned the EmployeeExpenseReportPermission, the user cannot modify an expense report that has already been approved\rejected

These checks can be added to the ExpenseReports Updating method. This method is added by opening ExpenseReports in the entity designer and selecting “ExpenseReports_Updating” from the “Write Code” drop-down menu.  Notice that this method passes in the current entity instance as a parameter.

C#
 partial void ExpenseReports_Updating(ExpenseReport entity)
{
    if (Application.Current.User.HasPermission(
        Permissions.EmplyeeExpenseReportPermission))
    {
        if (entity.Details.Properties.Status.IsChanged)
        {
            throw new Exception(
                "Permission denied: Unable to change expense report " + 
                "status. Report Id: " + entity.ReportId);
        }

        if (entity.Details.Properties.Status.Value != "Pending")
        {
            throw new Exception(
                "Permission denied: Unable to change an expense report" + 
                 "that has already been 'Approved' or 'Rejected'." + 
                 "Report Id: " + entity.ReportId);
        }
    }
}
VB
 Private Sub ExpenseReports_Updating(entity As ExpenseReport)

If Application.Current.User.HasPermission(
     Permissions.EmployeeExpenseReportPermission) Then If entity.Details.Properties.Status.IsChanged Then Throw New Exception(
               "Permission denied: Unable to change expense " +
               "report status. Report Id: " + entity.ReportId)
          End If If entity.Details.Properties.Status.Value <> "Pending" Then Throw New Exception(
                "Permission denied: Unable to change an expense " +
                "report that has already been 'Approved' or " +
                "'Rejected'. Report Id: " + entity.ReportId)
          End If End If End Sub 

You may have also noticed that we have a gap in our security because an employee can still potentially modify ExpenseItems that belong to an ExpenseReport that has already been approved\rejected. To bridge this gap, we need to add the following code to the “ExpenseItems_Inserting”, “ExpenseItems_Updating”, and “ExpenseItems_Deleting” methods.  These methods are added via the “Write Code” drop-down menu that is available in the entity designer for ExpenseItems.

C#

partial void ExpenseItems_Inserting(ExpenseItem entity) { if (Application.Current.User.HasPermission( Permissions.EmplyeeExpenseReportPermission) && entity.ExpenseReport.Status != "Pending") { throw new Exception("Permission Denied: Unable to modify " + "expense report that has already been 'Approved' or " + "'Rejected'. Report id: " + entity.ExpenseReport.ReportId); } } partial void ExpenseItems_Updating(ExpenseItem entity) { if (Application.Current.User.HasPermission( Permissions.EmplyeeExpenseReportPermission) && entity.ExpenseReport.Status != "Pending") { throw new Exception("Permission Denied: Unable to modify " + "expense report that has already been 'Approved' or " + "'Rejected'. Expense report id:" + entity.ExpenseReport.ReportId); } } partial void ExpenseItems_Deleting(ExpenseItem entity) { if (Application.Current.User.HasPermission( Permissions.EmplyeeExpenseReportPermission) && entity.ExpenseReport.Status != "Pending") { throw new Exception("Unable to modify expense report that has " + "already been 'Approved' or 'Rejected'. Expense report id: " + entity.ExpenseReport.ReportId); } }

VB 
 Private Sub ExpenseItems_Inserting(entity As ExpenseItem)

     If Application.Current.User.HasPermission(
        Permissions.EmployeeExpenseReportPermission) And entity.ExpenseReport.ReportId Then Throw New Exception("Permission Denied: Unable to modify " +
            "expense report that has already been 'Approved' or " +
            "'Rejected'. Report id: " + entity.ExpenseReport.ReportId)
     End If End Sub Private Sub ExpenseItems_Updating(entity As ExpenseItem)

     If Application.Current.User.HasPermission(
        Permissions.EmployeeExpenseReportPermission) And entity.ExpenseReport.ReportId Then Throw New Exception("Permission Denied: Unable to modify " +
              "expense report that has already been 'Approved' or " +
              "'Rejected'. Report id: " + entity.ExpenseReport.ReportId)
     End If End Sub Private Sub ExpenseItems_Deleting(entity As ExpenseItem)

      If Application.Current.User.HasPermission(
         Permissions.EmployeeExpenseReportPermission) And entity.ExpenseReport.ReportId Then Throw New Exception("Permission Denied: Unable to modify " +
             "expense report that has already been 'Approved' or " +
             "'Rejected'. Report id: " + entity.ExpenseReport.ReportId)

      End If End Sub 

This check prevents employees from inserting, updating, and deleting ExpenseItems for a report that already has the Status set to either “Approved” or “Rejected”.

It is important to note that when an exception is thrown in the save pipeline methods that the changes are rolled back and the user is displayed a dialog box with the specified exception message.

Summary

The first step to properly securing an application is to secure your data on the logic tier by adding permission checks.  This post has shown how to do this by adding permission checks to both the entity set “Can” methods and save pipeline methods.

Next time I will extend  the “Expense Report Tracker” scenario to show how you can customize the appearance of screens according to the underlying security permissions for the user. 

Thanks for joining me for my first blog post!