This article introduces the concept of permission elevation in LightSwitch. It presents a scenario at a shipping department of a store to show the need for elevation of privileges. Code samples to add, remove and control the scope of elevation are included.

Permission elevation in server code is a new feature introduced in LightSwitch Beta 2. This feature allows restricting access to entities when manipulated through the UI, but still allows changes via a process that runs on server on behalf of the end user.

Most business applications support multiple users. These users are categorized using roles assigned to them. Permissions assigned to a role will restrict access to portions of the application. Often, there is a need where a user requires elevated permissions to complete certain tasks. In such cases, granting a user higher privileges and promptly removing the privileges as soon as the task is complete, can be tricky.

For example, consider a business application designed for a small or a medium sized departmental store. In the shipping department, the employee who receives the shipment will have access to the Receivables screen. This screen allows the user to input the type and quantity of items received along with other logistics. This user will not have access to any other screens such as the Inventory or Billing or Customer details. Generally, based on the item and quantity received, there will be additional tasks that need to be performed. For instance:

  1. If the shipment contains item ‘A’ of quantity greater than 100 then update the Inventory table immediately.
  2. Increase the price of item ‘B’ by 0.5% when the quantity received is less than 15.
  3. Send an Email to the first customer who is in the waitlist for item ‘C’.

To perform the three additional tasks there are two solutions. First, grant access to all three screens for the person receiving the shipment. Second, a user with access to the Inventory, Billing and Customer details logs in to the application and does the necessary operations. Both these solutions have a disadvantage. The first solution makes the application less secure. It defeats the purpose of roles. The second is possible at the cost of additional resources i.e., have the employee find a co-worker that has access to the Inventory, Billing and Customer screens.

A better solution would be to elevate the access level for the user temporarily at the server. In the shipping department example, the employee finishes entering the details of items received and clicks save. Within this save operation, the system grants additional permissions that the user requires for performing the other three tasks. This elevation of privileges to perform the additional tasks during save is possible in LightSwitch due to availability of various server-pipeline interception methods.

A developer of a LightSwitch application can elevate the permissions within the server-pipeline logic. The developer can choose to control the scope of the elevation within the save operation. Once the save operation concludes the server state vanishes and thus there is no way to make elevation last longer than one save operation.

Add Or Remove Permission:

The following two APIs allow adding and removing of permissions on the current user:

  • AddPermission(params string[] permissions)
  • RemovePermission(params string [] permissions)

Here are some examples that show the usage of these APIs:

Application.Current.User.AddPermissions(Permissions.InventoryMaster, Permissions.CustomerSupport);
Application.Current.User.RemovePermissions(Permissions.InventoryMaster, Permissions.GenerateBill);
Application.Current.User.AddPermissions(Permissions.AllPermissions);
Application.Current.User.RemovePermissions(Permissions.AllPermissions);

Both the APIs return an IDisposable. This allows for calling the ‘Dispose’ method to remove the scope of elevation. Within the scope of elevation, all calls to HasPermission() and DemandPermission() will use the new set of permissions.

Where and Where Not To Elevate Privileges:

LightSwitch allows permission elevation in save related methods that run on the server. Here is a list of such methods:

Data Source Methods:

  • SaveChanges_Executing
  • SaveChanges_Executed

General Methods:

  • <Table Name>_Deleted
  • <Table Name>_Deleting
  • <Table Name>_Inserted
  • <Table Name>_Inserting
  • <Table Name>_Updated
  • <Table Name>_Updating

Permission elevation outside save related methods or in any client side methods will cause a “System.InvalidOperationException: Permissions can only be modified from within save-related user methods”. Permission elevation inside of query operations will cause the same exception. Here is a list of such methods where permission elevation will not be allowed:

General Methods:

  • <Table Name>_Created

Security Methods:

  • <Table Name>_CanDelete
  • <Table Name>_CanExecute
  • <Table Name>_CanInsert
  • <Table Name>_CanRead
  • <Table Name>_CanUpdated

Query Methods:

  • <Table Name>_All_ExecuteFailed
  • <Table Name>_All_Executed
  • <Table Name>_All_Executing
  • <Table Name>_All_PreprocessQuery
  • <Table Name>_Single_ExecuteFailed
  • <Table Name>_Single_Executed
  • <Table Name>_Single_Executing
  • <Table Name>_SingleOrDefault_ExecuteFailed
  • <Table Name>_SingleOrDefault _Executed
  • <Table Name>_SingleOrDefault _Executing
  • <Table Name>_SingleOrDefault_PreprocessQuery
  • <Table Name>_Single_PreprocessQuery

 

Example: Department Store Application

To demonstrate the department store scenario mentioned earlier, a simple LightSwitch application has been created. This application consists of four tables: Inventory, Customer, Billing and Receivable. Corresponding to these tables there are four screens. To control access these screens four permissions have been defined. Figure 1 shows the Access Control tab where permissions are created.

clip_image002

Figure 1 Define Permissions to Control Access to Screens

Control Access to Data:

Using these permissions, the developer can write code that will control the access to these four tables. In the shipping department scenario, the employee should not have permissions to update Inventory and Billing table, and will not have access to read customer information from Customer table. To implement this logic the following code helps:

Visual Basic:

        Private Sub Inventories_CanUpdate(ByRef result As Boolean)
            result = Application.Current.User.HasPermission(Permissions.InventoryMaster)
        End Sub

        Private Sub
Billings_CanUpdate(ByRef result As Boolean)
            result = Application.Current.User.HasPermission(Permissions.GenerateBill)
        End Sub

        Private Sub
Customers_CanRead(ByRef result As Boolean)
            result = Application.Current.User.HasPermission(Permissions.CustomerSupport)
        End Sub

 

C#:

partial void Inventories_CanUpdate(ref bool result)
{
    result = Application.Current.User.HasPermission(Permissions.InventoryMaster);
}

partial void Billings_CanUpdate(ref bool result)
{
    result = Application.Current.User.HasPermission(Permissions.GenerateBill);
}

partial void Customers_CanRead(ref bool result)
{
    result = Application.Current.User.HasPermission(Permissions.CustomerSupport);
}

Control Access to UI:

The shipping department employee should have access only to the Receivable screen. The other three screens should not be available in the navigation menu. This logic is implemented through the code shown below:

Visual Basic:

    Partial Public Class Application
       
Private Sub ReceivableScreen_CanRun(ByRef result As Boolean)
            result =
Application.Current.User.HasPermission(Permissions.ReceiveShipment)
        End Sub

        Private Sub
InventoryScreen_CanRun(ByRef result As Boolean)
            result =
Application.Current.User.HasPermission(Permissions.InventoryMaster)
       
End Sub

        Private Sub
BillingScreen_CanRun(ByRef result As Boolean)
            result =
Application.Current.User.HasPermission(Permissions.GenerateBill)
       
End Sub

        Private Sub
CustomerScreen_CanRun(ByRef result As Boolean)
           
' To access Customer Screen the user should have 'CustomerSupport' permission and
            ' should NOT have 'InventoryMaster' and 'ReceiveShipment' permissions
           
If (Application.Current.User.HasPermission(Permissions.CustomerSupport)) AndAlso
                Not
(Application.Current.User.HasPermission(Permissions.InventoryMaster) AndAlso
                    
Application.Current.User.HasPermission(Permissions.ReceiveShipment)) Then
               
result = True
            Else
               
result = False
            End If
        End Sub
    End Class


C#:

public partial class Application
{
    partial void ReceivableScreen_CanRun(ref bool result)
    {
        result = Application.Current.User.HasPermission(Permissions.ReceiveShipment);
    }

    partial void InventoryScreen_CanRun(ref bool result)
    {
        result = Application.Current.User.HasPermission(Permissions.InventoryMaster);
    }

    partial void BillingScreen_CanRun(ref bool result)
    {
        result = Application.Current.User.HasPermission(Permissions.GenerateBill);
    }

    partial void CustomerScreen_CanRun(ref bool result)
    {
        // To access Customer Screen the user should have 'CustomerSupport' permission and 
        // should NOT have 'InventoryMaster' and 'ReceiveShipment' permissions
        if ((Application.Current.User.HasPermission(Permissions.CustomerSupport)) &&
            !(Application.Current.User.HasPermission(Permissions.InventoryMaster) && 
            Application.Current.User.HasPermission(Permissions.ReceiveShipment)))
        {
            result = true;
        }
        else
        {
            result = false;
        }
    }
}

Grant for debug ‘ReceiveShipment’ permission as shown in Figure 1 and run the application (F5). Now, only ‘Receivable Screen’ will be displayed as shown in the Figure 2:

image

Figure 2 Shipping Department Employee can access Receivable Screen

Permission Elevation in Server Code:

The shipping department employee enters the shipment details and clicks save. During this save, the other three tasks need to be completed. This can be achieved by writing permission elevation code inside ‘Receivables_Inserting’ method as shown below:

Visual Basic:

        Private Sub Receivables_Inserting(entity As Receivable)
            Dim itemsReceived As EntityChangeSet = Me.DataWorkspace.ApplicationData.Details.GetChanges()

            For Each received As Receivable In itemsReceived.AddedEntities
                '  1.   If the shipment contains item ‘A’ of quantity greater than 100
                '       then update the Inventory table immediately.
               
If received.ItemName = "Item A" AndAlso received.UnitsReceived > 100 Then
                    If Not
Application.Current.User.HasPermission(Permissions.InventoryMaster) Then
                       
' Grant 'InventoryMaster'.
                       
Application.Current.User.AddPermissions(Permissions.InventoryMaster)

                        ' Locate 'Item A' record in the Inventory table.
                       
Dim invRec = (From p In Me.DataWorkspace.ApplicationData.Inventories
                                      Where p.ItemName = "Item A" Select p).SingleOrDefault()

                        ' Update Inventory.
                       
invRec.UnitsInStock += received.UnitsReceived
                    End If
                End If

               
'  2.    Increase the price of item ‘B’ by 0.5% when the quantity received is less than 15.
               
If received.ItemName = "Item B" AndAlso received.UnitsReceived < 15 Then
                    If Not
Application.Current.User.HasPermission(Permissions.GenerateBill) Then
                       
' Grant 'GenerateBill'.
                       
Application.Current.User.AddPermissions(Permissions.GenerateBill)

                        ' Locate 'Item B' record.
                       
Dim priceRec = (From b In Me.DataWorkspace.ApplicationData.Billings
                                        Where b.ItemName = "Item B" Select b).SingleOrDefault()

                        ' Increase the price 1.05 times.
                       
priceRec.Price *= 1.05D

                    End If
                End If

               
'  3.    Send an Email to the first customer who is in the waitlist for item ‘C’.
               
If received.ItemName = "Item C" AndAlso received.UnitsReceived > 0 Then
                   
' To access Customer Screen the user should have 'CustomerSupport' permission and
                    ' should NOT have 'InventoryMaster' and 'ReceiveShipment' permissions.

                    ' Just remove all permissions.
                   
Application.Current.User.RemovePermissions(Permissions.AllPermissions)

                    ' Grant only the permission needed to access Customers table.
                   
Application.Current.User.AddPermissions(Permissions.CustomerSupport)

                    ' Extract the email address of the first customer who has Item C in the waitlist.
                   
Dim custRec = (From c In Me.DataWorkspace.ApplicationData.Customers
                                   Where c.WaitlistItems.Contains("Item C") Select c).FirstOrDefault()

                    ' Send Email to the customer.
                   
SendWaitlistEmail(custRec.EmailAddress)
                End If
            Next
        End Sub

C#:

partial void Receivables_Inserting(Receivable entity)
{
    EntityChangeSet itemsReceived = this.DataWorkspace.ApplicationData.Details.GetChanges();

    foreach (Receivable received in itemsReceived.AddedEntities)
    {
        //  1.   If the shipment contains Item A of quantity greater than 100 
        //       then update the Inventory table immediately.
        if (received.ItemName == "Item A" && received.UnitsReceived > 100)
        {
            if (!Application.Current.User.HasPermission(Permissions.InventoryMaster))
            {
                // Grant 'InventoryMaster'.
                Application.Current.User.AddPermissions(Permissions.InventoryMaster);
                
                // Locate 'Item A' record in the Inventory table.
                var invRec = (from p in this.DataWorkspace.ApplicationData.Inventories 
                              where p.ItemName == "Item A" select p).SingleOrDefault();

                // Update Inventory.
                invRec.UnitsInStock += received.UnitsReceived;
            }
        }

        //  2.    Increase the price of Item B by 0.5% when the quantity received is less than 15.
        if (received.ItemName == "Item B" && received.UnitsReceived < 15)
        {
            if (!Application.Current.User.HasPermission(Permissions.GenerateBill))
            {
                // Grant 'GenerateBill'.
                Application.Current.User.AddPermissions(Permissions.GenerateBill);

                // Locate 'Item B' record.
                var priceRec = (from b in this.DataWorkspace.ApplicationData.Billings 
                                where b.ItemName == "Item B" select b).SingleOrDefault();

                // Increase the price 1.05 times.
                priceRec.Price *= 1.05M;
            }

        }

        //  3.    Send an Email to the first customer who is in the waitlist for Item C.
        if (received.ItemName == "Item C" && received.UnitsReceived > 0)
        {
            // To access Customer Screen the user should have 'CustomerSupport' permission and
            // should NOT have 'InventoryMaster' and 'ReceiveShipment' permissions.

            // Just remove all permissions.
            Application.Current.User.RemovePermissions(Permissions.AllPermissions);

            // Grant only the permission needed to access Customers table.
            Application.Current.User.AddPermissions(Permissions.CustomerSupport);

            // Extract the email address of the first customer who has Item C in the waitlist.
            var custRec = (from c in this.DataWorkspace.ApplicationData.Customers 
                           where c.WaitlistItems.Contains("Item C") select c).FirstOrDefault();

            // Send Email to the customer.
            SendWaitlistEmail(custRec.EmailAddress);
        }
    }
}

The above code runs at the server within the save operation. At the client side, the shipping department employee always had only ‘ReceiveShipment’ permission. The elevation happened only at the server. When the save pipeline ends the permission elevation also ceases.

The shipping department scenario demonstrated the need for elevation of privileges. LightSwitch’s permission elevation concept allowed restricting access to the Inventory, Billing and Customer tables but still allowed changes via the elevation that happened through the server code. Thus, LightSwitch gives the developer a simple and efficient way to control access in a business application.