Security

Labels
AJAX(112) App Studio(7) Apple(1) Application Builder(245) Application Factory(207) ASP.NET(95) ASP.NET 3.5(45) ASP.NET Code Generator(72) ASP.NET Membership(28) Azure(18) Barcode(2) Barcodes(3) BLOB(18) Business Rules(1) Business Rules/Logic(140) BYOD(13) Caching(2) Calendar(5) Charts(29) Cloud(14) Cloud On Time(2) Cloud On Time for Windows 7(2) Code Generator(54) Collaboration(11) command line(1) Conflict Detection(1) Content Management System(12) COT Tools for Excel(26) CRUD(1) Custom Actions(1) Data Aquarium Framework(122) Data Sheet(9) Data Sources(22) Database Lookups(50) Deployment(22) Designer(177) Device(1) DotNetNuke(12) EASE(20) Email(6) Features(101) Firebird(1) Form Builder(14) Globalization and Localization(6) How To(1) Hypermedia(2) Inline Editing(1) Installation(5) JavaScript(20) Kiosk(1) Low Code(3) Mac(1) Many-To-Many(4) Maps(6) Master/Detail(36) Microservices(4) Mobile(63) Mode Builder(3) Model Builder(3) MySQL(10) Native Apps(5) News(18) OAuth(9) OAuth Scopes(1) OAuth2(13) Offline(20) Offline Apps(4) Offline Sync(5) Oracle(11) PKCE(2) Postgre SQL(1) PostgreSQL(2) PWA(2) QR codes(2) Rapid Application Development(5) Reading Pane(2) Release Notes(183) Reports(48) REST(29) RESTful(29) RESTful Workshop(15) RFID tags(1) SaaS(7) Security(81) SharePoint(12) SPA(6) SQL Anywhere(3) SQL Server(26) SSO(1) Stored Procedure(4) Teamwork(15) Tips and Tricks(87) Tools for Excel(2) Touch UI(93) Transactions(5) Tutorials(183) Universal Windows Platform(3) User Interface(338) Video Tutorial(37) Web 2.0(100) Web App Generator(101) Web Application Generator(607) Web Form Builder(40) Web.Config(9) Workflow(28)
Archive
Blog
Security
Tuesday, December 13, 2011PrintSubscribe
EASE Auditing

Tracking of user activities is a frequent requirement in many line-of-business applications. Knowing when a particular database record has been created, as well as who changed it is crucial in many industries.

Application developers rely on a proven set of techniques to satisfy data auditing requirements. They introduce data auditing columns in relevant database tables and utilize event logs to keep a journal of the system events. Values of auditing columns are updated by the application.

If the field name contains words “create”, “modified” or “update” , it’s purpose is probably to track user actions.

Examples

Consider customer table of Sakila database used to train MySQL developers. Field create_date tells when the record was created. Field last_update indicates when the customer record has changed.

image

If you generate an app based on this database then you will find out that additional manual configuration will be required to prevent users from entering values directly in the input fields and to ensure that fields are updates as intended. Manual configuration of auditing fields is described at /Documents/CB-CreatedBy_CreatedOn_ModifiedBy_ModifiedOn.pdf.

image

Unlimited edition of Code On Time offers a powerful option that makes configuration of auditing fields virtually effortless.

Select your project on the start page of the code generator and choose Settings action.

Select Features and activate EASE Configuration section.

Click on the “here” link under Audit Field Map to create sample map entries. Change the entries as shown below or in the picture.

Modified By User Name =
Modified By User ID =
Modified On Date = last_update
Created By User Name =
Created By User ID =
Created On Date = create_date

image

Click Finish  and regenerate the project. Try to update existing records or create new ones - you will notice that users are not able to change the auditing  fields directly, and that values of the fields are processed automatically.

image

Consider another example.

Almost all tables in the sample Adventure Works database are provided with ModifiedDate column. There are 99 tables and views in the R2 release of this popular training database used to illustrate the capabilities of Microsoft SQL Server 2008.

image

Here is how the audit field ModifiedDate look is presented if you generate a brand new Adventure Works application.

image

Change the Audit Field Map as follows:

Modified By User Name =
Modified By User ID      =
Modified On Date          = ModifiedDate
Created By User Name  =
Created By User ID       =
Created On Date           =

Generate the app and the same screen will look differently. Try editing records and observe that the field value is changed accordingly.

image

Understanding Audit Field Map

Audit Field Map associates predefined “logical” field names with the patterns of actual field names of the application database tables and views.

The “logical” field names are self-explanatory. If your application does not require a specific type of auditing then do not enter a pattern matching the logical audit field or delete the logical field from the map.

The pattern is treated as a regular expression. If you are consistently using the same field names for auditing purposes then enter the full field names as patterns. If there are variations of the fields conceptually playing the same audit role then separate the full names or name fragments with the symbol of “|” (do not include  double quotes).

Let’s build an application with the complete set of audit fields in at least one database table.

Start by creating a Web Site Factory application using the Northwind Sample.

Locate the Products page in the generated web application and begin editing a product record. Here is our version of the Products page.

image

Modify the Products table in SQL Server Management Studio by adding the six audit columns that follow right after the Discontinued column in the next screenshot. Allow null values in the new columns and save the changes.

image

Code generator does not know that the database has changed. We need to refresh the application baseline.

Select the project name on the start page of the code generator and choose Refresh action.

image

Toggle the check box next to the name of the data controller Products and click OK button.

image

Generate the application and select a product record one more time. New fields are now available and clearly require additional configuration.

image

Select the project on the start page of the code generator and choose Settings. Proceed to Features | EASE Configuration and enter the following Audit Field Map.

Modified By User Name = ModifiedByUserName
Modified By User ID      = ModifiedByUserId
Modified On Date          = ModifiedOnDate
Created By User Name  = CreatedBy
Created By User ID       = CreateId
Created On Date           = CreatedOn

Generate the web application and edit the exact same record.

If you compare the previous screen with the next one then you will notice that we are left with the four visible audit fields. The two fields designed to capture user IDs are not visible. Both CreateId and ModifiedByUserId are hidden from the application end users. The rest of the new audit fields are blank.

Click OK button to save the record.

image

Start editing the same record one more time. If you are signed in as user admin then your will see “admin” in the fields Created By and Modified By User Name.

Notice that both field names are rendered as links. If you click on a link then the default email software installed on your computer will be activated with the user’s email in the “To” input of the new message.

image

The physical ID of the user has been captures as well.

image

We suggest that you experiment edited data under different user identities to see the audit processing in action.

If you start creating new records then the audit fields will be prepopulated right away.

Future Audit Enhancements

The impressive ease of EASE Auditing will not stop here.

We are working on adding three major auditing enhancements that will become available in February of 2012.

  1. Access By/On columns will allow tracking the event of the web application user selecting a master record on the page. Typically users are looking for a record and then drill in the details. This moment in time and the user identity will be captured when needed. A perfect example of such event is a customer service representative accessing a customer account. Nothing has been changed but the fact of a person looking at the data is captured.
  2. Deleted By/On columns will help implementing a “soft delete”. The application will detect the presence of such fields and will convert “Delete” action into “Update” of the field values. The application will also filter out the “deleted” rows. Many businesses are now trying to avoid a loss of important business information and “soft delete” is a popular technique.
  3. Code On Time  web applications have unique knowledge of the data and actions that pass from the client to the server. The upcoming release of Dynamic Access Control List will also introduce an automatic logging of all user actions as a part of EASE Auditing.
Sunday, September 4, 2011PrintSubscribe
URL Hashing

Powerful and simple mechanism of data controller URL parameters allows easy manipulation of the page behavior in Code On Time web applications. For example, consider Products page at http://northwind.cloudapp.net/pages/products.aspx. If you navigate to the page and login as admin/admin123% then you will see the following screen with a list of products.

image

If you want to navigate to a specific product then try the following link. The same page will open in edit mode on the product with the primary key equal to 7.

http://northwind.cloudapp.net/pages/products.aspx?ProductID=7&_controller=Products&_commandName=Edit&_commandArgument=editForm1

image

Data controller parameters offer a simple and powerful method of affecting user interface presentation. Couple that with robust Access Control Rules implementation and your web app will provide an excellent and secure mechanism of navigation to specific records.

Sometimes you may want to prevent any possibility of external commands sent to your application via URL parameters unless the commands were initiated by the web application itself or an external “friendly” source.

Enabling URL Hashing

Unlimited edition of Code On Time offers a new feature called URL Hashing, which is available as a component of EASE (Enterprise Application Services Engine). The purpose of URL Hashing is to ensure that only encrypted commands can be passed in the URL of your web application.

Start the web application generator, select your project and click Next button a few times until your reach the Features page. Enable URL Hashing under EASE Configuration.

image

Proceed to generate the project.

Navigate to any page of your web application displaying data and try passing any parameter in the URL. For example, if you are looking at Products.aspx page then change the URL in the address bar of the browser to Products.aspx?AnyParam=Hello and hit enter key to navigate to the page.

You will see the following response.

image

All applications pages are now protected and will not allow inclusion of any URL parameters.

Internal URL Parameters

Your web application may be using URL parameters for its own purpose. For example, configure a Navigate action in the Northwind sample as explained next.

Select your project on the start page of the web app generator and click Design. Select Products data controller on All Controllers tab and activate Action Groups page.

Select action group ag1 with the scope of Grid and switch to Actions tab.

Add a new action with the following properties. Note that Command Argument must be entered without line breaks.

Property Value
Command Name Navigate
Command Argument ?ProductID={ProductID}&_controller=Products&_commandName=Edit&_commandArgument=editForm1
Header Text Edit Product

The value of command argument instructs the application to navigate to the current page that hosts the data controller view and pass the ProductID of selected row in the URL. The other URL parameters will force the data controller to open the specified product in editForm1 in Edit mode.

Save the new action, exit the Designer and generate your project.

Navigate to Products page and select the context menu of a product row.

image

Your browser will navigate to the currently active page with the URL that looks as follows. Notice that there is a parameter “_link” with the cryptic looking value in the address bar of the browser.

image

URL Hashing mechanism embedded in your application only allows this particular parameter and demands that the value of “ _link” parameter is encrypted.

Alter any portion of the parameter or add any additional URL parameters and the request will fail to display the page with the same HTPP error code 403 presented above.

External URL Parameters

Sometimes you may need to pass URL parameters from an external web application. If URL Hashing feature is enabled then this task becomes impossible unless you encrypt the URLs passed by external web application.

The implementation of encryption can be found in the StringEncryptorBase class of your application source code. The partial code below shows encryption key (Key) and initialization vector (IV). Both properties are passed by methods Encrypt and Decrypt as arguments to the default implementation of Advanced Encryption Standard (AES) available in Microsoft.NET Framework.

C#:

public class StringEncryptorBase
{
    
    public virtual byte[] Key
    {
        get
        {
            return new byte[] {
                    253,
                    124,
                    8,
                    201,
                    31,
                    27,
                    89,
. . . . 153}; } } public virtual byte[] IV { get { return new byte[] { 87, 84, 163, 98, 205,
. . . . 112}; } } public virtual string Encrypt(string s) { . . . . . .
} public virtual string Decrypt(string s) {
        . . . . . .
    }
}

VB:

Public Class StringEncryptorBase

    Public Overridable ReadOnly Property Key() As Byte()
        Get
            Return New Byte() {253, 124, . . . ., 153}
        End Get
    End Property

    Public Overridable ReadOnly Property IV() As Byte()
        Get
            Return New Byte() {87, 84, 163, 98, . . . ., 112}
        End Get
    End Property

    Public Overridable Function Encrypt(ByVal s As String) As String
. . . . .
End Function Public Overridable Function Decrypt(ByVal s As String) As String
. . . . .
End Function End Class

Copy this class to the external application and make sure to encrypt the URLs that are passed to a Code On Time web application with URL Hashing enabled.

You may also want to change the key and initialization vector. The default values are hard-coded and shared by all applications generated with Unlimited edition of Code On Time.

Conclusion

URL Hashing is always performed for History and permalinks. If the application is generated without URL Hashing enabled then the permalinks are simply encoded with base-64 encoding to mask the nature of URL parameters.

Advanced encryption with hash code validation will be performed on links create with View Details command and on results of any actions with Command Name set to Navigate.

Tuesday, July 26, 2011PrintSubscribe
Access Control Rules

Web app generator Code On Time introduces Access Control Rules, the first component of EASE (Enterprise Application Services Engine). EASE components simplify implementation of enterprise-class features in line-of-business web applications created with Code On Time and require little or no programming at all.

Introduction

Why do we need access control rules?

Many line-of-business applications start with a simple spreadsheet that allows performing data analysis or calculations to satisfy a specific business requirement. The spreadsheet turns in an application as soon as business users realize that the spreadsheet must accept data input from multiple users. A database is set up and application user interface is built on top of it.

In a few instances the entire database contents may be available to all authorized application end users. In most situations there is a need to create a set of restrictions that would separate slices of data that are available to individual users or users in a given business role.

For example, a web application administrator may be authorized to see the entire list of customers. A sales person will likely be allowed to see only the customers that he or she has a relationship with.

Another example is a line-of-business web application integrated with a content management system such as DotNetNuke or Microsoft SharePoint. The application data must be isolated by a portal or site name. Data created by all  CMS users is stored in the same database tables but under no circumstances shall the users see each other’s records.

Preparing Database for Access Control

A common approach to facilitate access control implementation is to add columns reflecting ownership of data. For example, you can implement User ID column in each table of your database.

If more than one user has a relationship with a data record then developers opt to implement a dedicated table linking a a data record with multiple users.

A typical relational database structure provides certain natural means of establishing access control based on database table relationships. It may be sufficient to implement just one ownership column. For example, consider the following snippet from the Northwind Sample.

Access Control Rules

Table Orders has a reference to an Employee. If a web application user is in the role of Sales then one can make the following assumptions:

  • an employee shall see only his or her orders
  • an employee shall see only customers that have matching orders placed by the employee
  • a subset of order details are naturally visible to an employee when an order is selected
  • if an employee is viewing the global list of order details then only orders of her customers shall be accessible.

The described access control rules can be implemented if we associate each Employees table record with a User ID.

Code On Time allows integrating Microsoft ASP.NET Membership in the generated applications.  User accounts and roles are stored in dedicated tables and user and role management APIs are already built in ASP.NET. Let’s incorporate ASP.NET Membership plumbing in the application database.

Change Employees table to implement two additional columns UserID and UserName. Notice that if you are developing with a database other than Microsoft SQL Server then you may need to choose a different types for UserID and UserName columns. For example, MySQL implementation of ASP.NET Membership uses int as the type of the membership user ID.

Access Control Rules 2

Start Code On Time and begin creating a new Web Site Factory project . On the Database Connection page of project wizard press on a button next to connection string input field.

image

Set the database connection string and press the Add button to create membership tables and stored procedures in the application database.

image

Continue application configuration until you reach the Data Controllers summary page in the project wizard.

Creating a Business Rules Class

Access Control Rules in Code On Time web applications are implemented as methods in  business rules classes. These methods can be shared by all data controllers of your application or designed to address the needs of a specific data controller.

We will consider both situations and will show the real-live examples of custom and shared access control rules.

We will start by creating a business rules class associated with Customers data controller.

Click on Start Designer  button and select Customers data controller on All Controllers tab of Project Designer.

Enter CustomersBusinessRules in Handler field under Business Rules section and click OK button to persist changes.

image

Click Exit button at the top of the screen and click Next to generate the project.

The implementation of business rules will require source code text editing and can be done in Notepad or a development tool such as Visual Studio or Visual Web Developer.

If you do not have a development tool on your computer then click “open” under Actions column of the Code On Time start page. 

image

Select App_Code\Rules\CustomersBusinessRules.cs(vb) file in Windows Explorer and open the file in Notepad.

If you do have a development tool listed above then simply click “develop” link to activate the development tool and open the same file in Solution Explorer.

image

The extension of the file depends on your programming language. Double click the file name to open the file in the editor.

Next we will show examples in both C# and Visual Basic demonstrating various way of implementing access control rules.

Challenges in Implementing Access Control Rules

Modern database software make it exceptionally easy to select data. The application developer writes a data selection statement in declarative language SQL. Such statements typically list the source tables, table columns, and table join instructions. 

Data selection statements are frequently enhanced with filters to present the data that users would like to see.

There is this pesky requirement to segment the data based on user identity or business role. A developer will have to incorporate access control filters in every single data selection statement and foresee all sorts of business requirements that may call for exceptions to some of the filters.

Because of that, developers end up writing their data selection statement as stored procedures persisted in the database. These stored procedures are fundamentally basic selection statements enhanced with numerous checks and conditions to ensure proper access control.

Code On Time applications create SQL selection statements on-the-fly and incorporate user-defined adaptive filters, search criteria, and sort order. Selection statements are enhanced with parameters to prevent any possibility of an injection attack that plagues many applications with hand-written SQL.

It is impossible to write a stored procedure that accepts an unknown number of filtering and sorting parameters to match on-the-fly SQL statements created by your Code On Time web application. Access Control Rules are designed specifically to address the need to apply access control restrictions to dynamic SQL statements.

Next we will show you several examples of Access Control Rules and will implement filtering based on user identity and business role.

Restricting Access by a Single Value

Consider the list of customers presented in the following screenshot. We can see a list of customers from many countries.

List of customers without restrictions

Let’s limit this list of customers to USA only and have this rule apply to all application users.

Enter the following method in the CustomersBusinessRules.cs(vb) and save the file.

C#:

using System;
using System.Data;
using System.Collections.Generic;
using System.Linq;
using MyCompany.Data;

namespace MyCompany.Rules
{
    public partial class CustomersBusinessRules : MyCompany.Data.BusinessRules
    {
        [AccessControl("Customers", "Country")]
        public void CountryFilterThatAppliesToEverybody()
        {
            RestrictAccess("USA");
        }
    }
}

Visual Basic:

Imports MyCompany.Data
Imports System
Imports System.Collections.Generic
Imports System.Data
Imports System.Linq

Namespace MyCompany.Rules

    Partial Public Class CustomersBusinessRules
        Inherits MyCompany.Data.BusinessRules

        <AccessControl("Customers", "Country")> _
        Public Sub CountryFilterThatAppliesToEverybody()
            RestrictAccess("USA")
        End Sub
    End Class
End Namespace

The name of the method plays no role. Use you imagination to ensure that it will be easy to understand the purpose of the method in the future. The method is decorated with AccessControl attribute .

The first parameter of the attribute constructor indicates the data controller that will use this access control method. The second parameter indicates the field that will be filtered by the access control method.

If the data controller is requested to retrieve data then it will scan the list of methods of the associated business rules class and find those that are decorated with the matching AccessControl attribute. By default the data controller will assume that the method is designed to allow access to data. The data controller will invoke the method and if any calls to RestrictAccess are executed then every restriction will be incorporated in the access control filter of SQL data selection statement. In this instance the method will ensure that all users are restricted to see customers from USA regardless of their identity or business role.

List of customers with access restricted to USA only.

Notice that the list of options available in the adaptive filter is considerably shorter. Any user-defined filters will be applied on top of the filters produced by Access Control Rules. Access control filter in the example is activated in all instances of requests to retrieve a list of customers.

Access control methods can utilize any sort of logic to decide if a restriction is applicable at any given moment. For example, a filter can prevent access to the list of customers during non-working hours. The following access control method will allow access to the list of customer from 9 AM though 5:30 PM. If the data is being accessed outside of this time period then a restriction by a non-exiting country is activated.

C#:

[AccessControl("Customers", "Country")]
private void CountryFilterThatAppliesToEverybody()
{
    DateTime today = DateTime.Now;
    DateTime workDayBegins = new DateTime(today.Year, today.Month, today.Day, 9, 00, 00);
    DateTime workDayEnds = new DateTime(today.Year, today.Month, today.Day, 17, 30, 00);
    if (workDayBegins <= today && today <= workDayEnds)
        RestrictAccess("USA");
    else
        RestrictAccess("*****");

}

Visual Basic:

<AccessControl("Customers", "Country")> _
Private Sub CountryFilterThatAppliesToEverybody()
    Dim today As DateTime = DateTime.Now
    Dim workdayBegins As DateTime = New DateTime(today.Year, today.Month, today.Day, 9, 0, 0)
    Dim workdayEnds As DateTime = New DateTime(today.Year, today.Month, today.Day, 17, 30, 0)
    If (workdayBegins <= today And today <= workdayEnds) Then
        RestrictAccess("USA")
    Else
        RestrictAccess("*****")
    End If
End Sub

Multiple AccessControl attributes can be applied to the same method.

If several methods with AccessControl attribute are discovered then the data controller will incorporate SQL data selection statement restrictions by concentrating them with “and” logic.

Restricting Access by Multiple Values

If more than one value must be used to filter out the data then simply call RestrictAccess method multiple times and pass the values that are compatible with the data type of the field specified in AccessControl attribute.

The following method will limit the list of customers to three specific IDs when presenting the list to users with roles other than Administrators.

C#:

[AccessControl("CustomerID", AccessPermission.Allow)]
protected void NonAdministrativeUsersAreAuthorizedToSeeOnlyThreeCustomers()
{
    if (UserIsInRole("Administrators"))
        UnrestrictedAccess();
    else
    {
        RestrictAccess("GREAL");
        RestrictAccess("OLDWO");
        RestrictAccess("THEBI");
    }
}

Visual Basic:

<AccessControl("CustomerID", AccessPermission.Allow)> _
Protected Sub NonAdministrativeUsersAreAuthorizedToSeeOnlyThreeCustomers()
    If UserIsInRole("Administrators") Then
        UnrestrictedAccess()
    Else
        RestrictAccess("GREAL")
        RestrictAccess("OLDWO")
        RestrictAccess("THEBI")
    End If
End Sub
This example is using UnrestrictedAccess method to indicate that the restriction does not apply to Administrators. Access control methods will only result in data filtering if at least one call to RestrictAccess has been made. You can use UnrestrictedAccess method to negate the result of the access restriction previously applied within the same method execution path.

Here is the slightly shorter version of the method restricting access to specific customer accounts for non-administrative users. If a user does not belong to the role Administrators, then no restrictions will be imposed on the the list of customers.

C#:

[AccessControl("CustomerID", AccessPermission.Allow)]
protected void NonAdministrativeUsersAreAuthorizedToSeeOnlyThreeCustomers()
{
    if (!UserIsInRole("Administrators"))
    {
        RestrictAccess("GREAL");
        RestrictAccess("OLDWO");
        RestrictAccess("THEBI");
    }
}

Visual Basic:

<AccessControl("CustomerID", AccessPermission.Allow)> _
Protected Sub NonAdministrativeUsersAreAuthorizedToSeeOnlyThreeCustomers()
    If Not UserIsInRole("Administrators") Then
        RestrictAccess("GREAL")
        RestrictAccess("OLDWO")
        RestrictAccess("THEBI")
    End If
End Sub

The screenshot shows the list of customers presented to a user with role Users.

image

Denying Access

The previous examples of access control rules are specifying AccessPermission.Allow value passed as a parameter of AccessControl attribute. This is default permission of access control rules and can be omitted.

There is always a possibility that a data access exception must be implemented. For example, you may need to prevent administrators from being able to access The Big Cheese customer from USA. The following method does just that.

C#:

[AccessControl("CustomerID", AccessPermission.Deny)]
public void ExceptionForAdministrators()
{
    if (!UserIsInRole("Administrators"))
        UnrestrictedAccess();
    else
        RestrictAccess("THEBI");
}

Visual Basic:

<AccessControl("CustomerID", AccessPermission.Deny)> _
Public Sub ExceptionForAdministrators()
     If (Not UserIsInRole("Administrators")) Then
        UnrestrictedAccess()
    Else
        RestrictAccess("THEBI")
    End If
End Sub

If both “allow” and “deny” access control rules are imposing restrictions at runtime, then the data controller will compose an access control filter such as the one below.

(List of  “Allow” restrictions) and Not (List of “Deny” restrictions)

Restricting Access via Parameterized SQL

Our examples are using static values to compose restrictions. In real world situations you may need to examine a user identity and figure the appropriate restriction value on the fly.

It may be impractical to invoke RestrictAccess method for each restriction due to a very large number of such restrictions.

You can solve both problems by incorporating SQL statements in the definition of AccessControl attribute.

For example, you may want to consider restricting non-administrative accounts to see only those customers that they have a relationship with. The following method makes an assumption that the user with User Name = ‘user’ is a sales person and shall see only customers matching Employee ID = 1.

The total number of records in the Customers table of Northwind database is 91. If you implement the method presented next then the user with the name “user” will only see 65 records. Make sure to comment out or delete any previously defined access control methods described above to prevent the cumulative effect of access control restrictions.

C#:

[AccessControl("CustomerID", Sql = 
"select distinct CustomerID from Orders where EmployeeID = @EmployeeID")] public void LimitUserToSeeOnlyHerCustomers() { if (!UserIsInRole("Administrators")) if (Context.User.Identity.Name == "user") RestrictAccess("@EmployeeID", 1); else UnrestrictedAccess(); }

Visual Basic:

<AccessControl("Customers", "CustomerID", _
    "select distinct CustomerID from Orders where EmployeeID = @EmployeeID", _
    AccessPermission.Allow)> _
Public Sub LimitUserToSeeOnlyHerCustomers()
    If (Not UserIsInRole("Administrators")) Then
        If Context.User.Identity.Name = "user" Then
            RestrictAccess("@EmployeeID", 1)
        Else
            UnrestrictedAccess()
        End If
    End If
End Sub

The data controller will inject SQL statement defined in AccessControl attribute in the data selection statement.

The value of parameter @EmployeeID is assigned by the code in the access control method.

If you call method RestrictAccess with two arguments then the first argument is the name of the parameter in Sql statement of the attribute and the second argument is its value.

The data controller will compose a restriction that looks as follows.

“Customers.CustomerID” in (select distinct CustomerID from Orders where EmployeeID = @EmployeeID)

The expression in AccessControl attribute is inserted as-is. It is up to you to ensure that the expression is valid and will return a correct list of values.

Implementing Restrictions for Northwind Sample

Let’s implement access control rules in the Northwind sample that rely on columns User ID and User Name, implemented previously in this article.

We will make a few changes to the web application design.

Run Code On Time web application generator and select your project.

Click the Next button twice to reach Business Logic Layer page in the project wizard. Select “Generate a shared business rules class …” check box and continue pressing Next until you reach the summary of Data Controllers in your project.

Click Start Designer button to activate the project designer.

Select Employees data controller and activate Fields tab.

Select UserID field, activate Field tab, and change its Items Style to User ID Lookup under Lookup section.

Enter UserName=UserName in the Copy field right under the Items Style. This instruction will ensure that the value of UserName field from Membership Manager will be copied into Employees.UserName field.

image

Scroll down and modify the Security section if you want to ensure that only Administrators are able to assign/create users while editing the Employees record. The following configuration will put no restrictions on who can see the user name and will enable business users with role Administrators to change the user account associated with the employee.

image

Click OK and select this field one more time. Activate Data Fields tab.

Change both instances of the UserID data field to reference UserName field as an alias of UserID. Select each binding (data field) and edit Alias property under General section.

image

Bind UserID field to grid1 view to see the User Name associated with each record in Employees table when viewing the list of employees. Click New|New Data Field and proceed to create a data field (binding) linking field UserID to view grid1. Make sure to leave the field Category blank and choose UserName as the field’s Alias. Press OK.

Your list of data fields associated with field UserID will look as follows.

image

Select Controller:Employees link in the path of the Designer and select UserName field on the Fields tab.

Delete both bindings of UserName field to views editForm1 and createForm1. These bindings are shown prior to deletion in the next screenshot.

image

Exit the Project Designer and generate the project.

Sign-in as admin/admin123% and associate user account user with the employee with the last name of Davolio.

image

Open the CustomersBusinessRules.cs(vb) file in the text editor and delete any previously defined access control rules. Also, replace the base class with MyCompany.Rules.SharedBusinessRules. This will ensure that any global shared access control rules will be inherited in custom business rules associated with Customers data controller.

The new version of CustomersBusinessRules is presented next.

C#:

using System;
using System.Data;
using System.Collections.Generic;
using System.Linq;
using MyCompany.Data;

namespace MyCompany.Rules
{
    public partial class CustomersBusinessRules :
MyCompany.Rules.SharedBusinessRules { } }

Visual Basic:

Imports MyCompany.Data
Imports System
Imports System.Collections.Generic
Imports System.Data
Imports System.Linq

Namespace MyCompany.Rules

    Partial Public Class CustomersBusinessRules
        Inherits MyCompany.Rules.SharedBusinessRules

    End Class

End Namespace

Refresh the project tree in Solution Explorer if you are using Visual Studio or Visual Web Developer.

Open file ~/App_Code/Rules/SharedBusinessRules.cs(vb) in the editor.

This file implements MyCompany.Rules.SharedBusinessRules class. The data controller implementation of your web application will create an instance of this class when preparing to process requests from any data controllers of your project if a dedicated data controller is not available.

We will implement a collection of access control rules to perform consistent data selection restrictions based on user identity.

C#:

using System;
using System.Data;
using System.Collections.Generic;
using System.Linq;
using MyCompany.Data;

namespace MyCompany.Rules
{
    public partial class SharedBusinessRules : MyCompany.Data.BusinessRules
    {

        public object UserID
        {
            get
            {
                return System.Web.Security.Membership.GetUser().ProviderUserKey;
            }
        }

        [AccessControl("EmployeeID", Sql =
            "select EmployeeID from Employees where UserID = @UserId")]
        [AccessControl("CustomerID", Sql =
            @"select distinct CustomerID from Orders 
            inner join Employees 
            on Orders.EmployeeID = Employees.EmployeeID 
            where Employees.UserID = @UserID")]
        public void RestrictByEmployeeIdAndCustomerId()
        {
            if (!UserIsInRole("Administrators"))
                RestrictAccess("@UserID", UserID);
        }
    }
}

Visual Basic:

Imports MyCompany.Data
Imports System
Imports System.Collections.Generic
Imports System.Data
Imports System.Linq

Namespace MyCompany.Rules
    
    Partial Public Class SharedBusinessRules
        Inherits MyCompany.Data.BusinessRules

        Public ReadOnly Property UserID As Object
            Get
                Return System.Web.Security.Membership.GetUser().ProviderUserKey
            End Get
        End Property

        <AccessControl(Nothing, "EmployeeID", _
            "select EmployeeID from Employees where UserID = @UserID")> _
        <AccessControl(Nothing, "CustomerID", _
            "select distinct CustomerID from Orders " & _
            "inner join Employees " & _
            "on Orders.EmployeeID = Employees.EmployeeID " & _
            "where Employees.UserID = @UserID")> _
        Public Sub RestrictByEmployeeIdAndCustomerId()
            If Not UserIsInRole("Administrators") Then
                RestrictAccess("@UserID", UserID)
            End If
        End Sub

    End Class
End Namespace

Paste the source code defined above in the ~/App_Code/Rules/SharedBusinessRules.cs(vb), save it and try it in action by signing in as user/user123%.

You will notice that this simple restriction affects uniformly Customers, Order Details, Employees, Employee Territories, and Customer Demographics.

Any data view that has either CustomerID or EmployeeID field will be automatically restricted if the current is not a member of Administrators role.

You can widen the reach of shared restrictions to Order Details if you simply add EmployeeID field to this data controller and mark it as Hidden.

The command text of Order Details data controller is defined as follows. The command is a simple SQL selection statement joining relevant tables with main table dbo.[Order Details].

select
    "OrderDetails"."OrderID" "OrderID"
    ,"Order"."CustomerID" "OrderCustomerID"
    ,"OrderCustomer"."CompanyName" "OrderCustomerCompanyName"
    ,"OrderEmployee"."LastName" "OrderEmployeeLastName"
    ,"OrderShipVia"."CompanyName" "OrderShipViaCompanyName"
    ,"OrderDetails"."ProductID" "ProductID"
    ,"Product"."ProductName" "ProductProductName"
    ,"ProductCategory"."CategoryName" "ProductCategoryCategoryName"
    ,"ProductSupplier"."CompanyName" "ProductSupplierCompanyName"
    ,"OrderDetails"."UnitPrice" "UnitPrice"
    ,"OrderDetails"."Quantity" "Quantity"
    ,"OrderDetails"."Discount" "Discount"
from "dbo"."Order Details" "OrderDetails"
    left join "dbo"."Orders" "Order" on "OrderDetails"."OrderID" = "Order"."OrderID"
    left join "dbo"."Customers" "OrderCustomer" on "Order"."CustomerID" = "OrderCustomer"."CustomerID"
    left join "dbo"."Employees" "OrderEmployee" on "Order"."EmployeeID" = "OrderEmployee"."EmployeeID"
    left join "dbo"."Shippers" "OrderShipVia" on "Order"."ShipVia" = "OrderShipVia"."ShipperID"
    left join "dbo"."Products" "Product" on "OrderDetails"."ProductID" = "Product"."ProductID"
    left join "dbo"."Categories" "ProductCategory" on "Product"."CategoryID" = "ProductCategory"."CategoryID"
    left join "dbo"."Suppliers" "ProductSupplier" on "Product"."SupplierID" = "ProductSupplier"."SupplierID"

We will create a field that uses Order alias of table dbo.Orders to reference Orders.EmployeeID column in SQL Formula.

Start the Project Designer and select Order Details data controller. Activate Fields tab and and choose New|New Field option on the action bar. Enter the following properties for the new field under New Field section and save its settings by pressing OK button.

Notice that we have wrapped the word Order with double quotes to make sure that it will not be misinterpreted by the database server as SQL keyword “order” .

image

The field will be automatically available in all data views of controller Order Details but remain hidden from end users. The presence of the field will allow it to participate in the access control rules.

Generate the application and observe that shared business rules now extend to Order Details as well. Non-administrative users will only see the order details of orders that they have placed in the system.

Availability

The new mechanism of access control rules greatly simplifies creation of a consistent data segmentation in multi-tenant applications that require isolation of database content created by users.

Access Control Rules are available in the Premium and Unlimited editions of Code On Time web application generator. Owners of Free and Standard editions can use Filter Expressions as discussed later.

What’s Next?

Unlimited edition of Code On Time generator will soon be offering Dynamic Access Control List, another powerful component of EASE (Enterprise Application Services Engine).

Dynamic Access Control List is designed to complement Access Control Rules and allow luxury of defining precise access control at runtime. The real world business processes make it difficult to foresee all possible access control restrictions, and most importantly exceptions to the rules at design time.

Dynamic Access Control List will maintain access control rules in the application database. Administrative user interface will enable dynamic creation of access control rules to respond to the business requirements at runtime without making any changes to the application code.

Filter Expressions

Developers working with Free and Standard editions can use Filter Expressions to implement access control rules in their applications.

Filter expressions are defined on a level of a view and can reference properties of business rules classes as parameters.

For example, if you make changes to dbo.Employees table as described at the top of the article and add UserID and UserName columns then you can filter Orders by EmployeeID. You will have to enable shared business rules and define filter expressions on grid1 and editForm1, followed by implementation of EmployeeID property in the SharedBusinessRules class.

Select your project on the start page of Code On Time web application generator, click Design button, select Orders data controller on All Controllers tab, and activate Views tab.

Change both grid1 and editForm1 views to have the following Filter Expression under Sort and Filter section of the property page.

image

Generate your project and change ~/App_Code/Rules/SharedBusinessRules.cs(vb) file to have the following code.

C#:

using System;
using System.Data;
using System.Collections.Generic;
using System.Linq;
using MyCompany.Data;

namespace MyCompany.Rules
{
    public partial class SharedBusinessRules : MyCompany.Data.BusinessRules
    {

        public object UserID
        {
            get
            {
                return System.Web.Security.Membership.GetUser().ProviderUserKey;
            }
        }

        public int EmployeeID
        {
            get
            {
                using (SqlText findEmployee = new SqlText(
                    "select EmployeeID from Employees where UserID = @UserId"))
                {
                    findEmployee.AddParameter("@UserId", UserID);
                    object id = findEmployee.ExecuteScalar();
                    if (DBNull.Value.Equals(id))
                        return -1;
                    else
                        return Convert.ToInt32(id);
                }
            }
        }

    }
}

Visual Basic:

Imports MyCompany.Data
Imports System
Imports System.Collections.Generic
Imports System.Data
Imports System.Linq

Namespace MyCompany.Rules
    
    Partial Public Class SharedBusinessRules
        Inherits MyCompany.Data.BusinessRules

        Public ReadOnly Property UserID As Object
            Get
                Return System.Web.Security.Membership.GetUser().ProviderUserKey
            End Get
        End Property

        Public ReadOnly Property EmployeeID As Integer
            Get
                Using findEmployee As SqlText = New SqlText( _
                    "select EmployeeID from Employees where UserID = @UserId")
                    findEmployee.AddParameter("@UserId", UserID)
                    Dim id As Object = findEmployee.ExecuteScalar()
                    If DBNull.Value.Equals(id) Then
                        Return -1
                    Else
                        Return Convert.ToInt32(id)
                    End If
                End Using
            End Get
        End Property

    End Class
End Namespace

The value of property EmployeeID will be automatically evaluated and passed as a parameter in SQL statements created to render data for presentation in grid1 and editForm1 views of Orders data controller. Here is how a user associated with employee Davolio may see a list of Orders.

image

Class SharedBusinessRules is created to handle requests for all data controllers. This allows referencing EmployeeID as a parameter in other data controllers including EmployeeTerritories, Employees, and Order Details.

Filter expressions lack the flexibility of conditional restrictions available with Access Control Rules and Dynamic Access Control List. You will need to compose filter expressions that use more than one parameter to accomplish an effect of conditional filtering.

If you need to return more than one value for filtering purposes, then change the type of property to be a list of values or an array. Also refer to the property values as shown in the example below.

EmployeeID in @EmployeeID

Filter expressions can be used with Access Control Rules when needed if a presentation of data in a specific view requires additional filtering.