Custom AuthorizationProvider and AuthorizationProviderData

Topics: Building and extending application blocks, Security Application Block
May 10, 2010 at 10:11 PM
Edited May 10, 2010 at 10:24 PM

My overall goal is to create a user management sub-system that allows admin user's to assign one or more permissions to a role or a user.  The list of permissions will be populated and consist of all of the tasks users can perform in the application.  I'm using the SqlMembershipProvider and SqlRoleProvider in my system to provide authentication and role based authorization.  I'm writing a customer AuthorizationProvider to perform the task-based authorization.  The provider has two required configuration options the connection string name and the name of the applicaiton.

I used the following article to write the custom provider:  http://msdn.microsoft.com/en-us/library/ff664483(v=PandP.50).aspx.

I've been able to:
1. Get my provider to show up in the configuration tool.
2. Set my provider as the default authorization provider
3. Get my provider to be called when authorization is needed.

Although my provider shows up in the configuration tool and displays my custom configuration fields, the data I enter in the configuration tool is not saved to the app.config.  If I enter data into the app.config directly and then load the config file the configuration tool, the field values are not displayed.  I  debuged the standalone configuration tool to confirm that my custom AuthorizationProviderData properties are being called.  I've been searching the net for two days now--with not success--trying to find anything that might help.  I have been unable to find anything. 

Has anyone else had experience create a custom AuthorizationProvider with a custom AuthorizationProviderData object? 
Or perhaps see the error(s) in my code?

Below are my class implementations:

/// <summary>
 /// Represents an authorization provider that queries
 /// a database to determine whether
 /// <see cref="System.Security.Principal.IPrincipal"/> objects
 /// are authorized.
 /// </summary>
 [ConfigurationElementType(typeof(SqlPermissionProviderData))]
 public class SqlPermissionProvider : IAuthorizationProvider
 {
          private String connectionStringName;
         private String applicationName;

         /// <summary>
         /// Initialize an instance of the <see cref="AuthorizationRuleProvider"/> class.
         /// </summary>
         /// <param name="authorizationRules">The collection of rules.</param>
         public SqlPermissionProvider(String connectionStringName, String applicationName)
         {
              if(String.IsNullOrEmpty(connectionStringName) == true)
             connectionStringName = String.Empty;

             if(String.IsNullOrEmpty(applicationName) == true)
                  applicationName = String.Empty;

             Trace.Write(String.Format("SqlPermissionProvider ctor:  ConnectionStringName = {0}\tApplicationName = {1}", connectionStringName, applicationName));

             this.applicationName = applicationName;
             this.connectionStringName = connectionStringName;
             }

             /// <summary>
             /// Evaluates the specified authority against the specified context.
             /// </summary>
             /// <param name="principal">Must be an <see cref="IPrincipal"/> object.</param>
             /// <param name="ruleName">The name of the rule to evaluate.</param>
             /// <returns><c>true</c> if the expression evaluates to true,
            /// otherwise <c>false</c>.</returns>
             public bool Authorize(IPrincipal principal, string ruleName)
             {
                  bool result = true;
                  string userName = string.Empty;

                  if(principal != null)
                       userName = principal.Identity.Name;

                  Trace.WriteLine(String.Format("SqlPermissionProvider:  Authorizing user, {0}, to perform the {1} action.", userName, ruleName));

                  return result;
             }
}

  /// <summary>
  /// Configuration class for custom AuthorizationProvider
  /// </summary>
  [Description("This is my customer SqlPermissionProviderData configuration object.")]
  [DisplayName("SqlPermissionProvider")]
  public class SqlPermissionProviderData : AuthorizationProviderData
  {
          /// <summary>
         /// Initializes a new instance of the 
         /// <see cref="SqlAuthorizationProviderData2"/> class.
         /// </summary>
         public SqlPermissionProviderData()
          : base("SqlPermissionProvider", typeof(SqlPermissionProvider))
         {
               Type = typeof(SqlPermissionProvider);
         }

         /// <summary>
         /// Initializes a new instance of the 
         /// <see cref="SqlAuthorizationProviderData2"/> class.
         /// </summary>
         /// <param name="name">The name of the element.</param>
         public SqlPermissionProviderData(String name, String connectionString, String applicationName)
          : base(name, typeof(SqlPermissionProvider))
         {
              Trace.WriteLine(String.Format("SqlPermissionProviderData ctor:  Name = {0}, Connect = {1} AppName = {2}",
                  name == null ? "null" : name,
                  name == null ? "null" : connectionString,
                  name == null ? "null" : applicationName));

              this.applicationName = applicationName;
              this.connectionStringName = connectionString;
         }

         private const string connectionStringProperty = "connectionStringName";
         private const string applicationNameProperty = "applicationName";

         private String applicationName;
         private String connectionStringName;


         [ConfigurationProperty(applicationNameProperty, IsRequired = true)]
         [Description("This is the application name under which all users, roles, and operations have been entered into the database.")]
         [DisplayName("Application Name")]
         public String ApplicationName
         {
             get { return applicationName; }
             set { applicationName = value; }
         }

         [ConfigurationProperty(connectionStringProperty, IsRequired = true)]
         [Description("This is the name of the configured database conneciton string.")]
         [DisplayName("Connection String Name")]
         public String ConnectionStringName
         {
             get { return connectionStringName; }
            set { connectionStringName = value; }
         }

         protected override System.Linq.Expressions.Expression<Func<IAuthorizationProvider>> GetCreationExpression()
         {
              return base.GetCreationExpression();
         }

        public override IEnumerable<Microsoft.Practices.EnterpriseLibrary.Common.Configuration.ContainerModel.TypeRegistration> GetRegistrations(IConfigurationSource configurationSource)
        {
              //missing type is handled by configuration system.
   
              if (typeof(IAuthorizationProvider).IsAssignableFrom(this.Type) == false)
              {
                   throw new ConfigurationErrorsException(String.Format("Alvin! Exception thrown in {0}.GetRegistrations(...)", this.Type.Name));
              }

             yield return new TypeRegistration<IAuthorizationProvider>(() => new SqlPermissionProvider(ConnectionStringName, ApplicationName))
             {
                  Name = this.Name,
                  Lifetime = TypeRegistrationLifetime.Transient
             };
         }
 }

 

May 11, 2010 at 9:51 AM

I'm currently looking at this.  I'll let you know once I found out something.

 

Sarah Urmeneta
Global Technology & Solutions
Avanade, Inc.
entlib.support@Avanade.com

May 11, 2010 at 2:40 PM

You should modify the get and set accessor of your properties to:

[ConfigurationProperty(applicationNameProperty, IsRequired = true)]
[Description("This is the application name under which all users, roles, and operations have been entered into the database.")]
[DisplayName("Application Name")]
public String ApplicationName
{
             get { return (string)this[applicationNameProperty]; }
             set { this[applicationNameProperty] = value; }
}

[ConfigurationProperty(connectionStringProperty, IsRequired = true)]
[Description("This is the name of the configured database conneciton string.")]
[DisplayName("Connection String Name")]
public String ConnectionStringName
 {
             get { return (string)this[connectionStringProperty]; }
             set { this[connectionStringProperty]= value; }
 }

 

Sarah Urmeneta
Global Technology & Solutions
Avanade, Inc.
entlib.support@Avanade.com

May 11, 2010 at 11:11 PM

Sarah,

Thank you very much for your response.  Making the changes you suggested worked.  Unfortunately I don't understand why the changes were necessary.  Can you provide some additional information as to why the changes are necessary?  Or point me to documentation or better article I can read to get further understanding?

Thanks again,

Alvin

 

May 12, 2010 at 2:17 AM

The SqlPermissionProviderData represents an element in the configuration file.  It ultimately inherits from the ConfigurationElement class.  A ConfigurationElement class functions as an indexer to access each property value in its internal collection.  Here's an article if you still need more info - http://net.tutsplus.com/tutorials/asp-net/how-to-add-custom-configuration-settings-for-your-asp-net-application/

 

Sarah Urmeneta
Global Technology & Solutions
Avanade, Inc.
entlib.support@Avanade.com

Feb 1, 2011 at 3:59 AM

I tried using the example but I am not able to configure using the Enterprise application configuration editor.

I added a security application block.  selected a custom authorization provider and clicked on load from file option and selected my custom assembly.  It does not show up on the list to select the custom authorization provider.

Thanks and regards

Gana

Feb 1, 2011 at 8:23 AM

Hi Gana,

The above example is a full integration. To be able to use it, you have to "Compile your project, and copy your provider assembly into the same folder as the configuration tools". See full details on how to install Fully Integrated Provided in the documentation - http://msdn.microsoft.com/en-us/library/ff664483(v=PandP.50).aspx . HTH.

Gino Terrado
Global Technologies and Solutions
Avanade, Inc.
entlib.support@avanade.com

 

Feb 1, 2011 at 8:45 AM

Thank you very much it works perfectly.

Gana