Warning: This article will come off as some serious flamebait. If all you do is take it at face value then I will take a page from Justin's book and probably tell you that you make up part of the collection of people I will be talking about be a jerk. Alas, without further adieu...

Over the past few years there has been a great paradigm shift in developer maturity vis-a-via Test / Behavior Driven Development. This has been great and undoubtedly has helped our industry. Like all good things it has also started a downward spiral of dogmatism and a lemming effect for inexperienced developers. I am going to call these groups "Rcov Alchemists" and "Monkey Testers".

First the Alchemists. Rcov is a great tool, and arguably essential to the development process. That being said it's focus has been a better marketing tool than a development tool for some and is starting to hurt the development process. The reason I am coining this phrase Rcov alchemists is obvious when looking at code written by some more junior level developers that were not taught anything but "The code must be 100% covered". Now this is not the developers fault, so they do what they are told and write tests to make the red turn green. They don't understand or have the panache to defend their case even if they do realize that some of the stuff they are doing may be correct, but some edge case in the tool they are using just doesn't register the code as tested. This can breed bad habits and poor tests just to accomplish what seems to be a great goal. Now most of us know that 100% code coverage doesn't mean jack-sh%t. It really becomes more of a feel good for customers / managers, and a marketing tool. Now I will say that used in that context it's pretty powerful, but as developers we really need to take a step back and make sure that we aren't pushing code coverage for the sake of code coverage. I could go on further about this but for the sake of your attention span and my curiosity as to what you all think I will stop on this one here.

Now the Monkey Testers. If I had a Venn diagram as a visual aid here we would see a large overlap between this group and the aforementioned. I am defining them differently because their behavior creates a different problem. This problem often manifests itself the way of test bloat. This is where the paradigm shift has come to bite us in the ass. We have preached test test test to everyone, but not always backed up the test first part. When you examine the tests closer you will see a very one to one style of testing that tries to be overly exhaustive. Now here is where a lot of people will fight back and that's ok. For me this is testing for the sake of testing and beyond a project's needs. A test needs to cover the specifications / declarations that drive the code that gets written, no more, no less. That is an incredibly loaded statement and may sound like a cop out and just me trying to duck out of what I am complaining about, but take a step back and think about it. The overly exhaustive tests were obviously not part of TDD/BDD practices, and just serve to clutter up the intention of the test itself. A test should be able to communicate to the developer its intent in a clear and precise way. On that note and in spirit of wrapping up the Monkey Tester is also a trait that is not always the fault of the developer and can be helped if we take the time.

I am proposing to those of you out there that have figured out the art of testing not to just preach testing and 100% coverage, but to explain what it is you do to be successful and help some of the newcomers avoid these two problems. Let's take our industry beyond dogmatism and introduce it to real pragmatism and push it to the levels of success that we all know we can.

Let the floodgates open!
I just finished adding in support for postgresql in streamlined. So for all you streamlined && postgres fans let there be happiness and joy. Check it out over at http://streamlinedframework.org

P.S. It looks like it took me all of a couple hours to resort back to writing about tech stuff... How sad

Non-tech weekend posts

January 26th, 2008

Since I blog about tech all week I decided that I was going to only blog about non tech stuff on the weekend. So in my first attempt at this I am going to talk a little about one of my newest hobbies. Two weeks ago I set up a saltwater fish tank. Now that the nutrient cycle is over I put in the basic tank cleaner livestock. I added some snails, crabs, and shrimp to cleanup the remaining part of the algae bloom that takes place during the cycle. In a week I will be able to add some fish and then some corals and other creatures. I am very excited to see how it turns out. More info and pictures to come!


Opensource Friday updates

January 25th, 2008

I just updated the EncryptedCookieStore plugin. I had a couple of suggestions so I went ahead and implemented them. Here is a list of updates.
  • Created a rake task to generate secret keys. This forces you to run rake secret to generate the keys if you have the plugin installed.
  • Updated the code to support the newly generated secret keys, as well as some DRY-ing up of the code.
Please feel free to continue leaving comments on my site or the Relevance opensource trac at http://opensource.thinkrelevance.com.
I have pulled out the encrypted cookie store into a plugin that is now available for download. You can download the plugin at

https://opensource.thinkrelevance.com/svn/incubator/encrypted_cookie_store
Feel free to give it a shot. Let me know if you run into any unexpected behavior. I would like to know it's performance impact on your applications.
Following up to my first post on Rails 2 sessions I want to follow up and provide a decent solution to the problem. Let me first go on the record that you should default to the database session store, but if you still really want to go the cookie route for some reason there should be a safe way. That being said let's dig a little deeper into the cause and see if we can't duck punch our way right in to the session creation methods. If we attack this problem at the point where it falls down we are likely to have success encrypting the data and not have to tamper with the internals of rails too much. Let's take a look and the cookie_store file inside of rails. The two methods we are after are the marshal and unmarshal methods. Here they are in their natural state.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

private
    # Marshal a session hash into safe cookie data. Include an integrity hash.
    def marshal(session)
      data = ActiveSupport::Base64.encode64(Marshal.dump(session)).chop
      CGI.escape "#{data}--#{generate_digest(data)}"
    end

    # Unmarshal cookie data to a hash and verify its integrity.
    def unmarshal(cookie)
      if cookie
        data, digest = CGI.unescape(cookie).split('--')
        unless digest == generate_digest(data)
          delete
          raise TamperedWithCookie
        end
        Marshal.load(ActiveSupport::Base64.decode64(data))
      end
    end
You can see here that Base64 encoding is the only thing going on here. The digest at the end after the -- is the verification signature of the cookie to prevent tampering. It would be great if we could just do this to the entire contents of the cookie, but since we need to be able to decrypt it later we can't rely on a one way hash. So now we need to find a decent two way encryption algorithm that we can use to encrypt and then decrypt the contents of the cookie. This is a good problem for the Ruby OpenSSL library to solve.

Let's take a look at a couple of methods to encrypt and decrypt some data.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

  def encrypt(cookie)
    cipher = OpenSSL::Cipher::Cipher.new("des-ecb")
    cipher.encrypt
    cipher.key = "insert key here"
    cipher.iv = "insert initialization vector here"
    encrypted_cookie = cipher.update(cookie)
    encrypted_cookie << cipher.final
    return encrypted_cookie
  end
  
  def decrypt(cookie)
    cipher = OpenSSL::Cipher::Cipher.new("des-ecb")
    cipher.decrypt
    cipher.key = "insert above key"
    cipher.iv = "insert above iv"
    decrypted_cookie = cipher.update(cookie)
    decrypted_cookie << cipher.final
    return decrypted_cookie
  end
end
This will allow you to solve the encryption problem. There is still one small problem here. The DES encryption algoriithm is very crackable. The OpenSSL cipher has a lot of algorithms, so just pick one that isn't as easily crackable. Now that we have figured the hard part out let's put together a duck punch for Rails. For now it will work if you put this code into your environment.rb file.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

class CGI::Session::CookieStore
  
  def marshal(session)
    data = encrypt(Base64.encode64(Marshal.dump(session)).chop)
    CGI.escape "#{data}--#{generate_digest(data)}"
  end
  
  def unmarshal(cookie)
    if cookie
      data, digest = CGI.unescape(cookie).split('--')
      unless digest == generate_digest(data)
        delete
        raise TamperedWithCookie
      end
      Marshal.load(Base64.decode64(decrypt(data)))
    end
  end
  
  def encrypt(cookie)
    cipher = OpenSSL::Cipher::Cipher.new("des-ecb")
    cipher.encrypt
    cipher.key = "insert key here"
    cipher.iv = "insert initialization vector here"
    encrypted_cookie = cipher.update(cookie)
    encrypted_cookie << cipher.final
    return encrypted_cookie
  end
  
  def decrypt(cookie)
    cipher = OpenSSL::Cipher::Cipher.new("des-ecb")
    cipher.decrypt
    cipher.key = "insert above"
    cipher.iv = "insert above"
    decrypted_cookie = cipher.update(cookie)
    decrypted_cookie << cipher.final
    return decrypted_cookie
  end
end
Viola! You now have encrypted cookies. This code is still pretty rough, and I plan on refactoring it a bit and then turning it into a plugin for easier consumption.
Well I am finally all moved in to my new place after waiting for the movers to show up for 5 days. Now that I have the site back up I have a ton of new stuff to blog about which will be up soon. But for now I am tired from unpacking and need to sleep on my own bed instead of an air mattress. Stay tuned for more to come.

A little bit of downtime...

January 4th, 2008

I am moving to Chapel Hill tomorrow and since I host this site from a machine under my desk, the site will be down till Wednesday when the cable company comes and hooks me up with sweet sweet interwebs again. It will officially go down tomorrow afternoon, so the countdown begins.
Rails 2 introduces cookie based session storage as the default for sessions. This means a couple of things to everyone. On the upside you gain a serious increase in speed if you are relying heavily on sessions (which you shouldn't be). On the immediate downside you introduce a factor of risk by using this option if you store anything sensitive in the session (i.e. passwords, authorization rights, etc). The reason for this is that the only thing rails does for you by default is marshal the data (basically Base64 encode) and store that in the cookie. So you could easily read any session data if you had the desire.

Actually, since I desire to read everyone's sessions let's take a look at how easy it would be to open up a rails 2 session with the default options. Let's start with a cookie that I copied straight from a rails app.

BAh7CToOcmV0dXJuX3RvMDoMY3NyZl9pZCIlMGFlNGM2N2NiMjBhZWNiMGIy%250AOWQxZjNiYzExNWY5YjI6CXVzZXJpByIKZmxhc2hJ
QzonQWN0aW9uQ29udHJv%250AbGxlcjo6Rmxhc2g6OkZsYXNoSGFzaHsGOgtub3RpY2UiG0xvZ2dlZCBpbiBz%250AdWNjZXNzZnVsb
HkGOgpAdXNlZHsGOwlU--87ab0b1e092388efc814a06d932c0e9258bf2844

Now let's take a very minimal ruby script and have it bust this cookie wide open.
1
2
3
4

require 'base64'
cookie = ARGV[0]
puts Base64.decode64(cookie)
Running just this small bit of code gives us this result

\004\b{\t:\016return_to0:\fcsrf_id"%0ae4c67cb20aecb0b2\333\235\0009d1f3bc115f9b2:\tuseri\a"\n
flashIC:'ActionContro\333\235\000ller::Flash::FlashHash{\006:\vnotice"\eLogged in s\333\235\000uccessfully\006:\n
@used{\006;\tT\363\266\233\321\275^\323\335\267\363\307\237s\315xkN\235\367}\234\321\357v\347\306\337\333\3168

Now you can see that we can read the contents of this cookie. You can see that this particular app isn't storing any sensitive data, but imagine if it were. Not the best of news for those of you who didn't know this already. Now this is the part of the informercial where I say "But Wait! If you act now..." and explain that this problem can be solved rather easily. I will cover creating an encrypted cookie store for your rails app in a follow up to this article. It will most likely come in the form of a plugin sometime soon.

Spider Test, an Introduction

January 3rd, 2008

Since I recently blogged about Spider Test I have had a few people asking questions. In an attempt to clear some things up I will show a sample of how to put the test together. First install the plugin into your rails app

# script/plugin install svn://caboo.se/plugins/court3nay/spider_test

Then generate an integration test

# script/generate integration_test spider
Now put the essentials into your test
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

require "#{File.dirname(__FILE__)}/../test_helper"

class SpiderTest < ActionController::IntegrationTest
  fixtures :all
  
  include Caboose::SpiderIntegrator

  def test_spider
    get '/account/login'
    assert_response :success
    post '/account/login', :login => 'quentin', :password => 'test'
    assert session[:user]
    assert_response :redirect
    assert_redirected_to '/'
    follow_redirect!

    spider(@response.body, '/', 
      :ignore_urls => ['/login', %r{^.+logout}, %r{^.+delete.?}, %r{^.+/destroy.?},
      :ignore_forms => [], :verbose => true)
  end
end
This simulates a logged in user. When you run this test it will find and follow any link it finds and fill out any form it comes in contact with. This is just a primer, so use this to get you started and then go nuts on your own. You will really appreciate this plugin in short order!
I have run across some great tools for developing rails apps that you should be adding to your toolkit if you haven't already.
  • Safe ERB This tool will help you safeguard your application against cross site scripting attacks. It raises an error anytime you try to render an object without the h() or html_escape() methods in place. Keep in mind that you should only use this if you are starting a new project or have a very good set of tests that cover everything in your app. This will raise errors, so if the two aforementioned situations are not yours, keep away from this project if you want to keep your sanity.
  • Spider TestI just recently found this plugin while searching for ways to do integration testing that would hit every link in the application. This plugin goes a step futher though. Every time it finds a form it will mutate the form inputs and try to cause errors by throwing crazy data at it. I plan on helping out this project quite a bit when I can because it's really an awesome tool that will make your integration testing life a whole lot easier.
I will leave you with these for now so be sure to try them out, they will probably save your ass one day!

Time for a theme reboot

January 1st, 2008

Considering it's the new year, I am resetting my blog theme to the one that ships with mephisto and am going to work on something new. I will have it done sometime here in the near future.