Using dependency injection with entities

In this recipe, we will show you how you can inject services into your entities to separate implementation details from your real business logic.

Getting ready

How to do it…

  1. Create a new console application project named IoCByteCode.
  2. Add a reference to NHibernate.dll, Ninject.dll, CommonServiceLocator.NinjectAdapter.dll, uNHAddIns.CommonServiceLocatorAdapters.dll, and Microsoft.Practices.ServiceLocation.dll.
  3. Add an interface named IPasswordHasher with the following method definition:
    string HashPassword(string email, string password);
  4. Add an implementation named PasswordHasher using the following code:
    public class PasswordHasher : IPasswordHasher 
    {
    
      private readonly HashAlgorithm _algorithm;
    
      public PasswordHasher(HashAlgorithm algorithm)
      {
        _algorithm = algorithm;
      }
    
      public string HashPassword(string email, string password)
      {
        var plainText = email + password;
        var plainTextData = Encoding.Default.GetBytes(plainText);
        var hash = _algorithm.ComputeHash(plainTextData);
        return Convert.ToBase64String(hash);
      }
    
    }
  5. Add a UserAccount entity class using the following code:
    public class UserAccount
    {
    
      private readonly IPasswordHasher _passwordHasher;
    
      public UserAccount(IPasswordHasher passwordHasher)
      {
        _passwordHasher = passwordHasher;
      }
    
      public virtual Guid Id { get; protected set; }
      public virtual string EMail { get; protected set; }
      public virtual string HashedPassword { get; protected set; }
    
      public virtual void SetCredentials(
        string email, string plainTextPassword)
      {
        EMail = email;
        SetPassword(plainTextPassword);
      }
    
      public virtual void SetPassword(string plainTextPassword)
      {
        HashedPassword = _passwordHasher.HashPassword(
          EMail, plainTextPassword);
      }
    
    }
  6. Add the following mapping document:
    <?xml version="1.0" encoding="utf-8" ?>
    <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
        assembly="IoCByteCode"
        namespace="IoCByteCode">
      <class name="UserAccount">
        <id name="Id">
          <generator class="guid.comb" />
        </id>
        <natural-id>
          <property name="EMail" not-null="true" />
        </natural-id>
        <property name="HashedPassword" not-null="true" />
      </class>
    </hibernate-mapping>
  7. Add an App.config with the standard NHibernate and log4net configurations.
  8. Set the proxyfactory.factory_class property to uNHAddIns.CommonServiceLocatorAdapters.ProxyFactoryFactory, uNHAddIns.CommonServiceLocatorAdapters.
  9. In Program.cs, add the following methods:
    private static void ConfigureServiceLocator()
    {
      var kernel = BuildKernel();
      var sl = new NinjectServiceLocator(kernel);
      ServiceLocator.SetLocatorProvider(() => sl);
    }
    
    private static IKernel BuildKernel()
    {
      var kernel = new StandardKernel();
    
      kernel.Bind<NHibernate.Proxy.IProxyFactory>()
        .To<NHibernate.ByteCode.Castle.ProxyFactory>()
        .InSingletonScope();
    
      kernel.Bind<IPasswordHasher>()
        .To<PasswordHasherImpl>()
        .InSingletonScope();
    
      kernel.Bind<HashAlgorithm>()
        .To<MD5CryptoServiceProvider>()
        .InSingletonScope();
    
      return kernel;
    }
  10. In Program.cs, add the following code to the Main method:
    ConfigureServiceLocator();
    NHibernate.Cfg.Environment.BytecodeProvider =
      new BytecodeProvider();
    var cfg = new Configuration().Configure();
    var sessionFactory = cfg.BuildSessionFactory();

How it works…

An NHibernate bytecode provider is responsible for building several factories, including the reflection optimizer, which NHibernate uses to instantiate entity classes. The particular reflection optimizer included with this bytecode provider uses Microsoft's common service locator to instantiate our entity classes. This allows us to use dependency injection to inject services into our entities. It also disables NHibernate's checks for a default constructor. Because we are using dependency injection, we'll need constructor parameters.

A typical bytecode provider also provides a factory for creating proxies. Because the common service locator isn't a proxy framework, we need to get this functionality from somewhere else. To fill this requirement, the ProxyFactoryFactory included with this bytecode provider fetches an IProxyFactory instance from the service locator. We register the castle dynamic proxy factory as the implementation for the IProxyFactory service.

Additionally, we must register implementations for the services required for our entities, and all of their dependencies. In this case, we register PasswordHasher for IPasswordHasher, and register .NET's implementation of MD5 as our hash algorithm.

There's more…

uNHAddIns also includes inversion of control bytecode providers specifically for Ninject, castle windsor, and spring IoC, though any of these may also be used through the common service locator.

Bland passwords need salt

As we learned in the Creating an encrypted string type recipe, a hashing algorithm is used to generate a hash value from the data. The hash value can't be reverse-engineered to calculate the original data, but a hash of the same data, with the same key, will always result in the same hash value. Also, any change in the original data results in a wildly different hash value. Finally, there is a near zero chance of two different strings of data resulting in the same hash value.

If your database is compromised, the passwords are hashed. It's a one-way algorithm, so it's safe, right? Wrong. This is not as secure as you may think. Let's say 14 of your accounts have the same password hash value. You don't know what their password is, but you know it's the same across all 14 accounts. Two of those accounts have Twilight-related e-mail addresses: and . Could you guess the password in 42 attempts or less? Easily. That's three chances for each of the 14 accounts. Congratulations. You now know the password for e-mail accounts, Facebook, Twitter, and countless other websites for nearly all of those 14 people. Only two of them used a password that was easy to guess, but they led to the downfall of the others.

When hashing data for storage in the database, you should always salt the data. Append some non-secret data to the secret data before hashing. In this recipe, we prepend the e-mail address to the password. The e-mail is the salt. Now, those 14 accounts will each have a different hash value. A hacker won't know which passwords are the same, which makes it much more difficult. When a user attempts to log in, prepend the e-mail and calculate the hash value in exactly the same way. If the hashes match, log them in. In a real application, you'll most likely want to clean up the e-mail address and convert it to lowercase. The slightest difference will change the hash value.

See also

  • Creating an encrypted string type
  • Creating an audit-event listener
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.221.251.169