[
Advertise | Submit Code | About us | Contact us | Link us
]
Go!
Membership Services
Login
Register

Home
C# General

General

C# Language

Design & Architecture

Algorithms

Database

Security

Active Directory

COM Interop

Remoting
C# Windows Forms

General

Combo and List boxes

Miscellaneous Controls

Button Controls

Edit Controls
Cutting Edge

ASP.NET 2.0

Visual Studio 2005

Windows Longhorn

SQL Server 2005
C# Multimedia and GDI+

General

DirectX

GDI+

Audio
Internet & Web

General

Images and multimedia

Database

Utilities

Security

ASP.NET Controls

Design and Architecture

Webservices
.NET

General

Design & Architecture

Algorithms

Database

Security

Active Directory

COM Interop

Remoting

ADO.NET

XML.NET

Tools

Enterprise

IDE
Visual Basic .NET

VB.NET General

VB.NET Controls
General Reading

.NET Books Review

Product Showcase

Book Chapters

Business Design & Strategy
Community

Discuss

Job Board

Discussion

CodeXchange
DeveloperLand

Advertise

Submit Code

About us

Contact us

Link us
Miscellaneous

Favorite Links

Downloads

Programming Sites

Top Stories
Regular Expressions

E-Mail

Date/Time
Home > Visual Basic .NET > VB.NET General
.NET Interception
Posted by on Tuesday, January 25, 2005 (EST)

Using the .NET interception plumbing to add access control functionality to an existing monolithic application.

This article has been viewed: 2,480 times
Technology: VB.NET General.

Contents

The application I'm going to talk about in this article is a rather simple one (at least from an architectural point of view). It's used to analyze, display and amend data representing concentrations of various air pollution agents measured at stations spread throughout the Slovak republic

 

The data from the stations are collected and consolidated into one huge SQL Server database, which the application reads and updates directly.

Although the application is monolithic, I was fortunate enough :) to divide the application into layers internally. So there is the user interface layer, the business layer and the data access layer, as usually.

An App class represents my data access layer. It contains methods that connect to the database and execute the application's stored procedures. All methods of the App class are static (Shared).

A Measurement class represents the business layer. The class retrieves data from SQL Server by calling the App class' methods, allows the data to be displayed and manipulated by the user and finally, propagates the data modifications back to the database (by calling the App class, of course).

The application has been used for a couple of months already, when I've got a request to implement a new feature - authorization and access control:

Upon startup, the application should present a logon dialog box and validate the user name and password entered against a new _User table in the database.

Each row in the _User table represents an application user (you guessed that :-)) and contains a varchar column UserEnabled representing encoded access rights for several applications. My application has got the 6th character position in the UserEnabled field. (Please, don't ask me why they designed it that way; I don't know.) The 6th character could have the following values:

6th character Meaning
"0" Read-only access; no modifications
"1" Full access
"2" Restricted acces (details bellow)

Any other value (or missing user record altogether) should cause the application to terminate.

After some thinking, I realized that the access rights represented by the 6th character of the UserEnabled column are directly related to methods exposed by the Measurement class. In other words:

  • Full access means that ALL Measurement methods modifying data might be called.
  • Restricted access means that only SOME of the Measurement methods modifying data might be called.
  • Read-only access means that NO Measurement methods modifying data might be called.
Realizing that, I designed the authorization / access control feature as follows:

A new UserToken class wraps the _User table row and hides the awkward UserEnabled column parsing behind nice, read-only Boolean properties CanChangeDataValues (full access) and CanChangeDataAttributes (restricted access).

The App class exposes a new shared, read-only property User() As UserToken. The property is initialized by a new, shared method App.LogonUser(name, password). The App.LogonUser method does the _User table lookup and creates a new UserToken instance from the _User row found. Here [^] is the relevant code from the App class.

A new UserLogonDialog form enables the user to enter her name and password and then calls the App.LogonUser method. The dialog is displayed at application startup.

The access control algorithm itself was designed with extensibility in mind. Because I'm in love with table-driven algorithms, I have chosen a table-driven approach.

The logic is encapsulated in a new AccessControlGuard class. The class has a private Hashtable instance containing String-typed keys and elements. The keys are the Measurement class' method names. The elements are the UserToken class' property names (they have to return True in order for an access check to be passed):

Public Class AccessControlGuard
Private Shared _MethodToPropertyMap As New Hashtable
Shared Sub New()
  _MethodToPropertyMap.Add("MarkValuesInvalid", "CanModifyDataAttributes")
  _MethodToPropertyMap.Add("UpdateMinValues", "CanModifyDataValues")
  _MethodToPropertyMap.Add("ApplyLineConstants", "CanModifyDataValues")
  _MethodToPropertyMap.Add("MultiplyValues", "CanModifyDataValues")
  _MethodToPropertyMap.Add("UploadMeasuredValues", "CanModifyDataAttributes")
End Sub
Public Shared Function CanExecuteMethod( _
 ByVal method As String) As Boolean
  ' Lookup the UserToken property name that must
  ' return True in order to allow the method to execute.
  Dim PropName As String = CStr(_MethodToPropertyMap(method))
  If PropName Is Nothing Then
    ' The method is not in our table so by definition,
    ' we allow it to be executed (it is not data modifi-
    ' cation method) as long as the user token has ANY
    ' access to the application.
    Return Not App.User.NoAccess
  End If
  ' Invoke the property through reflection.
  Dim PropInfo As PropertyInfo = App.User.GetType().GetProperty(PropName)
  Debug.Assert(Not PropInfo Is Nothing)
  Return CBool(PropInfo.GetValue(App.User, Nothing))
End Function
Public Shared Sub AccessCheck( _
  ByVal method As String)
  If Not CanExecuteMethod(method) Then
    Throw GetAccessDeniedException()
  End If
End Sub
...
End Class
The last very important question remains: How to incorporate the access checks into the existing Measurement class?

The 'classic' approach would be to add the appropriate AccessControlGuard.AccessCheck calls at the start of every data modification method of the Measurement class.

But I didn't do it that way.

Recently, I've read a couple of articles about interception in .NET, and this project seemed to be an ideal medium to try the concepts by hand.

So I went out and implemented the interception code according to recommendations found in the great article "Decouple Components by Injecting Custom Services into Your Object's Interception Chain", by Juval Lowy (see links [^]).

Here are the implementation steps for adding interception to the existing Measurement class:

1. I've implemented the AccessControlServerSink class, which gets called for each method call coming INTO the context, where our Measurement class resides:

Public Class AccessControlServerSink
  Implements IMessageSink
2. I've implemented the AccessControlProperty class, which injects the AccessControlServerSink into the Measurement's context:
Public Class AccessControlProperty
  Implements IContextProperty
  Implements IContributeServerContextSink
3. I've implemented the AccessControlAttribute class, which has to be associated with our Measurement class and which adds the AccessControlProperty to the context:
<AttributeUsage(AttributeTargets.Class)> _
Public Class AccessControlAttribute
  Inherits ContextAttribute
4. I've derived the Measurement class from ContextBoundObject class and I've applied the AccessControlAttribute to it:
<AccessControl.AccessControl()> _
Public Class Measurement
  Inherits ContextBoundObject
Here is the complete code for the classes:
Imports System.Runtime.Remoting.Contexts
Imports System.Runtime.Remoting.Activation
Imports System.Runtime.Remoting.Messaging
Imports System.Reflection
Namespace AccessControl
  ' This attribute is applied to our Measurement class; 
  ' it injects the AccessControlProperty to the context, 
  ' which, in turn, adds the AccessControlServerSink to
  ' the context.
  <ATTRIBUTEUSAGE(ATTRIBUTETARGETS.CLASS)> _
  Public Class AccessControlAttribute
    Inherits ContextAttribute
    Public Sub New()
      ' Call the base class' constructor providing it with
      ' the attribute name.
      MyBase.New("AccessControlAttribute")
    End Sub
    Public Overrides Sub GetPropertiesForNewContext( _
     ByVal ctorMsg As IConstructionCallMessage)
      ' Add our access control property to the new context.
      ctorMsg.ContextProperties.Add(New AccessControlProperty)
    End Sub
    Public Overrides Function IsContextOK( _
     ByVal ctx As Context, _
     ByVal ctorMsg As IConstructionCallMessage) As Boolean
      ' Does the context already have the property and,
      ' is it of valid type?
      Return AccessControlProperty.ExistsInContext(ctx)
    End Function
  End Class

  ' The property injects the AccessControlSink into the context
  ' with which it is associated.
  Public Class AccessControlProperty
    Implements IContextProperty
    Implements IContributeServerContextSink
    Public Const PropertyName As String = "AccessControlProperty"
    Public Sub Freeze( _
     ByVal newContext As Context) _
     Implements IContextProperty.Freeze
    End Sub
    Public Function IsNewContextOK( _
     ByVal newCtx As Context) As Boolean _
     Implements IContextProperty.IsNewContextOK
      ' Verify the context has this property.
      Return ExistsInContext(newCtx)
    End Function
    Public ReadOnly Property Name() As String _
     Implements IContextProperty.Name
      Get
        Return PropertyName
      End Get
    End Property
    ' Shared helper employed by this class as well 
    ' as the AccessControlAttribute class.
    Public Shared Function ExistsInContext( _
     ByVal ctx As Context) As Boolean
      Dim ExistingProperty As IContextProperty = _
       ctx.GetProperty(PropertyName)
      If (ExistingProperty Is Nothing) OrElse _
      (Not TypeOf ExistingProperty Is AccessControlProperty) Then
        Return False
      End If
      Return True
    End Function

    ' Called by CLR when setting up a new context.
    ' Injects our sink into the context's sink chain.
    Public Function GetServerContextSink( _
     ByVal nextSink As IMessageSink) As IMessageSink _
     Implements IContributeServerContextSink.GetServerContextSink
      Return New AccessControlServerSink(nextSink)
    End Function
  End Class

  ' Preprocesses calls to the Measurement class (currently
  ' the only class within the context containing our
  ' AccessControlAttribute context attribute).
  Public Class AccessControlServerSink
    Implements IMessageSink
    Private _NextSink As IMessageSink
    Public Sub New(ByVal nextSink As IMessageSink)
      _NextSink = nextSink
    End Sub
    Public ReadOnly Property NextSink() As IMessageSink _
      Implements IMessageSink.NextSink
      Get
        Return _NextSink
      End Get
    End Property

    Public Function AsyncProcessMessage( _
     ByVal msg As IMessage, _
     ByVal replySink As IMessageSink) As IMessageCtrl _
     Implements IMessageSink.AsyncProcessMessage
      ' Access control not supported (yet:-)
      Return _NextSink.AsyncProcessMessage(msg, replySink)
    End Function

    Public Function SyncProcessMessage( _
     ByVal msg As IMessage) As IMessage _
     Implements IMessageSink.SyncProcessMessage
      ' If AccessCheck returns Nothing, the check has passed
      ' and we allow the method to execute.
      ' If it returns a valid return message, it has the 
      ' Exception member set to an access denied exception;
      ' we return that message and don't execute the method.
      Dim RetMsg As IMethodReturnMessage = Me.AccessCheck(msg)
      If RetMsg Is Nothing Then
        Return _NextSink.SyncProcessMessage(msg)
      Else
        Return RetMsg
      End If
    End Function

    ' Checks to see if execution of the method identified
    ' by the message is allowed. If the execution is allowed,
    ' the method return Nothing. If the execution is denied,
    ' the method returns an IMethodReturnMessage with the
    ' Exception member initialized.
    Private Function AccessCheck( _
     ByVal msg As IMessage) As IMethodReturnMessage
      If Not TypeOf msg Is IMethodCallMessage Then
        Return Nothing
      End If
      Dim CallMsg As IMethodCallMessage = _
       DirectCast(msg, IMethodCallMessage)
      If AccessControlGuard.CanExecuteMethod(CallMsg.MethodName) Then
        Return Nothing
      End If
      Return New ReturnMessage( _
       AccessControlGuard.GetAccessDeniedException(), _
       CallMsg)
    End Function
  End Class

  Public Class AccessControlGuard
    Private Shared _MethodToPropertyMap As New Hashtable
    Shared Sub New()
      _MethodToPropertyMap.Add("MarkValuesInvalid", "CanModifyDataAttributes")
      _MethodToPropertyMap.Add("UpdateMinValues", "CanModifyDataValues")
      _MethodToPropertyMap.Add("ApplyLineConstants", "CanModifyDataValues")
      _MethodToPropertyMap.Add("MultiplyValues", "CanModifyDataValues")
      _MethodToPropertyMap.Add("UploadMeasuredValues", "CanModifyDataAttributes")
    End Sub

    Public Shared Function CanExecuteMethod( _
     ByVal method As String) As Boolean
      ' Lookup the UserToken property name that must
      ' return True in order to allow the method to execute.
      Dim PropName As String = CStr(_MethodToPropertyMap(method))
      If PropName Is Nothing Then
        ' The method is not in our table so by definition,
        ' we allow it to be executed (it is not data modifi-
        ' cation method) as long as the user token has ANY
        ' access to the application.
        Return Not App.User.NoAccess
      End If
      ' Invoke the property through reflection.
      Dim PropInfo As PropertyInfo = App.User.GetType().GetProperty(PropName)
      Debug.Assert(Not PropInfo Is Nothing)
      Return CBool(PropInfo.GetValue(App.User, Nothing))
    End Function

    Public Shared Sub AccessCheck( _
     ByVal method As String)
      If Not CanExecuteMethod(method) Then
        Throw GetAccessDeniedException()
      End If
    End Sub

    Public Shared Function GetAccessDeniedException() As _
     System.Security.SecurityException
      Return New System.Security.SecurityException( _
       "Nemáte oprávnenie vykona požadovaný príkaz.")
    End Function
  End Class
End Namespace

I've built the project, tested with sample data and all kinds of users - everything went well.

I was excited!

I've started to write this article eager to share with you how easy is to implement interception in .NET. I didn't finish the article, however, because I had to go home (my wife called me that we are supposed to visit our friends...:-).

That was yesterday.

Today I came to work to do some more testing and finish the article eventually.

I've run some tests with real data and I've to say that my excitement was gone.

The application turned to be way too slow!

This is not surprising at all, given the way the Measurement class is used within the application. The interception code simply added too much overhead. For example, when the application displays a line graph, the data and attributes for each point are retrieved by calling five Measurement's methods. For 10 000 points that means 50 000 cross-context calls. Huh!

So in the end, I had to remove the interception-related code and I've added the access check to the prolog of every relevant Measurement class' methods.

This is certainly not to say that interception is slow. I still think it's a great technique provided the associated performance penalty is negligible. Remote marshal-by-reference objects, for example, are good candidates for interception, because they are accessed through proxy anyway.

So be careful when considering using interception in your own project. You have to take into account the associated performance overhead. If you are not sure, a quick prototype might be in order. After all, adding the interception code is easy (once you grasp the details, of course).

Obligatory warning: Most of the classes and interfaces discussed here and in the articles referred bellow are officially undocumented, so use them at your own risk. Nevertheless, the fact that they are described in the MSDN Magazine's article might say something, IMHO.

© Palo Mraz, Sunday, August 31, 2003

Links

http://msdn.microsoft.com/msdnmag/issues/03/03/ContextsinNET/default.aspx [^] - great article about .NET contexts and interception.

http://msdn.microsoft.com/msdnmag/issues/02/03/AOP/default.aspx [^] - describes a custom COM interception framework and relates it to the .NET approach.

http://oko.shmu.sk/ [^] - air pollution in the Slovak Republic.

Top Go to Table of Contents

About Palo Mraz

Click here if you want to know more about .

Other articles that may interest you

  • Write a Word Add-In – Part 0
  • Write a Word Add-In – Part I
  • Lengthy Operations on Single Thread in .NET Application
  • Learning Draughts
  • Exceptions and Performance
  • Average Rating :

    Discussion Forums
    Got a programming related question? Hopefully someone has the answer... Want to help out other developers? Visit our discussion forums.

    Sponsored by:

    New Articles

  • Exceptions and Performance
    Almost every time exceptions are mentioned in mailing lists and newsgroups, people say they're really expensive.Let's examine that claim, shall we?

  • Creating multilingual websites - Part 1
    Extend the existing globalization capabilities of .NET to create flexible and powerful multilingual web sites. First, create a custom ResourceManager, and then create custom localized-capable server controls to easily deploy multilingual functionality.

  • Parameter passing in C#
    Many people have become fairly confused about how parameters are passed in C#, particularly with regard to reference types. This page should help to clear up some of that confusion

  • Most Popular Articles

  • LDAP, IIS and WinNT Directory Services
    This article explains how to use .NET Directory Services to retrieve and search directory objects, create new directory objects and edit or delete existing directory objects. Describes Active Directory Application Mode (ADAM) and how to use the IIS, WinNT and LDAP directory (ADSI) provider.

  • An in-depth look at WMI and instrumentation, Part II
    WMI stands for Windows Management Instrumentation and, as the name indicates, is about managing your IT infrastructure this article is the second part of a two-part series.

  • An in-depth look at WMI and instrumentation, Part I
    WMI stands for Windows Management Instrumentation and, as the name indicates, is about managing your IT infrastructure this article provides an in-depth look at WMI and MOM 2005

  • New Books

  • Murach's ASP.NET 2.0 Upgrader's Guide: VB Edition
    What’s new and how to use it! That’s what this book delivers if you’re a VB developer who’s interested in upgrading from ASP.NET 1.x to ASP.NET 2.0.

  • C# in easy steps
    Learn to program with Microsoft’s premier programming language. No previous programming knowledge is assumed. With numerous easy-to-follow examples, this title explains the essentials of object-oriented programming with C#.

  • Murach's ASP.NET web programming with VB.NET
    Murach's ASP.NET web programming with VB.NET by Doug Lowe and Anne Prince is a in depth training and reference book for ASP.NET programming using VB.NET. The book builds upon Murach's previous books and covers more advanced concepts for programming ASP.NET pages.

  • Got Code?

    if you have any article , source code , or anything else you'd like to share with this community that you think others might find useful, please submit it here and we will gladly make it available on this site. submit@developerland.com.
    Partners

    All articles are copyrighted by their individual authors unless otherwise specified , everything else Copyright ©2004-2006 DeveloperLand