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. 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: 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.
A 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. 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.
- Get action on an entity field. This is the typical entity field read, e.g. reading the value of myCustomer.CreditCardNumber. This action is authorized on the GetValue method, so actions on the CurrentValue property of the EntityField(2) object in an entity aren’t authorized. As this is authorization at runtime for the end-user, the developer should access the properties directly and not use the CurrentValue property of an EntityField(2) object. The entity method GetCurrentFieldValue() also calls into the authorization logic, as well as entity fields which are bound to controls, like columns in a grid.
- Set action on an entity field. This is the typical entity field write, e.g. setting the value of myCustomer.CreditCardNumber. This action is authorized on the SetValue method. Setting an entity field to a value using the entity method SetNewFieldValue() is also calling into the authorization logic, as well as setting a field to a value through databinding scenario's. See also Get action on an entity field.
- Fetching of an entity (the data) into an entity class instance. This is the action where an entity is fetched from the database into an entity
class instance, either into an entity collection or in a single entity
class instance.
- Inserting a new entity to the database. This is the action where a new entity is saved into the database.
- Updating an entity in the database. This is the action where an existing entity is changed in memory and the changes are saved to the database. This action also describes the feature where entities are updated directly into the database.
- Deleting an entity from the database. This is the action where an existing entity is deleted from the database and also describes the feature where entities are deleted directly from the database using a single call.
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
(e.g. through partial classes), or add your action-authorizing code to an
Authorizer class which is plugged into the entity,
e.g. by using
Dependency Injection.
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. It's recommended 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.
- OnCanGetFieldValue. This method is called when a Get action on an entity field is about to be performed. By default it calls on an existing Authorizer object the method CanGetFieldValue, or if there's no Authorizer set for the entity object, it will simply return true, which means the action can proceed.
- OnCanSetFieldValue. This method is called when a Set action on an entity field is about to be performed. By default it calls on an existing Authorizer object the method CanSetFieldValue, or if there's no Authorizer set for the entity object, it will simply return true.
- OnCanLoadEntity. This method is called when the particular entity object is about to be filled with entity data from the database. By default it calls on existing Authorizer object the method CanLoadEntity, or if there's no Authorizer set for the entity object, it will simply return true, which means the action can proceed.
- OnCanSaveNewEntity. This method is called when the entity data in the entity object represents a new entity and is about to be saved into the database. By default it calls on an existing Authorizer object the method CanSaveNewEntity, or if there's no Authorizer set for the entity object, it will simply return true, which means the action can proceed.
- OnCanSaveExistingEntity. This method is called when the entity data in the entity object represents an existing entity, the data has been changed in the entity and these changes are about to be saved into the database. By default it calls on an existing Authorizer object the method CanSaveExistingEntity, or if there's no Authorizer set for the entity object, it will simply return true, which means the action can proceed.
- OnCanDeleteEntity. This method is called when the current entity is about to be deleted from the database. By default it calls on an existing Authorizer object the method CanDeleteEntity, or if there's no Authorizer set for the entity object, it will simply return true, which means the action can proceed.
- OnCanBatchUpdateEntitiesDirectly. This method is called when entities of the type of the entity object are updated directly in the database, either by entitycollection.UpdateMulti (Selfservicing) or DataAccessAdapter.UpdateEntitiesDirectly (Adapter). By default it calls on an existing Authorizer object in the entity the method CanBatchUpdateEntitiesDirectly, or if there's no Authorizer set for the entity object, it will simply return true, which means the action can proceed. The framework will use the entity with the values to set as the entity to authorize the call.
- OnCanBatchDeleteEntitiesDirectly. This method is called when entities of the type of the entity object are deleted directly from the database, either by entitycollection.DeleteMulti (SelfServicing) or DataAccessAdapter.DeleteEntitiesDirectly (Adapter). By default it calls on an existing Authorizer object in the entity the method CanBatchDeleteEntitiesDirectly, or if there's no Authorizer set for the entity object, it will simply return true, which means the action can proceed. The framework will use a new entity instance as the entity to authorize the call. For adapter, this has the side effect that developers should use the new DataAccessAdapter.DeleteEntitiesDirectly(Type) overload instead of the overload which accepts a string, if they want to have authorization on this action in adapter.
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:
- Get action on an entity field. If the action is denied, the field will be seen as if it's NULL. If the field's .NET type is a value type (e.g. int, bool), either the type's default value for NULL produced by the TypeDefaultValue class in the generated code will be returned or if the .NET type is Nullable(Of T), null/Nothing will be returned.
- Set action on an entity field. If the action is denied, the field is simply not set to the new value. SetNewFieldValue will return false.
- Fetching of an entity into an entity object. If this action is denied for a fetch of a single entity, the entity object is simply cleared / left empty. This is also the case for DataAccessAdapter.FetchNewEntity(). If this action is denied for a fetch of an entity in a collection fetch (e.g. entitycollection.GetMulti or lazy loading (SelfServicing) or DataAccessAdapter.FetchEntityCollection (Adapter)), it depends on the value returned from the entity method OnGetFetchNewAuthorizationFailureResultHint which will return a FetchNewAuthorizationFailureResultHint value. By default it will return FetchNewAuthorizationFailureResultHint.ThrowAway, which will result in the fact that the entity object is simply not added to the entity collection. You can change this behavior either by overriding this method in the particular entity class, or by returning the proper value from the Authorizer class' method GetFetchNewAuthorizationFailureResultHint. By default, if an Authorizer is set for the entity, OnGetFetchNewAuthorizationFailureResultHint will call into the Authorizer's GetFetchNewAuthorizationFailureResultHint method and return that value.
- Inserting a new entity to the database. If this action is denied, the particular entity is simply ignored for persistence. If more entities are to be persisted, the object persister will move on to the next entity to save. Denying an insert action on an entity could lead to PK-FK sync issues if the PK side (e.g. Customer) is denied to be saved by the current user, however the FK side (e.g. Order) is allowed to be saved by the current user. If that's a possibility in your system, you should throw an ORMSecurityException instead of returning false from the (On)CanSaveNewEntity method if the action has to be denied.
- Updating an entity in the database. If this action is denied on a single entity save (so no batch update), the save follows the same path as with Inserting a new entity to the database: the particular entity is simply ignored for persistence, and the object persister will move on to the next entity. If this action is denied on a batch update (UpdateMulti/UpdateEntitiesDirectly), the complete batch statement isn't executed.
- Deleting an entity from the database. If this action is denied on a single entity delete (so no batch delete), the delete action is simply aborted and will return false, leaving the entity state in-tact. If this action is denied on a batch delete (DeleteMulti/DeleteEntitiesDirectly), the complete batch statement isn't executed.
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 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:
- 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.
- 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.
- 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.
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