Generated code - Setting up and using Authorization

Preface

Authorization is one of those features which should be a first-class citizen in any O/R mapper framework and LLBLGen Pro therefore has full support for fine-grained Authorization. The authorization mechanism of which user or group has which access rights to which elements is a complex topic and it should be very flexible to pick any authorization scheme to work inside the framework, be it a .NET native authorization scheme or your own. LLBLGen Pro's implementation is setup in such a way that the framework has deep knowledge of the authorization concept and at runtime asks for permission to perform a given action. How that permission is checked, i.e. which authorization mechanism is used to check if the current user has the correct credentials, is left to the developer.

Authorization basics

The basic rule of LLBLGen Pro's authorization is that it's transparent: the LLBLGen Pro runtime framework will call out to authorization code to check if a given action can proceed or not and if not, what to do next. This has the advantage that the developer using the LLBLGen Pro generated code solely has to focus on which action should have authorization and also the developer should supply the credentials code, e.g. check the current user against the thread's current principal, or call into an authorization mechanism on another server or other authorization mechanism which checks credentials for the combination user-action.

LLBLGen Pro's authorization is targeted towards user authorization. This means that the current active user the application runs under (or is impersonated to) is performing an action and that action might be against the credentials the current active user has for that action. Typical example is reading the value of an entity field, e.g. myCustomer.CreditCardNumber: is the current user allowed to read that value? To be able to check whether the current user has the right to read the CreditCardNumber value, you have to intercept the call to the CreditCardNumber property. As LLBLGen Pro generates the entity class code for you, this can be a bit of a problem, as you have to override OnGetValue in the entity class itself, which ties your authorization code to the generated code project. What if you want to re-use an existing set of classes? LLBLGen Pro solves this for you with pluggable authorizer classes, which are discussed in a paragraph later in this section.

Authorizable actions

LLBLGen Pro defines the following actions as authorizable. This means that whenever such an action takes place on an entity it will try to call into authorization code to verify if the action can proceed or not.

The next paragraph describes the location of the authorization logic to authorize these actions after which each individual action is described more in detail with these locations so you know precisely where to place which logic to authorize which actions.

Location of your Authorization logic

The actions described in the previous paragraph will make the LLBLGen Pro runtime framework call into the entity the action is applied on to request permission to proceed. You've two choices: add your action-authorizing code to the entity itself, or add your action-authorizing code to an Authorizer class which is plugged into the entity. The framework calls all arrive at the protected virtual (Protected Overridable) method for the particular action. By default, these methods will check if there is an Authorizer object available in the entity and if so, will call the equivalent method on that Authorizer object, or if there's no Authorizer object, they'll simply return true and allow the action. If the method is overriden by you, the logic you've added to that method is used to authorize the action. The following list describes all methods called in the entity classes for a given action and its default behavior. You're adviced to also consult the LLBLGen Pro reference manual for more details on these methods, which are defined in the base classes for entities: EntityBase (SelfServicing) and EntityBase2 (adapter). For all these methods the same rule applies: if the action can proceed (i.e. is allowed for the current user), true is returned, otherwise false.

You can also use the discussed methods on the authorizer of the entity to obtain information if a user is able to perform any of these actions and for example modify your UI based on that information, e.g. display a Save button if the user is allowed to save, disable controls which would alter state of an entity if the user isn't allowed to alter the entity.

Authorization failures

When an action shouldn't proceed because the current active user doesn't have the right to perform the action, the authorization methods described above should return false. This will make the LLBLGen Pro runtime framework abort the action. However, this can have side-effects, for example code which wants to read the value for an entity field expects to receive something in return. If a fetch isn't allowed, should the entity object simply be empty, or shouldn't there be any entity object? The list of actions mentioned above have different results when they're denied for a given user. These results are described below:

In any authorizer method you are allowed to throw an ORMSecurityException exception to abort any transaction in progress. This has the result of you having to be prepared for the fact that there might be ORMSecurityExceptions thrown. The LLBLGen Pro runtime framework won't throw any ORMSecurityExceptions.

Authorizers

Adding authorization logic to entities via hard-coded methods in the entity classes can be inflexible and unwanted. As mentioned above, LLBLGen Pro defines so called Authorizers, which are classes you've to write yourself and which implement a common interface, IAuthorizer. To make life easier for you, you can derive your Authorizer classes from the common base class AuthorizerBase which is a class which simply implements IAuthorizer using virtual methods. This means that you only have to override and implement those methods of the interface which you want to use in your particular authorizer class. The AuthorizerBase class also contains a utility method which converts a .NET Type of an Entity class into an EntityType value. See for more information about the AuthorizerBase class the LLBLGen Pro reference manual.

You're free to add the Authorizer classes to a separate VS.NET project than the generated code.
Setting an entity's Authorizer
If you've decided to use Authorizer classes to place your authorization functionality in, you'll be confronted with the question: how to set the right Authorizer in every entity object? You've three options:

  1. Setting the AuthorizerToUse property of an entity object manually. This is straight forward, but error prone: if you forget to set an authorizer, authorization isn't performed. Also, entities fetched in bulk into a collection are created using the factories so you have to alter these as well. You could opt for overriding OnInitializing in an entity to add the creation of the Authorizer class.
  2. By overriding the Entity method CreateAuthorizer. This is a protected virtual (Protected Overridable) method which by default returns null / Nothing. You can override this method in a partial class or user code region of the Entity class to create the Authorizer to use for the entity. The LLBLGen Pro runtime framework will take care of calling the method. One way to create an override for most entities is by using a template. Please see the LLBLGen Pro SDK documentation for details about how to write templates to generate additional code into entities and in separate files. Also please see Adding Adding your own code to the generated classes for details.
  3. By using Dependency Injection. Using the Dependency Injection mechanism build into LLBLGen Pro, the defined Authorizers are injected into the entity objects for you. This option is the least amount of work.
note Note:
Dependency Injection isn't available on the Compact Framework. To utilize Authorizers on the Compact Framework, you're encouraged to create CreateAuthorizer overrides using include templates.

The following example shows how to write a simple Authorizer and set it up to be used with Dependency Injection.

Authorizer examples

Below are two different Authorizer classes which can be used with either SelfServicing or Adapter. The first checks for get/set actions and the other checks if a user can load an entity's data. The security mechanism used is Thread principal. The Authorizers are setup to be used with Dependency Injection: they're annotated with the DependencyInjectionInfoAttribute. To fully use this example together with Dependency Injection, please consult the Dependency Injection section about how to enable Dependency Injection and how to let the LLBLGen Pro runtime framework know which types (e.g. Authorizers) are usable as types to inject into other types (e.g. all IEntity implementing types). The example below is used with the SqlServer 2005 example database AdventureWorks.
Example 1, EmployeeAuthorizer
This Authorizer is injected as a Singleton, as it's stateless so every EmployeeEntity instance receives the same Authorizer instance. It uses a simple test scheme to test the user name if it starts with 'John'. You can add whatever authorization scheme you'd like instead.

// C#
public class AdventureWorksAuthorizerBase : AuthorizerBase
{
    /// <summary>
    /// Gets the current identity.
    /// </summary>
    /// <returns></returns>
    protected IIdentity GetCurrentIdentity()
    {
        return Thread.CurrentPrincipal.Identity;
    }
}

/// <summary>
/// Class which is used to authorize AdventureWorks entity access. 
/// </summary>
/// <remarks>Used as a singleton.</remarks>
[DependencyInjectionInfo(typeof(EmployeeEntity), "AuthorizerToUse", 
    ContextType = DependencyInjectionContextType.Singleton)]
public class AdventureWorksEmployeeAuthorizer : AdventureWorksAuthorizerBase
{
    public AdventureWorksEmployeeAuthorizer()
    {
    }

    /// <summary>
    /// Determines whether the caller can obtain the value for the field with the index 
    /// specified from the entity type specified.
    /// </summary>
    /// <param name="entity">The entity instance to obtain the value from.</param>
    /// <param name="fieldIndex">Index of the field to obtain the value for.</param>
    /// <returns>True if the caller is allowed to obtain the value, false otherwise</returns>
    public override bool CanGetFieldValue(IEntityCore entity, int fieldIndex)
    {
        bool toReturn = ((EntityType)entity.LLBLGenProEntityTypeValue == EntityType.EmployeeEntity);
        IIdentity currentIdentity = this.GetCurrentIdentity();

        switch((EmployeeFieldIndex)fieldIndex)
        {
            case EmployeeFieldIndex.NationalIdnumber:
                if(currentIdentity.Name.StartsWith("John"))
                {
                    // deny
                    toReturn = false;
                }
                break;
            default:
                toReturn = true;
                break;
        }

        return toReturn;
    } 

    /// <summary>
    /// Determines whether the caller can set the value for the field with the index 
    /// specified of the entity type specified.
    /// </summary>
    /// <param name="entity">The entity instance the field is located in.</param>
    /// <param name="fieldIndex">Index of the field to set the value of.</param>
    /// <returns>true if the caller is allowed to set the value, false otherwise</returns>
    public override bool CanSetFieldValue(IEntityCore entity, int fieldIndex)
    {
        bool toReturn = ((EntityType)entity.LLBLGenProEntityTypeValue == EntityType.EmployeeEntity);
        IIdentity currentIdentity = base.GetCurrentIdentity();

        switch((EmployeeFieldIndex)fieldIndex)
        {
            case EmployeeFieldIndex.SalariedFlag:
                if(currentIdentity.Name.StartsWith("John"))
                {
                    // deny
                    toReturn = false;
                }
                break;
            default:
                toReturn = true;
                break;
        }

        return toReturn;
    }
}
' VB.NET
Public Class AdventureWorksAuthorizerBase 
    Inherits AuthorizerBase
    
    ''' <summary>Gets the current identity.</summary>
    ''' <returns></returns>
    Protected Function GetCurrentIdentity() As IIdentity 
            Return Thread.CurrentPrincipal.Identity
    End Function
End Class

''' <summary>
''' Class which is used to authorize AdventureWorks entity access. 
''' </summary>
''' <remarks>Used as a singleton.</remarks>
<DependencyInjectionInfo(GetType(EmployeeEntity), "AuthorizerToUse", _ 
    ContextType := DependencyInjectionContextType.Singleton)> _
Public Class AdventureWorksEmployeeAuthorizer 
    Inherits  AdventureWorksAuthorizerBase
    
    Public Sub New ()
    End Sub

    ''' <summary>
    ''' Determines whether the caller can obtain the value for the field with the index 
    ''' specified from the entity type specified.
    ''' </summary>
    ''' <param name="entity">The entity instance to obtain the value from.</param>
    ''' <param name="fieldIndex">Index of the field to obtain the value for.</param>
    ''' <returns>True if the caller is allowed to obtain the value, false otherwise</returns>
    Public Overrides Function CanGetFieldValue(entity As IEntityCore, fieldIndex As Integer) As Boolean
        Dim toReturn As Boolean = (CType(entity.LLBLGenProEntityTypeValue, EntityType) = EntityType.EmployeeEntity)
        Dim currentIdentity As IIdentity = Me.GetCurrentIdentity()

        Select Case CType(fieldIndex, EmployeeFieldIndex)
            Case EmployeeFieldIndex.NationalIdnumber
                If currentIdentity.Name.StartsWith("John") Then
                    ' deny
                    ToReturn = False
                End If
            Case Else
                toReturn = true
                break
        End Select
        Return toReturn
    End Function

    ''' <summary>
    ''' Determines whether the caller can set the value for the field with the index 
    ''' specified of the entity type specified.
    ''' </summary>
    ''' <param name="entity">The entity instance the field is located in.</param>
    ''' <param name="fieldIndex">Index of the field to set the value of.</param>
    ''' <returns>true if the caller is allowed to set the value, false otherwise</returns>
    Public Overrides Function CanSetFieldValue(entity As IEntityCore, fieldIndex As Integer) As Boolean
        Dim toReturn As Boolean = (CType(entity.LLBLGenProEntityTypeValue, EntityType) = EntityType.EmployeeEntity)
        Dim currentIdentity As IIdentity = Me.GetCurrentIdentity()

        Select Case CType(fieldIndex, EmployeeFieldIndex)
            Case EmployeeFieldIndex.SalariedFlag
                If currentIdentity.Name.StartsWith("John") Then
                    ' deny
                    ToReturn = False
                End If
            Case Else
                toReturn = true
                break
        End Case

        Return toReturn
    End Function
End Class
Example 2, CreditCardAuthorizer
The following example is the authorizer for the CreditCardEntity and illustrates authorization when fetching the entity. It again uses the simple Thread principal check with the name but you're free to add whatever mechanism you want. The Authorizer inherits from the same utility class defined in the previous example.

// C#
/// <summary>
/// Class which is used to authorize AdventureWorks entity access. 
/// </summary>
/// <remarks>Used as a singleton.</remarks>
[DependencyInjectionInfo(typeof(CreditCardEntity), "AuthorizerToUse", 
    ContextType=DependencyInjectionContextType.Singleton)]
public class AdventureWorksCreditCardAuthorizer : AdventureWorksAuthorizerBase
{
    /// <summary>
    /// Gets the result hint what to do when authorization fails when fetch a new entity.
    /// </summary>
    /// <returns>
    /// any of the FetchNewAuthorizationFailureResultHint values
    /// </returns>
    public override FetchNewAuthorizationFailureResultHint 
        GetFetchNewAuthorizationFailureResultHint()
    {
        // default is throw away. This override gives us empty entities instead.
        return FetchNewAuthorizationFailureResultHint.ClearData; 
    }

    /// <summary>
    /// Determines whether the caller is allowed to load the data into the entity instance specified.
    /// </summary>
    /// <param name="entity">The entity instance to fill with data</param>
    /// <returns>true if the caller is allowed to load the data in the entity specified.</returns>
    /// <remarks>Data inside the entity is the data fetched from the db. If the method 
    /// returns false, the entity will be reset with a new set of empty fields</remarks>
    public override bool CanLoadEntity(IEntityCore entity)
    {
        bool toReturn = ((EntityType)entity.LLBLGenProEntityTypeValue == EntityType.CreditCardEntity);
        IIdentity currentIdentity = this.GetCurrentIdentity();
        if(currentIdentity.Name.StartsWith("John"))
        {
            // deny
            toReturn = false;
        }
        return toReturn;
    }
}
' VB.NET
''' <summary>
''' Class which is used to authorize AdventureWorks entity access. 
''' </summary>
''' <remarks>Used as a singleton.</remarks>
<DependencyInjectionInfo(typeof(CreditCardEntity), "AuthorizerToUse", _
    ContextType:=DependencyInjectionContextType.Singleton)>
Public Class AdventureWorksCreditCardAuthorizer 
	Inherits AdventureWorksAuthorizerBase

    ''' <summary>
    ''' Gets the result hint what to do when authorization fails when fetch a new entity.
    ''' </summary>
    ''' <returns>
    ''' any of the FetchNewAuthorizationFailureResultHint values
    ''' </returns>
    Public Overrides Function GetFetchNewAuthorizationFailureResultHint() _
	    As FetchNewAuthorizationFailureResultHint 
        
		' default is throw away. This override gives us empty entities instead.
        Return FetchNewAuthorizationFailureResultHint.ClearData 
    End Function

    ''' <summary>
    ''' Determines whether the caller is allowed to load the data into the entity instance specified.
    ''' </summary>
    ''' <param name="entity">The entity instance to fill with data</param>
    ''' <returns>true if the caller is allowed to load the data in the entity specified.</returns>
    ''' <remarks>Data inside the entity is the data fetched from the db. If the method returns 
    ''' false, the entity will be reset with a new set of empty fields</remarks>
    Public Overrides Function CanLoadEntity(entity As IEntityCore) As Boolean
        Dim toReturn As Boolean = _
               (CType(entity.LLBLGenProEntityTypeValue, EntityType) = EntityType.CreditCardEntity)
        Dim currentIdentity As IIdentity = Me.GetCurrentIdentity()
        If currentIdentity.Name.StartsWith("John") Then
            ' deny
            toReturn = False
        End If
        Return toReturn
    End Function
End Class


LLBLGen Pro v3.0 documentation. ©2010 Solutions Design