How I authenticate with FubuMVC, part 2

In my last post I described the beginning of how to authenticate using behaviours for fubuMVC – part of a change to my production application that I’m sharing my experiences on.

I already have an AuthenticationBehaviour class that hands over to an IProcessAuthenticatedUsers to process the current user identity, so I now need to define this processor. It is the responsibility of this processor to put the request’s current user in the fubu request so that models can have access to it - I have decided that it will use a user cache so that it does not have to go to the database for the user details on each request.

I’ve also decided to try out the new MemoryCache available in 4.0, but I am going to continue to use the ICacheProvider abstraction that essentially mimics the old ASP.NET cache – this is so I can easily revert should I decide to and is purely my own implementation decision because I’m trying something new.

Here’s the ICacheProvider abstraction in the fubuMVC library:

  public interface ICacheProvider
  {
    object Get(string key);

    void Insert(string key, object value, CacheDependency cacheDependency, 
                         DateTime absoluteExpiration, TimeSpan slidingExpiration);
  }

And here is my own wrapper to the MemoryCache:

    public class MemoryCacheProvider : ICacheProvider
    {
        readonly MemoryCache _memoryCache;

        public MemoryCacheProvider(MemoryCache memoryCache)
        {
            _memoryCache = memoryCache;
        }

        public object Get(string key)
        {
            return _memoryCache.Get(key);
        }

        public void Insert(string key, object value, CacheDependency cacheDependency, 
                    DateTime absoluteExpiration, TimeSpan slidingExpiration)
        {
            var cacheItemPolicy = new CacheItemPolicy
                {
                    AbsoluteExpiration = (absoluteExpiration == Cache.NoAbsoluteExpiration)
                                              ? ObjectCache.InfiniteAbsoluteExpiration
                                              : new DateTimeOffset(absoluteExpiration),
                    SlidingExpiration = (slidingExpiration == Cache.NoSlidingExpiration)
                                              ? ObjectCache.NoSlidingExpiration
                                              : slidingExpiration
                };

            _memoryCache.Add(key, value, cacheItemPolicy);
        }
    }

So here are the specifications for my AuthenticatedUserProcessor:

    [Subject(typeof(AuthenticatedUserProcessor))]
    public class when_processing_the_user_and_the_user_is_not_in_the_cache
    {
        Establish context = () =>
        {
            _emailAddress = "lknkjankjsdk";
            _identity = new Mock<IIdentity>();
            _identity.Setup(i => i.Name).Returns(_emailAddress);

            _user = new User();
            _userObtainer = new Mock<IObtainUsers>();
            _userObtainer.Setup(u => u.ObtainUserFromEmailAddress(_emailAddress))
                                     .Returns(_user);

            _fubuRequest = new Mock<IFubuRequest>();

            _userRequestCache = new MemoryCacheProvider(new MemoryCache("userCache"));
            _authenticatedUserProcessor = new AuthenticatedUserProcessor(_userRequestCache, 
                                       _userObtainer.Object, _fubuRequest.Object);
        };

        Because of = () =>
            _authenticatedUserProcessor.Process(_identity.Object);

        It caches_the_user_it_obtained = () =>
            (_userRequestCache.Get(_emailAddress) as TheRequestsCurrentUser)
                .User.ShouldEqual(_user);

        It should_place_the_user_in_the_fubu_request = () =>
            _fubuRequest.Verify(f => f.Set(Moq.It.Is<TheRequestsCurrentUser>(r => r.User == _user)));

        static AuthenticatedUserProcessor _authenticatedUserProcessor;
        static Mock<IObtainUsers> _userObtainer;
        static Mock<IFubuRequest> _fubuRequest;
        static Mock<IIdentity> _identity;
        static string _emailAddress;
        static User _user;
        static ICacheProvider _userRequestCache;
    }

    [Subject(typeof(AuthenticatedUserProcessor))]
    public class when_processing_the_user_and_the_user_is_already_in_the_cache
    {
        Establish context = () =>
        {
            _emailAddress = "onmojnouiwe";
            _identity = new Mock<IIdentity>();
            _identity.Setup(i => i.Name).Returns(_emailAddress);

            _userObtainer = new Mock<IObtainUsers>();
            _fubuRequest = new Mock<IFubuRequest>();

            _theRequestsCurrentUser = new TheRequestsCurrentUser();
            _userRequestCache = new MemoryCacheProvider(new MemoryCache("userCache"));
            _userRequestCache.Insert(_emailAddress, _theRequestsCurrentUser, null, 
                                       Cache.NoAbsoluteExpiration, Cache.NoSlidingExpiration);
            _authenticatedUserProcessor = new AuthenticatedUserProcessor(_userRequestCache, 
                                            _userObtainer.Object, _fubuRequest.Object);
        };

        Because of = () =>
            _authenticatedUserProcessor.Process(_identity.Object);

        It should_place_the_user_in_the_fubu_request = () =>
            _fubuRequest.Verify(f => f.Set(_theRequestsCurrentUser));

        static AuthenticatedUserProcessor _authenticatedUserProcessor;
        static Mock<IObtainUsers> _userObtainer;
        static Mock<IFubuRequest> _fubuRequest;
        static Mock<IIdentity> _identity;
        static string _emailAddress;
        static ICacheProvider _userRequestCache;
        static TheRequestsCurrentUser _theRequestsCurrentUser;
    }

These specifications are pretty simple – if not in the cache, the user is obtained from the database and stored in the cache – it is then stored in the current fubu request.
If it is in the cache, it simply sets the cached user into the request.
At a later date, I hope to revisit this to provide dependencies to update the cache when the user details change, but this is a good enough solution for the time being.

Here is the implementation of the processor:

    public class AuthenticatedUserProcessor : IProcessAuthenticatedUsers
    {
        readonly ICacheProvider _userRequestCache;
        readonly IObtainUsers _userObtainer;
        readonly IFubuRequest _fubuRequest;

        public AuthenticatedUserProcessor(ICacheProvider userRequestCache, 
                    IObtainUsers userObtainer, IFubuRequest fubuRequest)
        {
            _userRequestCache = userRequestCache;
            _userObtainer = userObtainer;
            _fubuRequest = fubuRequest;
        }

        public void Process(IIdentity identity)
        {
            var user = _userRequestCache.Get(identity.Name) as TheRequestsCurrentUser 
                        ?? AddUserToTheCache(identity.Name);

            _fubuRequest.Set(user);
        }

        TheRequestsCurrentUser AddUserToTheCache(string identifier)
        {
            var user = _userObtainer.ObtainUserFromEmailAddress(identifier);
            var currentUser = new TheRequestsCurrentUser {User = user};
            _userRequestCache.Insert(identifier, currentUser, null, 
                    Cache.NoAbsoluteExpiration, Cache.NoSlidingExpiration);
            return currentUser;
        }
    }

That's it, the processor's single responsibility is complete for now and I have a user obtainer to create, but that easy enough without showing here.

In my next post, the third part of my authentication posts, I’ll show how I ensure that input models in the fubuMVC request can be provided with the current user request so user information can always be at hand at my controller actions.



0 comments: