Ayende @ Rahien

Refunds available at head office

Reviewing OSS Project: Whiteboard Chat–setup belongs in the initializer

Originally posted at 3/8/2011

As a reminder, I am reviewing the problems that I found while reviewing the Whiteboard Chat project during one of my NHibernate’s Courses. Here is the method:

[Transaction]
[Authorize]
[HttpPost]
public ActionResult GetLatestPost(int boardId, string lastPost)
{
    DateTime lastPostDateTime = DateTime.Parse(lastPost);
    
    IList<Post> posts = 
        _postRepository
        .FindAll(new GetPostLastestForBoardById(lastPostDateTime, boardId))
        .OrderBy(x=>x.Id).ToList();

    //update the latest known post
    string lastKnownPost = posts.Count > 0 ? 
        posts.Max(x => x.Time).ToString()
        : lastPost; //no updates
    
    Mapper.CreateMap<Post, PostViewModel>()
        .ForMember(dest => dest.Time, opt => opt.MapFrom(src => src.Time.ToString()))
        .ForMember(dest => dest.Owner, opt => opt.MapFrom(src => src.Owner.Name));

    UpdatePostViewModel update = new UpdatePostViewModel();
    update.Time = lastKnownPost; 
    Mapper.Map(posts, update.Posts);

    return Json(update);
}

In this post, I want to focus on the usage of AutoMapper, in the form of Mapper.CreateMap() call.

It was fairly confusing to figure out why that was going on there. In fact, since I am not a regular user of AutoMapper, I assumed that this was the correct way of doing things, which bothered me. And then I looked deeper, and figured out just how troubling this thing is.

Usage of Mapper.CreateMap is part of the system initialization, in general. Putting it inside the controller method result in quite a few problems…

To start with, we are drastically violating the Single Responsibility Principle, since configuring the Auto Mapper really have very little to do with serving the latest posts.

But it actually gets worse, Auto Mapper assumes that you’ll make those calls at system initialization, and there is no attempt to make sure that those static method calls are thread safe.

In other words, you can only run the code in this action in a single thread. Once you start running it on multiple thread, you are open to race conditions, undefined behavior and plain out weirdness.

On the next post, I’ll focus on the security vulnerability that exists in this method, can you find it?

Comments

Ryan Heath
03/11/2011 10:57 AM by
Ryan Heath

DateTime lastPostDateTime = DateTime.Parse(lastPost);

They could have used DateTime.TryParse/.TryParseExact or

change the type from string to DateTime.

DateTime.Parse will throw exceptions when lastPost cannot be parsed.

Ironically, in my spoken language 'lastPost' means 'menace' :)

// Ryan

Walter Poch
03/11/2011 11:20 AM by
Walter Poch

I'm not sure how the system behaves but if there are some limitations in wich board a user can see, the security check is missing and the boardId is passed directly to the _postRepository, which isn't responsible of security concerns.

Pete
03/11/2011 12:39 PM by
Pete

At first, I didn't like that "lastPost" input string was being sent to the Model if there was no updates - always a bad idea to echo user input to your output - but that looks safe as the "Parse" method is effectively validating user input (it will throw if the string is not a date string)

Next thing is that you are not escaping output: Owner.Name is not being escaped on output.

Rule of thumb is to ALWAYS "validate input, escape output" and that will catch alot of security issues.

Ryan Heath
03/11/2011 01:43 PM by
Ryan Heath

I think escaping is a responsibility of the view.

In this case the conversion to Json.

It bothers me more that Time is converted to string.

-- .ForMember(dest => dest.Time, opt => opt.MapFrom(src => src.Time.ToString()))

// Ryan

Corey
03/11/2011 01:59 PM by
Corey

Once authenticated you can pass in whatever id's you want and there is no restriction to the logged in user.

Nick
03/11/2011 08:51 PM by
Nick

There are actually 2 security vulnerabilities.

If no anti-forgery token is required (you can set this globally in MVC 3 with special filters) then they are open to csrf attacks.

I think the main point would be the passing around of ID's though as the poster above says. Pretty obvious.

Nick
03/11/2011 08:52 PM by
Nick

Escaping output is usually handled by the view engine, pete. I don't think you could call that one a bug. But if they don't output encode in the view then you're right.

kshitij
03/12/2011 02:11 PM by
kshitij

What did automapper really them?

Syntax for mapping seems quite verbose.

Seems like all it avoided was a foreach on the posts.

Couldn't they have just created a mapper class of type IMapper <from,to> instantiated it once?

Corey
03/12/2011 05:24 PM by
Corey

Using AutoMapper like this ends up littering the code-base with a ton of dependency on AutoMapper. You could easily reduce that dependency to one place with an extension method. posts.Map(update.Posts);

Christopher Wright
03/12/2011 09:29 PM by
Christopher Wright

GET whiteboard/getlatestpost

This is a problem since a single call can create arbitrary amounts of work. If you require multiple calls, then you can have a firewall rule limiting the number of simultaneous connections from a given source or the number of connections from a source per minute. That guarantees that you can use the majority of server resources to process legitimate requests. If it's a single call, though, you're going to tie up an unbounded amount of memory and DB resources with no way to throttle individual users.

Also, your title for the next post kind of gave it away. Though since I've been reading your blog, the phrase "unbounded result set" is baked into my cerebellum, and it's also the major player in most of my nightmares.

Christopher Wright
03/12/2011 09:31 PM by
Christopher Wright

I really want a preview function for these comments. The essence is providing a lastPost value of DateTime.MinValue (or something else very far in the past).

If you're guaranteeing that boards have a limited lifecycle in terms of number of updates, though, that wouldn't be so bad.

Jeff
03/13/2011 05:00 AM by
Jeff

What benefit do you get from using AutoMapper at all in the scenario? Looks like somebody is using it just for the sake of using it...

Jimmy Bogard
03/14/2011 12:41 PM by
Jimmy Bogard

@Jeff

There's some pointless configuration there - the Time member configuration isn't needed, AutoMapper can handle object->string w/o configuration. The second one I'd argue isn't needed either, you could just name the destination member OwnerName and be done with it.

Comments have been closed on this topic.